 
  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 许可协议,转载请注明出处!