背景
復(fù)現(xiàn) 0ctf_2018 的 babyheap是碰見(jiàn)了十分奇怪的情況:
經(jīng)過(guò)一系列操作之后, 我需要fastbin attack 在main_arena中偽造一個(gè)fake chunk, 進(jìn)而修改top chunk的值, 從而達(dá)到任意地址寫(xiě)的目的.
經(jīng)過(guò)一系列操作, 我之一使得fast bin中0x50項(xiàng)中的指針指向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
照理說(shuō)我接下來(lái)調(diào)用一次alloc(0x40)
就可以得到一個(gè)以0x7f707e3bfb45
開(kāi)始的chunk了吧, 但是卻直接得到個(gè)錯(cuò)誤.
原因分析
經(jīng)過(guò)漫長(zhǎng)的debug之后我終于發(fā)現(xiàn)問(wèn)題出在fake chunk的size上了......0x55是不行滴.
經(jīng)過(guò)一系列的測(cè)試, 我發(fā)現(xiàn)當(dāng)fake chunk 的size為0x54, 0x55, 0x5c, 0x5d
的時(shí)候, 調(diào)用alloc(0x40)
都會(huì)報(bào)錯(cuò), 而其它的值則都沒(méi)有問(wèn)題.
那么這些值有什么共同點(diǎn)呢?
0x54 = 0101 0100b
0x55 = 0101 0101b
0x5c = 0101 1100b
0x5d = 0101 1101b
觀察它們的二進(jìn)制值發(fā)現(xiàn)它們的低數(shù)第3位都是1, 而根據(jù)定義: 這一位置1表示這個(gè)chunk不屬于main_arena. 而為什么這么就會(huì)報(bào)錯(cuò)的原因還是要看看libc源碼(省略了一些不必要的):
#define arena_for_chunk(ptr) \
(chunk_main_arena (ptr) ? &main_arena : heap_for_ptr (ptr)->ar_ptr) //根據(jù)chunksize地書(shū)第三位判斷是否屬于main_arena
void *
__libc_malloc (size_t bytes)
{
mstate ar_ptr;
void *victim;
arena_get (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes); //經(jīng)過(guò)debug可確定問(wèn)題出在_int_malloc之后
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
ar_ptr == arena_for_chunk (mem2chunk (victim))); //如果低數(shù)第三位置一, 則arena_for_chunk返回的肯定不是&main_arena
return victim;
}
因?yàn)榈蛿?shù)第三位置一, 所以arena_for_chunk返回的肯定不是&main_arena, 所以 ar_ptr == arena_for_chunk (mem2chunk (victim)))
為false
, 由于三個(gè)判斷之間為或關(guān)系, 所以當(dāng)chunk_is_mmapped (mem2chunk (victim))
返回true
(即低數(shù)第二位置一)時(shí), 也不會(huì)報(bào)錯(cuò). 所以只有上面四個(gè)值會(huì)報(bào)錯(cuò).
更新: 利用fastbin attack時(shí)構(gòu)造fakechunk 的size時(shí)又遇到了一個(gè)坑, 也卡了挺長(zhǎng)時(shí)間的
背景:
這次是在線程中調(diào)用malloc(), 然后我在bss段有64字節(jié)的可讀寫(xiě)段(稱其為A, 設(shè)其地址為 0x6020000), 我也知道這段的地址. 而且我也已經(jīng)可以覆蓋線程的heap_info 和 malloc_state了, 我的目的就是在A中構(gòu)造一個(gè)fakechunk, 從而可以覆蓋A后面的一些關(guān)鍵數(shù)據(jù). 示意圖如下:
bss: 0x602000(可寫(xiě)) heap info(可寫(xiě))
+----------^+-----------------+ +-----> +-----------------+
| | | |
+-----------------+ | |
| | malloc state(可寫(xiě)) |
+-----------------+ +-----> +-----------------+
| | | |
+-----------------+ | |
| | | |
+-----------------+ | |
| | | |
| | | |
| | | |
| | | |
+-----------------+ | |
+-----------------+
我的目的是malloc一個(gè)0x60大小的chunk. 所以我就將0x602000的8字節(jié)覆蓋為0x65:
這很合理, 將因?yàn)槭蔷€程分配的fast bin, 所以將A 和 P位設(shè)為1, 其余為設(shè)為0. 然后得到了SIGSEGV , 經(jīng)過(guò)debug發(fā)現(xiàn)_libc_malloc()中有一個(gè)檢測(cè):
//return_ptr 是調(diào)用 int_malloc返回的指向chunk內(nèi)容的指針
if ( return_ptr )
{
prev_size_ptr = *(_QWORD *)(return_ptr - 8);
if ( prev_size_ptr & 2 ) //檢測(cè)是否為 mmap的
return return_ptr;
v6 = &dword_7F5FC1CEAB20;
if ( prev_size_ptr & 4 ) //檢測(cè)是否為子線程的
v6 = *(int **)((return_ptr - 16) & 0xFFFFFFFFFC000000LL); //如果為子線程則計(jì)算該線程的heap info 的地址
if ( v6 == (int *)_RBX ) //檢測(cè)chunk的heap_info的地址是否合法(具體檢測(cè)原理不懂)
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");
程序會(huì)在第9行報(bào)SIGSEGV錯(cuò)誤, 因?yàn)橐驗(yàn)樵摰刂凡豢勺x.
通過(guò)觀察第5行可知如果把fakechunk的size的M位也設(shè)為1的話就可以通過(guò)檢測(cè)了.
于是乎將0x602000的8字節(jié)覆蓋為0x6f: 在設(shè)置一下對(duì)應(yīng)的fastbin, 成功得到fakechunk.
關(guān)于heapinfo可以參考這篇文章