![COM组件栈缓冲区溢出漏洞检测技术研究_第1页](http://file4.renrendoc.com/view11/M02/29/27/wKhkGWWHAKiAVd7FAAKmZprjGTo831.jpg)
![COM组件栈缓冲区溢出漏洞检测技术研究_第2页](http://file4.renrendoc.com/view11/M02/29/27/wKhkGWWHAKiAVd7FAAKmZprjGTo8312.jpg)
![COM组件栈缓冲区溢出漏洞检测技术研究_第3页](http://file4.renrendoc.com/view11/M02/29/27/wKhkGWWHAKiAVd7FAAKmZprjGTo8313.jpg)
![COM组件栈缓冲区溢出漏洞检测技术研究_第4页](http://file4.renrendoc.com/view11/M02/29/27/wKhkGWWHAKiAVd7FAAKmZprjGTo8314.jpg)
![COM组件栈缓冲区溢出漏洞检测技术研究_第5页](http://file4.renrendoc.com/view11/M02/29/27/wKhkGWWHAKiAVd7FAAKmZprjGTo8315.jpg)
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
摘要为了解决软件复用,缩短软件开发时间,降低维护成本和实现程序动态升级,软件设计领域产生了组件化程序设计结构,并且日益成为发展趋势。微软的COM组件对象模型是当今比较成熟的软件组件模型之一,被广泛应用于Windows操作系统和应用程序中。随着COM组件技术的大量使用,COM组件暴露出越来越多的安全问题,其中,缓冲区溢出安全问题占了很大比例。 缓冲区溢出漏洞一直是安全漏洞最常见的一种形式。缓冲区溢出问题主要出现在C/C++这类非类型安全语言中,而在新一代的编程语言,例如Java、C#中不存在。一个重要的原因就是C/C++允许通过指针进行间接内存访问但没有缓冲区边界检查和提供了大量对缓冲区可能存在不安全操作的库函数,在Windows操作系统中也存在类似的函数。因此,如果能采用有效的手段对COM组件的缓冲区溢出漏洞进行检测,将能极大地提高组件软件的安全性。 根据COM组件多数情况下源代码未可知的测试特点,基于COM组件的二进制代码实现对其可能存在的栈缓冲区溢出漏洞的检测。检测方法是将检测缓冲区溢出问题转化为整数范围分析问题。建立适用COM组件的危险函数库,在汇编代码中识别危险函数的调用位置,然后根据危险函数参数的类型通过扫描识别不同的缓冲区,将声明的缓冲区大小和使用的缓冲区大小等价为整数范围,依据制定的缓冲区溢出标准检测溢出漏洞。根据COM组件使用虚表定位函数的结构特点,实现了COM组件中用户函数的精确定位;同时利用IDC脚本语言提取了COM组件中的函数依赖关系图。基于以上工作,实现了一个COM组件栈缓冲区溢出漏洞检测原型系统。关键词:缓冲区溢出,静态分析,二进制代码,COM组件,危险函数AbstractInordertosolvetheproblemofsoftwarereuse,shortensoftwaredevelopingperiod,reducemaintenancecostandrealizesoftwareautomaticupdating,componentsoftwaredesignisputforwardandhasbecomeaninevitabledevelopmentaltrend.Microsoft'sComponentObjectModel(COM)isarelativelymatureoneofsoftwarecomponentmodels,whichiswidelyusedinWindowsoperatingsystemandapplicationprograms.AlongwiththeprevalenceofCOM,moreandmoresecurityproblemsareexposed,ofwhichbufferoverflowconstitutesahighproportion.Bufferoverflowhasbeenoneofthecommonestformsofsecurityholes.Itmainlyexistsinthenontype-safelanguages,suchasCandC++.However,newgenerationlanguages,suchasJavaandC#,donothavethisproblem.OneofthemostimportantreasonsisthatCallowindirectmemoryaccessbypointerwithoutboundarycheckandprovidemanyunsafefunctionswhichmaycausebufferoverflow.Meanwhile,therearesuchfunctionsinWindowsoperatingsystem.Therefore,ifeffectivemeanscanbeadoptedtodetectbufferoverflow,securityofcomponentsoftwarewillbegreatlyenhanced.BufferoverflowdetectionbasedonbinarycodeisthoroughlystudiedinthisthesisbecauseinmostcasesthesourcecodeofCOMisunknown.Adetectionalgorithmisproposedbymodelingthebufferoverflowproblemandtransformittorangescomparisonofintegers.First,establishanunsafefunctionlibraryofCOManduseittodistinguishunsafefunctioncallsinassemblycode.Second,accordingtotypesofargumentspassedtounsafefunctions,differentbuffersaredistinguished.Third,obtainrangesofintegersbasedonthesizeofdeclaredbuffersandallocatedones.Last,usebufferoverflowdetectioncriterionmadebythisthesistodetectbufferoverflow.COMusesvirtualtabletolocatefunctions.Accordingtothisstructuralcharacteristics,thisthesisrealiseaccuratepositioningofuserfunctionsandextracttheirdependencerelationsbymeansofIDCscriptlanguage.Basedontheworkmentionedabove,aprototypesystemthatcandetchstackbufferoverflowofCOMisrealised.Keywords:bufferoverflow,staticanalysis,binarycode,COMcomponent,unsafefunction目录TOC\o"1-2"\h\z\u摘要 IAbstract II1绪论1.1课题背景 (1)1.2国内外概况 (2)1.3论文主要研究工作 (4)1.4论文结构 (5)2COM组件及栈缓冲区溢出漏洞检测方法基础2.1COM组件 (6)2.2缓冲区溢出原理 (8)2.3栈缓冲区溢出检测技术 (11)2.4小结 (19)3COM组件栈缓冲区溢出漏洞检测技术3.1函数的识别 (21)3.2参数的识别 (25)3.3函数的返回值 (25)3.4变量的识别 (27)3.5COM组件危险函数库建立 (30)3.6小结 (34)4COM组件栈缓冲区溢出漏洞检测系统设计与实现4.1模块结构和功能 (35)4.2主要数据结构 (36)4.3函数定位模块实现 (37)4.4栈溢出静态分析模块实现 (38)4.5结果输出模块实现 (47)4.6与CSTS的接口 (49)4.7小结 (50)5实验与测试5.1实验目的 (51)5.2实验环境 (51)5.3测试用例 (52)5.4测试结果 (53)6总结与展望6.1工作总结 (59)6.2工作展望 (60)致谢 (61)参考文献 (62)附录攻读学位期间参与的科研项目 (67)1绪论1.1课题背景继面向对象的软件设计方法之后,基于组件的软件设计方法正在逐渐成为新的趋势[1]。按照组件化程序设计的思想,复杂的应用程序被设计成一些小的、功能单一的组件模块,这些模块可以运行在同一机器上,也可以运行在不同的机器上,每台机器的运行环境可以不同,甚至可以是不同的操作系统。为了实现这样的应用软件,组件需要一些细致的规范,只有组件程序遵守这些共同的规范,组件软件才能正常运行。目前流行的组件规范有三种,它们是OMG(ObjectManagementGroup)提出的CORBA(CommonObjectRequestBreakerArchitecture);微软提出的COM(ComponentObjectModel);还有SUN公司提出的EJB(EnterpriseJavaBean)。Cai[2]给出了三种规范的详细比较。微软的COM组件对象模型是当今比较成熟的软件组件规范之一,被广泛应用于Windows操作系统和应用程序中。在分布式计算、Internet网络、三层体系结构开发以及音视频处理等前沿领域,COM组件技术正在被大量使用。COM组件在受到广泛应用的同时,自身的安全问题也日益暴露。其中,缓冲区溢出安全问题占了很大的比例。在安全日益被人们所关注的今天,缓冲区溢出毫无疑问是最大的安全威胁之一。1988年,Internet上的第一例蠕虫(Morris)攻击,就是利用Vax和Sun机器fingerd中的缓冲区溢出漏洞。SANS评选出的2005年威胁最大的20个漏洞中,有8个跟缓冲区溢出有关。国内绿盟科技根据安全漏洞的严重程度、影响范围等因素综合评出2006年度的十大安全漏洞,缓冲区溢出漏洞占4个。绿盟科技在2006年发布CVE漏洞8个,而缓冲区溢出漏洞就占了5个。根据CERT的统计数据,近几年与缓冲区溢出有关的安全事件在50%以上。经过十几年的发展,缓冲区溢出已经成为一种成熟和最有效的黑客攻击手段。并且在接下来的许多年里,情况仍然会是这样。随着COM组件的广泛应用,COM组件中暴露的缓冲区溢出的安全问题的数量也是逐年上升。因此检测COM组件的缓冲区溢出漏洞具有十分重要的意义。COM组件多数情况下源代码不可知,目前常用的测试方法是黑盒测试。按照经验和一般规律设计大量测试用例,以期望触发组件的漏洞和异常。这种方法在某些时候会起到较好的效果,但效率太低,而且对测试人员的经验和技术水平要求很高。为了改进已有的COM组件测试方法,提高对COM组件安全漏洞的检测,本论文研究并实现了COM组件栈缓冲区溢出漏洞检测原型系统。 同时,对COM组件进行静态分析获取其结构信息和安全漏洞信息是实验室正在研发的针对COM组件的安全漏洞自动检测工具的需要。所以本论文研发的原型系统可以很好与实验室研发的自动检测工具结合,以完善该工具的效率和功能。1.2国内外概况软构件技术提供了一种较面向对象方法更为有效的软件设计模式,构件软件被广泛应用并成为一种主流软件形态[3](本段及后两段中的“构件”与论文中的“组件”概念一样,只是翻译的不同)。然而,构件的内部信息屏蔽、演变速度快以及构件间的异质、松耦合等特点给构件及构件软件的测试工作带来一系列的问题[4,5]:对于构件提供者,问题在于需要使用相当充分的覆盖准则进行测试以提高构件的可复用性;对用户使用构件的上下文环境并不完全了解;并且不能很好的获得构件的错误报告。对于构件使用者而言,问题在于源代码不可见性给测试设计和用例生成带来了极大的障碍[6];系统中构件的异质性不利于测试标准的统一及自动化的实现;构件版本更新快以及不确定性迫使要对构件系统进行较为频繁的回归测试等。构件软件测试通常分为构件测试、子系统测试、系统测试三个阶段[7],分别与传统软件测试过程的单元测试、集成测试和系统测试相对应。Harrold[8]认为应该从构件开发者和构件使用者两个不同的角度来看待构件软件的测试问题。一般来讲,构件测试主要由构件的开发者完成,而后两个阶段的测试则由使用者实施。总之,根据构件自身的特点,寻求高效的构件软件测试技术和开发实用的测试工具是当今软件业界一个亟待解决的问题。相对于构件测试的研究,针对缓冲区溢出攻击的研究比较成熟。缓冲区溢出攻击可追溯到1988年臭名昭著的Morris蠕虫,在随后的1989年里,Spafford提交了一份关于运行在VAX机上的BSD版UNIX的fingerd的缓冲区溢出程序的技术细节的分析报告,这引起了一部分安全人士对这个研究领域的重视。但真正让众人认识缓冲区溢出攻击的论文是1996年11月AlephOne在Phrack杂志第49期发表的论文“Smashingthestackforfunandprofit”[9]。该论文阐述了Linux系统中栈的结构和如何利用栈缓冲区溢出,并首次提出shellcode的概念。1999年,IIS4.0远程攻击代码的作者darkspyritAKABarnabyJack[10]在PhrackMagzine(第55期)上提出了使用系统核心DLL中的“jmpesp”的指令来完成到shellcode跳转的想法,从此开创了Win32平台下缓冲区溢出的新思想,大量Windows平台下的缓冲区溢出漏洞也被利用。在缓冲区溢出漏洞挖掘和检测方面,国外已经有一些较为深入的研究工作,并且这些技术都能应用到在Win32平台下开发的软件中,而国内的研究还处于初级阶段。通过对目前国内外相关研究文献的搜集整理,按照各种技术的研究对象的不同,可将缓冲区溢出漏洞检测的研究工作进行如下分类[11,12,13]:1、基于源代码的静态检测技术:将源代码作为输入,通过建立漏洞库,扫描源代码,匹配漏洞模式来检测缓冲区溢出漏洞。目前关于此方面的研究较多,典型的工具有ITS4[14]、BOON[15]、Splint[16,17]、ARCHER[18]等。2、基于源代码的动态检测技术:可通过扩展编译器功能和直接修改源代码来完成溢出漏洞的检测,如StackGuard[19,20]和STOBO[21]等工具。3、基于二进制代码的动态检测技术:通过静态或动态修改二进制代码和采用“黑盒测试”的方法实现栈溢出漏洞检测,如RAD[22]、libverify[23]和Fuzz[24]等工具。4、基于二进制代码的静态检测技术:该技术的研究目前比较少见,目前典型的研究是通过一些反汇编工具对目标代码进行处理,然后再依赖一些源代码静态检测技术进行处理[25,26,27],如TerryEruceCillette[28]、bugscam[29,30]等工具。漏洞检测方法各有利弊,需要根据具体情况选择相应的方法来确保最大程度上挖掘出缓冲区溢出漏洞。在上述的研究工作中,基于二进制代码的静态检测技术对于针对COM组件的缓冲区溢出检测具有指导意义。论文中的检测技术就是归属于基于二进制代码的静态检测技术的。1.3论文主要研究工作论文的主要工作为:在研究已有缓冲区溢出检测技术,分析现存成熟的缓冲区溢出检测工具,研究C/C++程序的逆向技术以及参加实验室的针对COM组件的安全漏洞自动化检测工具的开发的基础上,设计了一种针对COM组件二进制代码的栈缓冲区溢出漏洞检测算法SBOD-BC-CC(stackbufferoverflowdetectionbasedonbinarycodeofCOMcomponent),并最终实现一个COM组件栈缓冲区溢出检测原型系统。具体工作包括:1、现有的栈缓冲区溢出检测技术的总结对现有的栈缓冲区溢出检测技术进行了深入的研究,将溢出检测技术分为四大类:基于源码的静态检测、基于源码的动态检测、基于二进制代码的静态检测和基于二进制代码的动态检测。详细分析了这四类技术并介绍了各自的代表自动化工具。在分析现有检测技术的基础上,提出论文研究的检测技术。2、C/C++程序的逆向技术研究熟悉反汇编工具IDA、OllyICE和Win32dsm,熟练掌握IDC脚本语言。研究在汇编代码中识别程序流程,识别函数(库函数和用户编写的函数),识别函数的参数,识别函数的变量等技术,在研究识别库函数的过程中提出了一个切实可行的算法。3、函数定位模块的实现为了减少扫描汇编代码得到的无用信息,提高检测效率。根据COM组件结构特点,设计了一种自动识别接口函数的算法,并实现了一个函数定位模块,可以将COM组件中用户函数名和该函数在汇编代码中的线性地址实现精确定位。4、COM组件危险函数库的建立栈缓冲区溢出通常发生在字符串操作函数中,对于一般的C/C++程序,使用的是C字符串,所以危险函数库主要是由常规字符串操作函数(如strcpy、strcat等)的子集构成。但COM组件中使用的是混合字符串。常规的字符串操作函数无法处理该类型字符串。因此必须建立适用COM组件的危险函数库。5、栈溢出检测算法的实现在详细分析bugscam的基础上,对其两大核心函数SHeapBuffSize和函数StckBuffSize做了改进。利用IDA的脚本语言IDC实现了一个完整的栈溢出检测算法SBOD-BC-CC。该算法可以根据危险函数的参数类型,识别不同的赋值操作指令,从而较准确的得到参数代表的缓冲区的分配长度和使用长度。依据缓冲区溢出的标准,判断栈溢出漏洞。同时通过识别函数调用的操作指令,并结合函数定位模块,可以提取用户函数的依赖关系,以XML格式输出的函数依赖关系为COM组件的后续分析提供良好的输入信息。1.4论文结构第一章为绪论,主要介绍论文研究内容的一些背景情况,国内外研究现状和论文的主要内容。第二章首先介绍了COM组件,然后对缓冲区溢出的机理做了分析。重点介绍了栈缓冲区溢出的原因和分类,同时比较分析了现有的栈缓冲区溢出检测技术和工具。第三章重点介绍静态分析的理论基础,研究如何从二进制代码中识别高级语言的关键结构。介绍了在汇编代码中识别函数,识别函数参数,识别函数返回值以及识别函数变量的技术。同时讨论了COM组件危险函数库的建立。第四章详细介绍COM组件的栈缓冲区溢出漏洞检测系统的设计与实现。第五章介绍对实现的原型系统进行的测试与分析。第六章是对本文工作的总结和展望。2COM组件及栈缓冲区溢出漏洞检测方法基础COM组件在受到广泛应用的同时,自身的安全问题也日益暴露。其中,缓冲区溢出安全问题占了很大的比例。为了提高组件软件的健壮性,针对COM组件缓冲区溢出漏洞检测方法的研究具有十分重要的意义。本章首先简单介绍了COM组件,然后详细介绍了缓冲区溢出原理和目前成熟的检测方法及工具。2.1COM组件COM组件是符合COM规范编写的组件。COM是由Microsoft提出的组件标准,不仅提供了组件之间的接口标准,还引入了面向对象的思想。在COM标准中,对象是某个类的实例,称为COM对象。接口是一组方法的集合,其方法也称为接口成员函数。COM组件为COM对象提供活动空间,COM对象以COM接口方式提供服务。COM组件、COM对象和COM接口三者之间的关系如图2.1所示[31]。图2.1COM组件、COM对象和COM接口关系COM组件有两种,一是进程内组件,是一个DLL(动态链接库)文件;二是进程外组件,是一个EXE(可执行程序)文件。当组件的客户程序调用组件的功能时,首先创建一个COM对象,然后通过该对象实现的COM接口调用所提供的服务。当所有的服务结束后,如果客户程序不再使用该COM对象,那么应该释放掉COM对象所占有的资源,包括对象本身。组件的内部实现对客户程序是完全隐藏的。2.1.1COM对象COM提供的是面向对象的组件模型,COM组件提供给客户的是以对象形式封装的实体。客户程序与COM组件通过COM对象交互。COM规范采用了128位全局唯一标识符GUID来标识COM对象。与C++对象相比两点不同:1、COM对象的数据成员的封装以组件模块为最终边界,对于对象用户是完全透明的;而C++对象的封装特性相比之较差,可能对于用户是可见的。2、COM对象的可重用性通过COM对象的包容和聚合来实现;而C++对象的可重用性是通过继承机制实现的。2.1.2COM接口1、接口的定义接口是一组逻辑上相关的函数集合。COM对象通过接口成员函数对外提供服务。接口的传统命名前缀为I。COM模型中,客户程序通过接口获得对象的服务。每个接口由一个128位的全局唯一标识符IID来标识,客户通过IID获得接口的指针,再通过接口指针调用相应的接口成员函数。COM规范使用IDL(接口描述语言)来定义COM接口。MicrosoftVisualC++提供了MIDL工具,可以把IDL接口描述文件编译成C/C++兼容的接口描述头文件(.h),该文件可以被组件程序和客户程序所使用。2、接口的内存结构客户程序用一个指向接口的指针来调用接口方法,接口指针又指向另一个指针(pVtable),pVtable指向接口函数表(vtable),接口函数表的每一项为4个字节的函数指针,每个函数指针与对象的函数实现连接起来(如图2.2所示)。接口指向的虚函数表是确定的,即接口的成员函数个数和先后顺序是不变的;对于每个成员函数来说,其参数和返回值也是确定的。不管什么语言,只要能支持这样的接口内存结构描述,就可以定义接口。图2.2COM组件接口结构3、IUnknown接口COM定义的每一个接口都必须从IUnknown接口继承。IUnknown接口提供了接口查询和对象生存期管理的功能。如果客户要对对象进行操作,则必须保证对象存在于内存中;如果客户对对象的操作完成,则必须把对象从内存释放掉。IUnknown接口采用引用计数方法,可以有效的控制对象的生存期。IUnknown接口包含3个方法:QueryInterface、AddRef、Release。QueryInterface用于完成接口之间的跳转,查询COM对象的其它接口;AddRef和Release用于对引用计数进行操作:当调用AddRef时,对象的引用计数加1;当调用Release时,对象的引用计数减1。当对象的引用计数为0时,对象可以自己释放自己。2.2缓冲区溢出原理除了人为因素外,缓冲区溢出主要源于现有系统中的进程内存分配和结构布局,非类型安全语言的使用以及危险函数的使用等问题。2.2.1缓冲区溢出机理缓冲区溢出是向一个缓冲区填充超过它处理能力的数据所造成的结果[9]。危险函数是指可能导致缓冲区溢出的C标准库函数和系统API。广义的“缓冲区”是指应用程序中定义的变量在进程中对应的内存位置。在C程序中定义的全局变量或用关键字static定义的静态变量,已被初始化的分配在数据区,而未被初始化的则位于BSS区;用内存分配函数分配的变量位于堆区;函数中的局部变量被分配在堆栈区。这些内存区域的分配位置如图2.3所示。图2.3Windows内存分布一方面,程序中为变量分配的缓冲区在进程对应的内存结构中具有固定的大小,另一方面由于C语言不进行边界检查,在给变量直接或间接赋值时,如果不加以注意,变量有可能被分配到一个超过其对应缓冲区大小的数据,导致缓冲区溢出。当程序试图访问已分配的缓冲区以外的内存时,将出现下列三种情况之一:一是内存不存在,系统报错;二是系统提供了内存保护的功能,报段错或保护错;三是允许访问,但将覆盖缓冲区一侧的内容,轻则留下安全隐患,成为日后被入侵者攻击的弱点,严重的会导致系统崩溃。被溢出的缓冲区若位于堆栈段,则称为堆栈溢出,若位于data、bss、heap段,则称作堆溢出。本文设计的漏洞检测系统主要针对的是栈溢出漏洞。1、堆栈定义堆栈是一块保存数据的连续内存。一个名为堆栈指针(ESP)的寄存器指向堆栈的顶部。堆栈的底部在一个固定的地址。堆栈有两个重要的操作PUSH和POP,分别实现向堆栈中添加元素和从中移去元素。2、堆栈帧堆栈由逻辑堆栈帧组成。当调用函数时逻辑堆栈帧被压入栈中,当函数返回时逻辑堆栈帧被从堆栈中弹出。堆栈帧包括函数的参数,函数的局部变量,以及恢复前一个堆栈帧所需要的数据,其中包括在函数调用时指令指针(EIP)的值。3、堆栈增长方式堆栈既可以向下增长(低端地址)也可以向上增长,这依赖于具体的实现。在很多计算机的实现方式中,包括Intel,Motorola等处理器,堆栈是向下增长的。堆栈指针(ESP)也是依赖于具体实现的。它可以指向堆栈的最后地址,或者指向堆栈之后的下一个空闲可用地址。本文中,堆栈是向下增长,ESP指向堆栈的最后地址。4、栈溢出的原理函数调用时的堆栈分配过程中,非静态局部变量缓冲区的分配和填充不是同时进行的,并且依据不同的标准:局部变量缓冲区的分配是依据局部变量的声明,而填充则是依据其实际被赋予的值。因此这个过程中就出现了安全漏洞。当对局部变量填充的值长度超出分配的值长度,而程序中又缺乏边界检查机制时,数据就会继续向栈底写入,相继覆盖其他变量的缓冲区、EBP和EIP等,如图2.4所示。EBP是定位函数形参和局部变量的标杆,EIP决定函数调用结束后的返回地址,二者一旦出错轻则使程序运行不正常,重则出错终止,甚至被他人取得程序的运行权进而控制本机。攻击者只要在程序运行时传送给它一个足够大的参数,就可以在返回地址EIP中填入一个攻击者希望程序转向的任意内存地址,从而控制了程序的运行权。这也就是基于堆栈的缓冲区溢出攻击的原理。2.2.2栈缓冲区溢出分类1、根据溢出破坏的内容分类JohnWilander和MariamKamkar[32]根据栈溢出所破坏的内容将栈溢出分为六种类型:(1)破坏函数返回地址;(2)破坏原栈帧基指针;(3)破坏作为变量的函数指针;(4)破坏作为函数参数的函数指针;(5)破坏作为变量的longjmp缓冲区;(6)破坏作为函数参数的longjmp缓冲区;在C语言中包含了一个简单的检验/恢复系统,称为setjmp/longjmp。头文件<setjmp.h>中声明了这些函数,并且定义了jmp_buf数据类型。setjmp(jmp_bufj)必须首先被调用,它表示“使用变量j记录现在的位置,函数返回0”。longjmp(jmp_bufj,inti)可以接着被调用,根据jmp_buf结构中保存的程序现场实现跳转。jmp_buf结构存放了程序当前寄存器的值,以确保使用longjmp后可以跳回到该执行点上继续执行。VC6.0编译器对其在X86下的定义如图2.5所示。图2.4函数调用时的栈帧图2.5jmp_buf数据结构然而,如果能在longjmp函数执行以前覆盖掉jmp_buf,就能重写寄存器EIP的值,当longjmp函数恢复保存的堆栈栈帧后,程序就可能跳到指定的地方去执行。类似函数指针,longjmp缓冲区能够指向任何地方,所以攻击者所要做的就是找到一个可供溢出的缓冲区。2、根据溢出的方向分类根据栈溢出的方向可以分为“栈上溢”和“栈下溢”,比如固定大小的缓冲区的正负越界操作就会发生栈上溢和栈下溢。3、根据溢出改写数据的方式分类根据栈溢出时对数据的改写方式是否连续[24],可以分为连续的栈溢出和不连续的栈溢出,目前的大多数检测工具都只能检测连续的栈溢出。 2.3栈缓冲区溢出检测技术目前,在缓冲区溢出检测技术研究方面,国外的研究比较深入,而国内的研究尚处于起步阶段[33]。总体上,栈缓冲区溢出的检测技术可分为两类:静态检测技术和动态检测技术。静态检测技术指的是不依赖于程序的运行检测出栈溢出漏洞的技术。静态检测一般发生在编译前和编译后,分为基于源代码的静态检测和基于二进制代码的静态检测。基于源代码的静态检测技术是目前研究相对较多的一个分支。基于二进制代码的静态检测技术的研究目前比较少见,其本质也是基于“源码”的检测技术。动态检测技术指的是检测过程必须依赖于程序的运行,一般需要在程序中插入或者修改一些代码来检测栈溢出漏洞。动态检测可分为基于源代码的动态检测和基于二进制代码的动态检测。基于源代码的动态检测可通过扩展编译器和直接修改源代码来完成溢出漏洞的检测。基于二进制代码的动态检测通过静态或动态修改二进制代码和采用“黑盒测试”的方法实现栈溢出漏洞检测。当前栈溢出检测技术的总结如图2.6所示。图2.6现有栈溢出检测技术总结2.3.1基于源代码的静态检测技术基于源代码的静态检测技术是目前研究相对较多的一个分支,通过基于源代码的词法分析、模型化、标记驱动、语法分析等静态分析技术完成栈溢出漏洞的检测[34]。1、词法分析该技术的代表检测工具是ITS4。ITS4是最早的以命令行方式工作在Linux和Unix环境中的一种静态扫描C/C++源代码中安全漏洞的简单工具。ITS4构造了一种具有安全威胁模型结构的数据库,然后使用词法分析技术对源码进行对应模式的搜索匹配。它可以发现一些最普遍的安全漏洞。2001年发布的基于Python语言开发的用来安全审查C/C++语言程序的工具FlawFinder[35]和同年发布的RATS[35]与ITS4的工作原理相同,都是先建立漏洞数据库,然后通过对源程序进行词法分析进行模式匹配,找到潜在的漏洞。这类检测技术的特点是实现简单、算法效率高,但是由于没有考虑到语法和语义层次的信息,容易出现漏报和错报问题。2、模型化可以将缓冲区溢出检测形式化为整数限制求解问题。代表检测工具是BOON。BOON(BufferOverrunDetection)是加州伯克利大学的DavidWagner博士在其博士论文中实现的原型系统,将检测缓冲区溢出问题转化为整数范围分析问题。BOON不检测格式化字符串漏洞的扫描,而且检测结果存在大量的误报和错报,但作者明确表示不再有更新的版本,所以近年来在这方面并无新的研究进展发布。3、标记驱动该技术的代表检测工具是Splint。LCLint是DavidEvans等人在Lint工具的基础上开发的一种使用规范来检查代码安全性的工具。2002年一月初LCLint改名为Splint(SecureProgrammingLint),专门检测程序的安全问题。Splint要求程序员在C源码中按照固定的注释格式/*@...@*/加入标记,这些标记被当作规格属性,根据标记在每个函数中增加事先条件和事后条件(事先条件是指函数参数必须满足指定的限制,事后条件保证函数结果满足一些限制要求),然后利用词法分析等技术验证标记的限制条件与程序是否一致。Splint不能很好的处理指针的运算。使用标记驱动技术的另一成熟检测工具是NuritDor[36]等开发了一种名为CSSV的缓冲区检测器的原型系统,其创新处在于引入契约的概念。4、语法分析YichenXie等开发了一种名为ARCHER的检测工具。ARCHER的核心处理过程大致如下:首先对源码进行语法分析,生成抽象语法树AST,然后利用工具构造相应的程序流程控制图,在其上进行路径敏感分析。在对Sendmail、Linux等一些开放源码的系统测试中,ARCHER取得了不错的效果。除了上述几种工具外,VinodCanapathy[37]等人提出一种基于线性规划和静态分析理论的轻量级别检测算法,该算法首先使用工具软件生成一些指针相关的信息和抽象语法树,然后生成一些线性约束,并利用线性规划的思想来完成区间分析,检测可能出现的溢出漏洞。该算法主要围绕着商业工具软件Codesurfer来实现的。2.3.2基于源代码的动态检测技术基于源代码的动态检测技术一般需要在程序运行之前在程序中插入或者修改一些指令来检测栈溢出。根据插入代码方式的不同,可分为扩展编译器和直接修改源码的检测技术。1、扩展编译器扩展编译器的技术是指对现有的编译器进行扩展,通过编译器收集栈的信息,增加缓冲区边界信息并插入边界检查代码,以实现对栈溢出的检测。根据栈溢出检测方式的不同,目前常用的两种方法是基于“canary”的边界检查和数组边界检查。(1)基于“canary”的边界检查基于“canary”值的边界检测主要是由Cowan提出来的一种检测策略。目前该策略的实现工具StackGuard受到了广泛应用。StackGuard是一个GCC编译器的补丁,援引StackGuard作者的话,StackGuard是一种“用轻微的性能损失来消除堆栈溢出问题的编译器技术”。StackGuard主要是在内存中的返回地址及缓冲区之间插入一个“canary”值(一个单字),这样函数调用时的栈帧如图2.7所示。图2.7StackGuard作用下的函数栈帧布局如果试图用溢出的方法修改返回地址,就会破坏“canary”值。当函数返回时,发现“canary”值被改变了,就证明可能有人试图进行缓冲区溢出攻击,程序会立刻响应,发送一个警告消息,然后停止工作。为防止攻击者构造“canary”,StackGuard选用“终止符”和“随机数”来作为“检举字”的值。但由于“检举字”所在的位置是固定的,所以可能通过指针跳转被绕过。StackGuard的这个缺点被MariuszWoloszyn发现[38],并在Bulba和Kil3er的论文“BypassingStackGuardandStackShield”中公布。StackGuard对大多数返回地址溢出攻击非常有效,但是对于堆攻击,函数指针攻击,长跳转缓冲区攻击,堆栈帧指针溢出攻击以及格式化字符串攻击无法做出防御。后来又有很多工具在StackGuard的基础上进行了扩展,比如由Vendicator开发的基于Linux和IntelI386架构的GCC编译器补丁程序StackShield[38]、由IBM日本研究院的HiroakiEtoh和KunikazuYoda实现的编译器保护技术Propolice[39]和编译器补丁PointGuard[38]等,但其基本原理都是一样的,只不过在局部进行了优化而已。(2)数组边界检查缓冲区溢出的原因是C语言并不检查缓冲区的边界,所以数组边界检查能防止所有的缓冲区溢出的产生和攻击。为了实现数组边界检查,应当检查对数组的所有读写操作以确保对数组的操作在正确的范围内。RichardJones和PaulKelly开发了一个GCC补丁[40],对C语言进行完全的数组上下界检查。该技术采用了一种新的概念“指示目标”(referentobject),用它来代表指针所指向的对象的各种特征:大小、位置等。所有的“指示目标”存在一个全局表,而对堆空间的操作用另一个结构来记录。利用这些信息,在使用指针或进行指针运算的时候即可进行边界检查。CRED(TheCRangeErrorDetector)[41]是对Jones和Kelly方法的扩展。CRED扩展了“指示目标”内涵,允许指针指示的目标地址超出缓冲区的边界。这种方法在具有大量指针和数组运算的程序中会带来很大的性能损失,因为每次指针和数组的访问都要进行检查。2、修改源代码实际应用中,直接修改源代码不是一种合理的行为,因为涉及到大量的人力劳动。大多数栈溢出检测工具都是从编译器的角度进行静态插装,但并不是所有的思想都能通过修改编译器实现的,美国哥伦比亚大学的Sidiroglou、Giovanidis和Keromytis提出的“以堆替栈”的技术[42],即DYBOC(DynamicBufferOverflowContainment)技术和EricHaugh、MattBishop提出的修改源代码的STOBO工具(SystematicTestingofBufferOverflows)通过修改源代码体现出了非常有意义的栈溢出的检测思想。2.3.3基于二进制代码的静态检测技术基于二进制代码的静态检测技术的研究仍然比较少见,目前典型的研究是通过一些反汇编工具对二进制代码进行处理,得到汇编代码,然后再依赖一些源代码静态检测技术在汇编代码的基础上进行处理。在2001年的BlackHat大会上,HalVarFlake做了名为“AuditingClosed-SourceSoftware—Usingreverseengineeringinasecuritycontext”的报告,介绍了利用IDA的脚本语言IDC识别漏洞的方法,并介绍了bugscam工具。bugscam是一个基于IDAProIDC脚本机制、轻量级的漏洞分析工具。bugscam并不能处理所有的压栈指令,在很多情况下不能得到缓冲区的偏移地址,也就不能算出缓冲区的大小,更不能发现缓冲区溢出漏洞。反汇编工具IDAPro可以支持多种芯片和多种操作系统的可执行文件格式,对没有调试符号的文件,能正确地确定所引入的库和引进调用的库函数,并能标注函数的参数,bugscam就是利用这一点,利用IDA识别出来的函数参数,间接得到参数的缓冲区大小。其核心思想是通过两个相邻参数的偏移量之差得到参数的缓冲区大小。下面以栈中缓冲区分配为例,如图2.8所示。图2.8栈中缓冲区分配简略图比如函数内部有一个leaeax,[ebp-808h]形式的指令,那么可以判断ebp-808就是一个栈分配区域。这样也就确定了函数栈空间有一个var_808的变量,IDA可自动识别出栈中变量。如果要确定dst变量的大小,那么就是1008h-808h=800h。2.3.4基于二进制代码的动态检测技术1、静态修改二进制代码目前这类技术可以分为以下两类:一类是通过静态分析程序生成的二进制代码,找出相关函数的入口点和退出点,然后修改二进制代码。在相关函数的入口点和退出点插入一些语句来检测栈的运行情况。该技术的典型代表工具是由Prasad和Chiueh提出来的RAD工具。其检测方法是在函数调用产生新堆栈时,将函数返回地址副本保存到返回地址仓库RAR中,在函数返回时检查副本和返回地址的一致性,依此检测栈溢出。另一类工具是通过对带调式信息的程序提取出全局和局部缓冲区的大小信息,并把这些信息组织后,重新写回到二进制代码文件中,然后通过监控内存操作库函数对这些全局和局部缓冲区的操作行为来检测是否发生了栈溢出。该方法的典型是印度理工的KumarAvijit、PrateekGupta和DeepakGupaa提出来的两个工具:TIED和Libsafe-Plus[43]。2、动态修改二进制代码这类技术是在程序运行过程中对二进制代码进行修改,在函数的出入口插入一些指令来获取栈的运行情况。由于动态修改二进制代码技术可以不依赖源码的支持,因此对此技术的研究比较热。目前比较有代表性的技术为ArashBaratloo、TimothyTsai和NavjotSingh开发的libverify以及印度理工的SuhasGupta、PranayPratap、HuzurSaran和S.Arun-Kumar提出来的函数出入口替换技术[44]。3、黑盒测试该技术是通过自动化工具生成测试数据,通过仿真攻击状态下应用程序的执行状态来判断缓冲区溢出漏洞位置。该技术的代表是由威斯康星大学的BartonR.Milier等开发的Fuzz工具。该工具通过随机生成不同长度的字符串对目标代码的执行进行测试。Fuzz是可定制的,用户可以通过设置不同的选项来对软件进行不同类型的测试。即可用它来发现SQL注入、格式串、缓冲区溢出、目录遍历和其它漏洞。这种工具可以发现一些软件中的缓冲区溢出漏洞,但是这种对潜在漏洞的搜索是强力性和无策略的,而执行路径一般不可能完全估计到,因此存在潜在漏洞的可能性。另外,Fuzz执行时间很难估计,而且该工具给出的结果仅仅是一种判断,漏洞在源码中的具体位置不能确定。2.3.5现有缓冲区溢出检测技术总结1、静态检测技术(1)由于在静态环境下很难获得精确的栈的使用情况和递归调用的次数,而且静态工具也难以分析多线程环境,所以静态工具只能检测部分栈溢出漏洞;(2)静态分析工具的分析结果通常会包含大量的错误信息,由此带来很多额外的人工检查工作;(3)相对动态检测技术而言,静态检测技术不需要实际发生栈溢出,而动态检测技术只有在栈溢出发生时才能检测到,由于测试代码不太可能对所有的程序进行完全的覆盖测试,因此动态情况下有些栈溢出的代码实际上不能被触发,而静态分析工具有时能够检测出这样的一些栈溢出情况。所以静态检测技术不能作为检测栈缓冲区溢出的主要手段,但作为其他方法的补充还是很有价值的。2、动态检测技术(1)基于源码的动态检测技术必须依赖源代码[45]。目前主要有两种实现方法:扩展编译器的方法,实现难度比较高,需要重新编译源代码,但是能够提供比较完整的栈操作的信息和函数的信息;直接修改源码的方法,实现难度低,但可能会带来一些误操作,而且需要大量的人力。(2)基于二进制码的动态检测技术主要有三种实现方法:静态修改二进制码的方法,基于反汇编的修改方法容易出现反汇编的错误,导致检测效果大打折扣,甚至可能造成代码的运行不正常,基于调试信息的修改方法实际上还是要依赖于源代码;动态修改二进制码的方法,libverify不能检测出静态链接代码的栈溢出错误,而函数出入口替换技术目前只能用在特殊编译模式下的代码。(3)动态检测技术在检测到溢出时,通常是终止程序的执行,这虽然防范了攻击,但同时带来了拒绝服务攻击;(4)动态检测技术最主要的缺点是只能检测出真正发生动态溢出的代码,而实际中只有极少量的测试用例可以触发栈溢出,因此动态检测技术并不能检测出所有可能的栈溢出。所以,动态检测技术作为栈缓冲区溢出检测的主流技术,已经可以检测出大量的栈溢出漏洞,而且自动化程度高,但仍不能全面的检测出漏洞。因此软件开发人员应同时运用静态检测技术和动态检测技术,在软件编码和运行阶段分别开展检测,从而提高对栈溢出漏洞的检测效果。2.4小结本章首先介绍了COM组件,然后对缓冲区溢出的机理做了分析。重点介绍了栈缓冲区溢出的原因和分类,同时比较分析了现有的栈缓冲区溢出检测技术和工具。下一章将介绍COM组件栈缓冲区溢出漏洞检测技术。3COM组件栈缓冲区溢出漏洞检测技术造成缓冲区溢出的根本原因是非类型安全语言(如C++/C)没有对内存访问进行边界检查。换言之,出现缓冲区溢出问题的程序大都是C++/C语言编写的程序。因此论文中研究的COM组件栈缓冲区溢出漏洞检测技术的研究对象默认是借助VisualC++6.0的ATL(ActiveTemplateLibrary)开发得来的。鉴于COM组件是符合特定规范的C++程序,对COM组件缓冲区溢出漏洞的检测可以建立在对普通程序(C/C++程序)缓冲区溢出漏洞检测的基础上,再结合COM组件的特性,达到预期的研究目标。论文中提出的检测技术和目前已有的基于二进制代码的静态检测技术的不同点在于:1、对象不同基于二进制代码的静态检测技术的研究对象是一般意义的C/C++程序,但论文中的检测技术是针对源码未知的COM组件。虽然COM组件在实际应用中大部分是通过C++开发的,但是因为其遵循COM规范,所以在结构和特性上与一般的C/C++程序差别很大,这也就造成了检测技术的不同。2、危险函数库不同一般的C/C++程序中使用的是C类型字符串,以‘\0’截断符标识字符串的结束,这类型字符串的操作函数都是常规的字符串操作函数,如strcpy,strcat等,它们中的一些可能导致缓冲区溢出问题的操作函数构成危险函数库。但是COM组件使用的是混合类型字符串,这就导致了常规字符串操作函数的失效,需要根据COM组件的字符串操作函数建立特殊的危险函数库。这是进行缓冲区溢出检测的首要工作。3、核心算法不同论文中提出的系统,是基于IDA的IDC脚本语言开发的。通过对组件的接口分析,获取组件接口函数的线性地址,根据该线性地址准确定位至待测的组件函数。在检测范围内定位危险函数,识别危险函数的参数类型,根据不同类型获取参数赋值操作从而计算参数代表的缓冲区的分配长度和使用长度,进而进行溢出漏洞判断。同时提取函数依赖关系图,将以上信息以XML的格式输出,为COM组件的后续分析提供良好的输入信息。识别汇编代码中与栈溢出相关的语法成分和建立适用COM组件的危险函数库是论文检测的关键技术。本章将研究如何从汇编代码中识别高级语言的关键结构和建立COM组件危险函数库。3.1函数的识别3.1.1一般函数的识别函数是过程设计语言与面向对象设计语言的主要结构单元,因而,对汇编代码的分析通常是从识别函数以及传递给它们的参数开始[46]。在绝大多数情况下,编译器都使用CALL和RET专用机器指令来调用函数与返回到调用位置。CALL指令将紧接在它之后的指令的地址压入堆栈的顶部,而RET指令则将该地址弹出来,并把控制传递给它。如果被调函数平衡堆栈,那么被调函数通过RETn指令结束调用,其中,n是在函数返回时从堆栈弹出的字节数。如果调用函数平衡堆栈,那么被调用函数通过RET指令结束调用。在下文中,除非特别声明,否则都认为函数通过RET指令结束调用。绝大多数非优化编译器会在函数的开头放入称为起始标志的代码,起始标志如图3.1所示。图3.1汇编代码中函数的起始标志当打开被调用函数的栈帧时,首先保存前栈帧的EBP,然后栈顶指针寄存器(ESP)的当前值复制到EBP之中,即打开堆栈页面,并且ESP的值会随着为该局部变量分配的内存块大小而不断减少,最后为函数局部变量分配内存空间。(PUSHEBP)(MOVEBP,ESP)(SUBESP,XX)序列可用于找到分析文件中的所有函数,包括那些没有对它们进行直接引用的函数。但是对于优化编译器,不需要分配一个专用的寄存器(EBP)进行局部变量的寻址,通过使用ESP寄存器就可实现对局部变量进行寻址。在这种情况下,优化函数的起始标志仅仅含有一条SUBESP,XXX指令,在这种情况下函数起始序列太短而不能当作辨认函数的标记。在“生命”的结束时期,函数通过向下(高端地址)移动堆栈指针而关闭堆栈页面,然后恢复EBP的先前值(仅当编译器通过EBP进行寻址时)。函数的结束标志如图3.2所示。图3.2汇编代码中函数的结束标志(MOVESP,EBP)和(POPEBP)指令不需要紧接在一起,它们之间可以用其他指令进行分隔。因而,使用上下文搜索对于寻找结束标志是不合适的。但是不管函数遵循何种调用约定,函数均通过RET指令或RETn指令终止调用。所以RET对于表示函数的结束是足够的,但并不是任何结束标志都是函数的结束,如果函数体内有多个RET操作符(一般情况就是这样的),编译器通常为每个RET操作符生成一个结束标志,所以需要检查在结束标志之后是否有新的结束标志,或者是否后面还有旧函数的代码。识别函数就是把汇编代码中出现的函数代码范围识别出来。在汇编代码中,函数的起始地址可以通过起始标志确定或通过CALL指令的操作数直接或间接定位。而函数最后都是以“ret”的形式返回的,但并不是出现ret的地方就是函数结束的地方。所以根据程序本身的流程,可以得到函数代码段识别算法3.1。算法3.1函数代码段识别算法输入:函数模块的起始地址addr_start输出:函数模块的结束地址addr_end算法:addr_scan=addr_start;Begin:确定从addr_scan开始的第一条ret指令的地址addr_end;if(addr_start到addr_end之间不存在跳转指令)returnaddr_end;取出addr_start到addr_end之间所有跳转地址的最大值addr_jmp;if(addr_jmp<=addr_end)returnaddr_end;addr_scan=addr_jmp;gotoBegin;该算法的时间复杂度为O(n),其中n代表函数汇编代码的规模。3.1.2库函数的识别成熟的软件开发中会大量使用标准库函数,以增强软件的健壮性。但对于库函数的识别核心是识别库函数名以明确其功能。对于一些标准库函数,可通过一些成熟的反汇编工具实现快速自动识别。IDA的一项革命性的工作是库文件快速识别与鉴定技术(FastLibraryIdentificationandRecognitionTechnology,简称FLIRT)。这项技术使IDA能在一系列编译器的标准库文件里自动找出调用的函数,使反汇编清单清晰明了。但IDA一般情况下不能识别出所有的库函数。而库函数的识别对于逆向工作的质量和效率都有很大的影响。这时,可以考虑通过用库函数的机器码匹配静态库文件的机器码的方法来识别库函数[47,48],如图3.3所示。1、分析识别编译器识别出执行程序对应的源码所用的编译器,这是能否正确识别静态库的前提。可以借助编译器编译所产生的其他特殊信息,可以很好的达到识别执行程序所用编译器的目的。图3.3通过机器码匹配识别库函数的方法流程2、建立函数库当根据PE文件识别出该PE文件所用的编译器时,选取该编译器常用的lib文件。由于这些库文件都是COFF结构形式的,所以可以通过分析COFF文件格式得到库文件中的所有函数的函数名、函数代码等信息。可以采用VisualC++6.0自带的工具dumpbin获取上述信息,如图3.4所示,是从libc.lib中获取的printf函数的汇编代码。因为编译器在编译时会采取重定位操作,并且函数的参数会有变动,所以汇编指令的操作数会改变,但这不影响函数模块的匹配。图3.4从libc.lib中获取的printf函数的汇编代码3、匹配算法该匹配算法就是从静态库建立起来的函数库中找出一个函数与识别出的函数代码的操作码序列相等,关于该算法,已有比较成熟的研究,详看参考文献[49]中所提到的算法。对于最后的匹配结果,可能有多个函数结果集满足要求,通过实际的反编译过程发现,这种比例只占整个静态库中1%-3%左右,可以基本上满足需求。3.2参数的识别区分函数的参数是对程序的反汇编结果进行分析的关键步骤。给函数传递参数的方式有三种:利用堆栈传递参数、利用寄存器传递参数以及通过全局变量进行隐含参数的传递。参数的正确识别取决于函数调用约定(CallingConvention)的判断。调用约定决定以下内容:函数参数的压栈顺序;由调用者还是被调用者把参数弹出栈;产生函数修饰名的方法。函数调用有多种机制,每种机制都有自己的优点和不足,并且它与使用的编程语言有关。本论文主要是针对C/C++程序的逆向分析,最常见的C++程序的调用约定有__cdecl、__stdcall、thiscall和__fastcall,如表3.1所示。论文中仅考虑参数通过堆栈传递,通过IDA的高效辅助,可以较准确的识别函数参数。但这同时也是本文实现的原型系统需要改进的地方。3.3函数的返回值函数的返回值通常是由return操作符返回的一个值。从汇编角度来看,主要有五种形式:1、使用return操作符从函数返回值(通过寄存器或者协处理器堆栈);2、通过参数按引用方式返回函数值;3、通过全局变量返回函数值;4、通过处理器标志返回函数值;5、通过堆从函数返回值。论文仅讨论第一种情形。根据有关规范的规定,由return操作符返回的值放在EAX寄存器(在16位模式下是AX寄存器)之中。如果函数的处理结果超出了这个寄存器的位容量,那么该操作数的高32位会加载到EDX寄存器中(在16位模式下,高位字会加载到DX寄存器里面)。在大多数情况下,浮点型(float)结果是通过协处理器堆栈来返回的,这种值也可以通过EDX:EAX寄存器进行传递(在16位模式下是DX:AX寄存器)。如果返回一个含有几百个字节的结构或者一个近似大小的对象,编译器会在不告诉程序的情况下,给函数传递一个隐式参数,这个指针指向保存的返回结果。具体的返回方式如表3.2所示。表3.2不同类型的函数返回值的返回方式类型(长度)返回方式1字节AL或者AX或者EAX寄存器2字节AX或者EAX寄存器4字节EAX寄存器8字节EDX:EAX寄存器浮点型协处理器堆栈或者EAX寄存器双精度型协处理器堆栈或者EDX:EAX寄存器近指针EAX寄存器多于8字节引用方式的隐含参数表3.1C++程序常用调用约定比较特征\调用约定__cdecl__stdcall__fastcallthiscall进栈顺序从右到左从右到左从右到左从右到左清理堆栈方式调用者被调用者被调用者被调用者函数名修饰约定C编译方式仅在输出函数名前加上一个下划线前缀。函数名前加上一个下划线前缀,后面加上一个"@"符号和其参数的字节数。在输出函数名前加上一个"@"符号,后面也是一个"@"符号和其参数的字节数。不适用C++编译方式以“?”标识函数名的开始,后跟函数名,函数名后面以“@@YA”标识参数表的开始。以“?”标识函数名的开始,后跟函数名,函数名后面以“@@YG”标识参数表的开始。以“?”标识函数名的开始,后跟函数名,函数名后面以“@@YI”标识参数表的开始。差异较大特点C和C++程序的缺省调用规范多数WindowsAPI调用方式寄存器调用方式,通常是前两个DWORD类型的参数或较小的参数使用ECX和EDX寄存器传递,其余参数按照从右向左的顺序入栈C++类成员函数调用方式。类实例的this指针通过ECX寄存器传递3.4变量的识别1、局部变量函数的局部变量或存在于栈中,或存储在寄存器中,但由于一些寄存器会自动用于某些操作,如EDI用于MOVSD,ECX用于需计数的指令等,所以在函数中使用寄存器保存局部变量相当少见,在本文中,仅考虑局部变量在堆栈中存储的情况。一旦获取CPU控制权,函数就打开堆栈页面(保存EBP寄存器以前的值,接着设置其值使它指向栈顶的ESP寄存器值相等)。局部变量位于EBP指向的地址的上方(也就是位于低端地址),而服务性数据(存储的EBP寄存器值以及返回地址)同参数一样位于它的下方(即位于高端地址)。局部变量与堆栈参数以相似的方式进行寻址。唯一不同的地方是,参数位于EBP的下方,而局部变量驻留在它的上方。参数相对于EBP地址具有正的偏移量,而局部变量的偏移量是负的。指向堆栈页面的寄存器起到了一个栅栏的作用:函数参数位于它的一侧,而局部变量位于另一侧。如图3.5所示。优化编译器能够通过ESP寄存器直接对局部变量与参数进行寻址,从而将EBP寄存器腾出来进行更有用的工作。由此可见,[EBP-04]为第一个局部变量,[EBP+08]为第一个参数,以此类推。图3.5函数局部变量和参数的寻址方式初始化局部变量的方法有两种:通过MOV指令为变量赋予必要的值,或者使用PUSH指令直接将值压入堆栈。在大多数情况下,流行的编译器使用MOV指令执行初始化操作,而不合理的汇编器多使用PUSH指令,其目的往往在于误导黑客起到保护的作用。2、全局变量在函数中判定全局变量很容易,全局变量通过直接对内存进行寻址就可访问。如:MOVEAX,[00401066]就表明函数在寻址全局变量,其中0x00401066是全局变量的地址。但判定变量的类型就像判定局部变量的类型,要知道函数是如何使用该变量。3、识别立即数的类型80x86微处理器系列支持三种类型的操作数:立即数、寄存器与内存。识别操作数的类型并不困难。如图3.6所示。图3.6识别操作数的实例说明80x86微处理器支持两种内存寻址模式:直接寻址方式与寄存器间接寻址方式。如果操作数是立即数,则使用直接寻址方式。如果操作数是存储在寄存器中的一个指针,那么寻址方式就是寄存器间接寻址方式,如图3.7所示。图3.780x86微处理器支持的内存寻址模式为初始化寄存器指针,微处理器开发人员引入了一条专用指令LEAREG,[addr],它将计算地址表达式addr的值并把结果放入REG寄存器之中。LEA指令右边的操作数总是表示一个近指针(LEA用于计算常量之和的情况除外),如图3.8所示。近指针的内部表示形式与取值相同的常数是一样的。图3.8LEA指令的说明程序中的立即数,可以是常量,也可以是指针。对立即数的正确判断对于程序的逆向分析作用很大。对立即数的错误判断,很可能导致整个程序分析工作的失败。要确定一个立即操作数的类型,关键在于分析立即数如何被使用。如果立即数是用于内存操作数寻址的,那么它就是一个指针,否则,就是一个常量。存在两种类型的指针:指向数据的指针与指向函数的指针。指向数据的指针用于从内存单元中提取数值,这类指针出现在算术或者移动指令(如MOV、ADD、SUB等)之中。指向函数的指针用在间接调用指令(CALL)当中,并且偶尔用在间接跳转调用指令当中。4、字符串在汇编代码中识别字符串的一个难点是判定字符串的结束标志,据此,可以将字符串分为如下几种类型:以‘\0’字符结尾的C字符串,以‘$’字符结尾的DOS字符串和以表示字符串长度的单字节、双字节或者四字节开头的Pascal字符串以及混合类型的字符串。具备C字符串和Pascal字符串特性的字符串称为混合类型的字符串。混合字符串因为有表示字符串长度的专用字段,所以使用该类型字符串的处理速度很快,并且能够存放任何字符。同时,它为大量的C语言ASCIIZ字符串处理函数提供了兼容性。一般编程语言的库函数在处理该字符串类型时,与处理Pascal字符串类型基本相同。在调用C库函数时,编译器传递指向字符串第一个字符的指针,而不是真正指向字符串结构开头的指针。字符串的类型大致可以通过编译器的类型来确定,而要精确的确定字符串的类型,就得分析处理算法。3.5COM组件危险函数库建立1、COM使用的字符串类型COM规范的基本字符数据类型是OLECHAR,它是与平台无关的字符表示法。在创建该字符集时,OLECHAR的基本数据类型随操作系统的不同而不同。如今最流行的COM平台是基于Win32API的,基于此,OLECHAR就是wchar_t的typedef。对于C++,Windows头文件wtypes.h中定义如图3.9所示:图3.9wtypes.h中OLECHAR的定义 COM组件是运行在分布式环境中的,例如一个COM组件程序(DLL或EXE),组件使用者可能是在本机的某个进程内加载组件(INPROC_SERVER),也可能是从另一个进程中调用组件的进程(LOCAL_SERVER);也可能是在不同的主机之间调用组件程序(REMOTE_SERVER)。这就要求COM字符串的长度被预先存储好,以便当COM字符串在进程或者计算机之间被传递时,COM库知道需要传递的数据。基于以上对字符串特性的要求,COM规范采用BSTR字符串数据类型。BSTR是混合类型的字符串,标准BSTR是一个有长度前缀和null结束符的OLECHAR数组。在C++中,BSTR实际上就是一个指向UNICODE字符串中首字符的32位指针,且BSTR向前的4个字节中,使用DWORD(双字类型,4个字节)保存字符串的字节长度(没有含字符串的结束符‘\0’)。因此系统就能够正确处理并传送字符串到不同的地理位置。例如:输入程序片段BSTRp=::SysAllocString(L"Hello,COM");断点执行,然后观察p的内存,如图3.10所示。 图3.10BSTR内存结构2、BSTR的包装类_bstr_t和CComBSTRCOM提供了两个BSTR分配用的API:SysAllocString/SysReallocString。函数返回的指针指向BSTR的第一个字符。所有的BSTR都必须使SysFreeString()释放,否则会造成内存泄漏。但是存在很多BSTR封装类可以实现内存自动管理。_bstr_t是CRT提供的一个对BSTR的完整封装类,实际上它隐藏了底层的BSTR。_bstr_t没有访问BSTR本身的操作符,所以一个_bstr_t类型的字符串不能被作为输出参数传给一个COM方法。CComBSTR是ATL中的BSTR封装类,CComBSTR允许访问底层的BSTR,可以传递一个CComBSTR对象给COM的方法。CComBSTR对象能够自动的管理BSTR的内存。3、导致栈缓冲区溢出的危险函数库收集危险函数是检测漏洞的关键工作。论文对一些可能导致栈缓冲区溢出的C标准库函数和WindowsAPI进行分类总结,建立危险函数库。(1)strcpy原型:externchar*strcpy(char*dest,char*src)原因:函数将源字符串复制到缓冲区,如果src的长度大于dest,则导致溢出。类似函数:wcscpy、lstrcpy、_tcspy和_mbscpy。(2)strcat原型:externchar*strcat(char*dest,char*src)原因:dest必须有足够的空间来容纳src的字符串,否则导致溢出。类似函数:wcscat、lstrcat、_tcscat和_mbscat。(3)strncpy原型:externchar*strncpy(char*dest,char*src,intn)原因:不检查目标缓冲区的长度,也不检查null或其他无效的指针。如果源缓冲区不是以null结尾的,可能造成溢出。类似函数:wcsncpy、lstrcpyn、_tcsncpy和_mbsnbcpy。(4)strncat原型:externchar*strncat(char*dest,char*src,intn)原因:dest必须有足够的空间来容纳src的字符串,否则导致溢出。类似函数:wcsncat、_tcsncat和_mbsnbcat。(5)memcpy原型:externvoid*memcpy(void*dest,void*src,unsignedintcount)原因:不检查目标缓冲区的长度,也不检查null或其他无效的指针。如果源缓冲区不是以null结尾的,可能造成溢出。类似函数:memccpy、memmove、movmem、bcopy。(6)memset原型:externvoid*memset(void*buffer,intc,intcount)原因:把buffer所指内存区域的前count个字节设置成字符c,若count超过buffer的长度,则导致溢出。类似函数:setmem。(7)strtrns原型:externchar*strtrns(constchar*str,constchar*old,constchar*new,char*result)原因:若str的长度大于result,则导致溢出。(8)streadd、strecpy原因:确保分配的目的地参数大小是源参数大小的四倍,否则导致溢出。(9)gets原型:externchar*gets(char*buffer)原因:它不检查被拷贝的缓冲区大小。(10)fgets原型:char*fgets(char*string,intcount,FILE*stream)原因:将count个字符(自动包含null)拷贝至string中。不检查目标缓冲区的长度,也不检查null或其他无效的指针。如果源缓冲区不是以null结尾的,可能造成溢出。(11)scanf、sscanf、fscanf、vfscanf、vscanf、vsscanf、_tscanf、wscanf原因:类似gets,scanf、_tscanf、wscanf在使用%s格式化字符的时候,由于%s是没有边界的,因此都很难正确使用。相关函数的漏洞原因类似。(12)(v)sprintf、(v)snprintf
原型:intsprintf(char*string,constchar*format,...)intsnprintf(char*string,size_tcount,constchar*format,...)原因:当string所指缓冲区的长度过小不足以容纳format所指缓冲区长度时,发生溢出。vsprintf()和vsnprintf()与前面所述的函数用法相同。它们的原型是:intvsprintf(char*string,constchar*format,va_listvarg)intvsnprintf(char*string,size_tcount,constchar*format,va_listvarg)(13)getc()、fgetc()、getchar()和read()原因:这些函数用于循环中,如果达到目标变量的最大值之后循环没能适时地停止,函数就有可能读入过多
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 桩基技术服务合同
- 2025年真空采血管合作协议书
- 电信运营商服务合同
- 2025年酚类合作协议书
- 红楼梦征文古今情感交织的思考
- 皮带测速仪安装施工方案
- 租赁协议补充协议
- 2025年程序指令配电器项目发展计划
- 节能环保产品供应合同
- 2025年太原货运资格证考试题库
- 高职院校高水平现代物流管理专业群建设方案(现代物流管理专业群)
- 2024专升本英语答题卡浙江省
- 稿件修改说明(模板)
- (完整版)50028-城镇燃气设计规范
- 五年级下册生命、生态、安全教案
- 原发性肺癌手术临床路径(最全版)
- 最新工程招投标实训课程标准教案
- 建筑工程施工质量验收规范检验批填写全套表格+示范填写及说明
- 刺五加种植加工项目可行性研究报告写作范文
- 铁路劳动安全《安全生产法》培训PPT课件(带内容)
- 最新公司产品研发部门绩效考核方案
评论
0/150
提交评论