Loading

利用Soot对APK插桩实践

一、使用soot插装apk应用程序

1、编写apk

首先编写一个需要被插装代码的apk,为了方便起见,编写的apk非常简单,只有一个Activity,未插装前代码如下:

package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //Toast toast=Toast.makeText(MainActivity.this,"toast message", Toast.LENGTH_LONG);
        //toast.show();
        //Log.i("test", "log infor");
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        menu.add(Menu.NONE, Menu.FIRST + 1, 0, "设置");
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        /**
         * 在onCreateOptionsMenu执行后,菜单被显示前调用;如果菜单已经被创建,则在菜单显示前被调用。 同样的,
         * 返回true则显示该menu,false 则不显示; (可以通过此方法动态的改变菜单的状态,比如加载不同的菜单等) TODO
         * Auto-generated method stub
         */
        return super.onPrepareOptionsMenu(menu);
    }

    @Override
    public void onOptionsMenuClosed(Menu menu) {
        /**
         * 每次菜单被关闭时调用. (菜单被关闭有三种情形,menu按钮被再次点击、back按钮被点击或者用户选择了某一个菜单项) TODO
         * Auto-generated method stub
         */
        super.onOptionsMenuClosed(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        super.onOptionsItemSelected(item);
        switch (item.getItemId()) //得到被点击的item的itemId
        {
            case Menu.FIRST + 1:  //对应的ID就是在add方法中所设定的Id
                break;
            case Menu.FIRST + 2:
                break;
        }
        return true;
    }

}
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id=" @+id/menu_setting " android:title="设置"></item>
</menu>

apk运行在虚拟机中的样子如图所示:

image-20210314002114640

2、导入jar包

file->Project Structure

image-20210314191212540

点击SDK,点击加号,选中soot.jar

image-20210314191247781

image-20210314191349859

之后就可以使用soot包中的内容了

3、编写test.java插桩的脚本:

我们插装的目的是在函数onCreate函数里的setContentView()函数之后插入log消息,即插入Log.i("test","log infor!")语句。
我们新建一个Java项目,导入soot-trunk.jar包(nightly-build版本),编写代码如下

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;



import soot.Body;
import soot.BodyTransformer;
import soot.G;
import soot.Pack;
import soot.PackManager;
import soot.Scene;
import soot.SootClass;
import soot.SootMethod;
import soot.Transform;
import soot.Unit;
import soot.jimple.InvokeStmt;
import soot.jimple.Jimple;
import soot.jimple.StaticInvokeExpr;
import soot.jimple.Stmt;
import soot.jimple.StringConstant;
import soot.options.Options;


public class test {
    public final static String androidJar="/Users/wzx/Downloads/android-platforms-master";
    public final static String APK="/Users/wzx/Desktop/first.apk";
    public static void initsoot(String[] args){
        //G.reset();
        Options.v().set_allow_phantom_refs(true);//设置允许伪类(Phantom class),指的是soot为那些在其classpath找不到的类建立的模型
        Options.v().set_prepend_classpath(true);//prepend the VM's classpath to Soot's own classpath
        Options.v().set_output_format(Options.output_format_dex);//设置soot的输出格式
        Options.v().set_android_jars(androidJar);//设置android jar包路径
        Options.v().set_src_prec(Options.src_prec_apk);
        Options.v().set_process_dir(Collections.singletonList(APK));
        Options.v().set_force_overwrite(true);
        //Options.v().set_soot_classpath("");
        Scene.v().loadNecessaryClasses();
        //call soot.Main
        //soot.Main.main(args);
    }
    public static void main(String[] args) {
        initsoot(args);
        PackManager.v().getPack("jtp").add(
                new Transform("jtp.MyTransform", new MyTransform()));//添加自己的BodyTransformer
        PackManager.v().runPacks();
        PackManager.v().writeOutput();

    }

    // custom bodyTransformer
    static class MyTransform extends BodyTransformer {
        @Override
        protected void internalTransform(Body arg0, String arg1,
                                         Map<String, String> arg2) {
            Iterator<Unit> unitsIterator = arg0.getUnits().snapshotIterator();//获取Body里所有的units,一个Body对应Java里一个方法的方法体,Unit代表里面的语句
            while (unitsIterator.hasNext()) {
                Stmt stmt = (Stmt) unitsIterator.next();//将Unit强制转换为Stmt,Stmt为Jimple里每条语句的表示
                if (stmt.containsInvokeExpr()) {//如果是一条调用语句
                    String declaringClass = stmt.getInvokeExpr().getMethod().getDeclaringClass().getName();//获取这个方法所属的类
                    String methodName = stmt.getInvokeExpr().getMethod().getName();//获取这个方法的名称
                    if (methodName.equals("onCreate")) {
                        List<Unit> toastUnits = makeToast(arg0, "toast infor!");
                        arg0.getUnits().insertAfter(toastUnits, stmt);//在这条语句之后插入Toast消息
                        break;
                    }
                }
            }

        }

        private List<Unit> makeToast(Body body, String toast) {
            List<Unit> unitsList = new ArrayList<Unit>();
            //插入语句Log.i("test",toast);
            SootClass logClass = Scene.v().getSootClass("android.util.Log");//获取android.util.Log类
            SootMethod sootMethod = logClass.getMethod("int i(java.lang.String,java.lang.String)");
            StaticInvokeExpr staticInvokeExpr = Jimple.v().newStaticInvokeExpr(sootMethod.makeRef(), StringConstant.v("test"), StringConstant.v(toast));
            InvokeStmt invokeStmt = Jimple.v().newInvokeStmt(staticInvokeExpr);
            unitsList.add(invokeStmt);
            return unitsList;
        }
    }


}

运行之后,其会在sootOutput目录下生成一个同名字的apk,本例中first.apk,记得给这个apk签名(也可以不签名)

image-20210314002501772

我们用 dex2jar 反编译这个apk

E57B6EB4A4E28179119227D2FF1CBFED

产生了jar包文件

A975FCB7298C0B3B16EC02EB0E167402

用jd-gui 查看它代码究竟有没有插入成功,如下:

007B21570D25F62A06231E9BAF6792D0

可见已经插入成功了。

4、签名

首先生成 a.keystore

(base) ➜  Desktop keytool -genkey -alias a.keystore -keyalg RSA -validity 20000 -keystore a.keystore
输入密钥库口令:  
再次输入新口令: 
您的名字与姓氏是什么?
  [Unknown]:  w
您的组织单位名称是什么?
  [Unknown]:  w
您的组织名称是什么?
  [Unknown]:  w
您所在的城市或区域名称是什么?
  [Unknown]:  w
您所在的省/市/自治区名称是什么?
  [Unknown]:  w
该单位的双字母国家/地区代码是什么?
  [Unknown]:  w
CN=w, OU=w, O=w, L=w, ST=w, C=w是否正确?
  [否]:  y

输入 <a.keystore> 的密钥口令
	(如果和密钥库口令相同, 按回车):  
再次输入新口令: 

Warning:
JKS 密钥库使用专用格式。建议使用 "keytool -importkeystore -srckeystore a.keystore -destkeystore a.keystore -deststoretype pkcs12" 迁移到行业标准格式 PKCS12。

接着对未签名的包进行签名。

jarsigner -verbose -keystore a.keystore -signedjar first_output.apk first.apk a.keystore

image-20210314002840331

image-20210314003152143

此时就可以安装在android虚拟机中了。

posted @ 2021-03-14 19:22  xine  阅读(1857)  评论(0编辑  收藏  举报