lab2 soot
lab2 soot静态分析
- task1 活跃变量分析
- task2 死代码检测
lab2 task1 活跃变量分析
soot环境搭建
soot是什么
soot是一个字节码分析工具,它输入编译好的java字节码(class文件),并提高到中间语言“jimple”。
我一开始以为soot就是一个exec了,可以直接在命令行中soot -[options] file。但并非如此,soot是一整套工具,像一个工具箱,可以在项目中import它。soot也可以以jar包的形式出现,在命令行中使用。
获得soot
从github上获得源码即可,https://github.com/soot-oss/soot/
我获得的版本是4.6.0,将它放在~/lab2/soot-4.6.0
用maven管理soot
soot支持用maven构建,可以用maven把soot当作依赖引入项目,然后就能调用soot的工具了。可以直接用jar包安装soot,不过我在lab1时先尝试了自行构建,记录如下。
应该可以直接在IDEA中构建soot,在maven -> lifecycle -> install。但是不知为何没有成功,似乎是卡住了什么测试用例。于是在命令行中安装。
cd ~/lab1/soot-4.6.0
mvn clean install
ls ~/.m2/repository/org/soot-oss
按理说这就可以了,但是我之前由于有过限定maven版本和路径的经历,导致mvn的种种配置十分混乱。虽然输出了Build Success,但是根本找不到soot在哪。尽管如此,soot/target中仍然生成了jar包。所以接下来手动安装它
mvn install:install-file \
-Dfile=target/sootclasses-trunk.jar \
-DgroupId=org.soot-oss \
-DartifactId=soot \
-Dversion=4.6.0 \
-Dpackaging=jar
还是Build Success之后~/.m2里什么都没有,折腾了半天,发现控制台输出了安装路径,给安装到之前毕设项目的依赖目录里了。把soot拷贝进~/.m2对应的位置就可以了。所以说还是得看看控制台到底输出了什么,不能字多就不看。
cp -r ~/IdeaProjects/GP1/repo/org/soot-oss ~/.m2/repository/org/soot-oss
现在终于可以愉快地用maven管理soot了。只需要在pom.xml中引入依赖
<dependencies>
<dependency>
<groupId>org.soot-oss</groupId>
<artifactId>soot</artifactId>
<version>4.6.0</version>
</dependency>
</dependencies>
maven刷新一下,就可以import soot.xxx了。
构建测试用例
IDEA新建一个java项目lab2,注意jdk版本别太高,因为soot似乎只支持到java11。我一开始无脑选了1.8,但是后面报错了“类文件具有错误的版本 55.0, 应为 52.0”,所以还是得java11。添加soot依赖后,就完成了准备工作。
soot分析活跃变量
项目结构
使用/src/main/java/task1包作为分析的目录。SootInit.java初始化soot并作为入口,LiveVariables.java实现分析活跃变量的功能。
初始化soot
首先,需要指定soot分析的主类和工作的目录,以及想要分析的方法名。
String className = "lab3";
String classPath = "/Users/anpoliros/lab2/target";
String methodSignature = "void func1(int,int)";
两个注意事项,一是className不要加上.class后缀,这个typo很让人无语;二是不知何故lab2给的class文件的文件名和主类名不一致,需要把文件名改为lab3.class。一开始没有反编译,导致soot根本加载不了任何类,浪费了不少时间。
需要运行前指定的变量就这两个。除此之外还需要设置soot选项,而且为了debug我还加入了一段print功能,打印所有加载进来的类。这之后就可以调用LiveVariables的方法来分析活跃变量了。
soot框架机制
在 soot 中,每个方法会被转化为一个控制流图 UnitGraph,节点是 Jimple 中的语句(Unit),边是“下一条可能执行语句”。要分析活跃变量,需要进行逆向数据流分析,而soot提供了逆向数据流分析的框架BackwardFlowAnalysis<Unit, Set<Local>>。其中
Unit代表一个Jimple语句,也就是控制流图的节点Set<Local>是活跃变量的集合,也就是这个语句处的数据流状态
另外,soot提供了框架自动调用机制。BackwardFlowAnalysis是一个框架类,其核心逻辑封装在doAnalysis方法中,我们需要做的就是实现一些方法,然后让它来调用并执行逆向分析。
public abstract class BackwardFlowAnalysis<N, A> extends FlowAnalysis<N, A> {
public void doAnalysis() {
// 自动迭代所有节点
// 调用 flowThrough, merge, copy 等函数
}
}
public class LiveVariables extends BackwardFlowAnalysis<Unit, Set<Local>>{
public LiveVariables(UnitGraph graph) {
super(graph);
doAnalysis();
}
@Override
protected void flowThrough(Set<Local> out, Unit unit, Set<Local> in){}
}
总的来说,soot框架做了如下的事情:
- 从所有出口节点开始进行逆向分析
- 初始化每个节点的 in/out 状态为默认值(通过 newInitialFlow())
- 重复迭代节点:
- 调用 flowThrough 计算 in/out
- 判断集合是否有变化
- 直到所有 in/out 集合不再变化(达到“固定点”)
flowThrough
LiveVariables的核心逻辑在flowThrough中实现。我们的目标是,已知语句 s 的 out[s](执行后活跃变量),要推导出 in[s](执行前活跃变量)。而soot会分析出定义变量集合def[s]和使用变量集合use[s],所以核心的公式就是in[s] = (out[s] - def[s]) ∪ use[s]。
flowThrough的结构大致如下:
- 输入:入集in[s],出集out[s],语句单元s
- in[s] = (out[s] - def[s]) ∪ use[s]
- 将in/out保存在map中,两个map分别保存每条语句s前后的活跃变量集合
实现
SootInit.java
package task1;
import soot.*;
import soot.options.Options;
import soot.toolkits.graph.BriefUnitGraph;
import soot.jimple.JimpleBody;
import task1.LiveVariables;
public class SootInit {
public static void main(String[] args) {
String className = "lab3";
String classPath = "/Users/anpoliros/lab2/target";
// 1. 设置Soot基本选项
G.reset();
Options.v().set_prepend_classpath(true);
Options.v().set_soot_classpath(classPath + ":" + Scene.v().defaultClassPath());
Options.v().set_output_format(Options.output_format_none); // 不生成输出文件
Options.v().set_allow_phantom_refs(true);
Options.v().set_whole_program(true);
Options.v().set_keep_line_number(true);
// 2. 加载目标类
SootClass sClass = Scene.v().loadClassAndSupport(className);
sClass.setApplicationClass();
Scene.v().loadNecessaryClasses();
for (SootMethod m : sClass.getMethods()) {
System.out.println("FOUND: " + m.getSignature());
}//验证
System.out.println("-----LOAD FINISHED-----");
// 3. 遍历所有方法
for (SootMethod method : sClass.getMethods()) {
if (!method.isConcrete()) continue; // 跳过抽象或native方法
System.out.println("🟦🟦🟦Analyzing: " + method.getSignature());
try {
Body body = method.retrieveActiveBody();
// 4. 构建控制流图
BriefUnitGraph cfg = new BriefUnitGraph((JimpleBody) body);
// 5. 执行活跃变量分析
LiveVariables analysis = new LiveVariables(cfg);
// 6. 打印结果
analysis.printResults();
System.out.println("--------------------------------");
} catch (Exception e) {
System.err.println("Failed to analyze " + method.getSignature() + ": " + e.getMessage());
}
}
}
}
LiveVariables.java
package task1;
import soot.Local;
import soot.Unit;
import soot.toolkits.graph.UnitGraph;
import soot.toolkits.scalar.BackwardFlowAnalysis;
import java.util.*;
public class LiveVariables extends BackwardFlowAnalysis<Unit, Set<Local>> {
private final Map<Unit, Set<Local>> unitToInSet = new LinkedHashMap<>();
private final Map<Unit, Set<Local>> unitToOutSet = new LinkedHashMap<>();
public LiveVariables(UnitGraph graph) {
super(graph);
doAnalysis();
}
@Override
protected void flowThrough(Set<Local> out, Unit unit, Set<Local> in) {
// in[s] = (out[s] - def[s]) ∪ use[s]
Set<Local> uses = new HashSet<>(unit.getUseBoxes().size());
Set<Local> defs = new HashSet<>(unit.getDefBoxes().size());
unit.getUseBoxes().forEach(vb -> {
if (vb.getValue() instanceof Local) {
uses.add((Local) vb.getValue());
}
});
unit.getDefBoxes().forEach(vb -> {
if (vb.getValue() instanceof Local) {
defs.add((Local) vb.getValue());
}
});
Set<Local> temp = new HashSet<>(out);
temp.removeAll(defs);
temp.addAll(uses);
in.clear();
in.addAll(temp);
// 保存 in/out
unitToInSet.put(unit, new HashSet<>(in));
unitToOutSet.put(unit, new HashSet<>(out));
}
@Override
protected Set<Local> newInitialFlow() {
return new HashSet<>();
}
@Override
protected Set<Local> entryInitialFlow() {
return new HashSet<>();
}
@Override
protected void merge(Set<Local> in1, Set<Local> in2, Set<Local> out) {
out.clear();
out.addAll(in1);
out.addAll(in2);
}
@Override
protected void copy(Set<Local> source, Set<Local> dest) {
dest.clear();
dest.addAll(source);
}
public void printResults() {
for (Unit unit : graph) {
System.out.println("--------------------------------------------------");
System.out.println("Before: " + unitToInSet.get(unit));
System.out.println("Stmt: " + unit);
System.out.println("After: " + unitToOutSet.get(unit));
}
}
}
效果和验证
执行效果
执行SootInit,效果如图。


