Format String - GOT Overwrite
I always hated the formatted strings bugs or never understood it concisely, Therefore I’m writing to explain myself when I took out the challenge of nullcon ctf, baby_formatter.
lets start with analyzing the binary with our file command as we can see that our binary is 64 bit and dynamically linked
1 | └─$ file baby_formatter |
lets checksec the binary and see what are the mitigations implemented into the binary. Great we can see partial RELRO is present which means the GOT is above the program variables and NX enabled which means out own shellcode can’t be injected into the binary.
1 | └─$ pwn checksec baby_formatter |
Now lets open our file in ghidra to see what our binary for the interesting functions. When we open our on the left, we can see the symbol table where we have two interesting functions. main and win
When we open the main function in the ghidra decompiler, we can see that we have a local variable local_78 which take value from standard input and print out when the printf function is called. Apart from that we can confirm that the author has implement the proper size check which will not cause the buffer to overflow. And also the program will never return since we have a exit(1) function which is FUN_004010e0(1). what can we do now ?
As we can see that it takes input and return the input to the output with printf as we see in ghidra and prints bye! then exit(1)
Now if we give our input as %d we can see that we get unexpected output -129881392
now lets display our value in hexadecimal by using %x and now this time we give multiple %x %x %x we see we get hex output now lets go further and add 4 ‘A’ and form a payload like
AAAA%x.%x.%x.%x.%x.%x.%x.%x.%x.%x we can see that we have 41414141 which is the exact replica of AAAA in hexadecimal. Which means we can read the values somewhere on the stack by insert our own data using C formatter
In printf there is a formatter which helps in executing function $n, now if our function return want to write the return function the value we want may something like /bin/bash. Now lets go back ghidra and check for the functions where we can execute /bin/bash. Fortunately we have a function where we can write invoke /bin/bash, now let grep the function address of win.
we can execute objdump to grep the win function address
1 | └─$ objdump -t ./baby_formatter| grep win |
Now we need to know where our return function or the last exit function of the program.
lets open our gdb and try to find our exit function exit(1), we can see that we have a exit@plt. Disassemble the main exit@plt function, we can see that it jumps to exit@got address 0x404040.
1 | pwndbg> disass main |
Now lets set a break point break *0x4010e0 in exit@plt entry, now run the program input anything, when the break point hits. Lets access memory of our exit@got.plt, x 0x404040 and set the memory of the address 0x404040 to our win function address 0x0411d6 then access the memory of address 0x404040 again to check the value is modified by our win address 0x0411d6, continue the function we get win.
1 | ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── |
Now our goal is clear and concise that somehow we have to overwrite the exit address of the got.exit function to our win function, how can we process with it. umm lets use pwn tools this time and automate manual stuff below script will print index along with reflected ‘A’s or ‘41’s
1 | from pwn import * |
Now if we convert our win address to decimal which is 4198870 . let us crafted the payload
payload = (b”%4198870x%10$nAA”) + pack(elf.got.exit)
%4198870x — padding
%10$n - overwriting the value at 10 offset since our value main offset is 8 and next 2 offset of 16 bytes is use to overwrite the address
pack(elf.got.exit) — got.exit address
1 | from pwn import * |
executing we will get the flag