ASan原理及用途

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的布局改变了,原来的0400fdfd填充了,检测到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的用途

  1. 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,把出错的代码位置和相应的栈信息打了出来。

  1. 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漏洞

  1. 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: