看雪.WiFi万能钥匙 CTF 2017第四题 点评及解题思路

发布者:Editor
发布于:2017-06-29 17:30


今天周五了!看雪CTF 2017 比赛进行至第四题

截止至今天中午12点,第四题破解人数为14人!


攻击方排名前十名稍有变动,比赛排名有人升、有人降。

有两位选手表现格外引人关注,

loudy从第9名升至第5名!(鼓掌ing)

NearJMP进入前十。

六月骄阳似火,也抵不过选手们的热情。

大家继续加油!

接下来我们来回顾一下第四题

看看看雪评委和出题者是怎么说的ヾ(๑╹◡╹)ノ"



看雪评委 netwind 点评


作者设计了一个存在 double free 和 uaf 漏洞的程序,攻击者需要利用 fastbin 的 malloc_consolidate 函数来造成 unlink,最后再布置栈构造 ropchain 即可完成攻击。作为一道漏洞挖掘和利用的题目,引起了大家广泛关注,成功破解该题人数达14人,并且漏洞利用各有千秋,甚至有人通过作者疏忽利用栈溢出破解了此题,攻防双方表现都非常精彩!


作者简介:


Ree,大二在读,CTF比赛 Pwn 选手,兴趣广泛,热爱漏洞挖掘与利用。目前主要研究方向为二进制自动化分析。


看雪 CTF2017 第四题设计思路


考点

malloc_consolidate+unlink+rop


 运行环境

1. amd64(作者本地测试为kali,远程测试环境为ubuntu16.04),无libc要求,已经上传远程测试libc

2. 开启aslr


编译方式

gcc -no-pie main.c -o main ; strip main


题目说明

题目可以给出 bin 与 libc 供下载,也可以仅给出 bin 文件,通过查找 libc-db 的方式找到对应 libc 版本号(作为一只 pwn 狗,建议给出 libc,省去大家查 libc 的浪费的生命)


设计思路:

1. 首先给用户输入姓名,此时以 malloc 一个 chunk 的方式存储用户的输入

2. 给了 4 个功能,create、delete、edit、show,其中show功能无效

3. create 函数可以申请一个小于 4096 字节的 chunk,并往里面写入数据,然后置flag位为1,同时用一个全局变量 number 来记录已申请的 chunk 个数,number不得大于4。

4. delete 函数可以 free 一个指针并置 flag 位为 0,但是不检查是否已经 free 这个指针。

5. edit 检查 flag 位,只能修改已经 flag 为 1 的 chunk。

6. 数据结构如下:

漏洞点

1. uaf,在dele一个指针后没有清零,可以再次 free 这个 freed 的指针。

2. 漏洞什么的。


漏洞利用

1. 主要利用 fastbin 的 malloc_consolidate 这个函数来造成 unlink,后面再布置栈构造 ropchain 即可。

2. 在申请 large bin 的时候,会将 freed fastbin 的 inuse 位清零,同时进行合并,将合并后的堆块放入 unsortbins 中。然后遍历 unsortbins,按照大小分别放入 smallbins 和 largebins 中。

3. 这时利用 uaf,free 一个 fastbin,也就是我们刚才申请的 0x30 大小的块,将它链入 fastbins 的单向链表中。因为当我们从fastbins中分配不会置后一个 chunk 的 inuse位为1,但是由于之前的 malloc_consolidate 已经使得该 fastbin 的后一个 chunk 的 inuse 位为 0,所以造成一个矛盾,是的我们能够 unlink。

4. 分配一个 0x20 大小的 chunk,置后一个 chunk 的 presize 位为我们刚才分配的fastbin 的大小,为 unlink 做准备,0x20 会从之前合并的 smallbins 中切割,然后该 smallbins 移到 unsortbins 中,并成为 last_remainder。

5. 修改 0x30 的 fastbin,并填充 unlink 的 payload,再在之后分配一个 smallbin 大小的 chunk,使得 unsortbin 中的 chunk 移到smallbins(因为 unsortbins 有一个 check,会检查第一个 chunk 的 bk 是否指向 unsortbin 的头),最后 delete 触发 unlink。

