Soot支持阅读和重写Dalvik字节码。主要支持两个模块:一个是Dexpler,由Alexandre Bartel团队开发,并由Ben Bellamy,Eric Bodden等人扩展加强。Dexpler可以将Dalvik字节码转换成Jimple这种三地址码。
如何插桩:
首先获取soot的最新版本,例如nightly build。在点击打开链接目录中,包含Android不同的标准库,soot需要利用标准库解析待分析/插桩的应用程序中的类型。接着我们事先一个驱动类(driver class),这个驱动类中有一个主函数,代码如下:
//prefer Android APK files // -src-prec apk
Options.v().set_src_prec(Options.src_prec_apk);
//output as APK,too//-f J
Options.v().set_output_format(Options.output_format_dex);
//resolve the PrintStream and System soot-clases
Scene.v().addBasicClass(“java.io.PrintStream”,SootClass.SIGNATURES);
Scene.v().addBasicClass(“java.lang.System”,SootClass,SIGNATURES);
</span>
第1个Options指示soot加载Android APK文件(要分析的文件类型);第2个Options指示soot生成dex/apk文件作为输出文件。(理论上可以将Java转换为Dex或将Dex转换成Java等)。最后两个Options是让soot加载java.io.PrintStream和java.lang.System这两个类,根据插桩的目的(插入System.out.println("HELLO")),决定加载的类,这两个类在插桩System.out.println(“Hello”),所以需要加载,否则不需要。
下一步,向soot中加入Transform:
PackManager.v().getPack("jtp").add(new Transform("jtp.myInstrumenter", new BodyTransformer() {
@Override
protected void internalTransform(final Body b, String phaseName, @SuppressWarnings("rawtypes") Map options) {
final PatchingChain units = b.getUnits();
//important to use snapshotIterator here
for(Iterator iter = units.snapshotIterator(); iter.hasNext();) {
final Unit u = iter.next();
u.apply(new AbstractStmtSwitch() {
public void caseInvokeStmt(InvokeStmt stmt) {
//code here
}
});
}
}
}));
上述代码将遍历apk中所有bodies的所有units,在每一个InvokeStmt(调用语句)处调用写在上述的code here处的代码,code here中的代码如下:
InvokeExpr invokeExpr = stmt.getInvokeExpr();
if(invokeExpr.getMethod().getName().equals("onDraw")) {
Local tmpRef = addTmpRef(b);
Local tmpString = addTmpString(b);
// insert "tmpRef = java.lang.System.out;"
units.insertBefore(Jimple.v().newAssignStmt(
tmpRef, Jimple.v().newStaticFieldRef(
Scene.v().getField("").makeRef())), u);
// insert "tmpLong = 'HELLO';"
units.insertBefore(Jimple.v().newAssignStmt(tmpString,
StringConstant.v("HELLO")), u);
// insert "tmpRef.println(tmpString);"
SootMethod toCall = Scene.v().getSootClass("java.io.PrintStream").getMethod("void println(java.lang.String)");
units.insertBefore(Jimple.v().newInvokeStmt(
Jimple.v().newVirtualInvokeExpr(tmpRef, toCall.makeRef(), tmpString)), u);
//check that we did not mess up the Jimple
b.validate();
}
这个将使soot在调用方法为onDraw语句前插入System.out.println(“HELLO”)代码。
最后,不要忘记调用soot的主函数:
soot.Main.main(args);
很简单,不是吗?下面你需要使用下面参数运行你的驱动类(driver class)。
-android-jars path/to/android-platforms
–process-dir your.apk
其中,path/to/android-platforms是你之前下载的AndroidJAR文件,your.apk则是你需要插桩的apk。-process-dir选项告诉soot处理apk中的所有类。程序最终的结果是在目录./sootOutput下生成一个与你处理apk名字一样的apk。
下面是译者针对完整代码,做的一个较为详细的代码注释:
/* Soot - a J*va Optimization Framework
* Copyright (C) 2008 Eric Bodden
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
import java.util.Iterator;
import java.util.Map;
import soot.Body;
import soot.BodyTransformer;
import soot.G;
import soot.Local;
import soot.PackManager;
import soot.PatchingChain;
import soot.RefType;
import soot.Scene;
import soot.SootClass;
import soot.SootMethod;
import soot.Transform;
import soot.Unit;
import soot.jimple.AbstractStmtSwitch;
import soot.jimple.AssignStmt;
import soot.jimple.InvokeExpr;
import soot.jimple.InvokeStmt;
import soot.jimple.Jimple;
import soot.jimple.StringConstant;
import soot.options.Options;
import soot.toolkits.graph.ExceptionalUnitGraph;
public class MyMain {
public static void main(String[] args) {
//设置处理的文件类型:src_prec_apk表示处理的是Android apk
Options.v().set_src_prec(Options.src_prec_apk);
//设置输出的文件类型:output_format_dex表示输出的文件的apk/dex,将会输出在工程目录下的sootOutput目录
Options.v().set_output_format(Options.output_format_jimple);
//设置soot的soot-class路径
Options.v().set_soot_classpath("G:\\Soot\\sdk\\platforms\\android-17\\android.jar;C:\\Program Files\\Java\\jre7\\lib\\rt.jar;C:\\Program Files\\Java\\jre7\\lib\\jce.jar;G:\\李政桥\\工具\\android相关工具\\soot-trunk.jar");
//加载java.io.PrintStream和java.lang.System
//java.lang.System中存在字段static PrintStream out
//java.io.PrintStream中存在方法void println(java.lang.String)
//插入的代码是:System.out.println("HELLO");
Scene.v().addBasicClass("java.io.PrintStream",SootClass.SIGNATURES);
Scene.v().addBasicClass("java.lang.System",SootClass.SIGNATURES);
PackManager.v().getPack("jtp").add(
new Transform("jtp.myTransform", new BodyTransformer() {
protected void internalTransform(final Body body, String phase, Map options) {
//Body带代表一个method body;
//Body中存在三个基本元素:locals、statements、exceptions,即:getLocals();getUnits();getTraps()
final PatchingChain units = body.getUnits();
//important to use snapshotIterator here
for(Iterator iter = units.snapshotIterator();iter.hasNext();){
final Unit u = (Unit) iter.next();
u.apply(new AbstractStmtSwitch(){
//如果u是一个调用语句的时候,类似:staticinvoke <com.example.source_a.MainActivity: int add4(int,int)>($i0, $i1);
public void caseInvokeStmt(InvokeStmt stmt){
InvokeExpr invokeExpr = stmt.getInvokeExpr();
//该调用语句中,如果被调用的方法是add时
if(invokeExpr.getMethod().getName().equals("add")){
Local tmpRef = addTmpRef(body);
Local tmpString = addTmpString(body);
//插入:tmpRef = <java.lang.System: java.io.PrintStream out>;类似于类的实例化
//newAssignStmt()为赋值语句
units.insertBefore(Jimple.v().newAssignStmt(tmpRef, Jimple.v().newStaticFieldRef(
Scene.v().getField("<java.lang.System: java.io.PrintStream out>").makeRef())), u);
//插入:tmpLong = "HELLO";
units.insertBefore(Jimple.v().newAssignStmt(tmpString,
StringConstant.v("HELLO")), u);
//insert "tmpRef.println(tmpString);"
SootMethod toCall = Scene.v().getSootClass("java.io.PrintStream").getMethod("void println(java.lang.String)");
//newVirtualInvokeExpr()生成调用语句,第1个参数为类的实例化,第2个参数为类中的方法的引用,第3个为方法的参数
units.insertBefore(Jimple.v().newInvokeStmt(
Jimple.v().newVirtualInvokeExpr(tmpRef, toCall.makeRef(),tmpString)), u);
//check that we did not mess up the Jimple
body.validate();
}
}
//同样可以处理其他语句,如下面的赋值语句
public void caseAssignStmt(AssignStmt stmt){}
//这些都是class AbstraceStmtSwitch中可以查看
});
}
}
}));
soot.Main.main(args);
}
private static Local addTmpRef(Body body){
//生成Local tmpRef,生成的Jimple代码为:java.io.PrintStrem tmpRef;
Local tmpRef = Jimple.v().newLocal("tmpRef", RefType.v("java.io.PrintStream"));
//加到Locals链中
body.getLocals().add(tmpRef);
return tmpRef;
}
private static Local addTmpString(Body body){
//生成Local tmpString,生成的Jimple代码为:java.lang.String tmpString;
Local tmpString = Jimple.v().newLocal("tmpString", RefType.v("java.lang.String"));
body.getLocals().add(tmpString);
return tmpString;
}
}
运行注意事项:
Ecplise参数的配置:-android-jars E:\\IT-tools\\ adt-bundle-windows-x86-20130219\\sdk\\platforms\\
-process-dir G:\\Soot\\Source_A.apk
译者的sdk目录结构为:platforms下存在android-3、android-4、android-7等9个目录,soot会根据Source_A.apk在platforms下选择合适的android.jar<猜测>,因此,参数值不能设置为类似E:\\IT-tools\\ adt-bundle-windows-x86-20130219\\sdk\\platforms\\android-17\\的形式。但是,可以通过Options.v().set_force_android_jar(),则可以使用特定的android.jar。
注意上述完整代码中有一行:
Options.v().set_soot_classpath("……”);这是设置soot-class,请根据自身的环境变量配置设置。
最终的结果:
将在工程目录下的sootOutput文件夹中生成apk文件,签名后就可以直接安装运行。
插桩结果验证:
译者将生成的apk逆向成java,如下所示:
super.onCreate(paramBundle);
setConentView(2130903040);
...
...
System.out.println("HELLO");
add(2,3);
...
在调用方法add()前插入了System.out.println("HELLO");
译者注:
在三地址码中,一条指令的右侧最多有一个运算符。也就是说,不允许出现组合的算术表达式。因此,像x+y*z这样的源语言表达式要被翻译成如下的三地址指令序列:t1=y*z;
t2=x+t1;
其中t1和t2是编译器产生的临时名字。因为三地址代码拆分了多运算符算术表达式以及控制流语句的嵌套结构,所以适用于目标代码的生成和优化。