soot-And-jimple
参考
soot API:https://soot-oss.github.io/soot/docs/4.4.1/jdoc/index.html
官方文档:https://github.com/soot-oss/soot/wiki/Tutorials
PDF:sootsurvivorsguide.pdf
一篇很不错的入门文章:https://zhuanlan.zhihu.com/p/558087790
Soot
数据结构
Scene:表示进行分析一个类时产生的完整环境(上下文)。通过它,可以设置应用程序类(提供给 Soot 进行分析的类)、主类(包含主方法的类)和访问关于过程间分析的信息(例如,指针信息和调用图)。
SootClass:表示加载到 Soot 中或使用 Soot 创建的类。
SootMethod:表示类的方法。
SootFiled:表示类的成员字段。
Body:代表一个方法体,有不同的风格,对应于不同的 IR(例如 JimpleBody)。这些数据结构是使用面向对象技术实现的,并且尽可能地设计为易于使用和通用。
Unit: 表示 Soot 中的语句,jimple 使用 Stmt 表示。
Jimple 中间表示的处理方法集合在 soot.jimple、soot.jimple.internal 中,大量优化集合在 soot.jimple.toolkits.* 中,尤其是在 soot.jimple.toolkits.scalar 和 soot.jimple.toolkits.annotation.*
Values:一个数据被表示为一个 Value。包括:局部变量(Local)、常量(在 Jimple 中表示为 Constant)、表达式(在 Jimple 中表示为 Expr)等等。表达式有各种不同的实现,例如 BinopExpr 和 InvokeExpr ,但一般来说可以理解为对一个或多个值执行某些操作并返回另一个值。
Box:在 Soot 中的引用(类似于指针),一般用于修改代码,有以下两种类型。
- ValueBox:指 Values,每个 unit 中都有使用和定义的 Values 的概念,这对于替换 Units 中使用的或定义的 box 非常有用,例如在执行常量折叠时
- UnitBox:指 Units,当一个 unit 可以有多个后继时(例如分支语句),就会使用 UnitBox。
Soot 执行阶段
Soot 中每一个执行的部分被称为 pack,而 Soot 的执行一般分为几个 packs 的阶段。第一步是生成 Jimple 代码以供各种 packs 使用。这是通过解析类、jimple 或 java 文件并将其结果通过 Jimple Body(jb) pack 进行传递。
packs 的命名方案相当简单。第一个字母指定 pack 接受的 IR;s 表示 Shimple,j 表示 Jimple,b 表示 Baf,g 表示 Grimp。第二个字母指定 pack 的角色;b 表示方法主体,t 表示用户定义的转换,o 表示优化,a 表示属性生成(注解)。pack 名称末尾的 p 表示 pack。例如,jap(Jimple 注解包)包含所有程序内分析。
下图是 Inter-procedural analysis(程序间分析) 的执行流程,在 Soot 运行周期中包括三个额外的包:cg(调用图生成),wjtp(整个 Jimple 转换包)和 wjap(整个 Jimple 注解包)。这些包生成的信息可以通过 Scene 提供给 Soot 的其他部分使用。而之后的 jtp、jop、jap 则是给每个 body 使用的,最终的产生的 bb(Baf Body 是 Soot 基于栈的中间表示,用于创建字节码)和 tag
我们可以扩展 Soot 的 Main 类以包括我们自己的分析,将一个中间步骤注入到 Soot 中,使得 Soot 可以运行我们自定义的分析并处理传递给它的所有其他选项。过程间(inter-procedural analysis)需要注入到 wjtp 阶段而过程内(intra-procedural analysis)则注入到 jtp 阶段。下图的代码示例展示了如何将一个类 MyAnalysisTagger(执行某些 intra-procedural analysis)的实例注入到 Soot 中。
使用Soot创建一个类
1 |
|
- SootClass 对应着一个对象。
- SootMethod 只能有一个活动的 Body,可通过 SootMethod.getActiveBody() 获取。
- Body有三个重要的功能:Locals、Traps 和 Units。
- Locals 是主体中的局部变量。
- Traps 是哪些单元捕获的哪些异常。
- Units 是语句本身。
Soot配置与使用
1 |
|
apk配置
1 |
|
设置全程序分析,默认情况下是不进行全程序分析的。
1 |
|
一般使用 jtp、jop、jap 三个 pack 针对每个 body 进行分析。
- jtp 默认使用,并且是空的,需要我们放入自己的 intra-procedural analyses。
- jop 中有 Jimple 优化功能,但在默认情况下是禁用的。
- jap 默认启用,但是其中的处理功能会被禁用,当我们把自己的分析放入到 jap 时,会自动开启。
- 想要往 jtp、jop、jap 中加入 Transform 必须要使用 BodyTransformer。
下面是一份 jap 的使用示例。注册了一个新的 BodyTransformer,为每个方法中的每个语句打印出标记
1 |
|
数据流分析
- 确定分析的性质。是向前还是向后的流分析?是否需要特别考虑分支?
- 确定预期的近似值。是 may(并集) 分析还是 must(交集) 分析?即确定合并的信息流在通过一个节点时,采取并集还是交集。
- 执行实际的流分析。实质上为中间表示中的每种语句编写方程——例如,应如何处理赋值语句。
- 决定入口节点(如果是向后流)和内部节点的初始状态或近似值——通常是空集或全集,具体取决于分析的保守程度。 执行数据流分析需要一些表示数据如何在程序中流动的结构,例如控制流图(cfg)。 Soot 数据流框架通过 soot.toolkits.graph.DirectedGraph 接口进行处理 cfg。
Soot 提供了三种数据流分析的实现:
- ForwardFlowAnalysis 前向分析,从 UnitGraph 的入口语句开始进行向前传播。
- BackwardFlowAnalysis 后向分析,从 UnitGraph 的出口节点开始向后传播。
- ForwardBranchedFlowAnalysis 前向分支分析,实质上是一种前向分析,但是允许将不同的流集传播到不同的分支中。例如:处理 if(p != null) ,则可以将 p != nll 传播到为 true 的分支中,p == null 传播到为 false 的分支中。
继承类
实现分析,需要继承 ForwardFlowAnalysis(BackwardFlowAnalysis、ForwardBranchedFlowAnalysis ),其有两个参数:
- N:节点类型。通常是 Unit,但也可以对保存其他类型节点的图进行流分析。
- A: 抽象类型。通常使用 sets 或 maps 作为抽象类型,但一般来说,可以使用任何你喜欢的东西。但要注意,抽象类型必须正确地实现 equals(…) 和 hashCode(),这样 Soot 才能确定什么时候达到了固定点。
构造函数——真正的实现
实现一个构造函数,需要以 DirectedGraph<N> 作为参数(其中N是节点类型),并将其传递给父类的构造函数。在构造函数的末尾需要调用 doAnalysis() 方法以执行数据流分析,在调用父类构造函数和 doAnalysis() 方法之间可以自定义数据分析结构。
一些需要用到的方法和类:
- newInitialFlow() 返回一个自定义的抽象类型 A 的对象。这个对象被分配为每个语句的初始入集和出集,除了第一个语句的入集(如果实现的是一个向后分析,则为一个退出语句,例如 return 或 throw)
- entryInitialFlow() 初始化第一个语句的入集。
- copy(A source, A dest) 方法接受两个自定义的抽象类型 A 的参数:源和目标。实现将源复制到目标中。
- merge(A in1, A in2, A out) 方法用于在控制流的合并点(例如 if/then/else 语句的末尾)合并流集。接受三个参数,一个来自左分支的出集,一个来自右分支的出集,这将成为合并点之后下一个语句的新合并入集。即将两个 in 集合并为一个 out 集。
- flowThrough(A in, N d, A out) 方法接受三个参数,类型为 A 的入集; 要处理的类型为 N 的节点,通常是一个 Unit;类型为 A的出集。第一个参数始终是流函数的参数(即在正向分析中它是入集,在反向分析中它是出集),第三个参数始终是流函数的结果(即在正向分析中它是出集,在反向分析中它是入集)。是数据流分析的核心处理方法。
- FlowSet 接口中有一些集合操作的方法以及具体实现的类
- ArraySparseSet:使用稀疏数组实现集合,支持高效的添加、删除、查找和迭代操作。
- ArrayPackedSet:使用紧凑数组实现集合,可以存储更多的元素,但是由于使用了位运算,因此在操作时需要进行一些额外的计算,因此操作复杂度略高于ArraySparseSet。
- 一般来说,如果需要存储的元素数量较少,可以选择ArraySparseSet;如果需要存储更多的元素,可以选择ArrayPackedSet;如果需要表示不确定的分析结果,可以选择ToppedSet。
- ToppedSet:是ArraySparseSet的子类,额外支持一个元素作为“top”元素,用于表示未知的分析结果。
- a.intersection(b,c); 表示 c=a∩b
- a.union(b,c); 表示 c=a∪b
- c.complement(d); 返回 c 和 d 的对称差(即 c 中存在而 d 不存在的元素以及 d 中存在而 c 中不存在的元素)
- d.add(v); 表示 d=d∪{v}
最后可以把分析的结果放在 tag 中。
活跃变量分析
下面是一个分析 class 中的活跃变量的简单示例。
1 |
|
常量传播
跟活跃变量分析不同,常量传播需要设置初始集,需要把方法的参数先判断是否为常量,再将其添加初始集合。同时,在进行merge合并时,大部分时取交集或者是其他更加复杂的算法,但是不会取并集。
因为如果是两条支路中,在一条支路中是常量,但是在另外一条支路中不是常量,所以此时取交集会更加准确一点。
1 |
|
更改之后的代码如下:
1 |
|
死代码消除
- 控制流分析
- 从方法入口开始,遍历 CFG 并标记可达语句。当遍历结束时,那些没有被标记的语句即控制流不可达。
- 活跃变量分析
- 无用赋值:一个局部变量在一条语句中被赋值,但再也没有被该语句后面的语句读取,这样的变量和语句分别被称为无用变量和无用赋值。
- 所以如果一个赋值语句中,等号左侧是一个无用变量,那么就可把该语句标记为无用赋值,然后去除。
- 常量传播
- 分支不可达:预先对被检测代码应用常量传播分析,通过它来告诉我们条件值是否为常量,然后在遍历 CFG 时,我们不进入相应的不可达分支
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426import soot.*;
import soot.jimple.*;
import soot.jimple.infoflow.InfoflowConfiguration;
import soot.jimple.infoflow.android.InfoflowAndroidConfiguration;
import soot.jimple.infoflow.android.SetupApplication;
import soot.jimple.internal.*;
import soot.jimple.toolkits.callgraph.Edge;
import soot.options.Options;
import soot.toolkits.graph.CompleteUnitGraph;
import soot.toolkits.graph.UnitGraph;
import soot.toolkits.scalar.BackwardFlowAnalysis;
import soot.toolkits.scalar.ForwardFlowAnalysis;
import java.util.*;
public class DeadCodeEliminator {
public static void main(String[] args) {
initializeSoot();
initializeFlowDroid();
// 去除无需分析的androidx、R类
Collection<SootClass> toAnalysisClasses = Scene.v().getApplicationClasses();
toAnalysisClasses.removeIf(sootClass -> sootClass.getName().startsWith("androidx") || sootClass.getName().startsWith("com.example.lab_code.R") || sootClass.getName().startsWith("com.example.lab_code.BuildConfig"));
//添加未被调用的方法
Map<SootClass, Set<SootMethod>> hashMap = new HashMap<>();
for (SootClass sootClass : toAnalysisClasses){
if (sootClass.getName().equals("dummyMainClass")) continue;
Set<SootMethod> sootMethods = new HashSet<>();
for (SootMethod sootMethod : sootClass.getMethods()){
boolean isCalled = false;
Iterator<Edge> iterator = Scene.v().getCallGraph().edgesInto(sootMethod);
if (iterator.hasNext()){
isCalled = true;
}
if (!isCalled){
System.out.println("在类: " + sootClass.getName() + " 的方法: " + sootMethod.getName() + " 从未被调用");
sootMethods.add(sootMethod);
}
}
hashMap.put(sootClass, sootMethods);
}
//删除未被调用的方法
for (Map.Entry<SootClass, Set<SootMethod>> entry : hashMap.entrySet()){
SootClass sootClass = entry.getKey();
Set<SootMethod> sootMethods = entry.getValue();
for (SootMethod sootMethod : sootMethods){
Scene.v().getSootClass(sootClass.getName()).removeMethod(sootMethod);
System.out.println("删除类: " + sootClass.getName() + " 的方法: " + sootMethod.getName());
}
}
// 活跃变量分析去除无效赋值
for (SootClass sootClass : toAnalysisClasses){
if (sootClass.getName().equals("dummyMainClass")) continue;
for (SootMethod sootMethod : sootClass.getMethods()) {
CompleteUnitGraph graph = new CompleteUnitGraph(sootMethod.retrieveActiveBody());
LiveAnalysis liveAnalysis = new LiveAnalysis(graph);
for (Unit unit : graph) {
boolean toRemove = false;
Set<String> flowAfter = liveAnalysis.getFlowAfter(unit);
for (ValueBox box : unit.getDefBoxes()) {
Value value = box.getValue();
for (String liveVariable : flowAfter){
if (liveVariable.equals(value.toString())){
toRemove = false;
break;
}else if (value instanceof InstanceFieldRef && liveVariable.equals(((InstanceFieldRef) value).getBase().toString())){
toRemove = false;
break;
}
toRemove = true;
}
}
if (toRemove){
System.out.println("在类: " + sootClass.getName() + " 的方法 " + sootMethod.getName() + " 中移除的语句为: " + unit);
Scene.v().getCallGraph().removeAllEdgesOutOf(unit);
}
}
}
}
//常量传播
List<Unit> toRemoveUnits = new ArrayList<>();
for (SootClass sootClass : toAnalysisClasses){
if (sootClass.getName().equals("dummyMainClass")) continue;
for (SootMethod sootMethod : sootClass.getMethods()) {
CompleteUnitGraph graph = new CompleteUnitGraph(sootMethod.retrieveActiveBody());
ConstantPropagation constantPropagation = new ConstantPropagation(graph);
for (Unit unit : graph) {
Map<String, Object> flowBefore = constantPropagation.getFlowBefore(unit);
if (unit.branches()){
if (unit instanceof IfStmt){
Value condition = ((IfStmt)unit).getCondition();
Unit targetUnit = ((IfStmt)unit).getTarget();
if (condition instanceof EqExpr){
EqExpr eqExpr = (EqExpr) condition;
}else if (condition instanceof GeExpr){
GeExpr geExpr = (GeExpr) condition;
}else if (condition instanceof GtExpr){
GtExpr gtExpr = (GtExpr) condition;
}else if (condition instanceof LeExpr){
LeExpr leExpr = (LeExpr) condition;
Value op1 = leExpr.getOp1();
Value op2 = leExpr.getOp2();
if (flowBefore.containsKey(op1.toString()) &&
((String)flowBefore.get(op1.toString())).equals("NAC")) continue;
if (flowBefore.containsKey(op1.toString()) &&
((String)flowBefore.get(op1.toString())).equals("Constant")){
System.out.println("该值来自上面的分支,无法直接判断!");
}else if(flowBefore.containsKey(op1.toString()) &&
((String)flowBefore.get(op1.toString())).startsWith("@parameter")){
System.out.println("该变量为方法参数,无法在方法内部进行判断!");
Iterator<Edge> iterator = Scene.v().getCallGraph().edgesInto(sootMethod);
while (iterator.hasNext()){
Edge edge = iterator.next();
int idx = (((String)flowBefore.get(op1.toString())).charAt(10)) - 48;
Unit callEdge = edge.srcUnit();
InvokeExpr invokeExpr = ((InvokeStmt) callEdge).getInvokeExpr();
Value arg = invokeExpr.getArg(idx);
if (((IntConstant)arg).value <= ((IntConstant)op2).value){
List<Unit> preds = graph.getPredsOf(targetUnit);
for (Unit pred : preds){
System.out.println(pred);
}
}
}
}else if (flowBefore.containsKey(op1.toString()) &&
flowBefore.get(op1.toString()) instanceof IntConstant){
if (((IntConstant)flowBefore.get(op1.toString())).value <= ((IntConstant)op2).value){
RemoveUnit(toRemoveUnits, graph, unit);
}else {
toRemoveUnits.add(targetUnit);
RemoveUnit(toRemoveUnits, graph, targetUnit);
}
}
}else if (condition instanceof LtExpr){
LtExpr ltExpr = (LtExpr) condition;
}else if (condition instanceof IntConstant){
if (((IntConstant)(condition)).value == 0) {
toRemoveUnits.add(targetUnit);
RemoveUnit(toRemoveUnits, graph, targetUnit);
}else {
RemoveUnit(toRemoveUnits, graph, unit);
}
}
}
if (unit instanceof SwitchStmt){
List<Unit> caseTargets = ((SwitchStmt) unit).getTargets();
Value key = ((SwitchStmt) unit).getKey();
if (flowBefore.containsKey(key.toString()) &&
((String)flowBefore.get(key.toString())).equals("NAC")) continue;
if (flowBefore.containsKey(key.toString()) &&
((String)flowBefore.get(key.toString())).equals("Constant")){
System.out.println("该值来自上面的分支,无法直接判断!");
}else if(flowBefore.containsKey(key.toString()) &&
((String)flowBefore.get(key.toString())).startsWith("@parameter")){
System.out.println("该变量为方法参数,无法在方法内部进行判断!");
}else if(key instanceof IntConstant){
Unit notRemove = ((SwitchStmt) unit).getTarget(((IntConstant)key).value);
for (Unit caseTarget : caseTargets){
if (notRemove.toString().equals(caseTarget.toString())) continue;
RemoveUnit(toRemoveUnits, graph, caseTarget);
}
}
}
}
}
}
}
}
public static void RemoveUnit(List<Unit> toRemoveUnits, CompleteUnitGraph graph, Unit targetUnit) {
Unit succ = graph.getSuccsOf(targetUnit).get(0);
while (true){
toRemoveUnits.add(succ);
if ((succ instanceof JGotoStmt) || (succ instanceof JReturnStmt)
|| (succ instanceof JReturnVoidStmt)) break;
succ = graph.getSuccsOf(succ).get(0);
}
}
public static void initializeFlowDroid(){
InfoflowAndroidConfiguration config = new InfoflowAndroidConfiguration();
config.setInspectSinks(false);
config.setInspectSources(false);
config.setCodeEliminationMode(InfoflowConfiguration.CodeEliminationMode.NoCodeElimination);
config.getCallbackConfig().setCallbackAnalysisTimeout(120);
config.getAnalysisFileConfig().setAndroidPlatformDir("D:\\Android SDK\\platforms");
config.getAnalysisFileConfig().setTargetAPKFile("C:\\Users\\守城\\Desktop\\Step2\\Lab_4.apk");
SetupApplication setupApplication = new SetupApplication(config);
setupApplication.constructCallgraph();
}
public static void initializeSoot(){
G.reset();
Options.v().set_prepend_classpath(true);
Options.v().set_allow_phantom_refs(true);
Options.v().set_src_prec(Options.src_prec_apk);
Options.v().set_process_dir(Collections.singletonList("C:\\Users\\守城\\Desktop\\Step2\\Lab_4.apk"));
Options.v().set_android_jars("D:\\Android SDK\\platforms");
Options.v().set_whole_program(true);
List<String> excludeList = new ArrayList<>();
excludeList.add("java.*");
excludeList.add("javax.*");
excludeList.add("androidx.*");
Options.v().set_exclude(excludeList);//排除不需要分析的类和方法
Options.v().set_output_format(Options.output_format_jimple);
Options.v().setPhaseOption("cg.spark", "on");
Scene.v().loadNecessaryClasses();
}
public static class LiveAnalysis extends BackwardFlowAnalysis<Unit, Set<String>> {
public LiveAnalysis(UnitGraph graph) {
super(graph);
doAnalysis();
}
@Override
protected Set<String> newInitialFlow() {
return new HashSet<>();
}
@Override
protected Set<String> entryInitialFlow() {
return new HashSet<>();
}
@Override
protected void merge(Set<String> in1, Set<String> in2, Set<String> out) {
out.clear();
out.addAll(in1);
out.addAll(in2);
}
@Override
protected void copy(Set<String> source, Set<String> dest) {
dest.clear();
dest.addAll(source);
}
@Override
protected void flowThrough(Set<String> in, Unit unit, Set<String> out) {
out.clear();
out.addAll(in);
for (ValueBox box : unit.getDefBoxes()) {
Value value = box.getValue();
if (value instanceof Local) {
out.remove(((Local) value).getName());
}
}
for (ValueBox box : unit.getUseBoxes()){
Value value = box.getValue();
if (value instanceof Local){
out.add(((Local) value).getName());
}
}
}
}
public static class ConstantPropagation extends ForwardFlowAnalysis<Unit, Map<String, Object>> {
public ConstantPropagation(UnitGraph graph) {
super(graph);
doAnalysis();
}
@Override
protected Map<String, Object> newInitialFlow() {
return new HashMap<>();
}
@Override
protected Map<String, Object> entryInitialFlow() {
Map<String, Object> map = new HashMap<>();
List<Value> valueList = ((UnitGraph)graph).getBody().getParameterRefs();
for (Value value : valueList){
String className = ((RefType) value.getType()).getClassName();
if (className.equals("java.lang.Integer") || className.equals("java.lang.String")
|| className.equals("java.lang.Boolean") || className.equals("java.lang.Byte")
|| className.equals("java.lang.Character") || className.equals("java.lang.Double")
|| className.equals("java.lang.Float") || className.equals("java.lang.Long")
|| className.equals("java.lang.Short")){
map.put(value.toString(), value.toString().substring(0, 11));
}
}
return map;
}
@Override
protected void merge(Map<String, Object> in1, Map<String, Object> in2, Map<String, Object> out) {
for (Map.Entry<String, Object> entry1 : in1.entrySet()){
String parameter1 = entry1.getKey();
for (Map.Entry<String, Object> entry2 : in2.entrySet()){
String parameter2 = entry2.getKey();
if (parameter1.equals(parameter2)){
out.put(parameter1, "Constant");
}
}
}
}
@Override
protected void copy(Map<String, Object> source, Map<String, Object> dest) {
dest.clear();
dest.putAll(source);
}
@Override
protected void flowThrough(Map<String, Object> in, Unit unit, Map<String, Object> out) {
out.clear();
out.putAll(in);
boolean isConstant = false;
boolean isConstantValue = true;
boolean isParameter = false;
String para = null;
for (ValueBox box : unit.getUseBoxes()){
Value value = box.getValue();
// 判断 x = y - y 这种特殊情况
if (unit instanceof SubExpr && ((JSubExpr) unit).getOp1Box().getValue() == ((JSubExpr) unit).getOp2Box().getValue()){
isConstant = true;
break;
}
if (out.containsKey(value.toString()) && ((String)out.get(value.toString())).equals("NAC")){
isConstant = false;
break;
}
if (value instanceof Constant || (out.containsKey(value.toString()) && ((String)out.get(value.toString())).startsWith("@parameter"))){
isConstant = true;
if (out.containsKey(value.toString()) && ((String)out.get(value.toString())).equals("Constant")) isConstantValue = false;
if (out.containsKey(value.toString()) && ((String)out.get(value.toString())).startsWith("@parameter")){
isParameter = true;
para = ((String)out.get(value.toString())).substring(0, 11);
}
}
}
if (isConstant && isConstantValue && !isParameter){
int constantValue = 0;
// 单个赋值语句
if (unit instanceof JAssignStmt){
Value op = ((JAssignStmt)unit).getRightOp();
if (out.containsKey(op.toString())){
constantValue = (Integer)(out.get(op.toString()));
}else if (op instanceof Constant){
constantValue = ((IntConstant)op).value;
}
}
// 判断加法
if (unit instanceof AddExpr){
Value op1 = ((AddExpr) unit).getOp1();
Value op2 = ((AddExpr) unit).getOp2();
constantValue = ((IntConstant)op1).value + ((IntConstant)op2).value;
}
// 判断减法
if (unit instanceof SubExpr){
Value op1 = ((JSubExpr) unit).getOp1();
Value op2 = ((JSubExpr) unit).getOp2();
constantValue = ((IntConstant)op1).value - ((IntConstant)op2).value;
}
// 判断乘法
if (unit instanceof MulExpr){
Value op1 = ((MulExpr) unit).getOp1();
Value op2 = ((MulExpr) unit).getOp2();
constantValue = ((IntConstant)op1).value * ((IntConstant)op2).value;
}
// 判断除法
if (unit instanceof DivExpr){
Value op1 = ((DivExpr) unit).getOp1();
Value op2 = ((DivExpr) unit).getOp2();
constantValue = ((IntConstant)op1).value / ((IntConstant)op2).value;
}
// 判断取余
if (unit instanceof RemExpr){
Value op1 = ((RemExpr) unit).getOp1();
Value op2 = ((RemExpr) unit).getOp2();
constantValue = ((IntConstant)op1).value % ((IntConstant)op2).value;
}
// 判断与运算
if (unit instanceof AndExpr){
IntConstant op1 = (IntConstant) ((AndExpr) unit).getOp1();
IntConstant op2 = (IntConstant) ((AndExpr) unit).getOp2();
constantValue = op1.value & op2.value;
}
// 判断或运算
if (unit instanceof OrExpr){
IntConstant op1 = (IntConstant) ((OrExpr) unit).getOp1();
IntConstant op2 = (IntConstant) ((OrExpr) unit).getOp2();
constantValue = op1.value | op2.value;
}
// 判断异或运算
if (unit instanceof XorExpr){
IntConstant op1 = (IntConstant) ((XorExpr) unit).getOp1();
IntConstant op2 = (IntConstant) ((XorExpr) unit).getOp2();
constantValue = op1.value ^ op2.value;
}
// 判断左移
if (unit instanceof ShlExpr){
IntConstant op1 = (IntConstant) ((ShlExpr) unit).getOp1();
IntConstant op2 = (IntConstant) ((ShlExpr) unit).getOp2();
constantValue = op1.value << op2.value;
}
// 判断右移
if (unit instanceof ShrExpr){
IntConstant op1 = (IntConstant) ((ShrExpr) unit).getOp1();
IntConstant op2 = (IntConstant) ((ShrExpr) unit).getOp2();
constantValue = op1.value >> op2.value;
}
for (ValueBox box : unit.getDefBoxes()){
Value value = box.getValue();
out.put(value.toString(), constantValue);
}
}else if (isConstant && !isConstantValue){
for (ValueBox box : unit.getDefBoxes()){
Value value = box.getValue();
out.put(value.toString(), "Constant");
}
}else if (isConstant && isParameter){
for (ValueBox box : unit.getDefBoxes()){
Value value = box.getValue();
out.put(value.toString(), para);
}
}
else {
for (ValueBox box : unit.getDefBoxes()){
Value value = box.getValue();
out.put(value.toString(), "NAC");
}
}
}
}
}
FlowDroid
参考
https://segmentfault.com/a/1190000019977434
介绍
FlowDroid(github链接)是目前对 Android app 进行污点分析效果最好的工具之一,数据流算法是基于IFDS算法实现的。 污点分析的目的其实很简单,就是为了检查应用中是否存在从污点源到泄漏点的数据流。 但是它的优点在于它构建的数据流精度很高,可以对上下文、流、对象和字段敏感,从而使得分析结果非常精确。
FlowDroid的常规配置如下:
1 |
|
Soot中,CallGraph的算法选项主要包括:
- CHA(Call Hierarchy Analysis):默认的静态分析算法,基于类的继承关系分析方法调用;
- RTA(Rapid Type Analysis):基于CHA的分析算法,会预先分析虚函数表,提高精度;
- VTA(Virtual Call Target Analysis):基于RTA的分析算法,会分析方法内部的虚函数调用,进一步提高精度;
- Spark:另一种基于CHA的算法,相对于CHA,Spark的性能更高,但精度可能会降低。
区别主要体现在精度和性能上,CHA是最基本的分析算法,精度较低,但相对而言,性能较高,适用于大型的代码库;而RTA和VTA在提高精度的同时,也增加了分析的时间成本,适用于小型的代码库或者需要更精确的分析场景;Spark在精度和性能上都相对于CHA有一定的提升,但精度仍然不及RTA和VTA。因此,在选择算法时需要根据具体的分析场景来决定使用哪种算法。
1 |
|
污点分析
做污点分析的配置如下:
1 |
|
- AccessPath 是程序中指向内存位置的一条路径。例如一个Java对象的字段可以被表示为对象的引用AccessPath的一部分。AccessPath的一些常见例子包括用于追踪对象属性的“this.field”、“array[index]”等等。
- Abstraction 是指在程序分析中,为了简化程序,将某些复杂的细节隐藏在某些更抽象的形式下。例如,可以将整个对象视为单个抽象值,而不考虑它的具体结构和属性。在这种情况下,抽象表示仅仅是一个值或一些值的集合,它们代表了一些具体的值的集合。这种简化可以加速程序分析的速度,但会牺牲一些准确性。
流程
- 进行FlowDroid的配置,需要加载SourcesAndSinks.txt、AndroidCallbacks.txt文件
- 对Soot进行配置初始化,需要加载CG配置
- 构建一个虚拟的main方法,添加到入口点(entrypoint)
- 遍历entrypoint收集回调函数
- 有可能还需要进入xml文件中做进一步的回调函数的分析
- 将SourceAndSink.txt文件中包含的source、sink点,封装到sourceSinkManager中
- 生成CG图,并以此生成ICFG图
- 接下来基于CG、ICFG,计算存在的source、sink
- 确认source到sink间是否存在连通的数据路径,如果存在则认为是一个风险点
- 最终输出结果
jimple
Jimple 是 soot 中最主要的中间表示,是一种三地址码(三地址码 Three-Address Code 也可简写为3AC,要求:指令右侧至多有一个操作符。)只需要大约15种不同的指令,例如:
1 |
|
使用 r0,r1,r2….表示类型对象;i0,i1,i2…..表示基本数据类型变量;带有 $ 表示临时变量,如上面的 t2 的含义;@parameter 表示函数参数。
1 |
|
其他部分与 smali 挺相像的,并且是很好理解的,直接看是十分容易明白的。直接看下面的例子:
1 |
|
在 jimple 代码中,开头是一个叫 Loop 的类继承了 java.lang.Object 类(默认的所有类的父类),然后是一个初始化的过程,生成默认的构造函数,默认会调用父类的构造函数(即 java.lang.Object ),接下来就是 main 函数,在源代码里 main 函数有一个 String[] args 的参数,这在 jimple 代码中就对应了一个声明的参数 r0,源代码中 for 循环里面的 i 在 jimple 代码中用 i1 指代,label1 里面的内容就是 for 循环的条件内容,只要不满足循环条件,用一个 goto 语句跳转到 label2。这里出现了一个有意思的现象,那就是源代码中 x 值的变化在 jimple 中被“优化”掉了,这是因为 x 是不活跃变量,所以会被优化。
do-while循环
1 |
|
这里是给数据对象 arr 用 r0 来代替,并对它进行了初始化,接下来还是用 label 表示程序的位置,将每一次循环的条件都能表示出来。可以注意到:Do-While 循环是先进入循环执行对应的语句,再通过 if 语句进行循环的跳转。
Switch语句
1 |
|
在此Jimple代码示例中,switch 语句被转换为lookupswitch(或者tableswitch)指令,后面跟随着各个case标签的跳转指令。每个case标签下面的代码表示对应case的操作。在这个例子中,每个case都会调用System.out.println()来输出相应的字符串。
- lookupswitch是根据一个键值来匹配分支,这些键值是离散的,不一定是连续的整数。
- tableswitch则是基于一个偏移量来进行分支匹配,分支的键值必须是连续的整数。
方法调用
1 |
|
可以看到我们定义的方法 foo 在源代码和 jimple 源码中差不多,很好理解,就是用了一个中间变量来取得 i0 和 i1 的和,再将这个中间变量 $i2 返回。在 main 函数中使用 staticinvoke 表示静态方法的调用。
- 本文作者:ShouCheng
- 本文链接:http://shoucheng3.github.io/2023/03/24/2023-03-24-soot-And-jimple/index.html
- 版权声明:本博客所有文章均采用 BY-NC-SA 许可协议,转载请注明出处!