【安卓逆向】Frida配置和简单hook

📅 2026/7/3 2:21:42
【安卓逆向】Frida配置和简单hook
1. Frida 安装机型小米6frida-server版本16.2.1下载列表https://github.com/frida/frida/releases?page13#release-16.2.1版本链接https://github.com/frida/frida/releases/download/16.2.1/frida-server-16.2.1-android-arm64.xz2. Python包安装# 要与frida版本一致pipinstallfrida16.2.1 frida-tools12.0.03. Frida配置和启动服务3.1 配置和启动服务# 1. 将本地的 frida-server 文件推送到手机的 /data/local/tmp/ 目录adb push frida-server-16.2.1-android-arm64 /data/local/tmp/# 2. 进入手机的交互式命令行界面shelladb shell# 3. 切换到 root 用户获取最高权限su# 4. 进入存放 frida-server 的目录cd/data/local/tmp# 5. 修改文件权限为 755即属主可读写执行同组和其他人只可读执行chmod755frida-server-16.2.1-android-arm64# 6. 在后台启动 frida-server./frida-server-16.2.1-android-arm643.2 关闭服务# 查看所有 frida 进程ps-A|grepfrida# 输出root 26472 ... frida-server-16.2.1-android-arm64# 方式1用完整名杀掉killallfrida-server-16.2.1-android-arm64# 方式2用 pkill 模糊匹配pkill-ffrida-server# 方式3用 PID 杀掉kill-926472# 验证是否已关闭ps-A|grepfrida# 没有输出 已关闭[1] Done表示后台进程已经正常结束。4. hook模板4.1 py部分importfridaimportsysdefon_message(message,data): 消息处理回调函数 当 JavaScript 脚本调用 send() 时会触发此函数 Args: message: 包含消息类型的字典 data: 附加数据 # 如果是 send 发送的消息ifmessage[type]send:print([*] {0}.format(message[payload]))# 如果是错误消息elifmessage[type]error:print([!] Error: {0}.format(message.get(stack,message)))# 读取 JavaScript Hook 脚本文件# moder: 只读模式# encodingutf-8: 指定编码格式支持中文withopen(lession2_test_hook_rps.js,moder,encodingutf-8)asf:test_jsf.read()# # 方式一启动应用之后附加Attach 模式# # 适用场景应用已经在运行需要动态注入try:# 获取通过 USB 连接的设备devicefrida.get_usb_device()print([*] Device: {0}.format(device))# 方法1通过 PID 附加推荐更稳定# windows下查看PID(adb shell ps -A | grep rock_paper_scissors)PID24201print([*] Attaching to PID: {0}.format(PID))sessiondevice.attach(PID)# 方法2通过包名附加需要应用在前台注释掉了因为我是用这种方法会失败# session device.attach(com.example.seccon2015.rock_paper_scissors)print([✓] Attached!)# 创建脚本对象将 JavaScript 代码加载到 Frida 中scriptsession.create_script(test_js)# 注册消息回调script.on(message,on_message)# 注入并执行脚本script.load()print([*] Script loaded! Click buttons on phone)print([*] Press CtrlC to exit)# 阻塞主线程保持脚本运行# 等待用户输入按任意键退出sys.stdin.read()exceptExceptionase:print([!] Error: {0}.format(e))# # 方式二启动阶段注入Spawn 模式- 注释掉的代码# # 适用场景应用未启动需要在启动时就注入## device frida.get_usb_device(-1) # -1 表示默认设备## # 启动应用暂停在启动状态# pid device.spawn([com.example.seccon2015.rock_paper_scissors])## # 附加到新启动的进程# process device.attach(pid)## # 创建并加载脚本# script process.create_script(test_js)# script.on(message, on_message)# print([*] Script loaded!)# script.load()## # 恢复应用执行spawn 后应用是暂停状态# device.resume(pid)## # 阻塞使 hook 脚本不退出# sys.stdin.read()4.2 js模板/** * Frida Hook 脚本模板 * 用于 Hook Android Java 方法 */// Java.perform 是 Frida 的入口函数确保代码在 Java 虚拟机上下文中执行Java.perform(function(){console.log(start hook...);// // 1. 获取目标类// // Java.use() 获取 Java 类的引用varMainActivityJava.use(com.example.seccon2015.rock_paper_scissors.MainActivity);// // 2. Hook 方法// // 重写 onClick 方法// v: 方法参数MainActivity.onClick.implementationfunction(v){// 调用原始方法Frida 会自动保留原始方法引用不会递归this.onClick(v);// TODO: 在这里添加 Hook 逻辑// 例如打印参数、修改返回值等};// // 3. Hook 内部类// // 内部类使用 $ 连接如 MainActivity$1varMainActivity1Java.use(com.example.seccon2015.rock_paper_scissors.MainActivity$1);// 重写 run 方法MainActivity1.run.implementationfunction(){// TODO: 在这里添加 Hook 逻辑// 调用原始方法this.run();};});4.3 js示例/** * Frida Hook 脚本 - 剪刀石头布游戏 * 目标通过修改 CPU 的出拳逻辑让玩家总是获胜 * 包名com.example.seccon2015.rock_paper_scissors */// Java.perform 是 Frida 的入口函数// 确保所有 Java 相关操作在 Java 虚拟机环境中执行Java.perform(function(){console.log(start hook...);// // 1. Hook MainActivity 的 onClick 方法玩家点击按钮时触发// // Java.use() 获取 Java 类的引用返回一个包装对象varMainActivityJava.use(com.example.seccon2015.rock_paper_scissors.MainActivity);/** * 重写 onClick 方法 * param {View} v - 被点击的按钮视图P/R/S 按钮 * * 注意这里不会发生递归 * this.onClick(v) 调用的是 Frida 内部保留的原始方法 * 而不是当前重写的 implementation这是 Frida 的设计机制 */MainActivity.onClick.implementationfunction(v){// 1. 调用原始 onClick 方法保证游戏正常运行// 包括记录玩家选择、生成 CPU 选择、触发延迟显示等this.onClick(v);// 2. 打印玩家的选择// m: 玩家的出拳 (0布 Paper, 1石头 Rock, 2剪刀 Scissors)console.log([Hook] m,this.m.value);// 3. 打印 CPU 的选择// n: CPU 的出拳 (0布 Paper, 1石头 Rock, 2剪刀 Scissors)console.log([Hook] n real,this.n.value);// // 4. 胜负判定与原始代码逻辑一致// 规则布(0) 石头(1) 剪刀(2) 布(0)// // 情况1CPU 出拳数值 - 玩家出拳数值 1// 例如玩家出布(0)CPU出石头(1) → 玩家赢// 玩家出石头(1)CPU出剪刀(2) → 玩家赢// 玩家出剪刀(2)CPU出布(0) → 此时差值为 -2不满足此条件if(this.n.value-this.m.value1){console.log([Hook] You win!);}// 情况2玩家出拳数值 - CPU 出拳数值 1// 例如玩家出石头(1)CPU出布(0) → CPU赢// 玩家出剪刀(2)CPU出石头(1) → CPU赢elseif(this.m.value-this.n.value1){console.log([Hook] You lose!);}// 情况3玩家和 CPU 出拳相同 → 平局elseif(this.m.valuethis.n.value){console.log([Hook] Draw!);}// 情况4玩家出拳数值小于 CPU 出拳数值// 例如玩家出布(0)CPU出剪刀(2) → CPU赢elseif(this.m.valuethis.n.value){console.log([Hook] You lose!);}// 情况5其他情况 → 玩家赢// 例如玩家出剪刀(2)CPU出布(0) → 玩家赢else{console.log([Hook] You win!);}};// // 2. Hook 内部类 MainActivity$1 的 run 方法// MainActivity$1 是 MainActivity 中的匿名内部类// 实现了 Runnable 接口用于延迟 1 秒后显示游戏结果// varMainActivity1Java.use(com.example.seccon2015.rock_paper_scissors.MainActivity$1);/** * 重写 run 方法 * 核心破解逻辑修改 CPU 的出拳让玩家总是赢 * * 注意这里同样不会发生递归 * this.run() 调用的是原始 run 方法不是重写后的 implementation */MainActivity1.run.implementationfunction(){console.log([Hook] run);// // 关键破解逻辑修改 CPU 的出拳// // this.this$0 是内部类持有的外部类 MainActivity 的引用// 通过 this.this$0 可以访问外部类的成员变量//// 将 CPU 的出拳(n) 设置为 玩家的出拳(m) 1// 使得 CPU 总是出会被玩家打败的选项// 玩家出布(0) → CPU出石头(1) → 布包裹石头 → 玩家赢// 玩家出石头(1) → CPU出剪刀(2) → 石头砸剪刀 → 玩家赢// 玩家出剪刀(2) → CPU出布(0) → 剪刀剪布 → 玩家赢// 注意当 m2 时m13但数组索引只有 0-2// 实际游戏中3 会被映射为剪刀 vs 布的逻辑需要看原始代码如何处理this.this$0.value.n.valuethis.this$0.value.m.value1;console.log([Hook] n change,this.this$0.value.n.value);// 调用原始的 run 方法显示游戏结果// 此时 n 已经被修改所以显示的结果是玩家赢this.run();};});感谢关注【遇事不決洛必達】欢迎点赞收藏和交流指正我会持续分享我的学习经验和心得。