Frida Hook框架


Hook的原理简单地说就是 用一个新的函数替代掉原来的函数,在这个新的函数中你想做什么都可以,为所欲为。

安装

Python环境,直接打开命令行,执行一波

注意:保持frdia 与frida-server 版本一致。 不同手机支持的frdia-server 版本不一致;

查看frida 与frida-tools 对应版本:前往

# 最新版
pip install frida-tools


# nexus 6P 手机:
# frida-tools==1.2.2  对应的frida == 12.11.18.
pip install frida-tools==1.2.2

安装完毕以后,因为这一页文档的下半部分用于测试刚装好的库是否可用的话过于麻烦,我们这里就直接使用

frida-ps

查看当前运行的进程, 有输出则说明成功。

端口转发:
将PC端的27042端口收到的数据,转发给到手机中27042端口,
注意: 使用手机必须开启端口转发

// 第一个tcp:电脑端口、第二个tcp:手机端口
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043

手机/模拟器设置

  • 一个Root 的 Android 手机或模拟器,(6.0+)
  • 以Root 权限运行一个叫 frida-server 的东西(下载官网

打开GitHub之后你会发现,这里有很多个不同的版本,应该下载哪一个呢?

根据CPU架构型号选择。

查CPU架构的方法很多,这里介绍一个比较方便快捷的——使用一个名叫Device Info HW的APP。
在这里插入图片描述
命名行

adb shell getprop ro.product.cpu.abi

在这里插入图片描述
所以这里我需要下载的是x86_64版本的 frida-server,下载后解压出来一个没后缀的文件.

然后我们需要将这个文件放入手机中运行起来。先把这个文件的名字改成 frida-server

然后在这个文件所在的目录下打开命令行(Windows下为Shift+鼠标右键,选择“在此处打开CMD/PowerShell”),执行以下命令:

adb root
adb push frida-server /data/local/tmp/ 
adb shell "chmod 755 /data/local/tmp/frida-server"
adb shell "/data/local/tmp/frida-server &"

注意:
如果你的手机和我的一样,直接这么运行会提示权限不足的话,可以先进入
adb shell
,在执行
su
命令获取Root权限后(手机端可能会弹出Root授权提示),再运行
/data/local/tmp/frida-server &
启动 frida-server。

启动后,我们先照惯例来测试一下是否能正常使用了,和前面一样,使用
frida-ps -U
参数,这个参数的意思是让它对USB连接的设备操作,如果不出意外的话,你应该能看到与不加 -U 参数时截然不同的显示。

实战一: 官网案列

地址:前往

import frida, sys

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

jscode = """
Java.perform(() => {
  // Function to hook is defined here
  const MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');

  // Whenever button is clicked
  const onClick = MainActivity.onClick;
  onClick.implementation = function (v) {
    // Show a message to know that the function got called
    send('onClick');

    // Call the original onClick handler
    onClick.call(this, v);

    // Set our values after running the original onClick handler
    this.m.value = 0;
    this.n.value = 1;
    // this.cnt.value = 999;

    // Log to the console that it's done, and we should have the flag!
    console.log('Done:' + JSON.stringify(this.cnt));
  };
});
"""

process = frida.get_usb_device().attach('rock_paper_scissors')
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Running CTF')
script.load()
sys.stdin.read()

先看看
Java.perform
,在Frida官方文档的javascript-api页中可以看到,它的用途是确保当前线程已连接到VM用的,所以我们直接照着这么用就行了;

然后看看
Java.use
这个函数,它的用途是获取一个指向某个类的指针,参数中的
com.example.seccon2015.rock_paper_scissors.MainActivity
就是我们需要Hook的那个类;

接着就是真正执行Hook的部分了,这个代码中使用了一个
MainActivity.onClick.implementation
,意思就是Hook前面获取到的类中的onClick方法,后面跟着的赋值函数的部分,函数的参数为对应要Hook方法的参数,内部执行的部分就是在对应方法被调用时所执行的代码,这里它是先打了一个
onClick
日志,然后调用了原始方法(如果不调用的话原始方法不会被执行),接着它将m、n、cnt(变量具体含义请自行反编译APP后查看代码)的值做了修改,最后,它又打了一个携带着cnt变量值的日志。

最后是一些常规操作,
frida.get_usb_device().attach() 是获取指定APP(参数为包名)当前运行着的进程,
process.create_script(jscode)script.load() 是将前面的JS代码注入进去,
script.on('message', on_message)是设置消息传出时的回调,最后的
sys.stdin.read()是输出日志用的。

总结:除了JS代码部分,其他的其实只是个壳子,核心的Hook操作逻辑全在JS代码中,我们在使用时一般只改JS代码部分和指定包名的部分就可以了。

运行脚本

python 执行frida 脚本就和普通py脚本一样, 需要特别注意的是Hook的进程名称。
例:官网案例apk rock_paper_scissors 的Hook 进程为 com.example.seccon2015.rock_paper_scissors

# 官网python 原码
process = frida.get_usb_device().attach('com.example.seccon2015.rock_paper_scissors')
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Running CTF')
script.load()
sys.stdin.read()

然而此时,我执行frida-ps -U 获取的进程名称为rock_paper_scissors. 所以具体的进程名还是需要我们自己去查询确认一下。

Python hook 脚本

import frida, sys

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

jscode = """
Java.perform(() => {
  const MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');
  const onClick = MainActivity.onClick;
  onClick.implementation = function (v) {
    // so something
  };
  
  // 多个hook 点
  const MainActivity_n = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');
  const onClick = MainActivity_n.onClick;
  onClick.implementation = function (v) {
    // so something
  };
});
"""

# 获取进程
process = frida.get_usb_device().attach('rock_paper_scissors')
# 创建js脚本(于内存中)
script = process.create_script(jscode)
# 绑定回调函数
script.on('message', on_message)
print('[*] Running CTF')
# 注入脚本
script.load()
# 输出log
sys.stdin.read()

Hook案例

Frida 常用的Hook 方法: 包括Hook 一般函数、重载函数、构造函数、打印堆栈信息、类方法名等。

Hook 一般函数

这里只编写了js代码, 因为上面说的很清楚了, hook的主要逻辑是在js代码中,其他的python代码只是外壳。

// Hook xx.xxx.MainActivity 下的myMethod方法, 打印该方法的执行结果

var MainActivity = Java.use('xx.xxx.MainActivity');
MainActivity.myMethod.implementation = function (args) {
	var res = this.myMethod(args);
	// console.log('Done:' + JSON.stringify(res));
	console.log('Done: ' + res)
	return res;
};

Hook 重载函数

重载函数: 一个类中有两个或多个相同的方法名, 但是方法的参数类型或个数不同。在调用时需要根据参数类型及个数来匹配不同的方法。在Hook 时我们通过 overload 关键字来指定具体参数类型。如果通过implementation 会报错:Error:xx(): has more than one overload

// 案列一
myClass.myMethod.overload("java.lang.String").implementation = function (params1) {
	console.log(params1);
};

// 案例二
myClass.myMethod.overload("[B","[B").implementation = function (params1, params2) {
	// byte 转string
	const p1 = Java.use("java.lang.String").$new(params1);
	const p2 = Java.use("java.lang.String").$new(params2);
};

// 案列三
myClass.myMethod.overload("android.contexe.Context","boolean").implementation = function (params1, params2) {
	// do someting  做点什么......
};

Hook 构造函数

要hook构造函数,和普通的函数是有区别的,要用 $init这种形式,并且要return this.$init(arg1,arg2)
调用原始的函数实现

无参构造

//获取要hook的那个类的实例
var userObj=Java.use("com.alex.javahooktarget.MyUser");
userObj.$init.overload().implementation=function(){
    console.log("Hook 到 MyUser无参构造函数");
    this.$init();//注意这里使用的是当前这个实例化的对象this
}

有参构造

const StringBuilder = Java.use("java/lang.StringBuilder");
StringBuilder.$init.overload('java.lang.String').implementation = function (args) {
	var partial = "";
	// 返回对象
	var result = this.$init(args);
	if(args !== null){
		partial = args.toString().slice(0, 10);
	}
	console.log('new StringBuilder("' + partial +'");');
	return result;
}

StringBuilder.$init.overload('[B', 'int').implementation = function (args1, args2){
	// do something
}

Hook 成员方法

成员方法指没有前缀static 的类修饰符的方法, Hook 时需要对类实例化$new().

Java.perform(
	function (){
		// 实列化对象
		var obj = Java.use("com.qianfanyun.base.entity.InitIndexEntity").$new();
		if (obj != undefined){
			obj.setDefault_national_prefix.implementation = function (param){
				console.log("-------> str: " + param);
				obj.setDefault_national_prefix(param)
			}
		}
});

Hook 内部类

内部类是指一个类定义在里一个类里面或方法里面,
注意: 对于内部类,通过 类名$内部类名
如: Class$inner$inner 原理上是可以有无限嵌套的内部类的。
匿名类一般是$1或者$2$3

public class MyClass extends BaseMyClass {
    public String getText() {
        return "这是当前类属性";
    }
    private static class InnerClasses {
        public static boolean check1() {
            return false;
        }
        public static boolean check2() {
            return false;
        }
	}
}    

Hook 代码:

var myClass = Java.use("com.xx.xxx.MyClass");
myClass.getText = function(){
	return "这是Hook后返回的数据";
}

var InnerClasses Java.use("com.xx.xxx.MyClass$InnerClasses");
InnerClasses.check1.implementation = function (){
	return true
}
InnerClasses.check2.implementation = function () {
	return true
}

Hook native 函数

通过so名方法名查找方法位置,创建NativePointer指针对象,然后通过Interceptor.attach() 拦截器函数。
注意: 一般不需要添加setTimeout定时器,不过有时添加等待时间可能会避免掉一些异常。

function hookNativeFun(callback, funName, moduleName) {
    var time = 1000;
    var address = Module.findExportByName(moduleName, funName);
    if (address == null) {
        setTimeout(hookNativeFun, time, callback, funName, moduleName);
	} 
	else {
        console.log(funName + "hook result")
        var nativePointer = new NativePointer(address);
        Interceptor.attach(nativePointer, callback);
    }
}

从内存中主动调用Java方法

Java.perform(
    function(){
        Java.choose("com.xx.xx.xx", {
        onMatch: function (x) {
            ba.signInternal.implementation = function(a1,a2) {
            result= x.method(a1,a2);
            },
        onComplete: function () {
            }
        })
    }
)

获取所有已加载的类名

Java.perform(function(){
    Java.enumerateLoadedClasses({
        onMatch: function(className) {
            send(className);},
        onComplete:function(){
            send("done");
        }
    });
});

获取方法名

function getMethodName() {
    var ret;
    Java.perform(function() {
        var Thread = Java.use("java.lang.Thread")
        ret = Thread.currentThread().getStackTrace()[2].getMethodName();
    });
    return ret;
}

打印堆栈信息

function showStacks() {
    Java.perform(function() {
        console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
    });
}

打印类所有方法名

function enumMethods(targetClass) {
    var ret;
    Java.perform(function() {
            var hook = Java.use(targetClass);
            var ret = hook.class.getDeclaredMethods();
            ret.forEach(function(s) {
                console.log(s);
            })
    })
    return ret;
}

构造 context

var current_application = Java.use('android.app.ActivityThread').currentApplication();
var context = current_application.getApplicationContext();

Hook NO_Proxy

Java.perform(function(){
    console.log("1 start hook");
    try {
        var URL = Java.use('java.net.URL')
        URL.openConnection.overload('java.net.Proxy').implementation = function (arg1){
            return this.openConnection()
        }
    } catch (e) {
        console.log('' + e)
    }
    try {
        var Builder = Java.use('okhttp3.OkHttpClient$Builder')
        var mybuilder = Builder.$new()
        Builder.proxy.overload('java.net.Proxy').implementation = function (arg1) {
            return mybuilder
        }
    }   catch (e) {
        console.log('' + e)
    }

Hook HashMap-put

var hashMap = Java.use("java.util.HashMap");
hashMap.put.implementation = function (a, b) {
    // 不行可换 a.equals("username")
    if (a=="username") {
     console.log("hashMap.put: ", a, b);
        printStacks();
 }
 return this.put(a, b);
}

Hook ArrayList-add

var arrayList = Java.use("java.util.ArrayList");
arrayList.add.overload('java.lang.Object').implementation = function (a) {
    if (a == "username") {
        console.log("arrayList.add: ", a);
        showStacks();  // 打印堆栈信息
    }
    //console.log("arrayList.add: ", a);
    return this.add(a);
}
arrayList.add.overload('int', 'java.lang.Object').implementation = function (a, b) {
    console.log("arrayList.add: ", a, b);
    return this.add(a, b);
}

Hook TextUtils-isEmpty

// 判断输入框是否为空
var textUtils = Java.use("android.text.TextUtils");
textUtils.isEmpty.implementation = function (a) {
    if (a == "TURJNk1EQTZNREE2TURBNk1EQTZNREE9") {
        console.log("textUtils.isEmpty: ", a);
        printStacks();

    }
    //console.log("textUtils.isEmpty: ", a);
    return this.isEmpty(a);
}

hook Base64-encodeToString

var base64 = Java.use("android.util.Base64");
base64.encodeToString.overload('[B', 'int').implementation = function (a, b) {
    console.log("base64.encodeToString: ", JSON.stringify(a));
    var result = this.encodeToString(a, b);
    console.log("base64.encodeToString result: ", result)
    printStacks();
    return result;
}

okhttp3 rpc

rpc.exports = {
    request_url: function (url) {
        return new Promise(function(resolve, reject) {
            Java.perform(function () {
                var OkHttpClient=Java.use("okhttp3.OkHttpClient");
                var Builder=Java.use("okhttp3.Request$Builder");
                var client = OkHttpClient.$new()
                var request = Builder.$new().get().url(url).build()
                var response = client.newCall(request).execute()
                resolve(response.body().string())
            });
        });
    }
};

okhttp3 GET

function okhttp3_get(url, headers_str){
    let body = null;
    Java.perform(() => {
        const BuilderClazz = Java.use('okhttp3.Request$Builder');
        const OkHttpClientClazz = Java.use('okhttp3.OkHttpClient');
        try{
            let builder = BuilderClazz.$new();
            builder = builder.url(url);
            if(headers_str != null && headers_str != ''){
                let headers = JSON.parse(headers_str);
                for(let key in headers){
                    builder = builder.addHeader(key, headers[key]);
                }
            }
            let request = builder.build();
            if(!okhttp3_client){
                okhttp3_client = OkHttpClientClazz.$new();
            }
            let call = okhttp3_client.newCall(request);
            let response = call.execute();
            body = response.body().string();
        }catch(e){
            console.error(e);
        }
    });
    return body;
}

okhttp3 POST

function okhttp3_post(url, headers_str, media_type, body){
    let ret = null;
    Java.perform(() => {
        const BuilderClazz = Java.use('okhttp3.Request$Builder');
        const OkHttpClientClazz = Java.use('okhttp3.OkHttpClient');
        const MediaTypeClazz = Java.use('okhttp3.MediaType');
        const RequestBodyClazz = Java.use('okhttp3.RequestBody');
        try{
            let builder = BuilderClazz.$new();
            builder = builder.url(url);
            if(headers_str != null && headers_str != ''){
                let headers = JSON.parse(headers_str);
                for(let key in headers){
                    builder = builder.addHeader(key, headers[key]);
                }
            }
            let m_type = MediaTypeClazz.parse(media_type);
            let request_body = RequestBodyClazz.create(m_type, body);
            builder = builder.post(request_body);
            let request = builder.build();
            if(!okhttp3_client){
                okhttp3_client = OkHttpClientClazz.$new();
            }
            let call = okhttp3_client.newCall(request);
            let response = call.execute();
            ret = response.body().string();
        }catch(e){
            console.error(e);
        }
    });
    return ret;
}
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值