利用Soot插装Android App

原文地址:点击打开链接

  在Soot里我们增加了对读写Dalvik bytecode的支持。主要包含两个模块,第一个称作Dexpler ,主要由Alexandre Bartel 团队开发,Ben Bellamy 、Eric Bodden、Frank Hartmann 和 Michael Market 对其做了增强。Dexpler将 Dalvik 字节码转换成 Jimple的三地址格式代码(three-address code)。

  如何插装

首先下载soot的最新版本,比如 soot 的nightly buid版本。同时查看https://github.com/Sable/android-platforms下的目录。这个目录包含不同版本的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-classes
Scene.v().addBasicClass("java.io.PrintStream",SootClass.SIGNATURES);
Scene.v().addBasicClass("java.lang.System",SootClass.SIGNATURES);
第一条操作告知Soot装载Android apk文件,第二条告知Soot输出一个Dex/APK 文件(理论上,你也可以将Java转换成Dex或者将Dex转换成Java等),最后两个操作告诉Soot装着两个类,我们在插装的时候需要这两个类,但可能被插装的APK不需要这两个类。

接着我们往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在方法调用之前插入 System.out.println("HELLO"),但是这个方法调用的目标必须是 onDraw方法。

最后,别忘了调用Soot的主方法:

soot.Main.main(args);
很简单不是吗?最后你只需要带以下参数来运行你的驱动类(driver class):

-android-jars path/to/android-platforms -process-dir your.apk
这里,path/to/android-platforms 是你之前下载的Android 平台jar包路径,your.apk是你要插装的APK路径,选项 -process-dir告知Soot处理这个APK里所有的类。最后你会在目录 ./sootOutput里发现一个同名的APK。

完整代码如下:

import java.util.Iterator;
import java.util.Map;

import soot.Body;
import soot.BodyTransformer;
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.InvokeExpr;
import soot.jimple.InvokeStmt;
import soot.jimple.Jimple;
import soot.jimple.StringConstant;
import soot.options.Options;


public class AndroidInstrument {
	
	public static void main(String[] args) {
		
		//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-classes
		Scene.v().addBasicClass("java.io.PrintStream",SootClass.SIGNATURES);
        Scene.v().addBasicClass("java.lang.System",SootClass.SIGNATURES);

        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<Unit> units = b.getUnits();
				
				//important to use snapshotIterator here
				for(Iterator<Unit> iter = units.snapshotIterator(); iter.hasNext();) {
					final Unit u = iter.next();
					u.apply(new AbstractStmtSwitch() {
						
						public void caseInvokeStmt(InvokeStmt stmt) {
							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("<java.lang.System: java.io.PrintStream out>").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.Main.main(args);
	}

    private static Local addTmpRef(Body body)
    {
        Local tmpRef = Jimple.v().newLocal("tmpRef", RefType.v("java.io.PrintStream"));
        body.getLocals().add(tmpRef);
        return tmpRef;
    }
    
    private static Local addTmpString(Body body)
    {
        Local tmpString = Jimple.v().newLocal("tmpString", RefType.v("java.lang.String")); 
        body.getLocals().add(tmpString);
        return tmpString;
    }
}

下面说下译者在实践中运行的问题。

首先我配置Eclipse参数为

-android-jars D:\\android.jar -process-dir D:\\Eclipse\\SootTest\\SampleInfoSaveInSDcard.apk

其中D:\\android.jar是我用来测试的android标准库,但运行发现android.jar不存在

但事实上这个路径下是有android.jar这个文件的,后来查找soot官网的论坛发现不能这样指定,接着我配置参数为:

-android-jars D:\\Eclipse\\adt-bundle-windows-x86-20130917\\sdk\\platforms\\android-18\\
-process-dir D:\\Eclipse\\SootTest\\SampleInfoSaveInSDcard.apk

发现还是提示上面的错误,


把配置参数的第一行改为 -android-jars D:\\Eclipse\\adt-bundle-windows-x86-20130917\\sdk\\platforms\\

结果终于顺利运行成功,结果如图:


这是会在sootOutput目录下生成一个与你插装的apk同名的apk

这里说下我的android.jar目录结构

在D:\\...\\platforms 目录下有两个标准库 android-18目录和android-10目录,当不指定到android-18目录时soot能够正常运行,但当指定使用android-18目录下的标准库时,便会出现找不到android.jar的情况。

注意如果把设置soot选项的增加Options.v().set_force_android_jar(),则可以使用特定的android。jar





评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值