2.frida Java层Hook

测试用例

本次的hook代码都用 python接口方式 书写。首先写一个简单的程序用来测试。后续的测试就在这个程序上小修小改,不做赘述。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/layout_main"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    tools:context=".MainActivity">
    
    <Button
        android:id="@+id/btn_create"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="实例化一个Calc" />

    <LinearLayout
        android:id="@+id/layout_add1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="horizontal">

        <EditText
            android:id="@+id/edt_add1"
            android:layout_width="198dp"
            android:layout_height="wrap_content"
            android:autofillHints=""
            android:ems="5"
            android:hint="输入一个数"
            android:inputType="number" />

        <Button
            android:id="@+id/btn_add1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="add1()" />
    </LinearLayout>

</LinearLayout>
package com.zyc.fridademo;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private EditText edtAdd1;
    private Button btnCreate;
    private Button btnAdd1;
    private Calc calc;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        edtAdd1 = findViewById(R.id.edt_add1);
        btnCreate = findViewById(R.id.btn_create);
        btnCreate.setOnClickListener(this);
        btnAdd1 = findViewById(R.id.btn_add1);
        btnAdd1.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_create:
                calc = new Calc(1);
                Toast.makeText(this, "已创建一个Calc\nbase=" + calc.base, Toast.LENGTH_SHORT).show();
                break;
            case R.id.btn_add1:
                int p1 = Integer.parseInt(String.valueOf(edtAdd1.getText()));
                int res = calc.add(p1);
                Toast.makeText(this, "执行add(p1)\n结果=" + res, Toast.LENGTH_SHORT).show();
                break;
        }
    }
}
package com.zyc.fridademo;

public class Calc {
    public int base;

    /**
     * 构造方法
     */
    public Calc(int p1) {
        this.base = p1;
    }

    /**
     * 普通方法
     *
     * @return num1与base相加结果
     */
    public int add(int num1) {
        return base + num1;
    }
}

运行:
在这里插入图片描述

Hook普通函数

将之前的Hook模板稍作改动:

import frida, sys

jscode = """
Java.perform(function(){
    var clazz = Java.use("com.zyc.fridademo.Calc");
    clazz.add.implementation = function(p1)
    {
        console.log("Hook开始...");
        send("原p1="+p1);
        console.log("Hook修改参数...");
        p1+=100;
        send("现p1="+p1);
        return this.add(p1);
    }
});
"""

def message(message , data):
    if message["type"]=="send":
        print("[*] {0}".format(message['payload']))
    else:
        print(message)

process=frida.get_remote_device().attach('com.zyc.fridademo')
script=process.create_script(jscode)
script.on("message",message)
script.load()
sys.stdin.read()

启动frida-server,使用端口转发,执行上面的脚本,即可实现输入的数增加100之后再执行原函数。
在这里插入图片描述
 

Hook构造函数

如果是Hook构造函数,只需使用$init引用,将.py的js部分修改为:

Java.perform(function(){
    var clazz = Java.use("com.zyc.fridademo.Calc");
    clazz.$init.implementation = function(p1)
    {
        console.log("Hook构造开始...");
        send("原p1="+p1);
        p1+=200;
        send("现p1="+p1);
        return this.$init(p1);
    }
});

运行:
在这里插入图片描述
 

Hook重载函数

在Calc类中增加一个重载方法add(int num1,String num2):

public int add(int num1,String num2) {
    return base + num1;
}

由于Calc中有add(int num1)和add(int num1,String num2),像普通函数那样Hook是会报错的,涉及到重载就要用到 overload()

Java.perform(function(){
    var clazz = Java.use("com.zyc.fridademo.Calc");
    clazz.add.overload("int").implementation = function()
    {
        console.log("Hook add(int num1)...");
        return 888;
    }

    clazz.add.overload("int","java.lang.String").implementation = function()
    {
        console.log("Hook add(int num1,String num2)...");
        return 999;
    }
});

运行:
在这里插入图片描述
如果重载函数多,可以通过 overloads 获得全部重载对象,并通过js的 applyarguments 特性继续程序流程。下面例子遍历了add(int a)和add(int a,String b)函数,并修改了两个函数的第一个参数,打印了第二个参数(如果有)。

