ASan
Address Sanitizer(简称ASan)是一个用于C/C++的内存错误检测。开启之后,会在目标代码的关键位置,如:malloc(), free(),栈上buffer分配等处检查代码,一旦发生内存访问错误,如堆栈溢出、UAF、double free等等,它就会发出错误的报告。它可以检测到一以下的问题:
- use after free
- heap buffer overflow
- stack buffer overflow
- gloable buffer overflow
- use after return
- use after scope
- initialization order bugs
- memory leaks
由于有些内存访问错误并不一定会造成程序崩溃,如越界读,因此在没有开启ASan的情况下,许多内存漏洞是无法被AFL发现的。所以,编译目标二进制代码时,开启ASan,也是推荐的做法。对于使用afl-xxx编译来说,只需要设定环境变量AFL_USE_ASAN=1即可。
不过,由于开启ASan后fuzzing会消耗更多内存,所以这也是需要考虑的因素之一。对于32位程序,基本上800MB即可;但64位程序大概需要20TB!所以,如果要使用ASan,建议添加CFLAGS=-m32指定编译目标为32位;否则,很有可能因为64位消耗内存过多,程序崩溃。
如果使用了ASan,还需要注意为afl-fuzz通过选项-m 指定可使用的内存上限。一般对于启用了ASan的32位程序,-m 1024即可。
ASan原理
ASan的原理是利用额外的内存标记可用的内存。这部分额外的内存被称作shadow memory(影子区)。ASan将1/8的内存用作shadow memory。使用特殊的magic num填充shadow memory,在每一次load/store(load/store检查指令由编译器插入)内存的时候检测对应的shadow memory确定操作是否是valid。连续8bytes内存使用1 byte shadow memeory标记。如果8 bytes内存都可以访问,则shadow memeory的值为0;如果连续n(1<=n<=7)bytes可以访问,则shadow memory的值为n;如果8 bytes内存访问都是invalid,则shadow memory的值为负数。以UAF漏洞作为例子分析,观察shadow memory的变化。
程序开始申请了一个对,大小为0x14,内存的起始地址为:0xf6100be0

第一次___asan_report_store4,此时shadow memory的布局如下:

在0x3ec2017c这个地址上有3 bytes的shadow memory是可访问的。但是随着后面操作的检测,会发现shadow在不断的变化 。
第二次___asan_report_store_n

第三次___asan_report_store1

进行___asan_report_load8

然后调用free函数释放内存
1 2 3 4 5 6 7 8 9 10 11 12 13
| 0x80488e4 <main+393> call printf@plt <0x80485a0> 0x80488e9 <main+398> add esp, 0x10 0x80488ec <main+401> sub esp, 0xc 0x80488ef <main+404> push dword ptr [ebp - 0x20] 0x80488f2 <main+407> call free@plt <0x80485e0> ► 0x80488f7 <main+412> add esp, 0x10 0x80488fa <main+415> mov eax, dword ptr [ebp - 0x1c] 0x80488fd <main+418> mov edx, eax 0x80488ff <main+420> shr edx, 3 0x8048902 <main+423> add edx, 0x20000000 0x8048908 <main+429> movzx edx, byte ptr [edx]
|
此时shadow memory的布局改变了,原来的0400被fdfd填充了,检测到shadow memory发生了异常

第二次调用___asan_report_load8是就会把shadow memory poison的信息report,如果此时在使用这块free的空间就会报错

