从ByteCodeDL项目中学习白盒程序设计理念
2022-4-29 23:17:14 Author: mp.weixin.qq.com(查看原文) 阅读量:3 收藏

上面的蓝字关注我吧!


ByteCodeDL项目地址:https://github.com/BytecodeDL/ByteCodeDL
ByteCodeDL程序大致可以分为两个部分进行分析
  • 软件程序分析原理
  • datalog编程
因ByteCodeDL的核心引擎是一款叫Souffle的逻辑引擎,所以这里我先从Souffle开始介绍。而souffle又是有datalog一种声明式语言设计的查询语句,所以本篇文章我会先从datalog设计,到souffle的了解,最后到ByteCodeDL的实现来分析。
01
Datalog介绍
因为后续的知识点涉及到了Datalog,因此我在这里提前补充一些必备的datalog认识。同时章节中还提到了很多数据流分析原理以及CallGraph调用图理念,如果这块不是很懂的读者建议看看我之前讲静态代码工具的文章:https://mp.weixin.qq.com/s/4jYKPFGIkyr-t2QSqtQpQQ
总体来说,datalog语言是一种声明式的编程语言(declarative programming language),在非常多的白盒程序和运行时分析程序中应用广泛。最常见声明式编程就是SQL,SQL最大的特点就只告诉你我想要什么,但就是不说怎么做。
举个例子,有一个学生列表,我们需要计算出年龄小于18的学生数量,如果用Java的传统命令式编程,则代码会是这样的:
int count=0;Iterator<Student> iter = sudents.iterator();
while(iter.hasNext()){ Student s = iter.next(); if(s.getAge() < 18){ count++; }}
代码中用了迭代器遍历所有数据,并计算年龄小于18的总数量。
而如果使用了声明式编程(jdk8+支持lambda编程),则代码如下:
int count = sudents.stream().filter(s -> s.getAge() < 18).count();

只需要用一行lambda表达式声明即可,程序自己会将sudents转为stream流,并通过filter定义的筛选内容计算最后的count值。

声明式就是如此,相对来说代码简洁、可读性也更强。这种编程语言就是一种DSL(领域特定语言,Domain Specific Language),用于某一个特定领域的程序设计语言。而Datalog就是这样一种DSL,是Prolog的子集,其Datalog本名可以看作“Data+Logic”,接下来着重讲讲Datalog的一些理念。

Data

Data中包含了谓词和原子两个概念

谓词(Predicate)

Datalog中数据用谓词的方式表示,可以把谓词理解成一张表,其中表中的每条数据反应一个事实(Fact)。

如上这个表为balance表,则balance就是一个谓词,在balance表中("zhangsan",1800)表示“张三有1800元”,这是一个事实,而("lisi",5200)表示“李四有5200元”,该数据不存在,因此这不是一个事实。

原子(Atoms)

是Datalog中的基本元素,其分为关系原子和算术原子。其中关系原子形式记作P(x1, x2, x3),其中P为谓词名,xn为参数;算术原子记作attr >= var,其中attr为谓词中的一个列,var为一个变量或者常量。
还是拿balance表举例,可以得到表示balance(Name, Money)的方式,表示该谓词有两个参数,如:balance("zhangsan", 1800)。算术原子就可以用:Money >= 1800的方式来表达。
Logic
Logic中包含的概念非常多
Rules
Rules在Datalog中定义了facts的推导过程,一个rules的规则如"H <- B1,B2,...,Bn"的形式,其中H是head,是一个atom,表示结论(Consequent),B1,B2,...,Bn是body,表示前提条件(Antecedent),Bn表示一个子目标(subgoal),并且其中子目标之间的逗号表示逻辑“与”运算符。上述的Rules表达式,表示的意思是“当B1、B2、...、Bn都成立的时候,H成立”。
如果A和B都能推导出C,可以写成C<-A. C<-B或者C<-A;B。
EDB和IDB

