背景
复现 0ctf_2018 的 babyheap是碰见了十分奇怪的情况:
经过一系列操作之后, 我需要fastbin attack 在main_arena中伪造一个fake chunk, 进而修改top chunk的值, 从而达到任意地址写的目的.
经过一系列操作, 我之一使得fast bin中0x50项中的指针指向0x7f707e3bfb45
, 其指向空间如下:
0x7f707e3bfb45 <main_arena+37>: 0x00ca23e0d000007f 0x0000000000000055
0x7f707e3bfb55 <main_arena+53>: 0x0000000000000000 0x0000000000000000
0x7f707e3bfb65 <main_arena+69>: 0x0000000000000000 0x0000000000000000
0x7f707e3bfb75 <main_arena+85>: 0x00ca23e1a0000000 0x00ca23e130000056
照理说我接下来调用一次alloc(0x40)
就可以得到一个以0x7f707e3bfb45
开始的chunk了吧, 但是却直接得到个错误.
原因分析
经过漫长的debug之后我终于发现问题出在fake chunk的size上了......0x55是不行滴.
经过一系列的测试, 我发现当fake chunk 的size为0x54, 0x55, 0x5c, 0x5d
的时候, 调用alloc(0x40)
都会报错, 而其它的值则都没有问题.
那么这些值有什么共同点呢?
0x54 = 0101 0100b
0x55 = 0101 0101b
0x5c = 0101 1100b
0x5d = 0101 1101b
观察它们的二进制值发现它们的低数第3位都是1, 而根据定义: 这一位置1表示这个chunk不属于main_arena. 而为什么这么就会报错的原因还是要看看libc源码(省略了一些不必要的):
#define arena_for_chunk(ptr) \
(chunk_main_arena (ptr) ? &main_arena : heap_for_ptr (ptr)->ar_ptr) //根据chunksize地书第三位判断是否属于main_arena
void *
__libc_malloc (size_t bytes)
{
mstate ar_ptr;
void *victim;
arena_get (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes); //经过debug可确定问题出在_int_malloc之后
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
ar_ptr == arena_for_chunk (mem2chunk (victim))); //如果低数第三位置一, 则arena_for_chunk返回的肯定不是&main_arena
return victim;
}
因为低数第三位置一, 所以arena_for_chunk返回的肯定不是&main_arena, 所以 ar_ptr == arena_for_chunk (mem2chunk (victim)))
为false
, 由于三个判断之间为或关系, 所以当chunk_is_mmapped (mem2chunk (victim))
返回true
(即低数第二位置一)时, 也不会报错. 所以只有上面四个值会报错.
更新: 利用fastbin attack时构造fakechunk 的size时又遇到了一个坑, 也卡了挺长时间的
背景:
这次是在线程中调用malloc(), 然后我在bss段有64字节的可读写段(称其为A, 设其地址为 0x6020000), 我也知道这段的地址. 而且我也已经可以覆盖线程的heap_info 和 malloc_state了, 我的目的就是在A中构造一个fakechunk, 从而可以覆盖A后面的一些关键数据. 示意图如下:
bss: 0x602000(可写) heap info(可写)
+----------^+-----------------+ +-----> +-----------------+
| | | |
+-----------------+ | |
| | malloc state(可写) |
+-----------------+ +-----> +-----------------+
| | | |
+-----------------+ | |
| | | |
+-----------------+ | |
| | | |
| | | |
| | | |
| | | |
+-----------------+ | |
+-----------------+
我的目的是malloc一个0x60大小的chunk. 所以我就将0x602000的8字节覆盖为0x65:
这很合理, 将因为是线程分配的fast bin, 所以将A 和 P位设为1, 其余为设为0. 然后得到了SIGSEGV , 经过debug发现_libc_malloc()中有一个检测:
//return_ptr 是调用 int_malloc返回的指向chunk内容的指针
if ( return_ptr )
{
prev_size_ptr = *(_QWORD *)(return_ptr - 8);
if ( prev_size_ptr & 2 ) //检测是否为 mmap的
return return_ptr;
v6 = &dword_7F5FC1CEAB20;
if ( prev_size_ptr & 4 ) //检测是否为子线程的
v6 = *(int **)((return_ptr - 16) & 0xFFFFFFFFFC000000LL); //如果为子线程则计算该线程的heap info 的地址
if ( v6 == (int *)_RBX ) //检测chunk的heap_info的地址是否合法(具体检测原理不懂)
return return_ptr;
((void (__fastcall *)(const char *, const char *, signed __int64, const char *))assert)(
"!victim || chunk_is_mmapped (mem2chunk (victim)) || ar_ptr == arena_for_chunk (mem2chunk (victim))",
"malloc.c",
2927LL,
"__libc_malloc");
程序会在第9行报SIGSEGV错误, 因为因为该地址不可读.
通过观察第5行可知如果把fakechunk的size的M位也设为1的话就可以通过检测了.
于是乎将0x602000的8字节覆盖为0x6f: 在设置一下对应的fastbin, 成功得到fakechunk.
关于heapinfo可以参考这篇文章
网友评论