前言

为了避免每次都要打开一堆项目回顾,稍微记录一些内容。

基本概念

数据结构

Scene:分析环境

  • 代表 Soot 输入程序的整个运行、分析、变换的环境。

SootClass:类

  • library 类来自 Soot 的 classpath,但只在需要时加载;
  • application 序类是从进程目录中获取的,并且总是被加载和转换;
  • phantom 类是既不在进程目录中也不在 Soot 的 classpath 中的类,但它们被 Soot 加载的一些类/方法体所引用
    • 如果启用了 phantom 类,Soot 不会因为这种无法解决的引用而中止或失败,而是创建一个空的存根,称为 phantom 类,它包含 phantom 方法来弥补缺失的部分。

SootMethod:类中的单个方法

SootField:类中的单个属性

Body:方法体

  • 由 Locals 链(body.getLoclas())、Units 链(body.getUnits())、Traps 链(body.getTraps())组成。
  • Locals 链存储方法中的变量定义
  • Units 链存储方法中的句子
  • Traps 链存储方法中发生异常的语句

Box:指针

  • 包括 UnitBox、ValueBox
  • 一个 Unit 可以有多个 UnitBox,但是每个 UnitBox 只能指向一个 Unit。
  • ValueBox 包含在 Unit 中使用的值以及定义的值。

Stmt 与 Unit 都表示方法中的一个句子。

  • Unit 在 Jimple 中的实现是 Stmt
  • 不同:Unit 注重于句子的构成、而 AssignStmt 之类的则注重句式的种类
  • 注意:AssignStmt 表示赋值语句;而 IdentityStmt 表示将参数赋值给 Local 这样的语句
1
2
3
4
5
6
7
8
9
// 一般 Value 指的是 Local(变量)、Expr(表达式)、Constant(常量)
public List<ValueBox> getUseBoxes(); //返回 Unit 中使用的 Value 的引用
public List<ValueBox> getDefBoxes(); //返回 Unit 中定义的 Value 的引用
public List<ValueBox> getUseAndDefBox();//返回 Unit 中定义并使用的 Value 的引用
public List geUnitBoxes(); //获得 Unit 跳转到的 UnitxBox 列表
public List getBoxesPointingTothis(); //Unit 作为跳转对象时,获取所有跳转到该 Unit 的 UnitBox
public boolean fallsThrough(); //如果接下来执行后面的 Unit,则为 true
public boolean branches(); //如果执行时会跳转到其他 Unit,则返回 true。如:IfStmt、GotoStmt
public void rediectJumpsToThisTo(Unit newLocation);//把跳转到 Unit 重定向到 newLocation

Unit 表示某种用于执行的单元(例如 Jimple 中的 Stmt 和 Baf 中的 Inst)

  • Stmt 继承 Unit 接口,AssignStmt、IdentityStmt、IfStmt、RetrunVoidStmt、NopStmt 等继承 Stmt 接口。
  • Stmt 子接口

Block 是划分方法体的基本块,被实现为底层 Body 实例的视图;因此,对 Block 所做的更改将自动反映在其封闭的方法体中。

  • Block 存在于 BlockGraph 的上下文中(CFG),图节点是 Block 实例。
    • 可以查询后继块和前驱块

执行流

Soot 执行被分成几个阶段,这些阶段被称为 Packs。

  • 第一步生成 Jimple 代码,然后将 Jimple 代码输入到其他 Packs 中。
    • 通过解析 class、jimple 或 java 文件再通过 Jimple Body(jb)传递它们的结果而完成的。
  • Soot 根据分析问题是过程内分析还是过程间分析,会有不同的执行流。
    • 过程内分析简单的说就是被分析的程序中不存在函数调用。

    • 过程间分析简单的说就是存在函数调用。

      • 过程间分析时,需要指定 Soot 运行在 whole-program 模式下。此时 Soot 会增加三个阶段:cg(call-graph generation)、wjtp(whole Jimple transformation pack)、wjap(whole Jimple annotation pack)
  • Pack 命名规则:第一个字母表示采用哪种中间语言,例如s(shimple),j(jimple),b(baf),g(grimp);第二个字母表示进行到 Pack 的哪一步,例如:b(body creation),t(transformation),o(optimizations),a(annotion);p 表示“pack”,是处理阶段的意思。

Jimple

Stmt 类型

  • 核心语句:NopStmt,DefinitionStmt(Identitystmt,AssignStmt)
  • 过程内控制流:IfStmt,GotoStmt,TableSwitchStmt,LookupSwitchStmt
  • 过程间控制流:InvokeStmt,ReturnStmt,ReturnVoidStmt
  • 监控语句:EnterMonitorStmt,ExistMonitorStmt
  • 其他类型:ThrowStmt,RetStmt(不再使用)

Unit 表示某种用于执行的单元(例如 Jimple 中的 Stmt 和 Baf 中的 Inst)

  • Stmt 继承 Unit 接口,AssignStmt、IdentityStmt、IfStmt、RetrunVoidStmt、NopStmt 等继承 Stmt 接口。
  • Stmt 子接口