Java.perform(function(){
    var clazz = Java.use("com.zyc.fridademo.Calc");
    var count = clazz.add.overloads.length;
    for(var i=0;i<count;i++){
        clazz.add.overloads[i].implementation = function(){
            arguments[0] = 8;
            if(arguments[1]){
                send(arguments[1]);
            }
            return this.add.apply(this,arguments);
        }
    }
});

运行:
在这里插入图片描述
 

实例化类

使用 $new() 可以实例化一个类,这里我们在MainActivity onCreate()时实例化一个Calc,构造方法传入10(按钮实例化的传入的是1):

Java.perform(function(){
    var clazz = Java.use("com.zyc.fridademo.MainActivity");
    var calc = Java.use("com.zyc.fridademo.Calc");
    clazz.onCreate.implementation = function()
    {
        console.log("Hook MainActivity onCreate()...");
        var myCalc = calc.$new(10);
        return this.onCreate(arguments[0]);
    }

});

运行:在这里插入图片描述
 

访问类的属性

类的属性可通过 .属性名.value 访问。如果有函数与属性名相同,则需要使用下划线方式 ._属性名.value 访问。

Java.perform(function(){
    var clazz = Java.use("com.zyc.fridademo.MainActivity");
    var calc = Java.use("com.zyc.fridademo.Calc");
    clazz.onCreate.implementation = function()
    {
        console.log("Hook MainActivity onCreate()...");
        var myCalc = calc.$new(10);
        send(myCalc.base.value);
        console.log("修改一下base...");
        myCalc.base.value = 88;
        send(myCalc.base.value);
        return this.onCreate(arguments[0]);
    }
});

运行:
在这里插入图片描述
 

Hook内部类

使用 外部类$内部类 的写法可以实现内部类Hook,为了测试简单改写一下程序:

//Calc类中增加静态内部类InnerClass
static class InnerClass{
    public static String print(){
        return "我是内部类";
    }
}

//MainActivity的onCreate()中加入一个按钮,点击触发InnerClass的print()方法
btnInnerClass = findViewById(R.id.btn_inner_class);
btnInnerClass.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        String txt = Calc.InnerClass.print();
        Toast.makeText(MainActivity.this, txt, Toast.LENGTH_SHORT).show();
    }
});

Hook代码,修改print()方法的返回值:

Java.perform(function(){
    var innerClazz = Java.use("com.zyc.fridademo.Calc$InnerClass");
    innerClazz.print.implementation = function()
    {
        return "我是被hook的内部类";
    }
});

运行:
在这里插入图片描述
 

Hook匿名类

看过smali的都知道匿名类反编译出来是 类$数字 形式,如上面调用内部类方法时创建的View.OnClickListener就是一个匿名类,通过反编译工具能看到其名称为 MainActivity$1
在这里插入图片描述
于是可以这样frida中监听View.OnClickListener方法:

Java.perform(function(){
    var innerClazz = Java.use("com.zyc.fridademo.MainActivity$1");
    innerClazz.onClick.implementation = function()
    {
        send("执行了匿名类点击方法");
        return ;
    }
});

运行:
在这里插入图片描述
 

遍历已加载的类与类方法

enumerateLoadedClasses 可以异步获取已加载的类,再通过类名反射即可获得类方法:

Java.perform(function(){
    Java.enumerateLoadedClasses({
        onMatch : function(name,handle){
            if(name.indexOf("com.zyc.fridademo") != -1){
                console.log(name);
                var clazz = Java.use(name);
                var methods = clazz.class.getDeclaredMethods();
                for(var i=0;i<methods.length;i++){
                    console.log(methods[i]);
                }
            }
        },
        onComplete:function(){}
    });
});

运行:
在这里插入图片描述
这里引申一下用 类变量[方法名] 方式的Hook:

Java.perform(function(){
    var innerClazz = Java.use("com.zyc.fridademo.Calc$InnerClass");
    var methods = innerClazz.class.getDeclaredMethods();
    for(var i=0;i<methods.length;i++){
        var methodName = methods[i].getName();
        if(methodName.indexOf("print") != -1){
            innerClazz[methodName].implementation = function(){
                send("我是反射来的");
                return "123";
            }
        }
    }
});

运行:
在这里插入图片描述
 

遍历类实例

使用 choose 可查找堆中的类实例:

Java.perform(function(){
    var clazz = Java.use("com.zyc.fridademo.MainActivity");
    var calc = Java.use("com.zyc.fridademo.Calc");
    clazz.onCreate.implementation = function()
    {
        console.log("Hook MainActivity onCreate()...");

        //实例化3个Calc
        calc.$new(2);
        calc.$new(3);
        calc.$new(4);

        //打印每个实例的base值
        Java.choose("com.zyc.fridademo.Calc" , {
            onMatch : function(instance){
                console.log("Found instance: "+instance);
                send("instance.base="+instance.base.value);
            },
            onComplete:function(){}
        });
        return this.onCreate(arguments[0]);
    }
});

运行:
在这里插入图片描述
 

Hook动态加载的dex

首先写一个供动态加载的jar包放置在data/data/com.zyc.fridademo/files,反编译出来是这样的:
在这里插入图片描述
上面的案例程序中多加一个按钮来使用该jar包中类,详细流程自行参考DexClassLoader相关知识,这里只展示按钮部分代码:

case R.id.btn_mydex:
    //加载dex class
    if (dexClassLoader==null){ //确保只创建一个DexClassLoader,不然多个loader影响hook
        String dexPath = this.getFilesDir() + File.separator + "mydex.jar";
        dexClassLoader = new DexClassLoader(dexPath, this.getFilesDir().getAbsolutePath(), null, getClassLoader());
    }
    try {
        Class<?> clz = dexClassLoader.loadClass("com.zyc.mydex.Human");
        Object human = clz.newInstance() ;
        if (human != null) {
            Method say = clz.getDeclaredMethod("say");
            say.setAccessible(true);
            String what = String.valueOf(say.invoke(human)); //反射调用say,返回“我是动态dex”
            Toast.makeText(this, what, Toast.LENGTH_SHORT).show();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    break;

在获取loader时记得使用 try catch,否则loadClass()发生异常会导致程序终止。Hook动态dex的关键在于使用正确的loader:

Java.perform(function(){
    Java.enumerateClassLoaders({
        onMatch : function(loader){
            try{
                if(loader.loadClass("com.zyc.mydex.Human")){
                    console.log("正确loader");
                    Java.classFactory.loader = loader;
                    var clazz = Java.use("com.zyc.mydex.Human");
                    send(clazz);
                    clazz.say.implementation = function(){
                        return "我是被hook的动态dex";
                    }
                }
            }catch(err){
                console.log(err)
            }
        },
        onComplete:function(){}
    });
});

运行:
在这里插入图片描述
 

打印函数堆栈

打印函数堆栈的关键在于 android.util.Log.getStackTraceString(new Throwable())android.util.Log.getStackTraceString(new Exception()) ,于是我们可以这样Hook:

Java.perform(function(){
    var clazz = Java.use("com.zyc.fridademo.Calc");
    clazz.add.overload("int").implementation = function(num)
    {
        var log = Java.use("android.util.Log");
        var throwable = Java.use("java.lang.Throwable");
        var stack = log.getStackTraceString(throwable.$new());
        send(stack);
        return this.add(num);
    }
});

运行,可以看到除系统调用外,执行流程为 MainActivity.onCreate -> Calc.add()
在这里插入图片描述
 

注入dex文件

当程序本身功能不能满足我们需求时,如果要利用frida增加功能,可以通过下面方法:

  • Java.registerClass 注入自己的类,需要使用js撰写一个java类传入。
  • Java.openClassFile 注入自己的dex文件,只需传入dex路径。

显然,注入dex文件会方便很多。那么现在我写一个简单的类打包后提取其dex放置为 /data/local/tmp/injectiondex.dex:

package com.zyc.injectiondex;

public class Cat {
    public String say() {
        return "喵喵喵";
    }

    public String eat(){
        return "多吃几口";
    }
}

使用frida注入之前的内部类方法:

Java.perform(function(){
    Java.openClassFile("/data/local/tmp/injectiondex.dex").load();
    var cat = Java.use("com.zyc.injectiondex.Cat");
    var oneCat = cat.$new();
    var catsay = oneCat.say();
    var cateat = oneCat.eat();

    var innerClazz = Java.use("com.zyc.fridademo.Calc$InnerClass");
    innerClazz.print.implementation = function()
    {
        return catsay+cateat;
    }
});

运行:
在这里插入图片描述
 

相关资料

Android一键生成包含.dex的Jar及动态加载方案

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值