验证
反编译原class文件,得到
public class lab3 {
public lab3() {
}
public static void main(String[] var0) {
int var1 = func();
func1(var1, var1 + 10);
}
public static int func() {
byte var0 = 5;
byte var2 = 6;
boolean var3 = true;
byte var4 = 0;
int var5;
if (var0 > 0) {
var5 = var0 + var2;
} else {
var5 = var0 + 4;
}
int var10000 = var5 + var4;
return var5 + var4;
}
public static void func1(int var0, int var1) {
int var5 = var0 - 1;
int var6 = var1;
int[] var7 = new int[var1 + 1];
int var8 = var7[var1];
for(int var9 = 0; var9 <= var1; ++var9) {
var7[var9] = var9 * 3;
}
while(true) {
do {
++var5;
} while(var7[var5] < var8);
do {
--var6;
} while(var7[var6] < var8);
int var2;
if (var5 >= var6) {
var2 = var7[var5];
var7[var5] = var7[var1];
var7[var1] = var2;
return;
}
var2 = var7[var5];
var7[var5] = var7[var6];
var7[var6] = var2;
}
}
}
肉眼观察发现soot分析得没问题。
lab2 task2 死代码检测
准备工作
现在我们只有一个apk,首先人工看看它是什么。
安装进虚拟机
先启动虚拟机,直接install它试试看。
asemu -avd Small_Phone
cd ~/lab2
adb install ./lab2.apk
发现它就是个hello world,啥都没有。
jadx解包
jadx可以得到java文件,虽然会有一些细节的损失。
jadx -d upk-jadx lab2.apk
然后用Android Studio打开这个项目,可以看到源代码,包名com.example.lab_code。发现除了hello world还有一些用户管理和奇怪的比较,同时还有log写入。肉眼可见有不少死代码。

