通过Hook技术拦截App执行逻辑动态调试

逆向App之Hook

上几篇介绍了如何抓包App的网络请求,以及脱壳和反编译App,但是请求返回响应的是加密的,通过反编译代码发现,响应加密密钥是通过加载了本地so文件来返回的,尝试通过在线和Ida反编译so文件,以及新建java项目调用so文件未果,这时可以通过Frida Hook技术来动态劫持代码,来拦截、篡改数据加解密、界面弹窗等逻辑。Frida是一款基于Python和Javascript的Hook框架,可运行在Android、Ios、Linux、Win、Osx等各平台,主要使用动态二进制插桩技术。

image-20260529173730801

image-20260601110901639

Python环境安装

-- 安装python3环境,查看python版本
python --version

image-20260601113431545

Frida安装

安装在Windows等操作系统上。

# 通过python的pip工具安装frida
pip3 install frida
pip3 install frida-tools

# 查看frida版本
frida --version

image-20260601150822820

Frida服务端安装

安装在安卓手机或者模拟器上,需要注意Cpu架构是arm还是x86,通过Adb工具查看系统架构,Adb工具可以单独从官网下载或者直接使用雷电模拟器安装目录下的Adb工具,从Github上下载对应的Frida服务端版本。

# 已连接设备或者模拟器列表
.\adb.exe devices
# adb登录命令行
.\adb.exe shell
# 切换root,可选
su
# 查看cpu类型,或者进入shell之后执行后面的命令
.\adb.exe shell getprop ro.product.cpu.abi
getprop ro.product.cpu.abi

# 推送文件到模拟器
.\adb.exe push frida-server-17.9.7-android-x86_64 /data/local/tmp

# 进入shell刚刚推送的目录
.\adb.exe shell
cd /data/local/tmp
# 修改权限
chmod 777 frida-server-17.9.7-android-x86_64
# 切换到root用户启动,否则注入脚本会报错Failed to spawn: unexpectedly timed out while waiting for app to launch
su
# 启动frida服务端
./frida-server-17.9.7-android-x86_64

image-20260601154505775

image-20260601154449434

image-20260601180143471

在Windows上,再开一个命令行,执行frida-ps -Uai查看Usb设备上所有已安装应用命令查看是否运行正常。

image-20260601163251222

Hook代码

回到引言中的问题,请求返回的内容加密了,但是密钥是通过本地So文件中的方法获取的,那么我们就尝试通过Frida来拦截下NativeEncrypt.getSalt()方法,可以在Jadx上在方法名上右键复制为Frida代码并保存为hook.js文件。

image-20260601164006271

image-20260601164619100

// 注入脚本代码,常规写法,无法Hook
Java.perform(function () {
	let NativeEncrypt = Java.use("com.dsdqjx.tvhd.utils.NativeEncrypt");
	NativeEncrypt["getSalt"].implementation = function () {
		console.log(`NativeEncrypt.getSalt is called`);
		let result = this["getSalt"]();
		console.log(`NativeEncrypt.getSalt result=${result}`);
		return result;
	};
});

在Windows上执行frida -U -f com.xytv.tvhd -l .\hook.js命令注入脚本,执行后报错找不到类。

image-20260601180427138

后面发现可能是应用加固的原因,搜索资料说是可以先拿到加载应用本身dex的classloader,再通过这个classloader去找到被加固的类,最后再去Hook对应的方法,修改后的Hook脚本如下。

if(Java.available) {
    Java.perform(function(){
        var application = Java.use("android.app.Application");
        var reflectClass = Java.use("java.lang.Class");
 
        console.log("application: " + application);
 
        application.attach.overload('android.content.Context').implementation = function(context) {
				var result = this.attach(context); // 先执行原来的attach方法
				var classloader = context.getClassLoader(); // 获取classloader
				Java.classFactory.loader = classloader;
				//这里能直接使用Java.use,因为java.use会检查在不在perform里面,不在就会失败
				var NativeEncrypt = Java.classFactory.use("com.dsdqjx.tvhd.utils.NativeEncrypt");
				console.log("NativeEncrypt: " + NativeEncrypt);

				NativeEncrypt["getSalt"].overload('java.lang.String').implementation = function () {
				let result = this["getSalt"]();
				console.log(`com.dsdqjx.tvhd.utils.NativeEncrypt.getSalt result=${result}`);
				return result;
				}
 
            return result;
        }
 
    });
}

在Windows上执行frida -U -f com.xytv.tvhd -l .\hook.js命令注入脚本,执行后还是没有Hook到对应的方法。

image-20260603114147840

经过各种写法的尝试之后,发下如下写法才可以Hook到对应的方法获取密钥,在Windows上执行命令为frida -U -f com.xytv.tvhd -l .\hook.js --pause,且需要手动输入%resume

if (Java.available) {
    Java.perform(function () {
        console.log("✅ 启动加固兼容模式");

        // 循环查找(每 1 秒查一次,直到找到类)
        var timer = setInterval(function () {
            try {
                var NativeEncrypt = Java.use("com.dsdqjx.tvhd.utils.NativeEncrypt");
                
                NativeEncrypt.getSalt.implementation = function () {
                    // 获取原始返回值(可选)
                        var originalSalt = this.getSalt();
                        console.log("🔐 原始 salt: " + originalSalt);

                        // 返回你自定义的 salt
                        //var yourSalt = "abcdefg1234567"; // 你想要的盐值
                        //console.log("✅ 返回修改后的 salt: " + yourSalt);
                        
                        return originalSalt;
                };

                console.log("🎯 所有方法 Hook 成功!");

                clearInterval(timer); // 找到就停止

            } catch (e) {}
        }, 1000);
    });
}

image-20260603115318289