张 超,潘祖烈,樊 靖
(1.国防科技大学 电子对抗学院,合肥 230037; 2.北海舰队,山东 青岛 266000)
随着信息技术的发展,软件漏洞的挖掘与利用成为领域研究热点,而传统的软件漏洞主要以手工方式构造,但是手工构造过程需要大量的底层知识和分析经验。随着软件数量的增加及其功能的日益完善,软件漏洞呈现更加多样且复杂的趋势[1-2]。尽管目前不少二进制漏洞的自动化调试与检测方法已经能有效地发现程序错误,但其中只要有一部分程序错误可以被利用,就将导致严重后果[3]。因此,如何快速、准确地对漏洞的危险性进行评估,是当前漏洞自动分析与检测领域的关键问题之一[4-5]。
堆溢出漏洞是一种常见的缓冲区溢出漏洞[6]。通过对堆溢出漏洞的利用,可能导致程序控制流劫持以及执行任意代码的后果。其中,fastbin攻击是一种Linux环境下面向堆溢出漏洞的攻击方法[7]。
典型的fastbin攻击通过堆块的快速分配和释放等操作,修改相邻堆块的堆块头,造成相邻堆块的堆块头数据被覆盖,从而达到任意地址分配以及任意地址写的目的。为保护fastbin链表不被攻击,Linux系统设置了如size位检测等保护机制,避免程序流被劫持[8]。
本文通过分析已有的fastbin实例,总结fastbin攻击特征,构建fastbin攻击检测模型,并在该模型的基础上提出针对fastbin攻击的自动检测方法,通过污点分析[9-10]和符号执行[11-12]实现fastbin攻击的自动检测。
针对控制流劫持类漏洞的攻击检测及测试例生成,已有大量相关研究和成果。文献[13]提出基于二进制补丁比较的漏洞利用自动生成方法APEG。APEG通过比较程序的bug版本和补丁版本程序的不同,生成能在补丁版本中增加校验失败的利用。实验结果表明,该方法具有较强的可靠性和实用性。但该方法也有局限性,即存在无法处理补丁程序中不添加过滤判断的情况,以及构造的类型主要属于拒绝服务,只能造成源程序的崩溃,而无法直接造成控制流劫持。
为克服APEG对于补丁的依赖以及无法构造控制流劫持的缺陷,文献[14]提出漏洞自动挖掘与测试例生成方法AEG。AEG集成了优化后的符号执行和动态指令插桩技术,实现了从软件漏洞自动挖掘到软件漏洞自动利用的整个过程,并且生成的利用样本直接具备控制流劫持能力,是第1个真正意义上的面向控制流的漏洞利用自动化构建方案。该方案的局限性主要体现在:需要依赖源代码进行程序错误搜索;所构造的样本主要是面向栈溢出或者字符串格式化漏洞,并且利用样本受限于编译器和动态运行环境等因素。
在AEG方法的基础上,文献[15]提出了基于符号执行的CRAX方法,其主要构建在S2E[16-18]、KLEE[19]、QEMU[20]环境上。CRAX可以作为fuzzer的后端,借助前端生成的crash和二进制程序自动生成利用代码。CRAX使用符号执行技术,通过对程序动态运行过程的监视,检测EIP劫持状态。当发现EIP存在劫持的可能时,在符号化可控区域中布置shellcode并覆盖EIP的值为shellcode地址,生成可利用代码。其局限性在于检测过程未考虑系统保护机制与动态运行环境对漏洞攻击的影响。
对于堆内存管理而言,堆块(chunk)就是最小的操作单位。而Glibc针对分配的堆块大小的不同,将堆块分为3类:大小16 Byte到80 Byte的是fast chunk;堆块大小512 Byte以下的是small chunk;堆块大小大于512 Byte的是large chunk。
堆块在被释放后,会放入被称为bin的数据结构中,以链表的形式存储。Glibc定义了4种链表,分别为fastbin、smallbin、largebin和unsortedbin。Glibc维护了136个bins链表,其中,fastbins 10条,smallbins 62条,largebins 63条,unsortedbins 1条。
而fastbin所包含chunk的大小为:16 Bytes,24 Byte,32 Bytes,…,80 Byte,以单向链表的形式存储,每条fastbin链中存储的堆块大小都与本链大小相同。而其他3种链表以双向链表的形式存储。fastbin链表结构如图1所示。
图1 fastbin链表结构Fig.1 Structure of fastbin chain list
堆块的状态分为已分配堆块(allocated chunk)和已释放堆块(free chunk)2种状态,而系统通过堆头的标志位来区分堆块的不同的状态。
图2为fastbin chunk的头部结构。当堆块出入释放状态时,第1 Byte~第4 Byte是prev_size,表示前一个堆块的大小。第5 Byte~第8 Byte是size,表示当前堆块的大小。由于每个堆块的大小一定大于等于8 Byte,因此最后3 bit的内存空间没有实际意义,glibc将这3 bit的空间用来做标志位,最后1 bit表示前一个堆块的使用状态。当前一个堆块是释放(free)状态时,F标志位为0;若为已分配(allocated)状态,F标志位为1。第9 Byte~第12 Byte为fd,指向链表中的前一个堆块。因为是单链结构,所以fastbin堆块没有bk指针。
图2 fastbin堆块头部结构Fig.2 Head structure of fastbin heap block
glibc为了实现堆块的快速分配和释放,专门设置了fastbin链表。fastbin链表是单链,通过fd指针连接起来。当释放某一个堆块时,会首先检查其大小是否落在fastbin的范围中。如果是,则直接将其插入到对应的fastbin链表中,而且fastbin链表采用先进先出(LIFO)原则,即后释放的链表在下一次分配时会优先分配。
当连续分配两块大小相同的fastbin堆块时,2个堆块在实际内存中是相邻的。因此,当前一个堆块存在溢出漏洞时,以通过溢出修改相邻堆块的fd指针为fake_fd,使其指向任意地址,然后在目标地址构造size位,就可以实现任意地址分配,从而实现任意地址写的目的。如图3所示,通过fastbin攻击,实现任意地址分配与地址写,再通过进一步构造,即可以劫持控制流。
图3 fastbin攻击过程示意图Fig.3 Schematic diagram of fastbin attack process
针对传统的fastbin利用方法,glibc目前主要的针对性保护机制是size位检测。在glibc分配堆块之前,首先会检查待分配堆块的size位是否与所在的fastbin链的大小相同,如果不同,则系统报异常,无法继续分配。然后glibc会检查待分配堆块fd指针指向的下一个堆块的size位是否与所在的fastbin链大小相同,如果不同,同样系统会报错,停止堆块的分配。
由于已有的堆溢出漏洞攻击检测技术无法收集系统分配和释放堆块时的相关信息,因此已有的攻击检测技术无法识别面向堆溢出的漏洞的控制流劫持攻击。针对这一问题,本文基于堆溢出中的fastbin攻击方法,提出了基于符号执行的fastbin攻击检测方法。
在fastbin攻击检测中的总体过程分为种子输入、堆块信息收集、堆溢出错误检测、fastbin攻击检测等。初步方案为将模糊测试产生的crash作为种子输入,经符号执行引擎传递至被测程序,使被测程序沿确定路径运行至堆溢出错误代码区域。在堆溢出错误检测过程中,通过API函数挂钩进行符号变元引入,将外部输入标记为符号值,通过符号执行技术将程序中的变量表示为符号和常量的表达式,进而对目标程序进行检测,收集程序运行过程中堆块分配与释放的相关信息。同时,通过匹配fastbin攻击检测模型,构造相应的数据约束。在被测程序的动态运行过程中,根据数据约束将满足条件的符号化内存区域具体化,消除保护机制对检测过程的影响,生成fastbin攻击检测测试用例。本文以C/C++程序为分析对象,选取若干Linux环境下的测试集开展测试验证。
本文对面向堆溢出的fastbin攻击原理进行分析,提出通过堆溢出导致fastbin攻击控制流劫持的过程需要满足4个特征:堆块溢出特征,溢出堆块可控特征,fastbin操作触发特征,指针数据可控特征。
根据面向堆溢出的fastbin攻击特征,本文对程序的特征定义如下:
1)堆块溢出特征(ChunkOverflow):表示程序是否存在堆溢出错误的特征。
2)溢出堆块可控特征(ChunkControl):表示堆块溢出的数据可以受外部输入数据控制的特征。
3)fastbin操作触发特征(IsFastbin):表示程序发生的堆溢出错误是否满足fastbin攻击方法的特征。
4)指针数据可控特征(PtrControl):表示指针数据受外部输入数据控制的特征。
5)堆溢出漏洞的fastbin攻击方法触发特征(FastbinHijack):表示程序可以通过fastbin攻击方法导致控制流劫持。
当程序同时存在堆块溢出特征、溢出堆块可控特征、fastbin操作触发特征、指针数据可控特征这4种特征,就可以判断程序中存在堆溢出错误,且可以同过错误触发fastbin攻击。因此,上述的程序特征间的关系可以用下式表达:
FastbinHijack=ChunkOverflow∧ChunkControl∧
IsFastbin∧PtrControl
本文对程序运行过程中堆块状态定义如下:
定义三元组header=(pre_size,size,fd),描述堆块分配过程中头部header值的变化。
前堆大小pre_size:分配过程中该值为0。
本堆大小size:描述分配过程中堆块的大小,同时size的最后一位表示前一堆块分配状态,在free状态时F=0,在allocated状态时F=1。
链表指针fd:指向了fastbin链表中的下一个堆块。
定义函数F(header,t_presize,t_size,t_fd):描述fastbin攻击发生时,目标头部header的状态变化规则。其中,参数t_presize、t_size t_fd表示header变化后的目标值。t_presize和t_size值为堆块原来的pre_size和size位相同大小,而t_fd则为目标地址的值,t_fd = target_addr。
面向堆溢出的fastbin攻击主要是通过堆溢出修改相邻堆块的fd指针,并保持相邻堆块头的pre_size和size不变,因此需要具体化pre_size、size、fd,从而绕过glibc的保护机制。此过程中堆块头header的数据变化如下:
F(header,t_presize,t_size,t_fd1)=
为实现对面向堆溢出的fastbin攻击检测,构造测试用例,需要对fastbin攻击劫持程序控制流的过程进行监视。在堆块操作过程中,glibc保护机制对堆块的数据检查会导致程序异常退出,造成检测失败。为消除保护机制对检测过程的影响,本文根据fastbin攻击的场景设计相应的数据具体化过程,建立了面向堆溢出漏洞的fastbin攻击检测模型,如图4所示。
图4 堆溢出漏洞fastbin攻击检测模型Fig.4 Detection model of heap overflow vulnerability fastbin attack
检测模型通过程序函数挂钩,获取堆块分配函数参数,记录新建堆块的起始地址与分配长度。在程序动态运行过程中,监视针对已建堆块的写入与释放操作。当程序触发堆块写入操作时,通过符号内存搜索算法,比对内存中符号区域与写入对象堆块区域的地址与长度,检查写入对象堆块的堆溢出触发特征与溢出堆块可控性特征。若堆块溢出且可控,则记录写入对象堆块信息。当溢出堆块触发释放操作时,检查溢出堆块及其相邻堆块是否满足fastbin攻击操作条件,判断fastbin攻击能否发生。
本文在fastbin攻击检测模型的基础上,使用符号执行和污点分析技术,设计并实现了堆溢出fastbin攻击的检测系统。该系统通过对虚拟机中运行程序的函数进行挂钩,收集程序运行过程中堆块分配和释放的相关信息。同时通过fastbin攻击检测算法,将外部输入数据符号化,然后根据符号值的传播来确定外部数据在程序中的传播路径,并以此为基础,构建可到达漏洞发生点的约束条件,生成测试用例。针对程序动态运行中的符号化内存和堆块状态变化,有如下定义:
二元组S=(s_addr,s_size):二元组S描述了符号化区域的特征,s_addr和s_size分别表示符号化区域的起始地址和大小。
三元组H=(h_addr,h_size,h_state):三元组H描述了堆块的特征,h_addr表示堆块数据区的起始地址,h_size表示堆块数据区域的大小,h_state表示堆块状态是处于分配状态还是释放状态。
Symb_map:表示所有符号化区域的集合。
二元组Allo_map:表示所有处于分配状态的堆块的集合。
chunkoverflow:存在堆溢出特征的堆块的集合。
chunkexploitable:可被外部数据控制的堆块的集合。
系统在运行过程中,执行以下步骤:
步骤1系统通过对函数进行挂钩,记录了程序运行过程中堆块创建和释放的相关信息,包括堆块创建地址、堆块大小等。当有符号化数据进入程序时,首先使用堆块溢出检测算法,通过判断输入程序的符号化数据是否超过堆块边界,判断堆块是否发生溢出。
步骤2使用溢出堆块可利用性检测算法,检测溢出堆块是否可控。
步骤3系统通过对堆块的申请和释放的监控,通过fastbin攻击检测算法,检测堆块的申请和释放状态是否匹配fastbin攻击检测模型。
步骤4如果堆块的申请和释放状态符合fastbin攻击检测模型,可以通过fastbin攻击方法实现任意地址写。系统利用指针数据可控性检测算法对指定的数据指针进行符号化检查,若指针数据是符号值,则满足指针数据可控性要求。
同时要根据上述步骤建立相应的约束,然后与数据约束data_constraint进行舍取,建立测试用例约束条件FastbinHijack_constraint。
检测系统是执行在宿主机中通过对虚拟机模拟执行的程序进行函数挂钩获取相关的信息,来作为检测算法运行的基础。而虚拟机和宿主机的信息传递是通过寄存器实现的。比如对malloc函数的返回地址进行挂钩,可以知道新创建堆块的堆块指针和堆块大小以及malloc函数的返回地址,这些信息就是通过寄存器传输到宿主机,其中堆块指针存放在寄存器eax中,堆块大小存放在寄存器ecx中,malloc的返回地址存放在edx中。通过寄存器传输的方式,使算法可以实时获取程序运行过程中各种信息的变化。
3.3.1 堆溢出检测算法
程序存在不安全操作使输入信息长度大于数据区域长度从而导致溢出,是fastbin攻击发生的基本条件。本文利用挂钩技术可以获取程序堆块分配的起始地址和数据区长度等信息,而且还能获得外部输入数据的长度。通过检查写操作导致目标区域状态的变化,对比符号化区域长度和堆块数据区长度,可以实现堆溢出检测。若堆块发生溢出,则将溢出堆块加入溢出堆块集合chunkoverflow中。
堆溢出检测算法如算法1所示。
算法1堆溢出检测算法
输入符号化区域集合Symb_map,分配状态堆块集合Allo_map
输出溢出堆块集合chunkoverflow
for(a=0;a for(b=0;b { if(s_addr>h_addr&&s_addr< (h_addr+h_size)&&(s_addr+ s_size)>(h_addr+h_size)) { Allo_map->chunkoverflow break } end if } 算法1准确地描述了堆块发生溢出的基本特征,即堆块数据超出了堆块本身的长度,体现在算法中是存在符号区域的起始地址被包含在堆块数据区,但结束地址不被包含在当前堆块的数据区。算法对堆块溢出的特征定义准确。 3.3.2 堆块可利用性检测算法 由于fastbin攻击要求通过控制堆块的溢出数据,修改相邻堆块的头部,并构造假的fd指针,溢出堆块chunk1通过溢出修改相邻堆块chunk2头部后的构造如图5所示。同时,程序通过溢出堆块可利用性检测算法,建立伪堆块数据约束fakechunk_constraint。 图5 堆块溢出后的状态Fig.5 State after heap block overflow 根据图5的溢出堆块结构,伪堆块数据约束fakechunk_constraint由伪pre_size约束presize_constraint、伪size约束size_constraint、伪fd约束fd_constraint组成,关系式如下: fakechunk_constraint=presize_constraint∧ size_constraint∧ fd_constraint 堆块可利用性检测算法如算法2所示。 算法2堆块可利用性检测算法 输入堆块集合Allo_map,堆溢出堆块集合chunkoverflow,溢出后想实现任意地址写的地址target_addr 输出可控溢出堆块集合chunkexploitable 伪堆块数据约束fakechunk_constraint foreach(Heap∈chunkoverflow) foreach(byte∈Allo_map) fake_start = h_addr + h_size size=Hex2str(h_size+0x8+0x1) count = 0 for(a=0;a<4;a++) { cover = EqExpr::create(fake_start + count,0x00) presize_constraint=AndExpr::create( presize_constraint, cover) count++ } for(a=0;a<4;a++) { cover=EqExpr::create(fake_start + count,size[a]) size_constraint=AndExpr::create (size_constraint,cover) count++ } for(a=0;a<4;a++) { cover=EqExpr::create(fake_start + count, target_addr[a]) fd_constraint=AndExpr::create( fd_constraint,cover) count++ } fakechunk_constraint = presize_constraint∧ size_constraint∧ fd_constraint if(solve(fake_constraint) = true) Heap→chunkexploitable end if 算法2并未直接检测堆块是否可利用,而是通过在符号区域构建约束,然后对约束进行求解,如果约束有解,说明伪堆块可以构建成功,即说明堆块可利用,反之堆块不可利用。 3.3.3 fastbin攻击检测算法 程序触发fastbin攻击首先要满足分配的堆块大小不大于80 Byte,属于快堆。然后在已释堆块中寻找是否有物理地址相邻且与当前堆块大小相同的堆块,如果有,则可以通过第1个堆的溢出数据修改相邻堆的头部,伪造假的fd指针,使系统误认为fd指向的地址是下一个待分配快堆,从而实现任意地址写。 本文通过对堆块分配和释放函数挂钩,检查堆块分配和释放过程中堆块在内存中的布局,建立可导致fastbin攻击的溢出数据约束,根据溢出数据约束,具体化溢出数据覆盖的内存区域。算法3为程序fastbin攻击检测算法。 算法3fastbin攻击检测算法 输入溢出堆块集合chunkoverflow,分配堆块集合Allo_map,已释放堆块集合Free_map 输出fastbin攻击发生标识isFastbin isFastbin = false foreach(heap in Allo_map) if(heap∈chunkoverflow) if(h_size∈(0,0x38)) { foreach(chunk in Free_map) if(chunk->h_addr=heap->h_addr+ heap->h_size+0x8) IsFastbin = true } 算法3准确地描述了fastbin操作被触发的特征,即当前堆块是快堆且存在溢出错误,并且物理相邻的是一个相同的快堆且处于释放状态。 3.3.4 指针数据可控性检测算法 在程序符合fastbin攻击条件的基础上,检查指定的指针数据是否受到外部数据控制,导致程序控制流被劫持。在32 bit系统环境中,指针数据是一段连续的4 Byte内存区域(64 bit系统中为8 Byte)。本文以三元组p=(p_addr,p_sym,p_val)描述指针数据的存放地址、符号化状态与数据约束等属性。通过对指针数据建立可控性约束,实现指针数据具体化。算法4为指针数据可控性检测算法。 算法4指针数据可控性检测算法 输入指针数据属性p,具体化数据集合dataset 输出指针数据可控性约束ptr_constraint if(p_sym == true ) { p_val = create::Eq(dataset [p],Int8) ptr_constaint= ptr_constaint∧ p_val } end if 本文使用了3个含有堆溢出缺陷的实验程序进行验证。其中,fastbin.c来自ctf比赛试题,fastbin_dup.c来自shellphish/how2heap测试集,babyheap来自ctf-challenges测试集。上述所有程序均采用C语言编写。 在面向堆溢出的fastbin攻击检测过程中,为更好地验证系统对fastbin攻击过程的检测效果,实验中关闭了地址随机化,而保留了glibc针对堆块操作过程中相关的保护机制。 为体现系统的效果,对比系统与已有的漏洞攻击检测技术的不同,在实验过程中将每个测试程序分别交由fastbin攻击检测系统和CRAX系统进行测试,其中,t1表示系统完成实验样本分析所用时间,t2表示系统完成程序代码生成所用时间。测试用例生成情况如表1所示。其中,—为不能生成测试用例。 表1 测试用例生成情况对比Table 1 Comparison of test case generation situation s 从表1的结果可以看出,CRAX系统针对测试用例的分析时间比本文提出的检测系统要短,但是CRAX系统对3个测试用例均不能生成测试用例。而fastbin攻击检测系统对其中的1个程序生成了测试用例。这表明针对面向堆溢出的fastbin攻击,本文的方法有更好的检测效果。 而针对2个没有有生成测试用例的例子,本文也对其进行了分析与记录,其测试过程中的情况如表2所示。 表2 fastbin攻击检测系统的约束构建情况Table 2 Constraint construction situation of fastbin attack detection system 从表2可以看出,对于babyheap,系统检测出了堆溢出特征、溢出堆块可控特征、fastbin操作触发特征这3个特征,而无法检测到关键数据指针可控,通过堆源程序的分析发现,babyheap要想达到覆盖关键数据指针,劫持程序控制流,必须要泄露libc基地址,然后才能检测到关键数据指针可控。因此,如果针对babyheap在系统中专门添加libc基地址记录模块,可以实现babyheap的测试用例的生成。 而对fastbin_dup.c,经过对源程序的分析发现,程序只有fastbin操作触发特征,而无对堆块的操作,因此无法触发堆块溢出检测和溢出堆快可控性检测,更无法控制关键指针,因此无法生成测试用例。 为了更直观地表示fastbin攻击方法自动利用原型系统的代码自动生成过程,本文以fastbin程序的自动分析与利用过程为例进行案例分析。fastbin程序的漏洞触发关键代码如代码1所示。 代码1fastbin程序的fastbin操作触发关键代码 bin1 =malloc(0x38); bin2 =malloc(0x38); free(bin2); free(bin1); bin3 =malloc(0x38) read(handle1,bin3,0x64);//引入污点数据,发生堆溢出 //错误 bin4 =malloc(0x38); bin5 =malloc(0x38);//触发fastbin操作 为获得程序分配堆块的相关信息,系统通过在malloc函数的返回点进行挂钩,可以获取程序新创建堆块的相关信息,并将新创建堆块信息添加进入Allo_chunk集合中,新建堆块信息如下: 对read函数返回点进行函数挂钩,可以发现程序从外部读入的数据。然后根据通过挂钩获得外部读入数据在内存中的地址和读入数据大小等信息,将读入数据符号化,将相关内存进行污点标记,检测结果如下: linux read( ) return-hook invoked : fd : 3 foffset: 0 databuf : 0x804a03c size : 8 检测到堆块溢出特征后,使用堆块溢出数据可控算法检测溢出数据的可控性,并构建伪堆块数据约束,约束构建过程如下: 通过对free函数的入口点进行挂钩,系统可以获取堆块释放的相关信息,然后将新创建堆块集合Allo_chunk中的相关信息删除,并将堆块释放信息添加到已释放堆块集合Free_chunk。最后通过fastbin操作触发检测算法根据检测溢出数据可控堆块和已释放堆块的相对位置关系来确定fastbin操作是否触发,获取fastbin操作触发标识Is_Fastbin,Is_Fastbin标识获取结果如下: 在判断程序触发了fastbin操作的基础上,对关键指针变量进行可控性检查,发现指针数据被污点数据覆盖为符号值,生成指针数据可控约束PtrConstraint,指针可控性检查结果如下: 54 [State 0] Found Symbolic Array at 0x804a044, width 48 ptr = 0x804a044 can be controled! 系统通过对程序运行过程中生成的约束进行求解,生成的针对fastbin程序的二进制利用代码如图6所示。将生成的测试用例作为输入输入至程序中,触发fastbin攻击,成功劫持控制流,结果如图7所示。 图6 针对fastbin程序自动生成的利用代码Fig.6 Utilization code automatically generated for fastbin program 图7 劫持控制流的结果Fig.7 Result of hijacking control flow 本文通过总结面向堆溢出的fastbin攻击特征,利用符号执行和污点分析技术,在fastbin攻击检测模型和fastbin攻击检测算法的基础上设计并实现了fastbin攻击检测系统。从堆块溢出特征、溢出堆块可控性特征、fastbin操作触发特征、数据指针可控性特征4个方面来描述面向堆溢出的fastbin攻击,判断程序是否符合fastbin攻击特征并验证程序的可利用性。基于3个程序的实验证明了系统对相关程序检测结果的正确性。但本文fastbin攻击检测系统未考虑ASLR等系统保护机制对实验的影响,无法实现多漏洞的组合测试,只能单一地检测面向堆溢出的fastbin攻击检测。因此,如何实现多漏洞的组合检测将是下一步的研究工作。4 实验结果与分析
4.1 结果分析
4.2 案例分析
5 结束语