Block 是划分方法体的基本块,被实现为底层 Body 实例的视图;因此,对 Block 所做的更改将自动反映在其封闭的方法体中。

  • Block 存在于 BlockGraph 的上下文中(CFG),图节点是 Block 实例。
    • 可以查询后继块和前驱块

每条 Stmt 是一个 Unit,多个 Unit 构成 Block

  • Unit 中的跳转信息:控制流
  • Unit 中的数据:数据流

过程内分析

UnitGraph

  • BriefUnitGraph:正常,异常分离。将正常流程与异常处理流程进行了分离,不相交,在最后才汇聚。
  • ExceptionalUnitGraph:将正常流程与异常处理流程进行了融合
  • TrapUnitGraph:相对于 ExceptionalUnitGraph,一些普通的语句也会跳转到异常流程中。
    • 即认为 try 中包含的所有的语句都有可能触发异常
  • EnhancedUnitGraph:异常流在 try-catch-finally 块级别而不是 Unit 级别表示

BlockGraph

  • BriefBlockGraph:与异常相关的控制流被忽略,因此图将是一个森林,其中每个异常处理程序构成一个不相交的子图。
  • ExceptionalBlockGraph:考虑与异常相关的控制流:当一个 Unit 可能抛出一个被 Body 内的 Trap 捕获的异常时,异常 Unit 创建一个新的基本块。
    • 当 Unit 可能抛出的所有异常都将逃脱方法而未被捕获时,不会创建新块
  • ClassicCompleteBlockGraph:考虑与异常相关的控制流,当一个 Unit 可能抛出一个被 Body 内的 Trap 捕获的异常时,异常 Unit 会创建一个新的基本块。仅用于测试,不应用于实际分析。
  • ArrayRefBlockGraph:节点是 Block 实例,其中包含数组引用的 Unit 将创建新块。异常控制流被忽略,因此图将是一个森林,其中每个异常处理程序构成一个不相交的子图。
  • ZonedBlockGraph:节点是 Block 实例,并且在为提供的 Body 查找 Block 时会考虑异常边界。
    • 任何作为第一个被某个异常处理程序转换的 Unit 都将创建一个新块,任何作为最后一个被某个异常处理程序覆盖的单元,都将结束它所属的块。然而,这些 Zone 并没有被分割以表明异常可能导致控制在完成之前退出该区域。
  • EnhancedBlockGraph

CFGViewer 命令行工具

  • 在 jtp 阶段绘制 cfg,默认生成的是 BriefUnitGraph

过程间分析

  • CG 调用图,节点是方法,边是调用
    • 需要设置入口点,或多线程跟踪(多个入口点)
    • 手动构造:遍历所有类的方法,记录方法内的方法调用
  • ICFG = CG + CFG
    • 给所有方法的 CFG 加上方法之间互相调用的边(CG)所形成的图
    • 调用边(call edge)从调用语句(call site)连到被调方法(callee)的入口
    • 与 CG 不同的是,ICFG 除了调用边,还包含相应的返回边(return edge),从 callee 的出口连到 call site 之后执行的下一个语句。

ICFG

  • JimpleBasedInterproceduralCFG:InterproceduralCFG 接口的默认实现
    • 包括通过显式调用语句或通过调用 Thread.start()Scene.getEntryPoints() 可达的所有语句
    • 此类被设计为线程安全的,并且此类的子类也必须以线程安全的方式设计
  • BackwardsInterproceduralCFG:与 JimpleBasedInterproceduralCFG 相同,但基于倒置的 UnitGraph,用于反向分析。
  • OnTheFlyJimpleBasedICFG:实时计算 ICFG,即可以在不预先计算调用图的情况下使用。
    • 实现通过类层次结构分析 (CHA) 解析调用,通过 FastHierarchy 实现。
    • CHA 由 LocalMustNotAliasAnalysis 支持,用于确定在 InvokeVirtual 或 InvokeInterface 调用点上对象的具体类型已知的情况。在这些情况下,可以具体解决调用,即,对单个目标进行调用。
    • 为了更精确(sound),对于无法具体解析的 InvokeInterface 调用,OnTheFlyJimpleBasedICFG 要求类路径上的所有类至少加载到签名。这必须在计算 FastHierarchy 之前完成,以便层次结构完好无损。客户端应调用 loadAllClassesOnClassPathToSignatures() 将所有需要的类加载到此级别。

操作

把 jar 文件解压到文件夹,如图:

配置

使用 Scene.v().loadBasicClasses()Scene.v().loadNecessaryClasses(),不用都写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static final String output = "C:\\Users\\linki\\Desktop\\demo";
public static final String sourceDirectory = "C:\\Users\\linki\\Desktop\\demo\\commons-collections-3.1";

