2020羊城杯 PWN题。
Sign_in
程序分析

删除函数中,存在一个 doubel free漏洞,释放后没有将该处指针清空。
利用分析
首先申请一个大于 fastbin的块,并释放到unsortedbin中,然后申请一个小的堆块,并覆盖其低位,泄露libc地址;
然后使用 double free漏洞修改malloc_hooK 来 getshell。
EXP
1 | from pwn import * |
baby_pwn
程序分析

整体逻辑和上一题一样,不过这题没有了输出函数,需要我们自己想办法输出泄露地址。在 delete 函数中,仍然存在一个 double free 漏洞。
利用分析
- 泄露Libc地址
由于没有了输出函数,所以必须自己构造输出。可以先使用构造一个大的chunk,然后释放将其放入unsortedbin中,然后申请一个 0x60的 chunk0 划分该块。那么重新申请的chunk的 fd 和 bk 指针都会存储 main_arena+88 的地址,我们修改其fd 指针的 后两位为 \x25\xdd,使其有 1/16的几率碰撞到 stdout 结构体。
然后我们构造一个 double free 漏洞,chunk1->chunk2->chunk1,修改 chunk1 的fd 指针指向我们上面 构造的chunk0,随后再申请就可以成功分配到 stdout 结构体,我们修改 stdout 结构体的内容。就可以成功泄露出 Libc 地址。
- getshell
有了 libc 地址后,我们就可以使用我们上一题一致的 getshell的方法了。
EXP
1 | from pwn import * |
easy_heap
glibc是 2.29版本的,我们首先需要学习一点 glibc 2.29利用的前置知识。下面的知识点主要参考 这篇文章 总结而得。
2.29 chunk_overlap
2.29 下 对于 chunk_overlap 经常使用到的前向合并,增加了一个检测:
1 | /* consolidate backward */ |
其 检测了 prev_size 和前一块的 size值。对于 2.29 以前的版本,仅仅通过 prev_size找到前一个堆块,并没有这一步检测。而增加这步检测后,如果我们想直接更改 size 是十分困难的。但是,可以转变思路,在 heap 里伪造一个前向的 fake chunk,使其 size = fake prev_size,不过 前向合并时 还有一个检测:
就是前向合并时,会对前一块进行 unlink,里面的检测:
1 | if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) |
所以我们还需要伪造 前一块 P 的 FD块的 bk指针指向 P,BK 的 fd 指针指向 P。
为了达成这种伪造,可以充分利用 glibc 里 malloc 的分配技巧 和 free 时的数据残留,感觉这种利用方式对 glibc 的各种机制已经利用的极其深入,太优秀了。
本处结合本题,来说明如何绕过:
部署堆布局
由于程序中,存在一个 off-by-null,会在我们的输入结束处都增加一个 '\x00',所以我们首先需要将我们的 large chunk部署到 后两位为 0x00xx,为什么倒数第二字节必须是 0x00,是因为这样我们修改最后一字节来伪造地址时,off-by-null 漏洞覆盖倒数第二字节为 '\x00' 对我们的地址不影响。
1 | for i in range(7): # 0-6 |
由于,此处我的docker 环境启动的时候忘记开启 priveleged 模式了,导致没法关闭系统 ASLR,所以我下面部署的堆地址倒数第二字节并非是 '\x00'。但是除了最后结果会有影响外,其余整个部署过程并无差异。所以,就直接演示了。
刚开始 8个块都是为了将 chunk15 的倒数第二字节布置为 '\x00',堆喷操作。
然后申请的 chunk8到chunk14,都是后面为了防止 tcache干扰 会用到的填充chunk。
chunk15 是我们将会使用到的 chunk,其释放后首先会存在于 unsortedbin中,所以需要再申请一个 size 超过他的 chunk,使其被放入largebin中。被放入 largebin中后,其fd、bk、fd_nextsize 和 bk_nextsize 都会有值。如下图所示:

由于该 lagerbins 链表中只有一个 chunk,所以其 fd_nextsize 和 bk_nextszie 都会指向自身。而 fd 和 bk 指针则会指向 largebins 链表头。
部署fake_chunk 的 size和fd指针
然后,我们就需要划分上面的 large chunk,并布置我们的fake chunk 的size 和fd 指针:
1 | add(0x28) # idx:17 get a chunk from largebin |
申请一个 chunk,该chunk 会直接从 large chunk 中划分,然后我们布置 fake_chunk的size 为 0x521,并且将其 fd 指针指向 0x40 的位置,也就是物理相邻的下一块:

此时,fake_chunk 的 fd 指针指向其物理相邻的下一块,bk指针指向其物理相邻的上一块。接下来我们就要在 物理相邻的下一块BK 和上一块FD上 构造 fd 和 bk指针。
构造 FD 的bk指针
构造 FD 也就是上图以 0x40 结尾的块的 bk指针,首先我们需要在FD 指针的 bk位置处放入一个堆地址,然后覆盖这个堆地址的最后一字节来将 为 ‘\20’,来将这个 bk指针指向 fake_chunk。我们使用 smallbin 取堆块时,取得堆块的 bk处会残留堆地址的原理,来伪造我们需要的bk。
1 | add(0x28) # 18 |
首先需要继续在我们之前的 large chunk中划分堆块,此处的 chunk 18 即是 0x40 结尾的 FD堆块。然后填满对应size的tcache,然后依次释放 chunk20,chunk18,此时这两块都会被放入 fastbin中,链表为:chunk18->chunk20

接着,我们再将tcache清空,随后申请一个超过 smallbin 的chunk,此时 fastbin中的 chunk会被反过来放入 smallbin中,链表为:chunk20->chunk18。smallbin是通过bk指针从链表尾开始取堆块。

1 | # get a chunk from smallbin , another smallbin chunk to tcache |
最后,从smallbin中取出 FD chunk,并且修改其 bk 指针的 最后一字节为 ‘\x20’,使其指向 fake_chunk:

构造 BK 的fd指针
接下来,是对 BK 的 fd 指针构造,BK 按照我们之前的构造 是 0x10 结尾的块,根据上图我们可以看到其 fd 指针,其实也就是 fake_chunk 的 fd指针处。此时还为全 0。同 上一步的原则一样,我们首先需要在此处布置上一个 堆地址,然后修改其 最后一字节 使其指向 fake_chunk。此处,利用的是 fastbin 会在 fd 处残留 堆指针,不用 tcache 的原因是在 2.29以后 tcache 会在 bk处加上一个 key,这样会破坏我们之前构造的 fake size。
1 | # clear chunk from tcache |
首先将 之前遗留的 smallbin 清空和 tcache填满,最后依次释放 chunk19 和 chunk17(fake_chunk),此时两个chunk 都会进入 fastbin中,链表为:chunk17->chunk19。我们再将tcache 清空。再申请 chunk17(fake_chunk),此时 fake_chunk 的 fd 处还残留着 chunk 19 的地址。我们修改其最后一字节为 0x20,即可完成伪造。


chunk overlap
经过前面的伪造,我们已经成功伪造了 fake_chunk 的size,和 FD、BK块,他们能够成功满足以下unlink 检测。
1 | FD->bk = P |

紧接着,我们就只需要在fake_chunk 的后面,伪造 fake_prev_size 为 0x520,通过 off-by-null 修改堆头的 prev_inuse 为 0。然后释放堆块,即可完成 chunk_overlap。
1 | add(0x28) # 23 overwrite |

程序分析

在 edit 函数中,存在一个 off-by-null 漏洞。给的环境就是 2.29的。

开了沙箱,不能够执行 execve,只能够执行 orw链。
利用分析
chunk overlap泄露地址
利用上面所讲的 2.29下的 方法实现 chunk overlap,然后就可以泄露出 libc 地址。
- orw链读取 flag
此处还是运用传统的 setcontext函数调用 sigframe然后去执行 ROP 链。
EXP
1 | from pwn import * |
repwn
程序分析

delete 函数中,存在 double-free 漏洞。

并且有一个输出函数,可以输出一个 libc 地址和 栈地址,不过是经过加密的,解密即可输出。

开启了沙箱,禁用了 execve 函数,考虑通过 orw 来得到 flag。
利用分析
- 获取 libc 和 栈地址
首先对加密输出,进行解密,解密后得到 libc 地址 和 heap 地址。
- double_free 在栈上伪造 堆块
先通过 fastbin 的 double free 分配伪造的 堆块到栈上,然后在栈上部署 rop链。由于 程序中的 read 函数只有 0x68的长度,不够我们部署完整的 rop链,所以先调用 read 函数将我们的 rop链全都读到 栈上,然后再调用 ORW 去get flag。关于 ORW 沙箱的题 后面需要再整理一下,集中做一波题才行,太菜了。
EXP
EXP参考:https://bbs.pediy.com/thread-262024.htm
1 | from pwn import * |
参考
https://bbs.pediy.com/thread-257901.htm
https://bbs.pediy.com/thread-262024.htm
- 本文作者: A1ex
- 本文链接: http://yoursite.com/2020/09/25/2020羊城杯/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!