通常可以将Datalog的谓词分为两大类,即EDB和IDB:

  • EDB(Extensional database):EDB谓词是预先定义好的,通常不可变,作为datalog的输入(即外部传入,extensional)。

  • IDB(Intensional database):IDB 谓词通常由rules推导出,通常是datalog的输出。

并且在Datalog的规则中可以支持递归性

Reach(from,to) <- Edge(from, to).Reach(from,to) <- Reach(from, node), Reach(node, to)

Datalog执行图介绍
这里我用南大两位李樾、谭添老师的图来介绍

输入EDB和Rules会经过Datalog Engine进行分析执行,最后生成IDB
而后面所介绍的Souffle就是常见的Datalog引擎。
Datalog的指针分析原理

我会在这一小节着重介绍Datalog是如何做指针分析,并重点从上下文不敏感分析和上下文敏感分析两个部分讲解。

上下文不敏感分析

在做上下文不敏感指针分析的时候,之前介绍的几个概念在这里分别代表如下角色:

  • EDB:程序句法上可获得的指针相关信息。

  • IDB:指针分析的结果。

  • Rules:指针分析的规则。

EDB定义了变量集合为V,域为F,对象为O。并经过如New/Assign/Store/Load语句的时候,会提取如上语句为EDB:

  • New(x: V,o: O) <- i: x = new T()

  • Assign(x : V, y : V) <- x=y

  • Store(x : V, f : F, y : V) <- x.f = y

  • Load(y : V, x : V, f : F) <- y = x.f

再来看看IDB会输出什么东西:

  • VarPointsTo(v: V, o : O) ,如VarPointsTo(x,oi)表示oi ∈ 𝑝𝑡(𝑥)。pt是point-to set缩写。

  • FieldPointsTo(oi : O, f: V, oj : O) ,如FieldsPointsTo(𝑜i, 𝑓, 𝑜j)表示𝑜j ∈ 𝑝𝑡(𝑜i.𝑓)。

最后综合EDB、IDB和Rules可以得出:

这里的Rule分母是IDB,分子是EDB。

读者如果在这里看不懂规则,可以停下来稍微多看几眼有个印象,并结合后续分析慢慢深入理解。

这里借用南大软件分析课程Reference[2]的图来讲解。

程序会先将EDB指令,变成如下表格形式

进而会执行规则,继续看到规则列表的第一行,是一个New语句的处理

New语句处理会将Example中的两个new语句根据规则走向添加到VarPointsTo中
b = new C();c = new C();

之前介绍指针分析的IDB的时候说过VarPointsTo(x,oi)表示oi ∈ 𝑝𝑡(𝑥)。因此可以得出上图中的o1属于point(b)、o3属于point(c)。

再往后执行Rules,会解析Assign语句,看到之前的表格中的Statement中表达式是x = y,因此Example中属于x = y的表达式语句有如下:

a = b;d = c;
执行Assign规则后,VarPointsTo情况如下图所示

如此一来,便把o1、o3对象成功传递到a、d变量中。
继续看看后面的Store规则

比如上图的红框内容,对应的代码是:
c.f = d;
执行规则,首先会去EDB的Store表格中得到c-f-d的返回参数,随后带入VarPointsTo(x,oi)和VarPointsTo(y,oj)中查询对应的结果。最后添加到FieldPointsTo结构中。
再来看指针分析中字段复制给变量的情况,即是规则中的Load语句。

在Example中Load语句对应的代码是e = d.f;。读者在这里可以看到,虽然Example中并没有直接对d.f字段的赋值操作,但是有对应的fact可以通过VarPointsTo查询到,并把oi带入FieldPointsTo对应上该f字段对应的o1和o3。如果读者在这里疑惑,为什么我Example中第4行c.f = a;赋值了a,并在第6行又覆盖了c.f字段的变量,而这里最后IDB中VarPointsTo中的值还是加上了a变量的o1?其实这就是上下文不敏感分析的原理,不会过度关注代码的执行逻辑,因此也有该分析的痛点:大大降低了分析的精度。