public static void setupSoot() {
G.reset();

// 输出格式
Options.v().set_output_dir(output); // 设置 IR Jimple 的输出目录
Options.v().set_output_format(Options.output_format_jimple);

Options.v().set_process_dir(Collections.singletonList(sourceDirectory)); // 待分析的路径
// Options.v().set_soot_classpath(sourceDirectory); // JDK 和待分析的 jar 都要加入 classpath

Options.v().set_prepend_classpath(true);
Options.v().set_allow_phantom_refs(true);
Options.v().set_keep_line_number(true);

Options.v().set_whole_program(true); // 全程序分析
Options.v().set_verbose(true); // 显示详细信息

// Scene.v().loadBasicClasses();
Scene.v().loadNecessaryClasses(); // 加载 Soot 依赖的类和命令行指定的类
Scene.v().loadDynamicClasses();
}

遍历

遍历所有类及其方法,注意:

  • loadXXX 方法读取的类不多,直接遍历 JDK 路径读取才全面;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static void main(String[] args) {
// 配置
setupSoot();

// 执行分析
PackManager.v().runPacks();

// 合计
System.out.printf("%d = %d + %d + %d%n",
Scene.v().getClasses().size(),
Scene.v().getApplicationClasses().size(),
Scene.v().getLibraryClasses().size(),
Scene.v().getPhantomClasses().size()
);

// 遍历类
for (SootClass klass : Scene.v().getClasses()) {
// 类名
System.out.println(klass.getName());
// 遍历方法
for (SootMethod method : klass.getMethods()) {
// 方法签名
System.out.println(method.getSignature());
}
}
}

类信息

根据类名获得类

1
2
3
4
SootClass klass = Scene.v().getSootClass("java.io.ObjectInputStream");
System.out.println(klass.getJavaPackageName()); // 包名
System.out.println(klass.getSuperclass().getName()); // 父类名
System.out.println(klass.getName()); // 类名

方法信息

根据方法签名获得方法

1
2
3
4
5
SootMethod method = Scene.v().getMethod("<java.io.ObjectInputStream: java.lang.Object readObject()>");
System.out.println(method.getDeclaringClass().getName()); // 所属类
System.out.println(method.getSignature()); // 签名
System.out.println(method.getSubSignature()); // 子签名
System.out.println(method.getName()); // 方法名

分析方法体,注意:

  • 有些方法没有方法体,hasActiveBody 将返回 false
  • retrieveActiveBody 会为没有方法体的方法构建方法体,但可能抛出异常,处理之后第二次调用将得到方法体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
System.out.println(method.hasActiveBody() + ": " + method.getSignature());

// Jimple Body
JimpleBody jb = (JimpleBody) method.retrieveActiveBody();

// 遍历其中的 Unit
for (Unit u : jb.getUnits()) {
// 语句
Stmt stmt = (Stmt) u;
if (stmt.containsInvokeExpr()) {
InvokeExpr invokeExpr = stmt.getInvokeExpr();
invokeExpr.apply(new AbstractExprSwitch() {

// Invoke instance method; special handling for superclass, private, and instance initialization method invocations
@Override
public void caseSpecialInvokeExpr(SpecialInvokeExpr v) {
super.caseSpecialInvokeExpr(v);
System.out.println("(caseSpecialInvokeExpr): " + v.getMethod().getSignature());
}

// Invoke a class (static) method
@Override
public void caseStaticInvokeExpr(StaticInvokeExpr v) {
super.caseStaticInvokeExpr(v);
System.out.println("(caseStaticInvokeExpr): " + v.getMethod().getSignature());
}

// Invoke instance method; dispatch based on class
@Override
public void caseVirtualInvokeExpr(VirtualInvokeExpr v) {
super.caseVirtualInvokeExpr(v);
System.out.println("(caseVirtualInvokeExpr): " + v.getMethod().getSignature());
}

// Invoke dynamic method
@Override
public void caseDynamicInvokeExpr(DynamicInvokeExpr v) {
super.caseDynamicInvokeExpr(v);
System.out.println("(caseDynamicInvokeExpr): " + v.getMethod().getSignature());
}

// Invoke interface method
@Override
public void caseInterfaceInvokeExpr(InterfaceInvokeExpr v) {
super.defaultCase(v);
System.out.println("(caseInterfaceInvokeExpr): " + v.getMethod().getSignature());
}

@Override
public void defaultCase(Object v) {
super.defaultCase(v);
}
});
}
}

使用方式

执行流(Packs)没有用过,我个人的一般操作流程是:

  1. 设置 Soot 配置
  2. 读取 .jar
  3. 执行分析

Soot 配置

其实上面也有,在列一些

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void initSootOption() {
G.reset();

Options.v().set_prepend_classpath(true);
Options.v().set_allow_phantom_refs(true);
Options.v().set_keep_line_number(true);

Options.v().set_whole_program(true);
Options.v().set_verbose(true);

Scene.v().loadBasicClasses();
Scene.v().loadDynamicClasses();
Scene.v().loadClass("java.lang.Class", 3);

// Options.v().set_output_format(Options.output_format_jimple);
// Options.v().set_validate(true);
// Options.v().set_ignore_classpath_errors(true); // Ignores invalid entries on the Soot classpath.
// Options.v().set_no_writeout_body_releasing(true); // 当输出内容后不释放获取的body数据
// Options.v().set_no_bodies_for_excluded(true);
// Options.v().set_omit_excepting_unit_edges(true);
}