ASan结果分析
ASan简介
Address Sanitizer(ASan):是一个快速的C/C++内存错误检查器
该工具由一个编译器检测模块和一个提供内存操作函数(malloc/free)替代run-time库
该工具适用于x86,ARM,MIPS,PowerPC64架构,支持Linux,Darwin(OS X和IOS模拟器),FreeBSD,Android
ASan可以检测:
漏洞类型 |
---|
UAF |
Heap buffer overflow |
Stack buffer overflow |
Global buffer overflow |
Use after return |
Use after scpoe |
Initialization order bugs |
Memory leak |
demo分析
来自https://hollk.blog.csdn.net/?type=blog
stack buffer overflow
int main(int argc, char **argv) {
int closure[100];
closure[1] = 0;
return closure[argc + 100]; // overflow
}
编译:
clang -O -g -fsanitize=address stackdemo.cpp -o stackdemo
定义了一个int类型的,长度为100的数组==》int为4字节==》closure数组占400个字节
在最后返回closure[argc + 100]出现溢出==》argc+100超出了栈空间的大小
运行编译后的程序==》ASan显示检测日志:
初次分析日志内容:
==2045857==表示检测到问题的进程号为2045867
ERROR后面为检测到的漏洞类型:stack-buffer-overflow
漏洞所在地址为0x7fffffffdf34
漏洞触发时寄存器:
PC:0x0000004c32f4
bp:0x7fffffffdd70
sp:0x7fffffffdd68
描述主要采用READ操作,在线程T0的0x7fffffffdf34栈地址处读取大小为4的数据
#0,1,2为函数栈
漏洞地址0x7fffffffdf34位于T0线程的栈中的栈帧偏移量为436处,并指出被溢出的变量
其余部分为总结和警告
shaow字节图:
其中的一个字节表示源程序内存中的8个字节
该图为对shadow字节图解释:
ASan会对程序中的内存空间右侧或者两侧添加一个不可写区域redzone
f1为左侧增加区域
f3为右侧增加区域
在shadow图中f1与f3中的00有50个字节,每一个字节又代表源程序中的8个字节==》
对应内存中50*8 = 400个字节==》closure[100]开辟的空间大小
在00后第一个f3为**[f3]**:
表示该字节被溢出==》
在运行程序时没有添加任何参数==》argc为1(stackdemo参加计数)==》
所以返回为closure[101]==》溢出4个字节==》[f3]
==》日志分析总结:
被溢出变量:closure
溢出点所在地址:0x7fffffffdf34
溢出点距离变量起始点偏移:436
global-buffer-overflow
溢出全局变量部分的内存
int hollk[100] = {-1};
int main(int argc, char **argv) {
return hollk[argc + 100]; // overflow
}
编译:
clang -O -g -fsanitize=address glo.cpp -o glo
创建了一个int型数组closure[100]==》400字节
argc作为输入参数计数,即使运行程序时不添加额外的参数,argc也会将glo.cpp作为一个计数==》
return返回closure[argc+100]==》closure[101]==》超过closure数组的内存空间
编译后运行:
根据ASan日志分析:
溢出点位于:0x0000004f6d34
寄存器:
pc:0x0000004c3142
bp:0x7ffd2860b3b0
sp:0x7ffd2860b3a8
存在溢出点的全局变量为closure,溢出4个字节
shadow中f9为全局变量右侧添加的redzone,溢出4字节==》[f9]
stack-use-after-scope
局部变量在脱离作用域后再次被使用
volatile int *p = 0;
int main() {
{
int closure = 0;
p = &closure;
}
*p = 5;
return 0;
}
编译:
clang -O -g -fsanitize=address clo.cpp -o clo
使用volatile关键字创建int指针p,赋值为0
创建一个int类型的变量closure,并赋值为0==》局部变量
取closure变量地址赋值给指针p==》main函数结束,修改p指针指向的位置的值为5==》
closure变量中的值变为5==》因为closure是一个局部变量,超过了main函数的作用范围后发生了变化==》
触发stack-use-after-scope漏洞
volatile:提醒编译器该处创建的指针p是会发生变化的,编译后每一次读取或存储p指针,都将会重新进行读操作
例:
A,B两个线程同时读取p指针0x7ffffab0中的值==》
此时A读取的的是地址0x7ffffab0中的值0,B读取的也是0x7ffffab0处的0==》
使用该关键字:
如果A将p指针修改为0x7ffffcd0并将其中的值修改为1==》
B将重新读取指针p的地址0x7ffffcd0,再读取地址内的值1
未使用该关键字:
如果A将p指针修改为0x7ffffcd0并将其中的值修改为1==》
B将读取0x7ffffab0处的0
编译后运行:
ASan日志分析:
漏洞出现地址:0x7ffc238179a0
寄存器:
pc 0x0000004c3201
bp 0x7ffc23817970
sp 0x7ffc23817968
出现漏洞的变量closure
[f8]代表closure变量所在位置出现漏洞
stack-use-after-return
与stack-use-after-scope类似,都是局部变量脱离了作用域再次进行操作
int *ptr;
__attribute__((noinline))
void FunctionA() {
int closure[100];
ptr = &closure[0];
}
int main(int argc, char **argv) {
FunctionA();
return ptr[argc];
}
编译:
clang -O -g -fsanitize=address clo.cpp -o clo
首先定义了一个全局变量ptr,定义函数属性为noinline==》声明函数为非内敛函数
内敛函数与非内敛函数对比:
//非内敛
int funA(int a,int b){
return a+b;
}
int main(){
funA(1,1);
return 0;
}
//内敛
int main(){
int a = 1;
int b = 1;
int c = a + b;
return 0;
}
==》在funA函数内敛后,编译器会保持原有执行流程的情况下,将funA函数中的执行流程重组进main函数中==》demo中指定为非内敛属性==》在编译中保持FunctionA函数独立于main函数,成调用关系==》
FunctionA函数中定义了一个int类型的数组closure,并将closure数组起始地址赋值给全局变量指针ptr==》
进入main函数,调用FunctionA函数,返回ptr[argc]
注意点:closure是FunctionA函数中的局部变量,却在main函数中返回了closure数组中下标为argc位置的数据
==》在main函数中使用了closure数组属于脱离本身的作用域==》stack-use-after-return
编译后运行:
由于ASan默认是不检测stack-use-after-return,需要开启检测选项
ASAN_OPTIONS=detect_stack_use_after_return=1
ASan日志分析:
漏洞出现地址:0x7fb4a70e8024
寄存器:
pc 0x0000004c32dc
bp 0x7ffeae659470
sp 0x7ffeae659468
f5==》表示出现stack after return内存空间的范围
由于偏移量为36==》第五个f5的位置出现中括号(到达漏洞位置)
heap-buffer-overflow
int main(int argc, char **argv) {
int *closure = new int[100];
closure[0] = 0;
int res = closure[argc + 100];
delete [] closure;
return res;
}
编译:
gcc -O -g -fsanitize=address clo.cpp -o clo
在C++中使用new来创建堆块(底层由malloc实现),使用delete释放堆块
首先开辟一个存放整数的空间(int),整数的初值为100==》返回一个指向该存储空间的地址==》
closure = malloc(400)
将堆块中的第一个四字节空间赋值为0==》将argc+100处的内容赋值给res变量==》
释放closure堆块并返回res的值==》
argc+100超过了堆块400字节内存空间范围==》heap-buffer-overflow
编译后运行:
ASan日志分析:
漏洞出现地址:0x7ffc238179a0
寄存器:
pc 0x0000004c3201
bp 0x7ffc23817970
sp 0x7ffc23817968
shadow解析:fa为closure堆块两侧的redzone空间==》中间的00==》内存空间的400字节
[fa]==》溢出了argc字节
heap-use-after-free
释放的堆块没有置空,并且已经释放该指针又进行调用
int main(int argc, char **argv) {
int *closure = new int[100];
delete [] closure;
return closure[argc];
}
编译:
gcc -O -g -fsanitize=address clo.cpp -o clo
创建一个400字节大小的堆块,并将malloc指针赋给closure
释放堆块,返回closure堆块中argc数组下标对应的内容
编译后运行:
ASan日志分析:
漏洞出现地址:0x614000000044
寄存器:
pc 0x560f6b5b621f
bp 0x7ffd264ddd60
sp 0x7ffd264ddd50
shadow解析:紫色的fd表示被释放堆块区域
return返回时被释放的堆块的第argc数组下标中的内容==》[fd]在第一个
memory leaks
内存泄漏漏洞:
创建内存空间之后没有释放,导致数据在内存空间中没有消除,可以通过一些手段读取这段数据内容
#include <stdlib.h>
void *closure;
int main() {
closure = malloc(8);
closure = 0;
return 0;
}
编译:
gcc -g -fsanitize=address clo.cpp -o clo
创建一个8字节大小的堆块
在没有将堆块释放的情况下将堆块的malloc指针置空==》
8字节的内存空间只要进程不结束,会一直停留在堆区==》memory leaks
编译后运行:
ASan日志展示了泄露时的函数栈,泄漏的字节长度
initialization-order-fiasco
由变量初始化顺序导致
//a.cc
#include <stdio.h>
extern int extern_global;
int __attribute__((noinline)) read_extern_global() {
return extern_global;
}
int x = read_extern_global() + 1;
int main() {
printf("%d\n", x);
return 0;
}
首先使用extern定义了一个int型全局变量extern_global==》
extern:扩大作用域==》即使extern_global是在a.cc中进行定义的,但是如果是a.cc和b.cc联合编译==》
b.cc也可以使用extern_global变量
接下来定义一个费内敛函数read_extern_global()==》返回extern_global的值
定义一个变量x==》x为read_extern_global()的返回值extern_global的值+1
在main函数中打印X的值
注意点:在编译的时候会优先初始化read_extern_global()函数的返回值,并计算出x的值等待main()进行调用
//b.cc
int foo() { return -2; }
int extern_global = foo();
定义了一个foo()函数,函数直接返回-2,定义extern_global变量为foo()函数的返回值
编译运行ab:
clang++ a.cc b.cc -o a_b
==》
先编译a.cc==》优先初始化read_extern_global()==》继续初始化x = 0 + 1
编译运行ba:
clang++ b.cc a.cc -o ba
==》
先编译b.cc==》优先初始化foo()==》初始化x = -2 + 1
====》
若结果作为其他程序的参数==》
例:read()==》出现整数溢出:
read(0,buf,0xffffff)
若buf空间不足==》堆溢出/栈溢出
ASan分析:
联合编译后运行:
clang++ -g -fsanitize=address a.cc b.cc -o clo
ASan日志分析:
漏洞出现地址:0x000000e20e80
寄存器:
pc 0x0000004c6bc8
bp 0x7ffe376c2cb0
sp 0x7ffe376c2ca8