apktool解包
apktool解包的精确度更高,但是smali不好阅读。不过可以得到AndroidManifest.xml,也非常有用,可以找到入口类和申请权限等等。
apktool d lab2.apk -o upk-apktool
这里其实埋下了一个伏笔,就是我并没有仔细观察解包出来的项目结构,后面会说。
让分析器跑起来
新建一个项目,添加soot依赖。soot内置了apk分析的工具,可以在初始化时加载apk和android.jar
String apkPath = "/Users/anpoliros/lab2/lab2.apk"; // apk
String androidJarPath = "/Users/anpoliros/lab2"; // android.jar
G.reset();
Options.v().set_src_prec(Options.src_prec_apk);
Options.v().set_process_dir(Collections.singletonList(apkPath));
Options.v().set_android_jars(androidJarPath);
Options.v().set_force_android_jar(androidJarPath + "/android.jar");
这个android.jar在安卓sdk里,我把它拿出来了,不过似乎没什么必要。
直接扫描
非常自然的想法就是直接这样扫描。加一段自动扫描入口点的代码,例如onCreate等。找到入口点后挨个分析,输出检测到的死代码的类。然而,确实扫描出东西了,但是全是安卓自带的库,输出非常多,却一个包含com.example的都没有。

合并dex
经过一番debug,也毫无进展。这时注意到jadx解出来的代码中有
/* loaded from: classes3.dex */
这也许是因为soot只加载apk中的classes.dex导致的。所以我们之前的解包看来还是有问题,没有注意到有三个smali目录。
unzip lab2.apk -d upk-unzip
果然,里面有三个dex文件。使用sdk带的d8把它们合并。
cd upk-unzip
mkdir merge
d8 classes.dex classes2.dex classes3.dex --output merge
重新打包apk
我试图直接用soot分析这个dex,但是老是报错,干脆直接重新打包成apk。现在apktool解包出来有三个smali目录,这就对应的三个dex。只需要简单地把这三个目录合并成一个就可以了。然后重新打包。
apktool b . o lab2_merged.apk
这样之后不需要签名,把soot中的apk路径改成这个就可以了。这次soot终于找到了com.example,成功打印出了包含死代码的类。

经过对比,发现这几个类中确实有死代码。
实现
SootAPK.java
package task2;
import soot.*;
import soot.options.Options;
import soot.jimple.toolkits.callgraph.CallGraph;
import soot.jimple.toolkits.callgraph.Edge;
import java.util.*;
public class SootAPK {
public static void main(String[] args) {
String apkPath = "/Users/anpoliros/lab2/lab2_merged.apk";
String androidJarPath = "/Users/anpoliros/lab2";
// ---- 初始化soot ----
G.reset();
Options.v().set_src_prec(Options.src_prec_apk);
Options.v().set_process_dir(Collections.singletonList(apkPath));
Options.v().set_android_jars(androidJarPath);
Options.v().set_force_android_jar(androidJarPath + "/android.jar");
Options.v().set_output_format(Options.output_format_none);
Options.v().set_whole_program(true);
Options.v().set_allow_phantom_refs(true);
Options.v().setPhaseOption("cg.spark", "on");// 启用Spark
Scene.v().loadNecessaryClasses();
System.out.println("[+] Soot Initialized");
// ---- 分析入口点 ----
List<SootMethod> entryPoints = new ArrayList<>();
for (SootClass sc : Scene.v().getApplicationClasses()) {
for (SootMethod sm : sc.getMethods()) {
String name = sm.getName();
if (name.equals("onCreate") || name.equals("onStartCommand") ||
name.equals("onReceive") || name.equals("doInBackground")) {
if (sm.isConcrete()) {
entryPoints.add(sm);
System.out.println("[Auto EntryPoint] " + sm.getSignature());
}
}
}
}
Scene.v().setEntryPoints(entryPoints);
// 运行soot
PackManager.v().runPacks();
// --- 构造调用图 ---
CallGraph cg = Scene.v().getCallGraph();
Set<SootMethod> reachable = new HashSet<>();
for (Edge edge : cg) {
reachable.add(edge.getTgt().method());
}
// --- 输出筛选后的死代码 ---
System.out.println("\n=== Dead Code in com.example.lab_code ===");
int count = 0;
for (SootClass sc : Scene.v().getApplicationClasses()) {
if (!sc.getName().startsWith("com.example.lab_code")) continue;
for (SootMethod sm : sc.getMethods()) {
if (!reachable.contains(sm)) {
String methodName = sm.getName();
if (methodName.equals("<init>") || methodName.equals("toString") || methodName.equals("hashCode"))
continue; // 忽略构造器、toString等
System.out.println("[DeadCode] " + sm.getSignature());
count++;
}
}
}
System.out.println("\n[+] Total Dead Methods in com.example.lab_code: " + count);
}
}
更精确的分析
上面的工作虽然让分析器跑起来了,但是存在两个问题:
- 无法识别出具体的死代码类型
- 经过验证,存在误报漏报问题
死代码分类
我们主要识别三种死代码,并用颜色标识:
- 🟥方法未被调用,仅声明未使用的方法
- 🟨不可达代码,包括以下两种
- 分支不可达:
if(false) xxx; - 控制流不可达:
xxx; return; xxx;
- 分支不可达:
- 🟦无用赋值,赋值后从未使用过的变量
在这个环节,需要注意的是soot并不能识别出分支不可达的代码,例如
if (index.intValue() > 10) {
Log.d("Exec1", index.toString());
} else {
Log.d("Exec2", index.toString());
}
这属于更高级的静态值分析,soot默认功能无法实现。所以接下来的不可达代码仅包含控制流不可达的类型。
分类识别算法
🟥未调用方法,原理基于调用图(Call Graph)分析,找出程序中未被任何路径调用的方法。
- Soot 构建全程序调用图(Call Graph),表示方法之间的调用关系。
- 从一个或多个入口点(如 main()、onCreate())出发,进行深度或广度遍历。
- 如果某个方法在调用图中不可达,说明它从未被任何地方调用,即为死代码方法。
🟨不可达代码,原理基于控制流图(Control Flow Graph, CFG)分析,识别从入口点永远到达不了的代码块。
- Soot 使用 Jimple 生成方法的 CFG,表示基本块之间的跳转关系。
- 从入口语句(如第一条指令)出发,执行图遍历(如 BFS)。
- 如果某些语句或代码块在图中不可达,说明它们是永远不会被执行的。
🟦无用赋值,原理:基于活跃变量分析(Live Variable Analysis),识别赋值后从未被使用的局部变量。这和task1的实现异曲同工。
- 对方法的Jimple构建控制流图(CFG)。
- 使用反向数据流分析,计算每条语句之后哪些变量是“活跃”的(即未来会被读取)。
- 如果某条语句为变量赋值,但该变量在之后从未被读取,说明该赋值是无效的,即死赋值。
项目结构
由于功能复杂度提升了,前述的单文件探测器已经不能满足要求。采用三个文件来实现:
- Detector 程序入口,设置基本参数,构建调用图
- SootInit 初始化soot,并自动寻找入口点
- Analysis 分析模块,对三种情况分别分析
误报漏报问题
经过改进,分析器再次跑起来了。然而,误报问题依然存在。对于待测代码中的void pathTest(Integer index),明明没有无用赋值却报有。而我由基于原有代码新建了一个测试项目,加入了int a=0这种明显的无用赋值,却又识别不出来。为了debug,加入了一段打印Jimple的代码,终于搞清楚了问题所在。
误报无用赋值的原因在于
r0 := @this: com.example.lab_code.MainActivity
它的含义是,将当前对象引用 this 赋值给局部变量 r0,也就是 Java 中隐式存在的 this 被显式映射到 Jimple 中的变量。所以对this的显式赋值被误报成了无用变量。对于这类的误报,我的处理手段就是直接在后面加上对应的Jimple语句,毕竟也好认出。
漏报无用赋值的原因在于,这种显式的无用赋值会被编译优化掉,Jimple在生成过程中有内建的死局部变量消除。下面展示了Java源代码和Jimple的区别,可以看到Jimple中完全没有int a了。
public void assign_test(){
int a = 0;
}
Jimple of ‹com.anpoliros. lab2test.MainActivity: void assign_test()>:
public void assign_test() {
com.anpoliros.lab2test.MainActivity r0;
r0 := @this: com.anpoliros.lab2test.MainActivity;
return;
}
如果想要识别出这种无用赋值,就需要直接分析Java源代码或者class文件,不过因为附上了this赋值语句所以挺有辨识度的,就不额外剔除了。
效果

