前言
一些链接:
准备工作
Frida
是一个全平台注入框架,使用JavaScript
编写注入代码,可以很方便的实现动态Hook。这里用Frida-Demo
这个程序来演示。
-
一台Root过的手机或者带有完整Root权限的模拟器。
-
电脑端环境配置
-
安装
Frida
pip install frida frida-tools
-
测试
Frida
frida --version
-
配置好
ADB
,与真机或者模拟器连接,常用的连接方式如下:# USB连接 adb usb # 网络连接 adb connect 192.168.x.x
-
-
手机端环境配置
-
获取
Frida-Server
: 下载链接注意要下载
frida-server
开头的文件,末尾的arm
、x86
代表适用的平台,真机就选择arm
,模拟器选择x86
。
至于选择32位的还是64位的
frida-server
,主要取决于目标APP是32位还是64位的,根据需要选择。-
设置
Frida-Server
首先把
Frida-Server
传到手机上:# 请将【 】中的内容修改为文件所在路径 adb push 【frida-server-12.10.4-android-arm】 /data/tmp/frida-server adb push 【frida-server-12.10.4-android-arm64】 /data/tmp/frida-server64
然后打开一个
Shell
:adb shell
运行以下命令:
su cd /data/tmp chmod 700 frida-server* ./frida-server -l 0.0.0.0 &
最后一行加了个
&
,代表把frida-server
作为后台任务启动,这样即使我们Shell
连接,frida-server
也不会被终止。测试
Frida-Server
我们打开另一个命令行,输入以下命令:
# 如果使用USB连接 frida-ps -U # 如果使用网络连接 frida-ps -R
如果能看到手机进程列表说明设置成功了。
-
-
安装
Frida-Demo
练习程序下载地址:GitHub
安装过程就不赘述了。
-
准备开发环境
Frida
使用JavaScript
编写注入代码,我们选择Python
作为加载器。我使用的是
VSCode
+Python3.8
作为开发环境,配置方法参见另一篇博文:也可以选择
PyCharm
,看个人喜好了。首先新建一个
run.py
作为加载器,内容如下:import frida # PS: 现在的Frida好像是根据进程名称而不是包名来识别 # 如果提示attach失败就改成进程名,也就是Frida Demo再试试 process_name = 'infosecadventures.fridademo' # 发送信息回调函数 def on_message(message, data): if message['type'] == 'send': print(f"[*] {message['payload']}") else: print(message) if __name__ == '__main__': try: device = frida.get_usb_device() except: device = frida.get_remote_device() if not device: print("* 连接到Frida Server失败") else: process = device.attach(process_name) # 加载JS脚本 js = open('hook.js', encoding='utf-8').read() script = process.create_script(js) script.on('message', on_message) script.load() # 输入任意值结束 input() script.unload()
然后在相同的目录下新建一个
hook.js
来编写注入代码console.log("脚本载入成功"); Java.perform(function() { //在这里编写Hook函数 });
注入实战
我们打开Frida-Demo
如图,Frida Demo一共有3个模块,从左到右依次是【PIN密码爆破】【绕过root检测】【加密算法破解】。
我们先用工具分析一下代码,这个演示程序的代码没有经过混淆,而且去除了不必要的代码,很容易看。
我使用的是Jadx,把frida-demo.apk拖进去即可。
左侧展示了APK中所有模块和资源文件,黄色图标的是“包”(package),绿色图标的是“类”(Class)。
我们只需要关注infosecadventures.fridademo
包,其他3个包是一些通用系统组件。
数字密码爆破
我们定位到infosecadventures.fridademo.utils.PinUtil
类,代码非常简单,密码是一个Base64
字符串。
接下来我们尝试Hook这个函数,示例代码:
Java.perform(function() {
//获取指定类
var cls = Java.use('infosecadventures.fridademo.utils.PinUtil');
//HookCheckPin函数
cls.checkPin.overload("java.lang.String").implementation = function(arg1) {
//打印入参
console.log('checkPin-in:', arg1);
//调用原函数
var result = this.checkPin(arg1);
//打印出参
console.log('checkPin-out:', result);
//返回给原函数的调用
return result;
}
});
看起来挺复杂的,其实非常好理解,我们先获取了infosecadventures.fridademo.utils.PinUtil
类,然后使用overload
方法获取了checkPin
的一个重载方法,最后使用implementation
方法创建一个Hook,我们在Hook函数中使用this.checkPin()
即可调用原函数。
运行加载器,然后随意输几个数值测试,可以看到已经Hook成功了。
我们稍微修改一下Hook函数,让它把任何密码都当做正确的密码:
Java.perform(function() {
var cls = Java.use('infosecadventures.fridademo.utils.PinUtil');
//HookCheckPin函数
cls.checkPin.overload("java.lang.String").implementation = function(arg1) {
//打印入参
console.log('checkPin-in', arg1);
//直接返回true
return true;
}
});
这次我们不调用原函数,我们直接返回true
,相当于屏蔽了原函数。
随意输入的密码都被当成了正确密码。
在实战中这么做肯定是不行的,所以接下来我们改造一下,让它暴力破解密码。示例代码:
Java.perform(function() {
var cls = Java.use('infosecadventures.fridademo.utils.PinUtil');
//HookCheckPin函数
cls.checkPin.overload("java.lang.String").implementation = function(arg1) {
//函数进入
console.log('开始破解密码');
//暴力破解
for (var i = 0;; i++) {
var result = this.checkPin(i.toString());
if (result) {
console.log('破解完成,密码是', i)
break;
} else {
console.log('密码错误', i)
}
}
//返回给原函数的调用
return result;
}
});
运行加载器以后,点击PIN CHECK
开始破解密码。
稍等片刻即可看到破解结果。爆出的密码是4863
,经过Base64
编码以后就是NDg2Mw==
,与代码中的值是一样的。
绕过Root检测
我们定位到infosecadventures.fridademo.utils.RootUtil
类,可以看到实现原理是检测存不存在特征文件。
接下来我们尝试Hook这个函数,示例代码:
Java.perform(function() {
//获取指定类
var cls = Java.use('infosecadventures.fridademo.utils.RootUtil');
//Hook指定函数
cls.isRootAvailable.overload().implementation = function() {
//进入函数
console.log('isRootAvailable-in');
//调用原函数
var result = this.isRootAvailable();
//打印出参
console.log('isRootAvailable-out', result);
//返回给原函数的调用
return result;
}
});
点一下ROOT CHECK
,可以看到已经Hook成功了
接下来我们尝试绕过检测,有两种方法,可以直接屏蔽原函数,直接返回false
,也可以Hookexists
方法,让它在检测特征文件的时候,即使文件存在也返回false
。
第一种方法跟上面很像,不再赘述了,我们实现第二种方法,示例代码:
Java.perform(function() {
//获取指定类
var cls = Java.use('infosecadventures.fridademo.utils.RootUtil');
//Hook指定函数
cls.isRootAvailable.overload().implementation = function() {
//进入函数
console.log('isRootAvailable -in');
//调用原函数
var result = this.isRootAvailable();
//打印出参
console.log('isRootAvailable -out', result);
//返回给原函数的调用
return result;
}
var cls2 = Java.use('java.io.File');
//Hook指定函数
cls2.exists.overload().implementation = function() {
//进入函数
console.log('exists-in');
//获取自身的path
var mypath = this.getPath();
//root特征文件列表
var paths = new Array("/system/app/Superuser.apk", "/sbin/su",
"/system/bin/su", "/system/xbin/su",
"/data/local/xbin/su", "/data/local/bin/su",
"/system/sd/xbin/su", "/system/bin/failsafe/su",
"/data/local/su", "/su/bin/su");
//判断路径是否匹配root特征
var flag = false;
paths.forEach(function(path) {
if (mypath == path) {
console.log('检测到root特征文件', path);
flag = true;
return;
}
});
if (!flag) {
//调用原函数,避免影响正常功能
var result = this.exists();
} else {
//返回false,绕过root检测
result = false;
}
console.log('exists-out', result)
return result;
}
});
再点一下ROOT CHECK
,可以看到我们已经成功Hook了exists
方法,并且成功通过了Root检测。
加密函数破解
我们定位到infosecadventures.fridademo.utils.EncryptionUtil
类,可以看到他是一个AES加密算法,AES是一种对称加密算法,拿到密钥就可以解密了。
老样子,我们先HOOK加密函数,示例代码:
Java.perform(function() {
//获取指定类
var cls = Java.use('infosecadventures.fridademo.utils.EncryptionUtil');
//Hook指定函数
cls.encrypt.overload('java.lang.String', java.lang.String').implementation = function(arg1, arg2) {
//打印入参
console.log('encrypt-in', arg1, arg2);
//调用原函数
var result = this.encrypt(arg1, arg2);
//打印出参
console.log('encrypt-out', result);
//返回给原函数的调用
return result;
}
});
可以看到已经拿到了加密密钥infosecadventure
,通过它就可以对加密后的字符串解密了。
本文链接:https://blog.chrxw.com/archives/2020/07/18/1301.html
转载请保留本文链接,谢谢
1 条评论
发现bug,在代码框内行号盖住了里面的内容了。