最近做题就是套娃,做到一道题发现这个知识点不会,就去学这个知识点,结果学新知识点又又不会,又去学另一个知识点。结果学到最后,我前面的好多题都还没做。还是太菜了,一做题就会碰到不会的知识点。
Largebin attack
Largebin
的组织方式相比于 smallbin
,多了一个 fd_nextsize
和 bk_nextsize
,用来将chunk
根据 size
链接起来。其原型图如下,参考自这篇文章:
总结起来就是:
- 按照大小从大到小排序
- 若大小相同,按照free时间排序
- 若干个大小相同的堆块,只有首堆块的
fd_nextsize
和bk_nextsize
会指向其他堆块,后面的堆块的fd_nextsize
和bk_nextsize
均为0 - size最大的chunk的
bk_nextsize
指向最小的chunk; size最小的chunk的fd_nextsize
指向最大的chunk
首先看一下将unsortedbin
插入到 largebin
中的源码:
1 | for (;; ) |
我们主要关心的是 第一个 else
部分,其中 victim
表示需要插入largbin
中的unsorted chunk
,fwd
表示已经在 Large bin
中的chunk
。利用的源码也在代码中标识出来。
victim
(也即unsorted chunk
)的size
必须大于largebin chunk
,这样才能够绕过else
之前的检测;控制
fwd->bk_nextsize
为target_addr1
,那么执行victim->bk_nextsize = fwd->bk_nextsize
时,就能控制victim->bk_nextsize
为traget_addr1
;接着就能控制
victim->bk_nextsize->fd_nextsize = victim
,也就相当于target_addr1->fd_nextsize = victim
,我们在*(taget_addr1+0x20)=victim
写入了unsorted chunk
的地址;然后,我们修改
fwd->bk
为target_addr2
,就能控制bck
;最后,通过
bck->fd=victim
,就能在*(target_addr2+0x10)=victim
,又写入了unsortedbin chunk
的地址
总结就是,如果能够控制已经在largebin
中的chunk的bk、bk_nextsize
字段,那么就能实现往任意地址写入待插入largebin
的chunk的地址。一般待插入的chunk
地址为堆地址,所以通过largebin attack
可以实现往任意地址写入堆地址的目的
startctf 2019 heap_master
程序分析
程序漏洞在Edit
函数,可以实现Mem
内任意地址修改。然后Delete
函数也是可以释放Mem
内满足堆块释放要要求的地址。
利用分析
- largebin attack泄露libc
首先创造 0x330、0x410和0x410 的堆块,每个堆块之前都要构造一个 fastbin
,防止堆块合并。然后依次将 chunk1
和 chunk2
释放到 unsortedbin
中:
然后,分配一个小块,此时 0x410的chunk2
会进入 largebin
中,chunk1
剩余的部分仍会留在 unsortedbin
中。
接着我们在chunk2
中,构造两个大小大于0xb0
的chunk
,然后再次释放这两个堆块,他们会被放入 unsortedbin
中,这样他的 fd
和 bk
以及 fd_nextsize
和 bk_nextsize
就有了main_arena
附近的地址。我们后续只需要修改这个main_arena
的地址就有一定几率改为 stdout
的地址:
1 | #change chunk2 bk and bk_nextsize into target_addr |
然后再修改 largebin
的大小为正常的 Largebin
大小,修改其 bk
为 stdout-0x10
,修改其 bk_nextsize
为 stdout+0x19-0x20
,此时就可以发动 largebin attack
,修改 stdout
为一个 unsortedbin chunk
地址,修改 stdout+0x19
的地址为 unsortedbin
的地址。这样就符合了 overflow
的要求,能够泄露 Libc
地址。
1 | //flag的要求 |
这里,就是为什么一开始要将 offset
设为 0x8060
,因为这里要保证覆盖到 stdout
的 flag
的 倒数第3位满足 0x800
,当然还需要倒数 第4位为 0x1000
,也就是总体满足 0x1800
,但是倒数第4位也是需要一定几率 去爆破才行。
- largebin attack伪造 IO_list_all
泄露了地址,那么采用 FSOP
来 getshell
。通过再一次的 largebin attack
来伪造 IO_list_all
。此时 IO_list_all
就已经指向了一个堆地址。
这道题比我想象的更复杂,原题里他是禁止执行 system
的,只有通过 orw
来得到flag
。
不够我还是先尝试了直接使用 FSOP
来 getshell
。
然后又尝试通过 FSOP
执行堆栈的 orw
rop。
通过 setcontext
来执行 mprotect
,最终执行shellcode
获取 flag
。
EXP
1 | from pwn import * |
0ctf_2018_heapstorm2
程序分析
在 Edit
函数中存在一个 off-by-null
漏洞,此外Add
函数中使用了 calloc
函数,申请时 堆块会被清空。此外,Show
函数中需要mem_ptr[2]
和 mem_ptr[3]
满足条件才能使用。最后,一开始程序就将 fastbin
关闭了,导致fastbin attack
失效。
利用分析
虽然存在一个 off-by-null
,但是fastbin attack
已经不能够使用。也就意味着我们不能通过fastbin
堆块伪造来达到泄露地址或任意内存写了。
这道题的思路也是利用largebin attack
,上面一题我们已经能够知道 largebin attack
可以向任意两个地址写入 unsortedbin
的地址。这题的思路,则是加强版,利用largebin attack
和 unsortedbin
来 实现任意地址分配堆块。
- 构造两个堆重叠
首先为了能够修改 largebin
和 unsortedbin
两个堆块,我们就需要构造两个堆重叠。构造方式也是利用 off-by-null
的思路:
先申请6个如下堆块:
1 | Add(0x18) #0 |
然后,先修改 chunk1
的 chunk2-0x10
处为 0x500
,是为了错位,原因接下来讲;然后释放 Chunk1
,此时 chunk2
的 Prev_size
留下了 0x510
的大小,prev_inuse
为0;
接着,对 chunk0
使用 off-by-null
漏洞修改 chunk1
的size
为 0x500
, 再分别申请 0x18
和 0x4d8
的chunk1
和 chunk7
, 此时 我们之前伪造的 0x500
处的 prev_inuse
为1。但是 chunki2
处 的 prev_inuse
仍然为 0;
所以依次释放 chunk1
和 chunk2
,此时会发生堆块合并,chunk1
和chunk2
合并为 0x530
;
最后再依次申请 0x38
和 0x4e8
的 chunk1
和 chunk2
,那么此时 chunk7
就可以修改 chunk2
的内容。
然后 使用同样的方法构造 0x4d8
的 chunk8
能够覆盖 0x4d8
的 Chunk5
。
- lagrebin attack
有了两次堆重叠后,依次释放 chunk5
和 chunk2
,再申请 chunk2
,那么 Chunk5
进入 largebin
,最后再释放 chunk2
,那么 chunk2
进入 unsortedbin
。
我们想要在 mem_ptr-0x20
处伪造堆块,实现对 mem
的修改,将 mem_ptr-0x20
命名为 target_addr
我们利用两次堆重叠修改 Unsortedbin
和 largebin
:
修改 unsortedbin
的 bk
为 target_addr
;
修改 largebin
的 bk
为 target_addr+8
,修改 bk_nextsize
为 target_addr-0x20+8-5
最后,我们申请 0x48
的堆块,触发 Largebin attack
原因:
这样修改的原因,有两点:
- 申请
0x48
堆块时,先在unsortedbin
中查找,此时查找到chunk2
其大小不满足,会先把chunk2
放入largebin
中; - 触发了
largebin
,那么结果为:
1 | victim->bk_nextsize = fwd->bk_nextsize = target_addr-0x20+8-5 = target_addr - 0x1d |
可以看到,我们在 target_addr-0xd
处写下了 unsortedbin
的地址,根据错位,此时 target_addr+0x8
处刚好有可能为 0x56
,满足一个chunk
的size
:
- 接着,由于我们之前修改了
unsortedbin
的bk
指向了target_addr
,系统会查找target_addr
是否合适;此时target_addr
的大小 刚好合适,所以就会分配target_addr
的堆块。我们成功实现了任意地址堆块分配。
- 泄露地址
此时,我们可以修改 mem_ptr
的数据,我们修改 mem_ptr[0]
为0,修改 mem_ptr[2]
和 mem_ptr[3]
满足 Show
函数要求,在 mem_ptr[4]
和 Mem_ptr[5]
分别写上 mem_Ptr
和 0x1000
。
随后使用 Show
泄露地址,使用 free_hook
来 getshell
。
EXP
1 | from pwn import * |
参考
large bin attack & house of strom
- 本文作者: A1ex
- 本文链接: http://yoursite.com/2020/10/07/house-of-storm/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!