ASan算法
ASan的内存检测的原理之一是编译时插桩。在编译时会插入检测代码,检查每个内存访问的影子状态,ASan会在局部数组、局部变量、全局变量前后填充特定字段,然后在程序运行过程中,ASan会对这些特定字段进行检查,确定是否发生内存越界。为了检测对全局和堆栈对象的溢出访问,ASan会在这些对象的周围创建毒性的区域(redzones)。
对于全局变量,在编译时创建redzones。并且在运用程序启动是将redzones的地址传递到运行时库。运行时库函数会标记并记录地址以进一步报告错误。
为了捕获堆栈缓冲区溢出,AddressSanitizer检测如下代码:
Original code:
1 2 3 4 5
| void foo() { char a[8]; ... return; }
|
Instrumented code:
1 2 3 4 5 6 7 8 9 10 11 12 13
| void foo() { char redzone1[32]; // 32-byte aligned char a[8]; // 32-byte aligned char redzone2[24]; char redzone3[32]; // 32-byte aligned int *shadow_base = MemToShadow(redzone1); shadow_base[0] = 0xffffffff; // poison redzone1 shadow_base[1] = 0xffffff00; // poison redzone2, unpoison 'a' shadow_base[2] = 0xffffffff; // poison redzone3 ... shadow_base[0] = shadow_base[1] = shadow_base[2] = 0; // unpoison all return; }
|
当检测8字节内存访问时,ASan计算相应影子字节的地址,加载该字节,并检查其是否为零:
1 2 3
| Shadowaddr = (Mem>>3) + Offset; if(*ShadowAddr!=0) ReportAndCrash(Mem);
|
当检测1,2或4字节访问时,检测则稍微的复杂:如果阴影值为正(即,只有8字节中的前k个字节是可寻址的),我们需要比较k与这个字节的后3位。
1 2 3 4
| Shadow = (Mem>>3) + Offset; k = *ShadowAddr; if(k!=0 &&((Mem&7) + AccessSize>k)) ReportAndCrash(Mem);
|
64位
Shadow = (Mem >> 3) + 0x7fff8000;
[0x10007fff8000, 0x7fffffffffff] | HighMem
[0x02008fff7000, 0x10007fff7fff] | HighShadow
[0x00008fff7000, 0x02008fff6fff] | ShadowGap
[0x00007fff8000, 0x00008fff6fff] | LowShadow
[0x000000000000, 0x00007fff7fff] | LowMem
32位
Shadow = (Mem >> 3) + 0x20000000;
[0x40000000, 0xffffffff] | HighMem
[0x28000000, 0x3fffffff] | HighShadow
[0x24000000, 0x27ffffff] | ShadowGap
[0x20000000, 0x23ffffff] | LowShadow
[0x00000000, 0x1fffffff] | LowMem
ASan的用途
- use after free
example1.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include<stdio.h> struct elem { char *e; int a; double b; char c; }; int main() { char *p = malloc(sizeof(char) * 10); if (p == NULL) { return 0; } struct elem *e = malloc(sizeof(struct elem)); if (e == NULL) { free(p); return 0; } e->a = 10; e->b = 10.10; e->c = p; double *xx = &e->b; printf("%f\n", *xx); free(e); printf("%f\n", *xx); return 0; }
|
编译:afl-gcc uaf.c -o uaf -m32 -fsanitize=address
直接运行,它会自动打印检测到的内存错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| 10.100000 ================================================================= ==102558==ERROR: AddressSanitizer: heap-use-after-free on address 0xf6100be8 at pc 0x08048936 bp 0xffffd078 sp 0xffffd068 READ of size 8 at 0xf6100be8 thread T0 #0 0x8048935 in main /home/ruirui/Desktop/uaf.c:31 #1 0xf78bc636 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18636) #2 0x80489ab (/home/ruirui/Desktop/uaf+0x80489ab) 0xf6100be8 is located 8 bytes inside of 20-byte region [0xf6100be0,0xf6100bf4) freed by thread T0 here: #0 0xf7af0a84 in free (/usr/lib32/libasan.so.2+0x96a84) #1 0x804884b in main /home/ruirui/Desktop/uaf.c:30 #2 0xf78bc636 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18636) previously allocated by thread T0 here: #0 0xf7af0dee in malloc (/usr/lib32/libasan.so.2+0x96dee) #1 0x8048820 in main /home/ruirui/Desktop/uaf.c:17 #2 0xf78bc636 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18636) SUMMARY: AddressSanitizer: heap-use-after-free /home/ruirui/Desktop/uaf.c:31 main Shadow bytes around the buggy address: 0x3ec20120: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3ec20130: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3ec20140: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3ec20150: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3ec20160: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa =>0x3ec20170: fa fa fa fa fa fa fa fa fa fa fa fa fd[fd]fd fa 0x3ec20180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3ec20190: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3ec201a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3ec201b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3ec201c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Heap right redzone: fb Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack partial redzone: f4 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe ==102558==ABORTING
|
根据打印的结果可以看到,程序提示uaf.c第31行有个heap-use-after-free的错误,而且在最后还有个summary,把出错的代码位置和相应的栈信息打了出来。
- double-free
example2.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| #include<stdio.h> struct elem { char *e; char *e2; int a; double b; char c; }; int main() { char *p = malloc(sizeof(char) * 10); if (p == NULL) { return 0; } struct elem *e = malloc(sizeof(struct elem)); struct elem *e2 = e; if (e == NULL) { free(e); free(e2); return 0; } e->a = 10; e->b = 10.10; e->c = p; double *xx = &e->b; printf("%f\n", *xx); free(e); free(e2); printf("%f\n", *xx); return 0; }
|
在上面的代码上稍微改了下,使其存在double free漏洞。使用相同的命令编译程序,运行观察程序的结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 10.100000 ================================================================= ==102748==ERROR: AddressSanitizer: attempting double-free on 0xf6100be0 in thread T0: #0 0xf7af0a84 in free (/usr/lib32/libasan.so.2+0x96a84) #1 0x8048848 in main /home/ruirui/Desktop/double_free.c:34 #2 0xf78bc636 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18636) #3 0x80488fb (/home/ruirui/Desktop/double_free+0x80488fb) 0xf6100be0 is located 0 bytes inside of 24-byte region [0xf6100be0,0xf6100bf8) freed by thread T0 here: #0 0xf7af0a84 in free (/usr/lib32/libasan.so.2+0x96a84) #1 0x8048840 in main /home/ruirui/Desktop/double_free.c:33 #2 0xf78bc636 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18636) previously allocated by thread T0 here: #0 0xf7af0dee in malloc (/usr/lib32/libasan.so.2+0x96dee) #1 0x8048819 in main /home/ruirui/Desktop/double_free.c:18 #2 0xf78bc636 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18636) SUMMARY: AddressSanitizer: double-free ??:0 free ==102748==ABORTING
|
在程序的第34行存在double-free漏洞
- off-by-one
example3.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include<stdio.h> int my_gets(char *ptr ,int size) { int i; for(i=0; i<=size; i++) { ptr[i] = getchar(); } return i; } int main() { void *chunk1, *chunk2; chunk1 = malloc(16); chunk2 = malloc(16); puts("get input:"); my_gets(chunk1,16); return 0; }
|
编译后运行会提示存在heap-buffer-overflow漏洞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| get input: aaaaaaaaaaaaaaaaa ================================================================= ==21527==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xf5c00800 at pc 0x080486ef bp 0xff9d67d8 sp 0xff9d67c8 WRITE of size 1 at 0xf5c00800 thread T0 #0 0x80486ee in my_gets (/home/ruirui/Desktop/off-by-one+0x80486ee) #1 0x8048758 in main (/home/ruirui/Desktop/off-by-one+0x8048758) #2 0xf701b636 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18636) #3 0x80485c0 (/home/ruirui/Desktop/off-by-one+0x80485c0) 0xf5c00800 is located 0 bytes to the right of 16-byte region [0xf5c007f0,0xf5c00800) allocated by thread T0 here: #0 0xf724fdee in malloc (/usr/lib32/libasan.so.2+0x96dee) #1 0x8048725 in main (/home/ruirui/Desktop/off-by-one+0x8048725) #2 0xf701b636 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18636) SUMMARY: AddressSanitizer: heap-buffer-overflow ??:0 my_gets Shadow bytes around the buggy address: 0x3eb800b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3eb800c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3eb800d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3eb800e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3eb800f0: fa fa fa fa fa fa fa fa fa fa 00 00 fa fa 00 00 =>0x3eb80100:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3eb80110: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3eb80120: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3eb80130: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3eb80140: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3eb80150: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Heap right redzone: fb Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack partial redzone: f4 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal:
|