前言 这几天重操旧业开始日某个APP，Frida已经更新到15了，更新的时候遇到了一些问题 正文 直接 pip install frida 会报错，错误如图 其实就是需要手动下载一个egg包放在用户目录即可，可以在PyPI上下载到 可是没有预编译的适用于Python3.9的egg包，只好自己编译了 下载最上面的 Source Distribution 源码包，解压以后手动编译 # 构建 python setup.py build # 编译egg包 python setup.py bdist_egg 然后又报错了…… 这里出错是因为参数path是二进制字符串而不是普通的字符串，需要做一个decode操作转成普通字符串即可 在setup.py第191行加上.decode('utf-8')即可 改完就可以顺利编译了，编译好的egg文件在dist目录下，把这个文件拷到用户根目录，比如C:\Users\chr11 然后用pip安装即可 pip install frida frida-tools 然后就可以愉快地hook了
前言 本文仅供学习探讨之用，如果侵犯您的权益请联系我删除。 上文 [post cid="714"/] [post cid="1347"/] [post cid="1365"/] [post cid="1383"/] 前情提要 距离上一篇文章已经过去一年多了，前两天心血来潮找了最新版本研究了一下，熬了两天夜也没整明白，不打算继续研究下去了，做个笔记记录下分析过程。 @> 本文适用于小黑盒 1.3.191，小黑盒历史版本参考我的仓库 @> 全文评论后可见
前言 一些链接： Frida：主页链接 GitHub Frida-Demo：GitHub 本文使用的工程文件：GitHub (包含项目源码和用到的工具) 准备工作 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作为开发环境，配置方法参见另一篇博文：[post cid="1296" /] 也可以选择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，通过它就可以对加密后的字符串解密了。
前言 本文仅供学习探讨之用，如果侵犯您的权益请联系我删除。 [...]