上下文敏感分析

这一章节会多引进几个EDB和IDB概念,在Call指令中的EDB,定义了S代表指令,M代表方法。且共有3条Rules:

先来看第一个Rule

引进了如下3个EDB和2个IDB:

  • VCall(l:S,x:V,k:M):调用本身的信息,指令l,变量x,方法k。

  • Dispatch(o:O,k:M,m:M):根据对象o给定的方法k,找到实际调用的方法m。

  • ThisVar(m:M,this:V):获取m方法中的this变量。

  • Reachable(m:M):表示m方法可达。

  • CallGraph(l:S,m:M):表示l指令到m方法有一条调用边。

如此一来就添加了方法调用的调用图。

再来看看第二个Rule中,该Rule是对参数传递的规则:

其中EDB的内容Arg和Param对应实参和形参

  • Argument(l:S,i:N,ai:V):对应方法调用中的实参,l是调用点,i是代表方法调用中的第几个参数(索引),a是对应的参数变量。

  • Parameter(m:M,i:N,pi:V):m是调用的函数,i是调用的参数索引,p对应的参数变量。

再来看看这个规则,如果存在l到m的CallGraph调用边,就取出实参ai和形参pi,并最终都指向o。

再来看看第三条Rule:

该Rule有两个EDB:

  • MethodReturn(m:M,ret:V):m被调用函数的返回值是ret。

  • CallReturn(l:S,r:V):l调用点所接收的变量是r。

规则会先判断l到m是否存在一条调用边,并取出m函数的返回值ret,以及查询ret指向的对象o,并和l调用点的接受变量r对应上。

全程序指针分析

在做全指针分析的时候,只需要结合之前介绍的上下文敏感和不敏感分析,并给定一个方法入口EntryMethod,且该方法入口可达,即可对此方法进行全指针分析。
Datalog污点分析原理
当进行污点分析的时候,需要额外添加如下EDB和IDB:
  • EDB谓词:
    • Source(m:M):产生污点源的函数,通常是用户可控数据的输入点。
    • Sink(m:M):污点的汇聚点,污点在此处沉淀,并产出对应的分析报告。
    • Taint(l:S,t:T):关联调用点l和它产生的污点数据t的相关CallSite信息
  • IDB谓词:
    • TaintFlow(t:T,m:M):表示污点数据t最终会在Sink函数m中汇聚。
再来看看Call是如何处理Source和Sink两个EDB的

在处理Suorce的时候,首先确保CallGraph有一条l到m的调用边,并把m定义成source方法,用CallReturn获取调用m污点源方法的返回值r,并把l调用点和污点数据t关联起来。如果满足以上条件,就把r污点数据指向t。
再继续观察Sink点是如何判断污点汇聚的

规则的EDB侧,前两天照样判断是否有通过Sink函数m的调用边,之后会取出l的实参ai,并判断该参数在VarPointsTo中是否有对应的污点标记t,最后输出IDB证明污点t到m有一条污点流传播路径。EDB中的Taint(_,t)用于保证VarPointsTo指向的t是有污点标记的数据。
举个例子,有如下Example代码:

我们定义了Source点为getPassword函数,Sink点为log(String)函数,并给定该Sink的唯一ID为0。

执行第一条语句的时候,通过指针分析将o2对象指向了x。
往下执行到第二行的时候,程序执行了Source定义的函数,因此pw返回的值中被打上了t3污点标记。

之后经过了几次指针传播,对应的VarPointsTo的结果中如下图所示

因此变量s最后也被携带上t3污点标记,最后在第7行log函数中汇聚的时候

程序判断了该函数是否为ID是0的SInk函数,并判断实参ai(也就是变量s)是否有t的污点标记。最终输出污点数据流TaintFlows<3,7,0>,其中3表示Source点在第三行,7表示SInk函数在第7行,0表示是ID为0的Sink函数。至此污点分析结束,得出ID为0的漏洞存在。
小结

Datalog总体来说分析实现简单,只需要通过规则的编写和相关污点函数的定义就能对程序进行跟踪。这种涉及理念是不是觉得有点像LGTM的QL查询,只需要通过简单的声明式语言编写分析语句即可。对很多安全人员来说实现简单,可读性很强。同时上述指针分析和污点分析的形同点在于污点分析是关注Source到Sink的传播流,而指针分析是关注对象和变量之间的传播。

想去研究代码实践的读者们可以了解一下Reference[4]的jatalog项目,同时如果对这门分析课程感兴趣的读者也可以看看南大的李樾、谭添老师开的软件分析公开课(Reference[2]),相关详细的ppt也可以在(Reference[5])中找到。

02
Souffle介绍

上章节介绍了Datalog的原理,以及如何进行数据流分析的。

Souffle是一款受Datalog启发的逻辑编程语言,其设计之初就是为了方便Oracle实验室的成员进行逻辑静态代码分析。Souffle支持的应用广泛,包括但不限于有Java的静态代码分析、并行编译器、二进制反汇编器、云计算的安全分析和智能合约的分析。而应用广泛且强大的Java指针分析程序Doop(Reference[1])就是以Souffle为datalog引擎。

这里我们就重点关注Souffle在ByteCodeDL中的实际应用部分。

先来学习一下Souffle的声明式表达是如何分析的

Souffle datalog声明式语言

Souffle中的主要语言元素是关系声明(relation declarations)、事实(facts)、规则(rules)和指令(directives)。

认识了上面我介绍的Datalog语法,再来看看Souffle是如何定义规则的。这里我在我的Ubuntu上装好了souffle引擎。假设有如下example.dl规则:

.decl edge(x:number, y:number).input edge
.decl path(x:number, y:number).output path
path(x, y) :- edge(x, y).path(x, y) :- path(x, z), edge(z, y).

.decl声明了一个edge关系,寓意x到y的一条有向边;path是声明了一个可达路径,表示x到y有一条可达路径。

.input <filename>会在当前目录中读取filename.facts文件内容。

.output <filename>会输出一个filename.csv文件。

规则的最后两行表示:

  1. 如果存在一条x到y的有向边(edge),则表示也存在一条x到y的可达路径(path)。

  2. 如果x到z有一条可达路径,且z到y存在一条有向边,则表示x到y也存在一条可达路径。

而input指定的edge.facts文件里面的内容是

1  22  3

之后在Ubuntu系统中通过以下命令运行souffle引擎检测:

$ ./souffle -F. -D. example.dl

参数设定:

  • -F 指定了facts所在的目录

  • -D 指定了输出目录

  • example.dl 指定datalog文件名

之后output输出的path.csv结果集内容是

1  22  31  3

上述定义的变量是number数值类型,关于souffle支持的类型可以去souffle的官网查看,其类型大致分为如下四种:

  • Symbol type: symbol

  • Signed number type: number

  • Unsigned number type: unsigned

  • Float number type: float

Doop分析流程
Doop执行的流程大致分为三个步骤:
  1. 使用soot生成对应的jimple的IR文件。
  2. 将jimple文件再转换datalog的facts。
  3. 使用souffle引擎加载Rules并进行分析,并输出分析结果。

从Doot的分析流程中可以得出来,分析程序会分为上述三个步骤,我们再回过头来看看ByteCodeDL的分析。

03
ByteCodeDL分析

通过Doot提供的一个Soot生成facts小工具,来生成对应的jar包中的facts。

java -jar soot-fact-generator.jar -i SIDemo.jar  -l /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/rt.jar --generate-jimple --allow-phantom --full -d out
看看其中的参数详解:
  • -i 指定待分析的jar包
  • -l 指定依赖库
  • --generate-jimple 表示生成中间语言jimple
  • --allow-phantom 大概是允许解析依赖不存在的类
  • --full 表示对所有class进行解析
  • -d 指定输出目录

进入out目录下面,会生成很多facts文件

但是我在这里发现一个bug,工具生成jimple并不支持SpringBoot程序。而需要soot生成对应的jimple文件是需要对应依赖项的,也就是需要把SpringBoot的lib下面的所有jar包加入到soot的classpath目录中。

于是乎我修改了Doop工具的soot-fact-generator生成工具,将判断-i参数指定的jar文件中是否存在“BOOT-INF/lib(classes)”的路径,如果存在则将路径下的所有依赖解压到当前目录的lib(classes)目录下,否则不做处理。

在生成过程中,如果遇到了OutOfMemory异常,并提示Java  heap space,则是给的堆空间小了,加上jvm参数-Xms200m -Xmx4096m即可(根据机器配置而定)。

如果你是跑在虚拟机上,一定要多给虚拟机几核CPU。因为程序中会通过循环遍历所有依赖的类,并生成对应的中间语言,我这里用的SpringBoot的靶场,Xmx参数可以给高一点,这样程序能更快的得出结果。之后就能在out/jimple目录下看到生成的中间语言。

至此,分析流程的三部曲已经完成前两步了,还差最后一步对生成的事实进行分析。

比如我这里想检索所有的方法中,有没有类名包含XSSController的方法,并输出。

于是我编写了如下的datalog规则:

#include "inputDeclaration.dl"
.decl QueryResult(class:Class, method:Method).output QueryResult
QueryResult(class, method) :- MethodInfo(method, simplename, param, class, _, _, arity), contains("XSSController", class), arity = 1.

EDB中是从soot-fact-generator.jar工具生成的facts文件读入,并输出最后的csv结果。

至于inputDeclaration.dl,是针对souffle规则和生成好的facts定义的类和方法等属性名称。

打开inputDeclaration.dl文件,前几行就是基础变量属性定义

// 指令 可以定位出动作(比如call,load,sotre,assign)发生时的代码位置.type Insn <: symbol// 变量 .type Var <: symbol// 堆 也可以理解为内存中的对象.type Heap <: symbol// 字段.type Field <: symbol// 方法.type Method <: symbol// 类.type Class <: symbol

在里面就定义了上面用到的MethodInfo的EDB输入

可以明确看到

.input MethodInfo(IO="file", filename="Method.facts", delimiter="\t")

程序是直接从Method.facts中读入数据,并对其进行分析。

定义污点分析规则

先来看看污点源参数是如何定义的,这里拿之前的SpringBoot的靶场SIDemo来说明案例。

先来看看对应的XSSController的Jimple中间代码:

public java.lang.String reflection(java.lang.String){  org.sec.sidemo.web.XSSController this#_0;  java.lang.String message#_0, $stack3;  org.sec.sidemo.service.XSSService $stack2;
this#_0 := @this: org.sec.sidemo.web.XSSController;
message#_0 := @parameter0: java.lang.String;
$stack2 = this#_0.<org.sec.sidemo.web.XSSController: org.sec.sidemo.service.XSSService xssService>;
$stack3 = interfaceinvoke $stack2.<org.sec.sidemo.service.XSSService: java.lang.String reflection(java.lang.String)>(message#_0);
return $stack3;}

这里我贴了XSSController的reflection方法的IR代码,其源代码如下图所示

可以很清楚的知道污点参数是reflection方法的第一个RequestParam注解的参数message,后经过xssService的reflection方法返回之后直接由@ResponseBody注解返回到了页面上。所以我们的首要目的就是讲该RequestParam注解的参数标记上污点。

这里我先贴出相关的souffle规则代码:

#include "inputDeclaration.dl"#include "utils.dl"#include "ptaint.dl"
.init ptaint = PTaint //定义P/Taint指针分析
ptaint.cipt.Reachable("<org.sec.sidemo.web.XSSController: java.lang.String reflection(java.lang.String)>"). //定义入口函数
ptaint.SourceMethodArg("<org.sec.sidemo.web.XSSController: java.lang.String reflection(java.lang.String)>",0). //因为后续会检测RequestParam注解是打在哪个参数上,所以这里的0不是this对象,而是reflection的参数。
.decl TaintVar(heap:Heap,var:Var)
TaintVar(heap,var) :- ptaint.cipt.VarPointsTo(heap, var), //heap就是污点标记,这里将污点标记到var上 ptaint.TaintHeap(_, heap). //创建污点 //因为这两个EDB是逗号链接,所有heap必须满足两个条件,或者说两个EDB的heap变量必须相等
.output TaintVar

首先规则中定义了入口函数、Source点。在TaintVar查询语句中,有两个EDB,这两个EDB是经过ptaint中的IDB检索过来的,所以我们跟进PTaint规则中。

因为原先的规则并没有针对SpringBoot的,所以我这里添加了一条针对SpringBoot的参数定义污点的规则:

TaintHeap(insn, heap),    cipt.VarPointsTo(heap, to) :-        SourceMethodArg(callee,n),        AnnotationParam(callee,n,"org.springframework.web.bind.annotation.RequestParam"),        FormalParam(n, callee, from),        AssignLocal(_, _, from, to, callee),        ActualParam(_,insn,to),        heap = cat("NewArgTainted::", insn).

规则中首先查询了SouceMethodArg的callee方法,也就是我们刚才定义的

ptaint.SourceMethodArg("<org.sec.sidemo.web.XSSController: java.lang.String reflection(java.lang.String)>",0).

然后依据该callee到AnnotationParam中进行查询,查询该callee的索引为n的参数是否满足有RequestParam注解

因为所有的参数注解都会在Param-Annotation.facts中呈现,所以我在inputDeclaration.dl规则中我添加了如下规则:

.decl AnnotationParam(method:Method, n:number, descriptor:symbol).input AnnotationParam(filename="Param-Annotation.facts")

再查询定义好的SourceMethodArg中的值

可以查询到对应的值,表示该Rule成立,继续往后看。后续带入到了FormalParam中查询对应的from值

正好对应了前面的jimple中间语言reflection的第一个参数parameter0,之后又使用了AssignLocal查询该parameter0转到了本地变量的哪一个值上。

查询的结果是<org.sec.sidemo.web.XSSController: java.lang.String reflection(java.lang.String)>/message#_0,正好对应jimple的parameter0转移。

之后通过ActualParam找到它对应的Insn的值,并用cat函数链接两个字符串传递给heap,从而得出最终的IDB。

读取最后生成的TaintVar结果集

就可以看到insn和var的值都输出到结果中,成功把污点标记到message#_0本地变量上。

既然污点源已经分析出来了,后续就要分析污点是如何传播的,且该Xss的Sink点的返回值是否包含污点标记(因为是ResponseBody注解修饰的方法,因此需要判断返回值是否有污点标记)。

污点是message参数,同时Controller中经过this.xssService.reflection(message)方法传递了污点,并把污点传递到了返回值上。

ptaint.ArgToRetTransfer("<org.sec.sidemo.service.XSSService: java.lang.String reflection(java.lang.String)>", 0).
.decl TransferTaint(heap:Heap, var:Var)
TransferTaint(heap, var) :- ptaint.ArgToRetTransfer(callee, n), VirtualMethodInvocation(insn, _, callee, _, _), ActualParam(n, insn, arg), ptaint.cipt.VarPointsTo(heap, arg), //判断调用XSSService的实参是否是污点标记的对象 AssignReturnValue(insn, var).

如上,首先定义一个参数到返回值的转移规则,之后在TransferTaint的EDB里面判断了对应的方法调用,并判断对应的实参是否有污点标记。如果有,则用AssignReturnValue找到对应的返回值由什么参数接受,并输出IDB关联起heap和新的接受值var。

关联起来之后,只需要判断对应的Sink点的返回值是否和heap污点指向的参数值var相等即可判断是否存在XSS漏洞

ptaint.SinkMethod("<org.sec.sidemo.web.XSSController: java.lang.String reflection(java.lang.String)>", 0).
IsSink(TaintFlow) :- TransferTaint(heap,var), ptaint.SinkMethod(method,_), Return(_, _, var, method), //判断是否是Sink点的返回 TaintFlow = cat("TaintFlow:",cat(method,cat(">>>>>",heap))).

最终输出TaintFlow结果。

类型继承关系分析(CHA)原理刨析

在Java中相关的调用图分析有大致四种分析技术:

  • Class hierarchy analysis(CHA)

  • Rapid type analysis(RTA)

  • Variable type analysis(VTA)

  • Pointer analysis(k-CFA)

而其中Class Hierarchy Analysis(CHA)就是类型继承关系分析,其目的就是通过类之间的调用关系来分析程序的走向,相比较指针分析的方式,CHA的精度更低,但耗时更短。而且后续还需要投入大量的人力成本去排除可能存在的误报情况。本章节就简单的介绍CHA的分析原理和BytecodeDL是如何对靶场进行分析定位的。

在Java中,其方法调用可以分为四种调用:

  • invokestatic

  • invokespecial

  • invokeinterface

  • invokevirtual

invokestatic和invokespecial在编译解析的时候就能确定对应的调用方法是什么,而invokeinterface和invokevirtual无法在编译解析的时候无法知道是调用的何种方法,因此就需要

就好比重写方法的时候

因为B类里面没有重写foo方法,所以程序调用的是A的foo方法;而程序C中重写了foo方法,因此最终调用的是C的foo方法,这有关于程序运行时的对象决定的。
之后来看一下CHA是如何处理invokevirtual调用的

程序检测到调用是virtual类型的,就会循环遍历相关Subclasses,并找到对应相同Signature的方法。

看到上述的调用图生成,就是通过解析目标对应的invoke类型,并添加到对应的CG调用图中。
而在ByteCodeDL中,对应的cha.dl规则也有相似的算法
Reachable(callee, n+1),CallGraph(insn, caller, callee) :-    Reachable(caller, n),    n < MAXSTEP,    VirtualMethodInvocation(insn, _, method, receiver, caller),    MethodInfo(method, simplename, _, _, _, descriptor, _),    VarType(receiver, class),    SubEqClass(subeqclass, class),    !ClassModifier("abstract", subeqclass),    Dispatch(simplename, descriptor, subeqclass, callee).
InvokeVirtual调用的方法通过MethodInfo查询到对应的方法signature,再通过Dispatch找到对应的实现。
我再通过实战使用ByteCodeDL分析漏洞,帮助各位读者理解。
这里我改了下ByteCodeDL的规则,分析SSRF漏洞产生的路径,将相关函数的调用过程结果输出来。
#define MAXSTEP 5#define CHAO 2
#include "inputDeclaration.dl"#include "utils.dl"#include "cha.dl"
SinkDesc("getInputStream", "java.net.HttpURLConnection").

EntryMethod(method),Reachable(method, 0) :- MethodInfo(method, simplename, _, class, _, _, arity), MethodModifier("public", method), simplename = "ssrf1", class = "org.sec.sidemo.web.SSRFController", descriptor = "(Ljava/lang/String;)Ljava/lang/String;".
.output SinkMethod
因为这里定义了SinkMethod,所以导入到neo4j图形数据库的neoImportCall.sh脚本里面需要添加对SinkMethod.csv规则的导入。
neo4j-admin import --relationships=Call=/bytecodedl/neo4j/CallEdgeHeader.csv,/bytecodedl/output/CallEdge.csv --nodes=/bytecodedl/neo4j/CallNodeHeader.csv,/bytecodedl/output/CallNode.csv --nodes=/bytecodedl/neo4j/SinkMethodHeader.csv,/bytecodedl/output/SinkMethod.csv --database=$dbname --delimiter="\t" --skip-duplicate-nodes=true

导入之后方法http://ip:7474/,用neo4j/bytecodedl登录系统,主数据库就是我们刚才导入的数据库。
之后用下列查询语句查询entry到sink的单向有向图:
MATCH n = (e:entry)-[*]->(s:sink) RETURN n

之后再通过人工审计代码即可定位漏洞

04
总结与思考

这一段时间的学习和接触发现白盒设计其实是非常聪明的,通过将程序转换成对应的facts,并用相关的datalog查询语句关联起对应的CallGraph和定义相应需要查询的规则数据库。之后通过neo4j类似的图形数据库来查询相应的漏洞。

对于ByteCodeDL来说,使用了Doop的Soot Facts生成工具,程序无法支持解析SpringBoot的程序,刚开始的时候和作者一样爬了不少的坑。看源码、改程序,但因为没有相关全面的文档,只能一点点通过自己的理解来更改相应的程序,但是觉得可以进一步发展以下功能:

  • 更改Doop的Soot-Facts-Generate工具,增强对SpringBoot的支持。

  • 因为目前ByteCode项目暂时只有对基础语法的支持,后续可以考虑增加定义Spring的Controller方法为入口函数,并增加相关漏洞Sink点的规则。

  • 可以编写一个程序接口,专门添加入口函数和Sink的规则,后端再通过上传Jar包的功能自动化生成和导入图形数据库中。

  • ByteCodeDL暂时只有作者提供的CHA查询规则,后续是否可以根据指针分析的规则添加相应的datalog查询。

其实ByteCodeDL和主流的CodeQL白盒工具原理相似,CodeQL有对数据流分析的支持,而ByteCodeDL处于刚开源的阶段,相关分析规则还不够完善。但好处在于ByteCodeDL提供了相关导入图形数据库查询的方式,方便安全人员甚至是开发人员查询相关漏洞,同时还有完整的图形显示,利于定位相关漏洞形成。

也很荣幸能够和作者认识,在学习ByteCodeDL的时候解答了我不少问题。

05
Reference

[1].https://bitbucket.org/yanniss/doop/

[2].https://www.bilibili.com/video/BV1wa411k7Uv

[3].https://anemone.top/pl-%E9%9D%99%E6%80%81%E7%A8%8B%E5%BA%8F%E5%88%86%E6%9E%90%E8%AF%BE%E7%A8%8B%E7%AC%94%E8%AE%B0%EF%BC%88Datalog%EF%BC%89/

[4].https://github.com/wernsey/jatalog

[5].https://pascal-group.bitbucket.io/lectures/Datalog.pdf

[6].https://yanniss.github.io/ptaint-oopsla17-prelim.pdf

[7].https://jckling.github.io/2021/12/17/Security/%E6%8C%87%E9%92%88%E5%88%86%E6%9E%90%E5%B7%A5%E5%85%B7%20Doop%20%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/

[8].https://souffle-lang.github.io/docs.html

[9].https://www.cnblogs.com/xuesu/p/14321627.html

[10].https://blog.csdn.net/WDWAGAAFGAGDADSA/article/details/122906141

[11].https://www.jianshu.com/p/5cbc5bb5c4da



文章来源: https://mp.weixin.qq.com/s?__biz=MzAxNDk0MDU2MA==&mid=2247484231&idx=1&sn=c6de1b4c559efb4a9a40e1ac5be7030a&chksm=9b8ae3b8acfd6aaec81ef5bee4f80d9c53379d7e23b269e7905e92e7c2cda9b45818b0abf63b&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh