6.1.22 pwn HITCONCTF2016 Sleepy_Holder
题目复现
$ file SleepyHolder
SleepyHolder: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=46f0e70abd9460828444d7f0975a8b2f2ddbad46, stripped
$ checksec -f SleepyHolder
RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH Yes 0 2 SleepyHolder
$ strings libc-2.23.so | grep "GNU C"
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu3) stable release version 2.23, by Roland McGrath et al.
Compiled by GNU CC version 5.3.1 20160413.64 位程序,开启了 Canary 和 NX,默认开启 ASLR。
在 Ubuntu-16.04 上玩一下:
$ ./SleepyHolder
Waking Sleepy Holder up ...
Hey! Do you have any secret?
I can help you to hold your secrets, and no one will be able to see it :)
1. Keep secret
2. Wipe secret
3. Renew secret
1
What secret do you want to keep?
1. Small secret
2. Big secret
3. Keep a huge secret and lock it forever
1
Tell me your secret:
AAAA
1. Keep secret
2. Wipe secret
3. Renew secret
1
What secret do you want to keep?
1. Small secret
2. Big secret
3. Keep a huge secret and lock it forever
3
Tell me your secret:
CCCC
1. Keep secret
2. Wipe secret
3. Renew secret
3
Which Secret do you want to renew?
1. Small secret
2. Big secret
1
Tell me your secret:
BBBB
1. Keep secret
2. Wipe secret
3. Renew secret
2
Which Secret do you want to wipe?
1. Small secret
2. Big secret
1这一题看起来和上一题 Secret_Holder 差不多。同样是 small、big、huge 三种 secret,不同的是这里的 huge secret 是不可修改和删除的。
题目解析
下面我们逐个来逆向这些功能。
Keep secret
还是一样的,该函数使用 calloc() 为三种 secret 分别了不同大小的 chunk,small secret 属于 small chunk,big secret 和 huge secret 属于 large chunk。在分配前,会检查对应的 secret 是否已经存在,即每种 chunk 只能有一个。另外看函数开头部分,huge secret 显然受到了特殊处理。
small secret: small chunk, 40 bytes
small_ptr: 0x006020d0
small_flag: 0x006020e0
big secret: large chunk, 4000 bytes
big_ptr: 0x006020c0
big_flag: 0x006020d8
huge secret: large chunk, 400000 bytes
huge_ptr: 0x006020c8
huge_flag: 0x006020dc
Wipe secret
该函数只能释放 small secret 和 big secret。释放的过程首先将对应的 chunk 释放掉,然后设置对应 flag 为 0。漏洞很明显,就是没有将 chunk 指针清空,存在悬指针,可能导致 use-after-free,然后在释放前,也没有检查 flag,可能导致 double-free。
Renew secret
该函数只能对 small secret 和 big secret 进行修改,所以 huge secret 就是一次分配,永远存在且内容不可修改了。过程是首先判断对应的 flag 是否为 1,即 secret 是否已经存在,如果不存在,则读入 secret,否则函数直接返回。
漏洞利用
总结一下我们知道的东西:
small secret: small chunk, 40 bytes
small_ptr: 0x006020d0
small_flag: 0x006020e0
big secret: large chunk, 4000 bytes
big_ptr: 0x006020c0
big_flag: 0x006020d8
huge secret: large chunk, 400000 bytes
huge_ptr: 0x006020c8
huge_flag: 0x006020dc
漏洞:
double-free:在 free chunk 的位置 calloc 另一个 chunk,即可再次 free 这个 chunk
use-after-free:由于 double-free,calloc 出来的那个 chunk 被认为是 free 的,但可以使用
看到这里该题与上一题的差别很明显了,就是我们没有办法再通过 keep(huge) -> wipe(huge) -> keep(huge) 来利用 brk() 分配内存,制造 unsafe unlink。
然后我们又在 _int_malloc() 中发现了另一个东西:
当需求 chunk 是一个 large chunk 时,glibc 会将把 fastbins 中的 chunk 移除,设置 PREV_INUSE 为 0,合并 free chunk,然后放到 unsorted bin。接着 glibc 尝试从 unsorted bin 中取出 chunk,由于大小不合适,这些 chunk 又被放到 small bin 中:
这时就可以再次释放 small secret 而不触发 double-free 的检测。
那么为什么一定要将 small secret 放进 small bin 呢?因为当 chunk 被放进 small bin 时,会相应的修改 next chunk(即big secret)的 chunk header(设置prev_size,PREV_INUSE置0),而当 chunk 被放进 fastbins 时是不会有这样的操作的。接下来我们需要通过 double-free 将 small secret 再次放进 fastbins(这时small secret同时存在于fastbins和small bin中),再从 fastbins 中取出 small secret,原因和上面类似,从 fastbins 中取出 chunk 不会设置 next chunk 的 chunk header。这样我们才能正确地触发 unlink。
unsafe unlink
制造 double-free:
上面的过程一方面通过 malloc_consolidate 设置了 big secret 的 PREV_INUSE,另一方面通过 double-free 将 small secret 放进 fastbins。
在 small secret 中布置上一个 fake chunk:
释放 big secret 即可触发 unsafe unlink:
于是我们就获得了修改 .bss 段的能力。
后面的过程就和上一题完全一样了。
leak libc
pwn
开启 ASLR,Bingo!!!
exploit
完整的 exp 如下:
参考资料
Last updated
Was this helpful?