一、前言作者将从本文开始,开始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
