本发明属于软件安全领域,涉及对恶意攻击的防护,具体是一种基于llvm编译器的程序非控制数据攻击防护方法,可用于保护程序的安全运行。
背景技术:
::由于很多程序都是由不安全的语言编写而成的,比如c或c++语言,从而容易受到基于内存安全的攻击。例如,攻击者可以利用一个缓冲区溢出漏洞,覆盖内存中的某个函数返回地址或程序分支变量数据,从而改变程序的执行流程。为了应对攻击,近年来,业界研究人员提出了许多保护机制来抵御基于内存安全的攻击。然而,分析发现,大多数现存的保护机制都是针对程序的控制数据(比如,函数指针和函数返回地址)提供保护,从而使得程序能够抵御基于控制流篡改的攻击。例如,美国加州大学圣克鲁兹分校等研究机构提出的cfi(control-flowintegrity)和西安电子科技大学等研究机构提出的fine-cfi系统,通过对程序的控制数据提供保护,使其免受攻击者基于控制流篡改的攻击。但是,业界对程序非控制数据的保护却没有提出足够有效的防御技术,而攻击者同样可以通过篡改程序的非控制数据实施攻击。非控制数据,即non-controldata,非控制数据是程序中一种区别于控制数据的关键数据结构,比如用户身份信息、用户配置信息、程序分支信息数据等。如果程序中的非控制数据被篡改,也会导致非常严重的问题,比如使得攻击者获得更高的权限(例如linux系统下的root权限),窃取用户隐私数据,开启后门漏洞等,因此必须对程序中的非控制数据进行保护。现有的非控制数据保护技术,大多针对32位应用程序,比如微软公司提出的data-flowintegrity[castroetal.,usenixosdi2006]和writeintegritytesting[akritidisetal.,ieees&p2008],而且由于其采用的指针分析方法精确性不足,导致分析结果可能存在部分错误,从而影响保护的效果。同时,大多数现存的非控制数据保护技术都是针对程序的整个虚拟地址空间提供保护,可移植性差,性能开销也比较大。技术实现要素:本发明的目的在于针对上述现有技术中的问题,提供一种基于llvm编译器的程序非控制数据攻击防护方法。它针对现今广泛应用的64位应用程序,将程序源代码编译成中间代码,通过对中间代码进行静态分析得到指向性分析结果,并且获得需要保护的非控制数据;同时,它在中间代码的基础上进行指令插桩,以保证程序的非控制数据不被恶意篡改。这种方法克服了原有静态分析方法的不足,提高了分析的精确程度,并且利用较低的空间和时间复杂度解决了攻击者对程序非控制数据恶意篡改所带来的安全威胁。本发明是通过以下技术方案来实现:一种基于llvm编译器的程序非控制数据攻击防护方法,包括如下步骤:(1)将程序源代码通过llvm编译器编译成中间代码;(2)通过对中间代码进行指针分析,得到程序中数据对象的指针别名分析结果;(3)确定程序的内存空间布局,得到程序的data/bss段的空间地址范围;(4)遍历整个程序,通过对中间代码的分析,判断数据对象是否属于程序的data/bss段,并结合指针别名分析结果,得到每条指令能够写入的数据对象集合;(5)为每条store指令及其能够写入的数据对象集合分配标识符;(6)利用步骤(3)中得到的data/bss段的空间地址范围,创建标识符表,同时对程序中间代码进行插桩,将标识符填入标识符表中,同时进行数据写入检查,若发生非控制数据篡改攻击,则抛出异常,否则,正常运行程序。优选的,步骤(1)中,具体是通过指令clang-emit-llvmfilename.c-s-ofilename.ll和clang-emit-llvm-cfilename.c-ofilename.bc编译出程序的中间代码。优选的,步骤(2)具体包括如下步骤:(2.1)实现指针分析算法;(2.2)将步骤(2.1)的指针分析算法放在llvm源代码目录下,并书写与指针分析算法相关的cmakelists和makefile文件,cmakelists和makefile文件定义了编译过程中的规则;(2.3)修改llvm源代码:将指针分析算法的文件目录写入llvm源代码文件的cmakelists文件中;(2.4)编译修改后的llvm源代码,生成能够调用的指针分析动态链接库文件;(2.5)执行如下命令:llvmcompiledirectory/opt-loadpassdirectory/llvmandersenpa.so-andpafilename.bc-oprogram.out,获得程序中数据对象的指针别名分析结果。进一步的,步骤(2.1)中,改进andersen指针分析算法,形成字段敏感的指针分析算法;具体过程为:(2.1.1)使用<o,f,sf>来表示一个指针指向目标的位置,其中,o表示所在数据对象的名字,f表示偏移量,sf表示指针指向目标的结束位置;(2.1.2)约束生成:在约束生成的过程中,遍历程序每一条指令,并按照andersen算法提出的约束规则为其生成相应约束,将整个程序系统转化为一个约束集合,将整个程序表示为一个约束图;(2.1.3)约束求解:约束求解的输入为初始约束图,输出为求解之后的约束图,该过程分为两步:处理复杂约束和传递指向集;处理复杂约束的过程是向约束图中添加新边,导致新的指向集传递过程,指向集的更新导致工作集的更新,从而进入下一次的迭代过程;(2.1.4)指向性结果获得:约束求解后,得到一个完整的指向图,将指向图转化为指向分析的结果。优选的,步骤(4)具体包括如下步骤:(4.1)修改/llvm/lib/transforms/hello/下的hello.cpp文件,生成用于store指令分析的动态链接库文件,从而得到store指令分析结果;(4.2)编译llvm源代码,并执行如下命令:llvmcompiledirectory/opt-loadpassdirectory/hello.so-hello-filename.bc-oprogram2.out,得到每条store指令能够写入的数据对象集合;(4.3)结合指针别名分析结果,将指针别名分析结果与store指令分析结果进行合并,并将最后结果写入文件之中。进一步的,步骤(4.1)具体过程为:(4.1.1)标记每一条store指令,给每一条store指令分配一个标识符;(4.1.2)遍历程序每一条指令,如果该指令是store指令,调用getoperand()函数判断指令的操作数,如果是变量,则跳转至4.1.3;如果是指针则跳转至4.1.4,否则继续下一条指令的判断;(4.1.3)判断操作数是否属于程序的data/bss段,如果是则将其放入一个链表中,跳转到4.1.2;(4.1.4)针对指针进行指针回溯,直到找到变量,否则回溯到内存分配指令,终止循环,将本条store指令能够写入的数据对象集合输出并跳转到4.1.2。优选的,步骤(5)中,使用一个或者两个字节作为标识符。优选的,步骤(6)具体包括如下步骤:(6.1)基于llvm编译器进行指令转换;(6.2)向llvm源代码导出的头文件中添加步骤6.1中所实现方法的声明;(6.3)修改llvm源代码中与机器架构相关的转换代码,实现汇编指令到二进制代码的转换;(6.4)编译llvm源代码,然后用编译好的llvm编译程序,输出结果。进一步的,步骤(6.1)具体包括如下步骤:(6.1.1)在llvm编译器后端的机器指令生成阶段,llvm编译器取得程序中间表示ir指令集合中的一条指令i;(6.1.2)判断指令i的类型,如果i是store指令,则执行步骤(6.1.3);如果i是全局变量的声明指令,则执行步骤(6.1.4);否则执行步骤(6.1.5);(6.1.3)对store指令i进行如下转换:(6.1.3a)在指令i前插入指令“leaqtar_add,%r11”,其中tar_add是store指令要写入的目标地址,r11为寄存器;(6.1.3b)在指令i前插入指令“shrq$0x3,%r11”;(6.1.3c)在指令i前插入指令“cmpb$0x3,idt_base(,%r11,)”;idt_base是标识符表的基地址,3代表该store指令的标识符;(6.1.3d)取得指令i的下一条指令的地址dst;(6.1.3e)在指令i前插入指令“jedst”;(6.1.3f)在指令i前插入指令“jmgerrhandler”,其中errhandler为系统中定义的错误处理例程;(6.1.3g)跳转到步骤(6.1.5);(6.1.4)对全局变量的声明指令i进行如下转换:(6.1.4a)在指令i前插入指令“push%r11”;(6.1.4b)在指令i前插入指令“leaqobj_add,%r11”,obj_add代表数据对象的地址;(6.1.4c)在指令i前插入指令“shrq$0x3,%r11”;(6.1.4d)在指令i前插入指令“movw$0x3,idt_base(,%r11)”;(6.1.4e)在指令i前插入指令“pop%r11”;(6.1.4f)跳转到步骤(6.1.5);(6.1.5)如果程序中间表示ir指令集中还有未处理的指令,返回步骤(6.1.2),开始下一条指令的处理;否则结束指令插桩;(6.1.6)修改llvm源代码/lib/codegen/目录下的cmakelists.txt及pass.cpp文件,以实现将6.1.1中算法编译到llvm源代码当中;同时修改/lib/ir/目录下instructions.cpp文件,实现中间代码指令的创建。进一步的,步骤(6.3)具体包括如下步骤:(6.3.1)开辟出一段内存,用于存放变量所对应内存的标识符,称为标识符表idtable,并找到其空间起始地址;(6.3.2)通过llvm-mc-show-inst命令将所需插桩的汇编指令转换为mc指令,在llvm编译的过程中,llvm编译器将mc指令转变为相应的二进制代码;(6.3.3)修改lib/target/x86/目录下的x86mcinstlower.cpp文件,将上述mc指令插入到相应位置;(6.3.4)将标识符表设置为只读。与现有技术相比,本发明具有以下有益的技术效果:本发明所述的基于llvm编译器的程序非控制数据攻击防护方法,针对现今广泛应用的64位应用程序,将程序源代码编译成中间代码,通过对中间代码进行静态分析得到指向性分析结果,并且获得需要保护的非控制数据;同时,它在中间代码的基础上进行指令插桩,以保证程序的非控制数据不被恶意篡改。这种方法克服了原有静态分析方法的不足,提高了分析的精确程度,并且利用较低的空间和时间复杂度解决了攻击者对程序非控制数据恶意篡改所带来的安全威胁。由于64位应用程序虚拟地址空间较大,现有技术大多针对32位应用程序上,本发明将数据完整性保护技术有效的实现在64位应用程序之上,为应用程序中的所有全局变量提供保护,有效防御了相关攻击。本发明利用数据完整性保护技术实现了对程序非控制数据攻击的有效保护;本发明基于llvm编译器修改技术,在程序的中间代码层进行分析以及指令插桩操作,不会影响源程序的运行流程,更加符合实际应用的需求;进一步的,在字段非敏感的指针分析过程中,将一个对象所包含的所有字段数据笼统设置为同一个内存位置,这会导致指针分析结果不精确。本发明在进行指向目标对象表示时,使用一种新的方式表示用来实现字段敏感的指针分析,即使用<o,f,sf>来表示一个指针指向目标的位置,即本发明在指针分析阶段采用了更加精确的字段敏感的指针分析技术,提高了分析的准确度,有效提高了后续处理过程的精确度。进一步的,为了减少对程序非控制数据保护带来的性能损耗,本发明利用程序内存8字节对齐的特性,可以高效的实现对程序非控制数据的保护。附图说明图1是本发明的总流程图;图2是本发明的基于编译器的指令转换子流程图;图3是本发明的指针分析与中间代码分析的子流程图;图4是本发明的指令转换具体实现图。具体实施方式下面结合具体的实施例对本发明做进一步的详细说明,所述是对本发明的解释而不是限定。本发明是基于这样一种观测而提出的:无论哪种类型针对数据篡改的攻击,它们想要篡改数据,必须要通过非安全的数据写入操作,如果能将所有的数据写入操作进行保护,就可以从根本上抵御针对数据篡改的攻击。本发明的核心思想是设计并实现了一种针对非控制数据的写入保护技术。该机制为每个数据写入指令分配一个标识符,同时在程序编译阶段,为程序data/bss段的数据分配标识符,形成一个标识符表;通过基于编译器的指令转换,程序在进行数据写入时,不直接写入,而是首先通过对比写入指令与写入对象的标识符,如果标识符相同则安全写入,如果不相同,则抛出异常。程序通过对标识符表的保护,比如将它们设置为只读,就提供了对数据段标识符表的保护。本发明使得攻击者无法完成实施攻击的第二步,即它需要改写系统中的某个非控制数据来达到攻击目的,比如提权,从而达到防御的目的。参照图1,本发明主要包括基于编译器的指针分析以及指令转换两部分。其中基于编译器的指令转换主要包括store指令以及程序data/bss段数据声明相关指令的转换,指令转换完成后进行程序编译等工作。本发明总体流程图如图1所示,其主要步骤如下:步骤一、将程序源代码通过llvm编译器编译成中间代码,用于后续指针分析以及进行指令插桩操作。通过指令clang-emit-llvmfilename.c-s-ofilename.ll和clang-emit-llvm-cfilename.c-ofilename.bc编译出程序的中间代码,其中.ll文件是可读的中间代码文件,.bc文件是字节码文件,本发明的静态分析以及指令插桩都是基于字节码文件。步骤二、通过对中间代码进行指针分析,得到程序中数据对象的指针别名分析结果,如图3所示。具体包括:(2.1)改进andersen指针分析算法:在andersen指针分析算法的基础上,实现一种字段敏感的指针分析算法;(2.1.1)在字段非敏感的指针分析过程中,将一个对象所包含的所有字段数据笼统设置为同一个内存位置,这会导致指针分析结果不精确。本发明在进行指向目标对象表示时,使用一种新的方式表示用来实现字段敏感的指针分析,即使用<o,f,sf>来表示一个指针指向目标的位置,其中o表示所在数据对象的名字,f表示偏移量,sf表示指针指向目标的结束位置,然后利用andersen指针分析算法思想,实现一个字节敏感的指针分析算法;(2.1.2)约束生成:在约束生成的过程中,遍历程序每一条指令,并按照andersen算法提出的约束规则为其生成相应约束,将整个程序系统转化为一个约束集合,最后整个程序会被表示为一个约束图;(2.1.3)约束求解:约束求解的输入为初始约束图,输出为求解之后的约束图,该过程主要分为两步:处理复杂约束和传递指向集;约束求解算法是使用基于工作集的迭代求解方法,处理复杂约束的过程就是向约束图中添加新边,这就会导致新的指向集传递过程,指向集的更新会导致工作集的更新,从而进入下一次的迭代过程;(2.1.4)指向性结果获得:在进行完处理复杂约束和传递指向集两个过程后,会得到一个完整的指向图,将指向图转化为指向分析的结果;(2.2)将步骤(2.1)中指针分析算法放在llvm源代码目录下,并书写与指针分析算法相关的cmakelists和makefile文件,cmakelists和makefile文件是定义了编译过程中的规则,使指针分析算法可以正常编译;(2.3)修改llvm源代码文件中的cmakelists文件:将指针分析算法的文件目录写入llvm源代码文件中的cmakelists文件中,以保证在llvm编译过程中可以生成对应的动态链接库文件;(2.4)执行makellvm-build命令,编译修改过后的llvm源代码,生成可调用的指针分析动态链接库文件;(2.5)执行如下命令:llvmcompiledirectory/opt-loadpassdirectory/llvmandersenpa.so-andpafilename.bc-oprogram.out,程序中数据对象的指针别名分析结果,opt命令是模块化的llvm优化器和分析器,使用opt把代码编译为一个共享库并对其进行加载。步骤三、确定程序的内存空间布局,得到程序的data/bss段的空间地址范围,用于之后根据空间地址范围创建标识符表;通过“readelf-sprogram”命令显示程序所使用的内存以及输出进程内存的状况,并找到程序的data/bss段,确定其内存地址范围。步骤四、遍历整个程序,通过对中间代码的分析,判断数据对象是否属于程序的data/bss段,并结合指针别名分析结果,得到每条指令可以写入的数据对象集合;如图3所示。(4.1)修改/llvm/lib/transforms/hello/下的hello.cpp文件,生成用于store指令分析的动态链接库文件,从而得到store指令分析结果;(4.1.1)标记每一条store指令,给每一条store指令分配一个标识符。(4.1.2)遍历程序每一条指令,如果该指令是store指令,调用getoperand()函数判断指令的操作数,如果是变量,则跳转至4.1.3;如果是指针则跳转至4.1.4,否则继续下一条指令的判断。(4.1.3)判断操作数是否属于程序的data/bss段,如果是则将其放入一个链表中,跳转到4.1.2。(4.1.4)针对指针进行指针回溯,直到找到变量,否则回溯到内存分配指令,终止循环,将本条store指令可以写入的数据对象集合输出并跳转到4.1.2。(4.2)编译llvm源代码;并用步骤(2.5)中所用方法得到每条指令可以写入的对象的集合,即,执行如下命令:llvmcompiledirectory/opt-loadpassdirectory/hello.so-hellofilename.bc-oprogram2.out,得到每条store指令能够写入的数据对象集合。(4.3)结合指针别名分析结果,将指针别名分析结果与store指令分析结果进行合并,并将最后结果写入文件之中。步骤五、为每条store指令及其可以写入的数据对象集合分配标识符。由于程序中内存写入指令所占比例较小,而且对于64位应用程序,内存是8字节对齐的,同时由于本发明是针对于程序的data/bss段提供保护,所以本发明使用一个字节作为程序的标识符,这样本发明的空间的复杂度就只有原来的12.5%,如果程序较为庞杂,可以使用两个字节作为程序的标识符,这样空间的复杂度就只有原来的25%。步骤六、利用步骤三中得到的data/bss段的空间地址范围,创建标识符表,同时对程序中间代码进行插桩,将标识符填入标识符表中,同时进行数据写入检查,若发生非控制数据篡改攻击,则抛出异常,否则,正常运行程序。在/lib/codegen/目录下增加一个新的文件,用于处理指令识别以及在中间代码中插桩,由于程序data/bss段的变量在编译过程中就可以确定其空间地址,所以对变量的标识符分配只需要找到其声明位置即可。(6.1)基于llvm编译器的指令转换步骤,如图2所示。(6.1.1)在llvm编译器后端的机器指令生成阶段,llvm编译器取得程序中间表示ir指令集合中的一条指令i;(6.1.2)判断指令i的类型,如果i是store指令,则执行步骤(6.1.3);如果i是全局变量的声明指令,则执行步骤(6.1.4);否则执行步骤(6.1.5);(6.1.3)对store指令i进行如下转换,如图4所示:(6.1.3a)在指令i前插入指令“leaqtar_add,%r11”,其中tar_add是store指令写入的目标地址,r11为寄存器;该lea指令是按照intel硬件平台at&格式书写的,源操作数放在前面,目的操作数放在后面,其他硬件平台的汇编指令格式类似;(6.1.3b)在指令i前插入指令“shrq$0x3,%r11”;该shrq指令是按照intel硬件平台at&格式书写的,源操作数放在前面,目的操作数放在后面,其他硬件平台的汇编指令格式类似;该shr指令的作用是通过将寄存器里的值右移3位,即除以8而转换成标识符表的偏移;(6.1.3c)在指令i前插入指令“cmpb$0x3,idt_base(,%r11,)”;idt_base是标识符表的基地址,3代表着该store指令的标识符;该cmpb指令是按照intel硬件平台at&格式书写的,源操作数放在前面,目的操作数放在后面,其他硬件平台的汇编指令格式类似;(6.1.3d)取得指令i的下一条指令的地址dst;(6.1.3e)在指令i前插入指令“jedst”;(6.1.3f)在指令i前插入指令“jmgerrhandler”,其中errhandler为系统中定义的错误处理例程;(6.1.3g)跳转到步骤(6.1.5);上述步骤(6.1.3c)-(6.1.3f)是为了判断store指令与其要写入的目标地址的标识符是否相同。如果两者的标识符相同,则表明此次写入是安全的,程序将按顺序执行下一条语句,如果不相同,系统将跳转到错误处理例程errhandler,这是保证store指令必须写入正确的地址;(6.1.4)对全局变量的声明指令i进行如下转换,如图4所示:(6.1.4a)在指令i前插入指令“push%r11”;(6.1.4b)在指令i前插入指令“leaqobj_add,%r11”,obj_add代表对象的地址;该lea指令是按照intel硬件平台at&格式书写的,源操作数放在前面,目的操作数放在后面,其他硬件平台的汇编指令格式类似;(6.1.4c)在指令i前插入指令“shrq$0x3,%r11”;该shrq指令是按照intel硬件平台at&格式书写的,源操作数放在前面,目的操作数放在后面,其他硬件平台的汇编指令格式类似;该shr指令的作用是通过将寄存器里的值右移3位,即除以8而转换成标识符表的偏移;(6.1.4d)在指令i前插入指令“movw$0x3,idt_base(,%r11)”;该mov指令是按照intel硬件平台at&格式书写的,源操作数放在前面,目的操作数放在后面,其他硬件平台的汇编指令格式类似;(6.1.4e)在指令i前插入指令“pop%r11”;(6.1.4f)跳转到步骤(6.1.5);(6.1.5)如果程序中间表示ir指令集中还有未处理的指令,返回步骤(6.1.2),开始下一条指令的处理;否则结束指令插桩;(6.1.6)修改llvm源代码/lib/codegen/目录下的cmakelists.txt及pass.cpp文件,以实现将6.1.1中算法编译到llvm源代码当中;同时修改/lib/ir/目录下instructions.cpp文件,来具体的实现中间代码指令的创建。(6.2)修改llvm源代码中导出的头文件:向llvm源代码导出的头文件中添加步骤6.1中所实现方法的声明,其中主要包括在步骤6.1.2中所实现方法的声明。(6.3)修改llvm源代码中与机器架构相关的转换代码,实现汇编指令到二进制代码的转换。(6.3.1)开辟出一段内存用于存放变量所对应内存的标识符,称为标识符表idtable,并找到其空间起始地址。(6.3.2)通过llvm-mc-show-inst命令将所需插桩的汇编指令转换为mc指令,在llvm编译的过程中,llvm编译器会将mc指令转变为相应的二进制代码。(6.3.3)修改lib/target/x86/目录下的x86mcinstlower.cpp文件,将上述mc指令插入到相应位置;(6.3.4)将标识符表设置为只读。标识符表是存放变量标识符的表格,将标识符设置为只读,可以防止攻击者对标识符表的篡改,从而保证了数据的安全性。(6.4)重新编译llvm源代码,然后用编译好的llvm编译程序,并验证此发明的有效性与高效性。本发明的功能效果可以通过以下实验进一步说明:1)实验条件本发明是以llvm(lowlevelvirtualmachine)编译器为基础实现的。本发明利用llvm编译器来完成一个针对ftp服务器程序进行指令转换以及攻击防护的实验。pc机为惠普pro3380mt台式机,操作系统为ubuntu14.04,cpu为intel(r)core(tm)i5-3470,内存为8gb。2)实验内容针对一个存在漏洞的ftp服务器,首先对其进行攻击,获得相应的权限,然后将本发明应用到该应用程序上,其中包括程序内存布局分析、改编后的llvm进行编译等操作,当再次运行程序并对其进行攻击时,程序会抛出异常,并进入中断状态,说明本发明成功地阻止了非控制数据攻击对数据的篡改。3)结果分析通过上述实验,在对程序进行静态分析的基础上,通过修改编译器源代码使得应用程序在编译过程中被插桩了指令,应用程序在运行后,可以动态的检测到攻击的发生,并且在攻击发生时抛出异常。由此实验结果可知,本发明针对非控制数据防护措施的研究达到了预期目标。当前第1页12当前第1页12