当前位置: 首页 > 科技观察

C语言文章静态代码分析_0

时间:2023-03-12 06:12:34 科技观察

一、前言作者将从本文开始,开始c语言代码安全分析的章节,详细分析c语言静态代码分析的各种技术细节。2.依赖分析依赖分析是C语言静态代码分析中非常重要的一个环节。其分析的准确与否关系到后续漏洞分析的准确性。什么是依赖分析依赖图是源代码文件与其依赖库之间依赖关系的图形表示。我们知道,在c语言中,项目实际使用的一些组件库只能在编译的时候才能确定。它不像java项目,一段代码,到处跑,而是一段代码,多次编译,因为很多时候,我们需要为同一个组件准备不同的适配方案,来满足跨平台的需求,这也是为什么C语言项目有长期被一些开发商诟病。在分析c/c++项目时,我们首先要解决的就是程序文件之间的依赖关系,因为这直接影响到我们后续静态代码分析的准确性。依赖图的作用根据生成的文件依赖图,我们可以清晰准确的了解整个项目的组织脉络。在符号识别和函数调用链生成的后续阶段,需要依赖依赖图来寻找目标库文件,以保证符号识别和函数调用链构建的准确性。如何进行依赖分析对于c/c++项目文件依赖图的生成,目前业界主流的方法是通过分析compile_commands.json文件中记录的编译命令来生成。详情请参考商用sca扫描器pvs-stdio的技术文档(pvs-studio.com)。compile_commands.json文件可以通过编译工具(make、ninja等)生成,具体步骤为:1.收集编译过程的输出信息。2、将输出信息重定向到解析器,通过正则匹配生成compile_commands.json文件。当然cmake等变异工具也支持compile_commands.json文件的制作。具体来说,在构建程序时,指定参数:-DCMAKE_EXPORT_COMPILE_COMMANDS=1。那么,有了compile_commands.json文件后,如何做依赖分析呢?我们先看一下compile_commands.json文件的数据结构:[{"directory":"/Users/pony/work/sourcehub/cmake-examples/01-basic/B-hello-headers/build","command":"/Library/Developer/CommandLineTools/usr/bin/cc-I/Users/pony/work/sourcehub/cmake-examples/01-basic/B-hello-headers/include-isysroot/Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk-oCMakeFiles/hello_headers.dir/src/Hello.c.o-c/Users/pony/work/sourcehub/cmake-examples/01-basic/B-hello-headers/src/Hello.c","file":"/Users/pony/work/sourcehub/cmake-examples/01-basic/B-hello-headers/src/Hello.c"},{"directory":"/Users/pony/work/sourcehub/cmake-examples/01-basic/B-hello-headers/build","command":"/Library/Developer/CommandLineTools/usr/bin/cc-I/Users/pony/work/sourcehub/cmake-示例/01-basic/B-hello-headers/include-isysroot/Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk-oCMakeFiles/hello_headers.dir/src/main.c.o-c/Users/pony/work/sourcehub/cmake-examples/01-basic/B-hello-headers/src/main.c","文件":"/Users/pony/work/sourcehub/cmake-examples/01-basic/B-hello-headers/src/main.c"}]其中directory表示当前编译目录,command表示当前正在执行的编译命令,file表示要编译的源码文件。其中command命令中,参数I后面是当前源代码所依赖的头文件目录的路径,编译器在编译时会在给定的目录下寻找相关的头文件。但是,仅仅找到头文件显然是不够的,我们还需要找到函数的定义位置,这样才能真正建立函数调用所在文件和函数定义所在文件之间的关系。为了更好的说明极光代码安全分析引擎是如何进行依赖的c项目分析,下面,我们用一段c代码该项目作为示例进行说明。项目目录:.├──CMakeLists.txt├──README.adoc├──include│└──Hello.h└──src├──Hello.c└──main.c2目录,5个文件CMakeLists.txt#设置可以使用的最低CMake版本#要查找cmake版本运行#$cmake--versioncmake_minimum_required(VERSION3.5)#设置项目名称项目(hello_headers)#创建一个源变量,其中包含所有c文件的链接到compileset(SOURCESsrc/Hello.csrc/main.c)#添加一个带有上述源的可执行文件add_executable(hello_headers${SOURCES})#设置应该包含在这个目标的构建命令中的目录#当运行g++时这些将被包含作为-I/directory/path/target_include_directories(hello_headersPRIVATE${PROJECT_SOURCE_DIR}/include)include/Hello.h#ifndef__HELLO_H__#define__HELLO_H__voidprint();#endifsrc/Hello.c#include"Hello.h"voidprint(){printf("helloworld.");}src/main.c#include"Hello.h"intmain(intargc,char*argv[]){print();return0;}本项目主要由一个主程序和一个组件组成。主程序使用头文件Hello.h调用组件中定义的打印函数。那么,首先要明确我们期望构建的依赖是main.c和Hello.c之间的依赖。通过对compile_commands文件的分析,我们已经可以知道main.c和Hello.c都依赖于Hello.h,只不过一个是API调用,一个是API定义。那么我们可以直接声明这两个文件是依赖的吗?那显然是行不通的,因为我们无法确定main.c中调用的print函数是否定义在Hello.c中。只有在main.c中调用了在Hello.h中声明的打印函数,在Hello.c中定义了在Hello.h中声明的打印函数,那么我们才可以认为main.c和Hello.c的区别在于函数之间的依赖关系,这样我们就可以后续建立函数之间的调用关系,即函数调用链(这对于分析全局数据流构建非常重要)。我们可以用下图来表示这个推导过程。其中,依赖判断模块1,通过分析compile_commands.json文件和相关源码的AST,判断两个源码文件引入了相同的头文件,依赖判断模块2,通过分析是否同时导入同一个头文件,其中一方是函数调用,另一方是函数定义,判断main.c文件和Hello.c之间的依赖关系,调用链判断模块,通过分析main.c中的main函数是否包含对print函数的调用来判断main.c中的main函数与src/Hello.c中print函数的调用关系。3.代码数据库构建代码数据库,顾名思义,存放一些与代码相关的数据。这里的代码数据库可以认为是代码属性图的第一层,我们这里一般存放源代码的AST表示。至于源码的AST表示,我们可以通过一些AST提取工具获取,比如eclipse提供的cdt工具,或者一些开源的前端分析工具,比如antlr,可以完成这部分工作。当然,如果我们使用这些工具来提取ast,那么我们得到的只是一个ast单元类的集合,为了后续展示的方便,也为了能够通过分布式架构进行高效的分析,我们还需要对这些astunitclasses进行分析必要的初步分析工作,然后将其转换为json格式进行持久化存储。这部分的处理流程可以用下图表示:4.函数调用图构建什么是函数调用图?函数调用图是函数之间调用关系的一种图形表示。在分析过程间数据流时,我们需要依靠函数调用图来求解函数调用链,从而沿着这条调用链分析过程间数据流。如何构建函数调用图函数调用链的构建需要建立在依赖分析的基础上。其实这部分在依赖分析中已经解释的很形象了,这里就不赘述了。一般来说,依靠依赖分析的结果,我们可以得到两个源代码文件之间的依赖关系,然后以此为基础,我们可以遍历分析这些源代码中的函数定义信息,判断函数签名和是否函数定义的函数签名一致可以构建我们后续分析所需的函数调用链。5.控制流和数据流分析什么是控制流和数据流?程序控制流表示程序各语句结构之间的控制关系。一般来说有两种形式,一种是基本块控制关系图,另一种是表达式控制图。前者侧重于基本块之间的控制关系,后者侧重于表达式之间的控制关系。数据流表示数据在程序中的流动关系。其中,我们会比较关注变量的定义和引用关系,即def-use链,以及一些赋值语句引起的数据流关系。为什么要分析控制流和数据流控制流分析和数据流分析是程序静态分析中的核心分析环节。我们在进行代码安全审计的时候,确定某个地方存在漏洞,比如SQL注入漏洞。需要有一个比较完整的污点传播路径才能有充分的理由判断其漏洞,而污点传播路径依赖于数据流分析,数据流分析也依赖于控制流分析。总的来说,java和c的数据流分析方法大同小异。java的控制流和数据流分析方法我在之前的文章中已经详细描述过,这里不再赘述,详见:《DevSecOps建设之白盒续篇 - FreeBuf网络安全行业门户》。6、代码安全分析堆栈溢出漏洞那么,我们如何将静态代码分析技术应用到代码安全分析中呢?下面以pwnable.kr上的一个stackoverflowshortingrangebof为例,详细讲解aurora白盒引擎是如何进行c代码安全分析的。漏洞代码如下:#include#include#includevoidfunc(intkey){charoverflowme[32];printf("溢出我:");得到(溢出);//粉碎我!if(key==0xcafebabe){系统("/bin/sh");}else{printf("不..\n");}}intmain(intargc,char*argv[]){func(0xdeadbeef);return0;}简要说明:这是一段典型的栈溢出漏洞代码。在函数func中,我们声明并定义了一个字符数组overflowme,并将32个字符赋值给它。一段内存空间。在程序中调用gets函数获取命令行的输入流。如果字符流的长度在变量overflowme定义的合法范围内,那么程序就会遵循正常的逻辑。如果超过overlfowme定义的合法范围,那么,在没有检查输入流长度合法性的前提下,会造成栈溢出漏洞,覆盖变量key的内存空间,影响预期的分支流程趋势,即转到if的then逻辑部分,执行系统函数,跳出一个bash窗口。当这个调用过程发生时,内存栈帧的分布如下:其中,下层是func栈帧,上层是主栈帧。栈帧的排列是从高地址到低地址排列的,最先调用的函数占据高地址。后面调用的函数占用低地址,数据写入规则是从低地址到高地址。我们实际使用的时候,如果想改变if分支中的逻辑,可以输入如下payload来实现攻击:payload="a"*52+chr(0xbe)+chr(0xba)+chr(0xfe)+chr(0xca);其中chr(0xbe)+chr(0xba)+chr(0xfe)+chr(0xca)就是0xdeadbeef,52代表key到overflow的距离,这些数据可以通过gdb调试得到。通过给出一个overflowme超出其合法取值范围的payload,我们就可以实现对原有逻辑的篡改。那么,对于这种漏洞,我们如何通过静态分析的方法检测出来呢?我们可以从栈溢出的原理出发来思考我们的保护策略。我们知道栈溢出的本质原因是在给变量传值的时候没有检查输入数据的字节长度是否合法。那么,我们可以通过枚举函数调用表达式(如gets、memcpy等)是否大于声明的区间来判断是否存在漏洞。然后,问题集中在两个值的大小分析(区间分析)上。对于buffer的大小,我们可以根据变量对应的def-use链找到变量定义的位置,然后结合变量定义表达式中变量定义表达式相关的ast数据进行综合分析和判断。对于输入数据的区间大小,我们也可以进行类似的分析。当然,区间的值不一定是常数。如果要追求分析的准确性,那么还需要进一步解决对区间范围的约束。7.区间分析什么是区间分析?区间分析是指通过约束求解算法跟踪变量和表达式的取值范围,为进一步的程序分析提供准确的数据支持。为什么要做区间分析?从上面我们可以看出,我们在做一些代码安全分析的时候,比如栈溢出分析,如果不能准确的分析出输入数据的缓冲区和区间大小,那么就不能准确的判断是否存在栈溢出漏洞.如何进行区间分析学术上,区间分析的相关分析方法很多。比如王亚文、龚云展等在第五届中国测试学术会议上发表的论文《区间运算在软件缺陷检测中的应用》中提到了一种区间分析方法。论文中提出可以针对不同的变量类型设置不同的初始区间值,然后通过表达式区间分析、条件区间分析、控制流区间三种不同的纬度来约束和求解变量的取值范围分析。1、表达式区间分析如表达式3*(++i),如果i的取值范围为[1,1],则执行3*(++i)后,其取值范围变为:[6,6].2.条件区间分析例如:if(x>2){}else{}区间分析应用于代码质量领域。区间分析可以应用于程序中的不可达代码块检测和代码覆盖分析。例如:voidfunc(){inti=5;如果(我<0){我++;}}初始控制流图:转换为带区间信息的控制流图:从带区间信息的控制流图可以发现,在执行stmt_3语句时,由于条件i<0的取值范围为[-∞,-1]n[5,5]=?,将i<0分支下的所有表达式语句中i的区间范围置为null,表示不可达。语句覆盖率可以通过统计所有执行的语句数和语句总数来计算。计算公式为:可执行语句数/语句总数。分支覆盖率可以通过计算所有可达分支的数量和分支总数来计算。计算公式为:可达分支/总分支数。区间分析在代码安全领域的应用,比如上面的溢出漏洞案例。我们可以得到它的初始控制流图如下:将其转化为带有区间信息的控制流图:在stmt_3中(即语句gets(overflowme)),很容易得到overflowme的区间范围为[0,32],而用户输入数据的范围是[0,+∞]。这里,1000用作默认值。当然,为了以防万一,可以设置大一些。很明显,用户输入的数据范围远远大于overflowme的最大值。值为32,那么这里肯定存在溢出漏洞。当然,为了更加准确的判断溢出漏洞,我们也可以结合compile_commands.json中的编译命令(比如编译时设置的一些栈溢出保护策略)进行综合判断。八、CI流程自动化一个静态代码安全分析工具,要好用,不仅引擎能力要硬核,而且流程要自动化,方便用户配置,方便集成到一些主流CI在管道中。下面我们使用gitlabci来详细说明我们是如何实现c代码自动化安全和代码质量检测的。以下是示例ci示例#Thisfileisatemplate,mightneededitedbeforeitworksonyourproject.#ThisisasampleGitLabCI/CDconfigurationfilethatshouldrunwithoutanymodifications.#Itdemonstratesabasic3stageCI/光盘流水线。它不是真正的测试或脚本,#它使用回显命令来模拟管道执行。##管道由运行脚本的独立作业组成,分为阶段。#阶段按顺序运行,但阶段内的作业并行运行。##有关详细信息,请参阅:https://docs.gitlab.com/ee/ci/yaml/README.html#stagesstages:#作业的阶段列表及其执行顺序-build-test-deploybuild-job:#这个作业在构建阶段运行,它首先运行。图片:ci_vtsmap:最新阶段:构建脚本:-回显“编译代码...”-mkdir构建&&cd构建-cmake-DCMAKE_EXPORT_COMPILE_COMMANDS=1..-curl-F“文件=@compile_commands.json”-F“用户=${地理标志TLAB_USER_NAME}"-F"projectname=${CI_PROJECT_NAME}"-F"language=c"-F"email=xxx@gmail.com"-F"type=git"-F"branch=${CI_COMMIT_BRANCH}"-F"commitid=${CI_COMMIT_SHA}"-F"gitaddr=${CI_PROJECT_URL}"-F"token=xxx"-xPOSThttp://[domain]/pushjob/-make.-echo"编译完成。"deploy-job:#这个作业在部署阶段运行。stage:deploy#它只有在测试阶段的*两个*作业都成功完成时才运行。script:-echo"Deployingapplication..."-echo"Applicationsuccessfullydeployed."where,我们会在原来的ci脚本中加入两行命令,在原来的cmake命令中一行加入参数-DCMAKE_EXPORT_COMPILE_COMMANDS=1生成compile_commands.json文件,以便后端静态分析引擎进行依赖分析以下命令用于提交任务信息:curl-F"file=@compile_commands.json"-F"user=${GITLAB_USER_NAME}"-F"projectname=${CI_PROJECT_NAME}"-F"language=c"-F"email=xxx@gmail.com"-F"type=git"-F"branch=${CI_COMMIT_BRANCH}"-F"commitid=${CI_COMMIT_SHA}"-F"gitaddr=${CI_PROJECT_URL}"-F"令牌=xxx"-xPOSThttp://[domain]/pushjob/九.总结总的来说,c代码静态分析的主要难点是区间分析。这部分分析的准确性对后续漏洞分析的准确性有很大的影响。大的影响。所以对于c代码的静态分析来说,区间分析是需要花很大功夫去研究的,不仅要保证分析的准确性,还要考虑分析的效率,因为很多c代码的项目,比如linux内核等,代码量非常大。如果没有合理的算法来加速代码分析,那么访问企业内部使用的体验会很差。当然,准确率和速度一般是一个权衡的关系。如何达到平衡,需要不断的检验和实践。王亚文,龚云展,杨朝红,肖青,区间计算在软件缺陷检测中的应用,第五届中国测试学术会议论文集,2008,51-52。