可以看到,分析器正确地检测出了这两种死代码。
分支不可达的检测
对于方法未调用和无用赋值的检测其实比较简单也比较直观,控制流不可达也好识别,但是分支不可达却非常难。分支不可达又可以分为两个类型(个人理解):
- 参数上下文型,分支不可达仅涉及到过程内
- 传参型,分支不可达涉及到过程间
参数上下文型
Integer index = 8;
if (index.intValue() > 10) {
Log.d("Exec1", index.toString());
} else {
Log.d("Exec2", index.toString());
}
这种比较好识别,因为在cfg中控制流总体还是线性的,变量没有改过名字,追踪常量就可以发现有些语句到达不了。
另外还需要注意的是这里面的Integer index的声明方式。如果这样声明变量,相当于初始化了一个包装类,和int index是不同的。这就要求分析器能够进行拆箱操作,找出常量的传播线索。
参数上下文型的检测
为了找到这种死代码,需要实现一个恒值条件分析器。主要算法如下:
- 遍历方法中的所有 IfStmt(条件跳转语句)
- 对每个条件表达式,尝试获取其两个操作数的常量值
- 如果两个操作数均为常量,计算条件结果:
- 若恒为真:报告为 [恒为真分支]
- 若恒为假:报告为 [恒为假分支 => 不可达]
还需要一个辅助方法执行简易常量传播,收集形如 a = 8 或 a = Integer.valueOf(8) 等对变量赋定值的语句。支持识别的语句类型:
- a = 8 (常量赋值)
- a = Integer.valueOf(8)(自动装箱)
- a = new Integer(8); a.init(8)(手动装箱)
- int a = index.intValue();(拆箱)
- a = b 且 b 是已知常量(变量传递)
- 遇到非常量表达式赋值时,丢弃原有常量信息
传参型
public void onCreate(){
//...
pathTest(12);
}
public void pathTest(Integer index) {
if (index.intValue() > 10) {
Log.d("Inter Exec1", index.toString());
} else {
Log.d("Inter Exec2", index.toString());
}
}
这就涉及到传参的识别,难度又上了一个台阶,因为参数在传递过程中在Jimple/smali级别发生了名字上的变化。让我们看看这个传参过程的Jimple代码就能明白了。这段代码十分清晰地展示了变量名的变化。
$r3 = staticinvoke <java.lang.Integer: java.lang.Integer valueOf(int)>(12);
virtualinvoke r0.<com.example.lab_code.MainActivity: void pathTest(java.lang.Integer)>($r3);
public void pathTest(java.lang.Integer)
{
//...
int $i0;
$r1 := @parameter0: java.lang.Integer;
$i0 = virtualinvoke $r1.<java.lang.Integer: int intValue()>();
if $i0 <= 10 goto label1;
//...
}
传参型的检测
传参问题的检测实现起来复杂一些,需要用cg.spark来准确分析调用中发生的事情,实现一个基于调用图与实参恒定传播分析的恒值分支检测器。
一个首先需要解决的问题是:虽然 $r1 是参数且恒值为 12,但在 CFG 中判断的是 $i0,我们要让 Soot知道 $i0 == 12,而不是 $r1 == 12。这就涉及到常量传播,需要在分析时增加一段捕获常量的代码
Map<Local, Integer> propagatedConsts = new HashMap<>(constMap);
for (Unit u : body.getUnits()) {
if (u instanceof AssignStmt assign &&
assign.getLeftOp() instanceof Local left &&
assign.getRightOp() instanceof VirtualInvokeExpr virt &&
virt.getMethod().getSignature().equals("<java.lang.Integer: int intValue()>")) {
Value base = virt.getBase();
if (base instanceof Local baseLocal && constMap.containsKey(baseLocal)) {
propagatedConsts.put(left, constMap.get(baseLocal));
}
}
}
整体上,算法分为两个部分:
Step 1:遍历调用图CallGraph收集实参
- 遍历 CallGraph 中所有显式边(edge.isExplicit());
- 对每个被调用方法 callee,提取调用语句中的实参;
- 若该实参在 caller 方法体中可以还原为 IntConstant,则记录为恒定参数;
- 将每个 callee 方法的参数映射为 Map<Integer, Value>(参数索引 → 恒定值),存入 methodArgConstMap;
- 排除不是恒值的情况(出现多个不同值或有一个非常量),标记为 null。
Step 2:对具有恒参的方法做条件分支分析
- 匹配参数:
- 在被调方法体中,找到所有 IdentityStmt;
- 如果它是形如 param0 = @parameter0 且在调用图中已有恒值映射,则记录为参数恒值。
- 传播拆箱结果:
- 支持 int x = param0.intValue(); 的情况;
- 将其对应的常量继续传播。
- 不可达分支检测:
- 利用 SootIfConditionAdapter 适配器+ BranchUnreachableDetector 工具类;
- 结合控制流图 BriefUnitGraph;
- 根据已知的参数恒值判断条件跳转是否永远不成立;
- 打印所有检测到的不可达 if 分支语句。
SootInit的调整
我在传参型的实现中浪费了大量时间debug,分析器可以找到cg边但就是无法找到参数传递的过程。具体体现为,可以识别出类之间的调用,但无法识别到类内部的调用。例如,可以发现MainActivity对User的调用,但无法发现onCreate对pathTest的调用。最后发现这个问题是SootInit中初始化的问题导致的。
首先,要明确把app包设为ApplicationClass。这是因为Soot 只对 application classes 内部方法做深度分析,library class 会被跳过。
for (SootClass sc : Scene.v().getApplicationClasses()) {
if (sc.getName().startsWith("com.example.lab_code")) {
sc.setApplicationClass();
}
}
其次,要强制启用精确调用图。
Options.v().set_whole_program(true);
Options.v().setPhaseOption("cg.spark", "on");
Options.v().setPhaseOption("cg", "safe-newinstance:true");
Options.v().setPhaseOption("cg.cha", "on");
加上这两个选项后,soot识别到的cg边从2000+增长到了260000+,足以说明问题。
整合实现
在之前工作的基础上,把ConstantBranch和ArgumentBranch整合进Detector中,同时设置一些bool来开关各个功能。
Detector.java的结构:
- 入口参数设定
- apkPath
- androidJarPath
- filter 包名过滤器
- 功能开关设定
- printJimple 是否打印Jimple
- detectBasic
- detectConstantBranch
- detectArgumentBranch
- soot初始化(调用SootInit.init)
- 构造调用图cg
- 功能调用
- Analysis 方法未调用、无用赋值、控制流不可达
- ConstantBranch 参数上下文型分支不可达(过程内)
- ArgumentBranch 传参型分支不可达(过程间)
参数上下文型的检测结果

传参型的检测结果