6. 接下来的事情就简单了,修改 fflush 的 got 表为 add rsp 8; ret 的 gadget,然后用rop 泄露 libc 基址,最后修改 free_got 为 system 即可。

7. free 一个事先写好 /bin/sh\0 的 chunk 拿 shell 即可。

PS:

因为可以 malloc 任意大小,还有 uaf,漏洞略大,但已经尽力避免未预料解法了。

PPS:

好像国内的 CTF 还没见过考 malloc_consolidate的,也可能我比赛打的少了XD...



下面选取攻击者 BPG 的破解分析


一、ALL

这题也是一般堆溢出的流程,主要有下面几个操作:

1. 创建


这一段代码比较多,但可以看到问题还是存在的,size和cun都是可以通过输入负数,接着通过强类型转换`unsigned int`来转换成很大的整数来整形溢出。

2. 删除

可以看到这里也是存在问题的,删除之前没有判断块是否被删除了,所以应该可以构造`double-free`

3. 编辑

这里就是正常的编辑,不过验证了块是否存在。

二、DOUBLE-FREE

一年前做过一道`double-free`之后就再也没碰过了,这里正好把相关知识点再温习一下。

堆结构如下,其中0,1,2,3表示一个单位长度。


正常`unlink`的函数主要代码如下:

如果能覆盖一个块的头部来达到控制其`bk`和`fd`的话,就能够成功修改任意地址的值了,但由于存在判断`FD->bk != P || BK->fd != P`,所以没法随便修改,这时就需要利用`double-free`了。

首先判断是需要通过的,所以可以有下面两个等

FD->bk = P   <=> FD+3 = P
BK->fd = P   <=> BK+2 =

接着就是构造一个堆头满足这两个等式了

pre_size     0
size         chunk_size + 1 //这两个条件表示堆仍在使用中
fd           p_addr - 3
bk           p_addr - 2

那么就能得到绕过判断后能实现的效果

FD->bk = BK => P = BK = P - 2
BK->fd = FD => P = FD = P - 3

所以最后实现的效果是`P=P-3`。

接着就是实现`double-free`的流程了,稍微盗一下图...

1. 新建两个 chunk 分别为 chunk0 和 chunk1

2. free 掉这两个块,第一块作为稍后 unlink 的块,另一块作为 free 的块

3. 新建一个块,这个块要能覆盖到 chunk1 的头部,从而伪造两个 chunk 的头,chunk0 的头部之前已经说过了,而 chunk1 需要欺骗操作系统 chunk0 是已经被free 了的,所以其头部应该如下

pre_size     chunk0_size
size         chunk1_size

然后整体实现如下:

4. 那么新建这个块就相当于是两个块了,然后我们 free chunk1,就能实现 unlink chunk0 从而使得 chunk0 的地址存放的值变成了 P-0x18

5. 接下来修改 chunk0 的值,修改的值为 0x18 个 padding,然后就能修改 chunk0 的地址的值了,这时修改成 free 的 GOT 表地址

6. 接下来再修改一次chunk0, 这次的值修改成 system 那么就会使得 free 的 GOT 表指向 system,接下来 free 的时候传入 /bin/sh 就能获取 shell 了

三、 EXP

在调试 EXP 过程中出现了这样俩个问题,需要注意一下。

第一个是这里的 P 指的是指向堆地址的指针,也就是`0x6020e0`,不要误以为是堆的地址哦。

第二个在这里我是把`/bin/sh`写在第0个堆,而用2,3来进行`double-free`,主要原因是由于在调试的过程中发现把`/bin/sh`写后面会被系统发现`double-free`。

具体EXP:



最后感谢 WiFi 万能钥匙安全应急响应中心的赞助支持,

接下来的比赛大家一定要使出洪荒之力哦!↖(^ω^)↗

比心


赞助商

上海连尚网络科技有限公司成立于 2013 年,是一家专注于提供免费上网和内容服务的移动互联网企业。连尚网络自主研发的核心产品 WiFi 万能钥匙,以分享经济的模式,通过云计算和大数据技术,利用热点主人分享的闲置WiFi资源,为用户提供免费、稳定、安全的上网服务,以帮助更多的人上网,找到属于他们的机会,改变自己的命运。



声明:该文观点仅代表作者本人,转载请注明来自看雪