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 许可协议。转载请注明出处!