Soot 使用记录
前言
为了避免每次都要打开一堆项目回顾,稍微记录一些内容。
基本概念
数据结构
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 | // 一般 Value 指的是 Local(变量)、Expr(表达式)、Constant(常量) |
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)
- 过程间分析时,需要指定 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 | public static final String output = "C:\\Users\\linki\\Desktop\\demo"; |
遍历
遍历所有类及其方法,注意:
loadXXX
方法读取的类不多,直接遍历 JDK 路径读取才全面;
1 | public static void main(String[] args) { |
类信息
根据类名获得类
1 | SootClass klass = Scene.v().getSootClass("java.io.ObjectInputStream"); |
方法信息
根据方法签名获得方法
1 | SootMethod method = Scene.v().getMethod("<java.io.ObjectInputStream: java.lang.Object readObject()>"); |
分析方法体,注意:
- 有些方法没有方法体,
hasActiveBody
将返回 false retrieveActiveBody
会为没有方法体的方法构建方法体,但可能抛出异常,处理之后第二次调用将得到方法体
1 | System.out.println(method.hasActiveBody() + ": " + method.getSignature()); |
使用方式
执行流(Packs)没有用过,我个人的一般操作流程是:
- 设置 Soot 配置
- 读取 .jar
- 执行分析
Soot 配置
其实上面也有,在列一些
1 | public static void initSootOption() { |