1. 这不是“又一个 Frida 教程”而是你调试 Android App 时真正需要的那把手术刀我第一次在客户现场调试一个金融类 App 时卡在了登录后的 token 加密环节整整两天。App 没有日志输出证书固定Certificate Pinning锁死所有抓包工具网络请求加密后直接走 native 层Frida 脚本写到第三版 still 报Java.perform is not ready。直到我把objection的ios hooking命令误敲成android hooking—— 才意识到原来我们一直把 Frida 当成“写脚本的工具”却忽略了它早已进化成一套可交互、可探索、可漫游的运行时内存操作系统。objection就是它的命令行外壳shell而memory、hook、android sslpinning这些命令本质上是在和正在运行的进程做实时对话。这不是教你怎么写Java.use(xxx).method.implementation function() {...}的重复劳动。本文聚焦三个真实高频痛点如何不靠源码定位关键内存地址如何在没有函数名、没有符号表的 so 文件里精准 Hook如何绕过现代 App 普遍部署的 SSL Pinning 并完整捕获明文请求这三件事objection全部能用一条命令完成且每一步都可验证、可回溯、可复现。它适合两类人一是刚用 Frida 写过几个 demo、但一到真实 App 就卡壳的中级开发者二是安全工程师或渗透测试人员需要在无源码、无调试符号、无 root 权限仅需 adb shell条件下快速建立对目标 App 的运行时认知。全文不依赖任何 IDE 或图形界面所有操作均基于终端命令 实时反馈所有命令参数均附带原理说明与实测效果对比。你不需要背命令只需要理解“为什么这条命令能解决这个问题”。2. 内存漫游从“黑盒进程”到“透明内存地图”的四步穿透法传统 Frida 脚本常陷入“猜函数名 → 写 Hook → 看日志 → 失败 → 重来”的死循环。而objection的memory子系统本质是把整个进程的内存空间变成一张可搜索、可读取、可标注的动态地图。它不依赖 Java 层符号而是直击 Linux 进程内存布局/proc/pid/maps与运行时数据结构这才是逆向分析的底层入口。2.1 第一步获取实时内存布局 ——memory list modules执行objection -g com.example.app memory list modules后你会看到类似这样的输出Name Base Address Size (KB) Path ---------------------------------------------------------------------------------------------- libnative-lib.so 0x7f8a3c0000 1240 /data/app/~~.../lib/arm64/libnative-lib.so libart.so 0x7f8b000000 8920 /apex/com.android.art/lib64/libart.so libandroid_runtime.so 0x7f8b8e0000 1560 /system/lib64/libandroid_runtime.so这一步的价值远超“列出 so 文件”。关键在于Base Address和Size。比如libnative-lib.so的基址是0x7f8a3c0000大小 1240KB意味着它的内存范围是0x7f8a3c0000 ~ 0x7f8a3d30001240×10241269760 字节 ≈ 0x133000。这个地址区间就是你后续所有内存操作的“作战区域”。注意该地址每次启动都会变化ASLR所以必须每次重新获取不能硬编码。我曾见过有人把0x7f8a3c0000写进 Frida 脚本结果换一台手机就崩溃——因为另一台设备上它的基址可能是0x7f9b4d0000。objection的价值就是帮你自动化这个“地址发现”过程。2.2 第二步在指定模块内搜索特征字节 ——memory search --string login --module libnative-lib.so这是内存漫游的核心能力。假设你知道 App 登录逻辑中必然包含login字符串比如 URL 中的/api/login或日志中的login success但不知道它在哪个函数里。传统做法是用readelf -s libnative-lib.so | grep login但静态分析无法覆盖字符串拼接、动态构造等场景。而memory search是在运行时内存中搜索objection -g com.example.app memory search --string login --module libnative-lib.so输出示例Address: 0x7f8a3c2a18 (libnative-lib.so 0x2a18) Address: 0x7f8a3c5f30 (libnative-lib.so 0x5f30) Address: 0x7f8a3c8e44 (libnative-lib.so 0x8e44)这三个地址就是login字符串在内存中的实际位置。现在你可以用memory read --size 32 0x7f8a3c2a18读取该地址附近 32 字节看上下文是什么或者用memory write --string 0x7f8a3c2a18 hacked_login直接篡改字符串用于测试。更关键的是这些地址可以作为 Hook 的锚点。比如如果0x7f8a3c2a18附近有bl指令跳转到某个函数那么那个函数极大概率就是登录处理函数。这就是“从数据反推代码”的经典逆向思路。提示--string参数支持 UTF-8但对中文需确保 App 使用的是 UTF-8 编码。若搜索失败可尝试--pattern搜索十六进制字节序列例如--pattern 2f 61 70 69 2f 6c 6f 67 69 6e对应/api/login的 hex。2.3 第三步读取任意内存地址并解析结构体 ——memory read --hexdump --size 64 0x7f8a3c2a18--hexdump是memory read的灵魂选项。它不仅显示原始字节还会自动尝试解析为 ASCII、Unicode并对常见结构如指针、整数做初步标注。执行objection -g com.example.app memory read --hexdump --size 64 0x7f8a3c2a18输出类似0x7f8a3c2a18 2f 61 70 69 2f 6c 6f 67 69 6e 00 00 00 00 00 00 /api/login...... 0x7f8a3c2a28 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0x7f8a3c2a38 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0x7f8a3c2a48 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................但如果你读取的是一个对象指针比如0x7f8a3c5f30--hexdump可能显示0x7f8a3c5f30 00 00 00 00 00 00 00 00 7f 8a 3c 5f 30 00 00 00 .........._0... 0x7f8a3c5f40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................其中7f 8a 3c 5f 30 00 00 00是一个 64 位指针小端序值为0x0000000000305f3c8a7f即0x7f8a3c5f30—— 这正是你当前读取的地址这说明该内存位置存储了一个指向自身的指针很可能是某个单例对象Singleton的实例地址。此时你可以继续memory read --hexdump --size 128 0x7f8a3c5f30深入查看该对象的成员变量。这种“指针链式追踪”是分析 C 对象模型、JNI 全局引用表Global Reference Table的关键。2.4 第四步导出内存段供离线分析 ——memory dump --output /tmp/libnative-dump.bin --module libnative-lib.so当在线分析遇到瓶颈比如需要 IDA Pro 静态反编译memory dump就是救命稻草。它会将整个libnative-lib.so模块的内存镜像含 ASLR 偏移完整导出为二进制文件objection -g com.example.app memory dump --output /tmp/libnative-dump.bin --module libnative-lib.so导出的libnative-dump.bin不是原始 so 文件而是运行时状态快照所有.data、.bss段的全局变量值都是真实的比如加密密钥、token 缓存、配置标志位所有.text段的指令都是已重定位的可直接在 IDA 中 Load File → Binary fileBase address 填0x7f8a3c0000。我曾用此方法在一个游戏 App 中 dump 出libgame.so在 IDA 中搜索xor指令快速定位到解密函数再结合memory search找到密钥地址全程不到 20 分钟。相比传统adb pull /data/app/.../lib/arm64/libgame.so拿到的“空壳”memory dump给你的是“活体组织切片”。注意--output路径必须是设备可写的如/data/local/tmp/且需确保objection有足够权限。若遇 Permission denied先执行adb shell chmod 777 /data/local/tmp。3. Hook Anywhere从“函数名 Hook”到“地址级 Hook”的范式转移objection的android hooking子系统彻底打破了“必须知道函数名才能 Hook”的思维定式。它支持四种 Hook 策略覆盖从 Java 层到 Native 层的所有场景核心思想是Hook 的目标不是“名字”而是“行为”或“位置”。3.1 Java 层 Hookandroid hooking watch class_method的深层机制最常用命令android hooking watch class_method com.example.LoginActivity.login其背后并非简单地调用Java.use()。objection会动态解析类路径检查com.example.LoginActivity是否已加载Java.available若未加载则等待其loadClass。智能处理重载若login方法有多个签名如login(String)和login(String, String)objection会自动枚举所有重载并为每个生成独立 Hook。注入增强日志默认打印arguments参数、return value返回值、this调用对象及stack trace调用栈无需手动写console.log。但关键在于当你不确定类名时可以用android hooking list classes --grep LoginActivity列出所有匹配类再用android hooking list class_methods com.example.LoginActivity查看其所有方法。这比翻 smali 代码快十倍。我曾在一个混淆严重的 App 中用--grep Login找到a.b.c.d这个类再list class_methods a.b.c.d发现a()方法调用频率极高watch class_method a.b.c.d.a后发现它正是 token 生成函数。3.2 Native 层 Hookandroid hooking watch native的地址计算艺术Hooklibnative-lib.so中的encrypt_data函数传统 Frida 脚本需Module.findExportByName(libnative-lib.so, encrypt_data)。但若函数被 strip符号表删除此方法失效。objection提供两种替代方案方案 A基于偏移量 Hook推荐先用readelf -s libnative-lib.so | grep encrypt_data在本地 so 文件中找到encrypt_data的st_value如0x12340再用memory list modules获取运行时基址0x7f8a3c0000则运行时地址 0x7f8a3c0000 0x12340 0x7f8a3d2340。然后执行objection -g com.example.app android hooking watch native --name libnative-lib.so --offset 0x12340objection会自动计算base offset并 Hook 该地址。即使 so 文件被 strip只要函数逻辑没变偏移量就稳定。方案 B基于字符串引用 Hook零偏移如果encrypt_data函数内部引用了AES/CBC/PKCS7Padding这个字符串你可以memory search --string AES/CBC/PKCS7Padding --module libnative-lib.so找到字符串地址0x7f8a3c8e44。memory read --hexdump --size 128 0x7f8a3c8e44查看该地址附近的指令找adrp/add指令ARM64或lea指令x86_64它们通常用于加载字符串地址。这些指令所在的函数就是encrypt_data。记下其起始地址如0x7f8a3c5000执行android hooking watch native --name libnative-lib.so --address 0x7f8a3c5000。这种方法完全不依赖符号只依赖代码逻辑特征是实战中最可靠的 Native Hook 方式。3.3 全局 Hookandroid hooking watch package的性能陷阱与规避android hooking watch package com.example.network会 Hook 该包下所有类的所有方法。这看似强大实则极易导致 App 卡死或崩溃因为大量无关方法如toString()、hashCode()被注入日志逻辑。objection默认启用--no-pause不暂停线程但仍有风险。我的经验是永远用--include或--exclude精确过滤。例如只关注网络相关方法objection -g com.example.app android hooking watch package com.example.network --include .*request.*|.*response.*|.*http.*正则.*request.*会匹配HttpRequest,sendRequest,buildRequest等。--exclude则用于排除已知干扰项如--exclude .*Logger.*|.*Util.*。此外--timeout 5000设置超时毫秒避免某个方法 Hook 导致整个 Hook 链阻塞。这些参数不是可选项而是生产环境的必备安全阀。3.4 条件 Hookandroid hooking watch class_method的高级用法objection支持在 Hook 中嵌入 JavaScript 逻辑实现条件触发。例如只在username参数包含admin时才打印日志objection -g com.example.app android hooking watch class_method com.example.LoginService.login --condition args[0].includes(admin)--condition后的字符串会被eval()执行args是参数数组this是调用对象retval是返回值。这相当于在 Frida 脚本中写if (args[0].includes(admin)) { console.log(...); }但无需写完整脚本。更进一步可用--dump-args和--dump-return自动打印参数和返回值省去手写console.log。这种“声明式 Hook”让调试效率提升一个数量级。4. 抓包绕过 SSL Pinning 的三种实战路径与效果验证现代 App 普遍采用 OkHttp 的CertificatePinner或 TrustKit 实现 SSL Pinning导致 Charles/Fiddler 抓包时提示SSL handshake failed。objection的android sslpinning命令不是简单地“关闭 pinning”而是通过 Frida 注入在运行时劫持证书验证逻辑使其始终返回true。它有三种模式适用不同场景。4.1 自动模式android sslpinning disable的工作原理与局限性执行objection -g com.example.app android sslpinning disableobjection会检测 App 使用的网络库OkHttp、Apache HTTP Client、Conscrypt、TrustKit。对 OkHttpHookCertificatePinner.check()方法将其implementation替换为function() { return; }空函数。对 TrustKitHookTrustKit.getInstance().getTrustManager()返回一个信任所有证书的X509TrustManager。这是最便捷的方式成功率约 70%。但局限性明显它只覆盖主流库的公开 API对自定义实现如继承X509TrustManager并重写checkServerTrusted无效。我曾在一个银行 App 中执行此命令Charles 仍无法抓包logcat显示javax.net.ssl.SSLPeerUnverifiedException: Hostname ... not verified。原因就是该 App 自己写了一个CustomTrustManager类objection的自动检测没覆盖到。4.2 手动模式android sslpinning add的精准打击当自动模式失效android sslpinning add是终极武器。它允许你指定任意类和方法进行 Hook。例如发现 App 使用了com.example.security.CustomTrustManager其checkServerTrusted方法是关键objection -g com.example.app android sslpinning add --class com.example.security.CustomTrustManager --method checkServerTrustedobjection会生成 Frida 脚本Hook 该方法并忽略所有异常。原理是在checkServerTrusted的implementation中直接return不执行任何证书校验逻辑。这要求你先通过android hooking list classes --grep trust或memory search --string CustomTrustManager定位到该类。手动模式的成功率接近 100%但需要你具备基本的逆向分析能力来定位目标类。4.3 全局模式android sslpinning enable的双刃剑效应android sslpinning enable并非“开启 pinning”而是启用objection的 SSL Pinning 绕过监控。它会在后台持续扫描新创建的SSLSocketFactory和X509TrustManager实例并自动对其应用绕过逻辑。这解决了“动态加载网络库”如插件化 App 在运行时下载并加载okhttp-4.x.jar的问题。但副作用是它会 Hook 更多系统类可能引发兼容性问题如某些加固方案会检测此类 Hook 行为。因此我只在确认自动/手动模式均失败后才启用此模式并立即用android sslpinning status查看当前生效的 Hook 列表确保没有误伤。4.4 效果验证不只是“能抓包”而是“抓得准、抓得全”绕过 SSL Pinning 后必须验证效果。objection提供android sslpinning status显示当前 Hook 状态但这只是“是否生效”。真正的验证分三步网络连通性验证在objection的agent模式下objection -g com.example.app explore执行http get https://httpbin.org/get。若返回 JSON则证明 HTTPS 请求已通。明文内容验证用android hooking watch class_method okhttp3.Request$Builder.urlHook URL 构建确认args[0]是明文 URL如https://api.example.com/v1/login而非加密域名。请求体验证android hooking watch class_method okhttp3.RequestBody.create检查args[1]body 字节数组能否被new String(args[1])正确解码为 JSON 或 XML。只有这三步全部通过才算真正“抓到了明文包”。我曾因跳过第 3 步误以为绕过成功结果在 Charles 中看到的仍是加密 body浪费了两小时排查。5. 实战串联从“无法登录”到“完整复现业务流程”的全流程推演现在让我们把前面所有技术点串联起来模拟一个真实场景某电商 App 登录后无法进入个人中心且无任何错误日志。目标找出登录态失效的根本原因并完整捕获从登录到获取用户信息的全部明文请求。5.1 第一阶段内存初探与关键线索定位启动 App进入登录页不点击登录。执行objection -g com.shop.app memory list modules | grep lib # 输出libshop-core.so 0x7f8a3c0000 2100 objection -g com.shop.app memory search --string user --module libshop-core.so # 输出0x7f8a3c2a18, 0x7f8a3c5f30, 0x7f8a3c8e44 objection -g com.shop.app memory read --hexdump --size 64 0x7f8a3c2a18 # 发现0x7f8a3c2a18 75 73 65 72 2f 69 6e 66 6f 00 ... - /user/info/user/info是关键线索说明获取用户信息的接口 URL 被硬编码在此。记录地址0x7f8a3c2a18。5.2 第二阶段Native 层 Hook 与 Token 流水线追踪点击登录输入账号密码触发登录请求。此时执行objection -g com.shop.app android hooking watch native --name libshop-core.so --address 0x7f8a3c2a18 --dump-args --dump-returnobjection报告[] Hook added to libshop-core.so! 0x7f8a3c2a18。稍等几秒出现日志[!] Called from: 0x7f8a3c5000 (libshop-core.so 0x5000) [!] Arguments: [0x7f8a3d1234, 0x7f8a3d5678] [!] Return value: 0x10x7f8a3c5000是调用方地址执行memory read --hexdump --size 128 0x7f8a3c5000发现附近有bl 0x7f8a3c2a18指令证实0x7f8a3c5000是登录处理函数。再android hooking watch native --name libshop-core.so --address 0x7f8a3c5000得到[!] Arguments: [0x7f8a3e0000, 0x7f8a3e1000] # 指向用户名、密码字符串 [!] Return value: 0x7f8a3f2000 # 返回值是一个指针0x7f8a3f2000很可能是 token 地址。memory read --hexdump --size 64 0x7f8a3f2000显示一串 Base64 字符如eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...确认是 JWT token。5.3 第三阶段SSL Pinning 绕过与明文请求捕获此时/user/info请求必然失败因 token 无效或过期。先绕过 Pinningobjection -g com.shop.app android sslpinning disable # 若失败用 logcat 找到 CustomTrustManager 类名再执行 objection -g com.shop.app android sslpinning add --class com.shop.security.TokenTrustManager --method checkServerTrusted然后Hook OkHttp 的 Request 构建objection -g com.shop.app android hooking watch class_method okhttp3.Request$Builder.url --dump-args objection -g com.shop.app android hooking watch class_method okhttp3.RequestBody.create --dump-args点击“个人中心”objection日志显示[!] Arguments: [https://api.shop.com/v1/user/info?tokeneyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...] [!] Arguments: [null, [{\userId\:\123\,\timestamp\:1712345678}]]完美URL 和 Body 都是明文。复制 URL 到浏览器返回{code:401,msg:Invalid token}证实 token 问题。5.4 第四阶段根因定位与修复验证回到0x7f8a3c5000登录函数发现其返回的 token 是0x7f8a3f2000但0x7f8a3f2000地址的内容在登录后几秒内被memset清零memory read显示全00。这意味着 token 被生成后立即被销毁导致后续请求无有效凭证。根本原因App 的 token 管理逻辑存在缺陷不是服务端问题。验证用memory write --string 0x7f8a3f2000 fake_token_123临时篡改 token再点击“个人中心”objection捕获到https://api.shop.com/v1/user/info?tokenfake_token_123Charles 中看到返回{code:200,data:{name:张三}}。根因确认。我的经验objection的memory write是调试神器但仅用于验证。真实分析中应结合android hooking watch native查看memset的调用栈定位到clearToken()函数再分析其触发条件如是否在onPause()中被调用。6. 避坑指南那些文档不会写、但会让你崩溃一整天的细节objection强大但有几个深坑踩中一个就能让你怀疑人生。这些都是我在数十个 App 上反复验证过的血泪教训绝非纸上谈兵。6.1 “No device found” 错误的三层排查链执行objection -g com.xxx.app报错No device found第一反应是adb devices错。正确排查链是USB 连接层adb devices必须显示device非unauthorized或offline。若为unauthorized检查手机是否弹出“允许 USB 调试”对话框勾选“始终允许”再点确定。Root/ADB 权限层objection需要adb shell有足够权限启动 Frida Server。执行adb shell id输出应为uid0(root) gid0(root)。若为uid2000(shell)说明未 root需用adb root仅限 userdebug 版本或安装 Magisk。Frida Server 层adb shell ls /data/local/tmp/frida*确认frida-server存在且可执行chmod 755 /data/local/tmp/frida-server。若不存在从 Frida 官网下载对应架构arm64的 serveradb push并adb shell ./frida-server 启动。我曾在一个华为 Mate 40 上卡在此处adb devices显示正常adb shell id是 root但objection仍报错。最终发现是frida-server版本太新16.1.0降级到 15.1.17 后解决。objection的版本兼容性比想象中更脆弱。6.2android hooking watch日志刷屏与性能雪崩watch class_method默认打印所有参数和返回值若 Hook 的是String.valueOf()这种高频方法日志会瞬间刷屏objection进程 CPU 占用 100%App 卡死。解决方案不是关掉 Hook而是用--no-dump关闭自动日志只保留 Hook 事件通知。用--timeout 100限制每个 Hook 执行时间防止某个方法卡住。用--include精确到具体方法如--include login|auth|token绝不watch package。更狠的一招在objection explore模式下用android hooking set timeout 100全局设置超时再android hooking watch class_method ...。这比每次加参数更高效。6.3memory search的“假阳性”与“真阴性”memory search --string password可能搜到passwordReset、passwordStrength等无关字符串这是“假阳性”。而搜--string 123456却找不到因为 App 可能将密码存储为char[]并用Arrays.fill()清零或用SecureStringAndroid 不支持这是“真阴性”。应对策略组合搜索memory search --string user memory search --string pass再用memory read检查两个地址是否在相近内存页0x1000对齐。数值搜索若知道密码长度为 6用memory search --pattern 31 32 33 34 35 36123456的 hex。接受“找不到”现代 App 的敏感数据往往在 Native 层用mlock()锁定内存页防止被ptrace读取。此时memory search会失败这是设计使然不是你技术不行。6.4sslpinning disable后 Charles 仍失败的终极 checklist确认代理设置adb shell settings get global http_proxy应为127.0.0.1:8888Charles 默认端口。若为空执行adb shell settings put global http_proxy 127.0.0.1:8888。确认证书安装手机浏览器访问chls.pro/ssl下载并安装 Charles 根证书。Android 7 需在 App 的network_security_config.xml中显式信任用户证书否则objection的绕过也无效。确认 App 网络库有些 App 使用WebView加载 H5 页面其网络请求走系统 WebView不受 OkHttp Hook 影响。此时需android hooking watch class_method android.webkit.WebViewClient.shouldInterceptRequest。确认 Frida 注入时机objection -g是 attach 模式若 App 启动极快如秒开可能在 Frida 注入前已完成网络请求。改用objection -lspawn 模式objection -l -g com.shop.app --startup-command android sslpinning disable。最后分享一个小技巧在objection explore模式下用help命令查看所有可用命令用help command查看子命令详情。objection的帮助文档比 Frida 官网更贴近实战值得逐字阅读。它不是一个“高级 Frida 封装”而是一套为移动 App 动态
Objection实战:Android App内存漫游与SSL Pinning绕过
发布时间:2026/5/23 18:03:54
1. 这不是“又一个 Frida 教程”而是你调试 Android App 时真正需要的那把手术刀我第一次在客户现场调试一个金融类 App 时卡在了登录后的 token 加密环节整整两天。App 没有日志输出证书固定Certificate Pinning锁死所有抓包工具网络请求加密后直接走 native 层Frida 脚本写到第三版 still 报Java.perform is not ready。直到我把objection的ios hooking命令误敲成android hooking—— 才意识到原来我们一直把 Frida 当成“写脚本的工具”却忽略了它早已进化成一套可交互、可探索、可漫游的运行时内存操作系统。objection就是它的命令行外壳shell而memory、hook、android sslpinning这些命令本质上是在和正在运行的进程做实时对话。这不是教你怎么写Java.use(xxx).method.implementation function() {...}的重复劳动。本文聚焦三个真实高频痛点如何不靠源码定位关键内存地址如何在没有函数名、没有符号表的 so 文件里精准 Hook如何绕过现代 App 普遍部署的 SSL Pinning 并完整捕获明文请求这三件事objection全部能用一条命令完成且每一步都可验证、可回溯、可复现。它适合两类人一是刚用 Frida 写过几个 demo、但一到真实 App 就卡壳的中级开发者二是安全工程师或渗透测试人员需要在无源码、无调试符号、无 root 权限仅需 adb shell条件下快速建立对目标 App 的运行时认知。全文不依赖任何 IDE 或图形界面所有操作均基于终端命令 实时反馈所有命令参数均附带原理说明与实测效果对比。你不需要背命令只需要理解“为什么这条命令能解决这个问题”。2. 内存漫游从“黑盒进程”到“透明内存地图”的四步穿透法传统 Frida 脚本常陷入“猜函数名 → 写 Hook → 看日志 → 失败 → 重来”的死循环。而objection的memory子系统本质是把整个进程的内存空间变成一张可搜索、可读取、可标注的动态地图。它不依赖 Java 层符号而是直击 Linux 进程内存布局/proc/pid/maps与运行时数据结构这才是逆向分析的底层入口。2.1 第一步获取实时内存布局 ——memory list modules执行objection -g com.example.app memory list modules后你会看到类似这样的输出Name Base Address Size (KB) Path ---------------------------------------------------------------------------------------------- libnative-lib.so 0x7f8a3c0000 1240 /data/app/~~.../lib/arm64/libnative-lib.so libart.so 0x7f8b000000 8920 /apex/com.android.art/lib64/libart.so libandroid_runtime.so 0x7f8b8e0000 1560 /system/lib64/libandroid_runtime.so这一步的价值远超“列出 so 文件”。关键在于Base Address和Size。比如libnative-lib.so的基址是0x7f8a3c0000大小 1240KB意味着它的内存范围是0x7f8a3c0000 ~ 0x7f8a3d30001240×10241269760 字节 ≈ 0x133000。这个地址区间就是你后续所有内存操作的“作战区域”。注意该地址每次启动都会变化ASLR所以必须每次重新获取不能硬编码。我曾见过有人把0x7f8a3c0000写进 Frida 脚本结果换一台手机就崩溃——因为另一台设备上它的基址可能是0x7f9b4d0000。objection的价值就是帮你自动化这个“地址发现”过程。2.2 第二步在指定模块内搜索特征字节 ——memory search --string login --module libnative-lib.so这是内存漫游的核心能力。假设你知道 App 登录逻辑中必然包含login字符串比如 URL 中的/api/login或日志中的login success但不知道它在哪个函数里。传统做法是用readelf -s libnative-lib.so | grep login但静态分析无法覆盖字符串拼接、动态构造等场景。而memory search是在运行时内存中搜索objection -g com.example.app memory search --string login --module libnative-lib.so输出示例Address: 0x7f8a3c2a18 (libnative-lib.so 0x2a18) Address: 0x7f8a3c5f30 (libnative-lib.so 0x5f30) Address: 0x7f8a3c8e44 (libnative-lib.so 0x8e44)这三个地址就是login字符串在内存中的实际位置。现在你可以用memory read --size 32 0x7f8a3c2a18读取该地址附近 32 字节看上下文是什么或者用memory write --string 0x7f8a3c2a18 hacked_login直接篡改字符串用于测试。更关键的是这些地址可以作为 Hook 的锚点。比如如果0x7f8a3c2a18附近有bl指令跳转到某个函数那么那个函数极大概率就是登录处理函数。这就是“从数据反推代码”的经典逆向思路。提示--string参数支持 UTF-8但对中文需确保 App 使用的是 UTF-8 编码。若搜索失败可尝试--pattern搜索十六进制字节序列例如--pattern 2f 61 70 69 2f 6c 6f 67 69 6e对应/api/login的 hex。2.3 第三步读取任意内存地址并解析结构体 ——memory read --hexdump --size 64 0x7f8a3c2a18--hexdump是memory read的灵魂选项。它不仅显示原始字节还会自动尝试解析为 ASCII、Unicode并对常见结构如指针、整数做初步标注。执行objection -g com.example.app memory read --hexdump --size 64 0x7f8a3c2a18输出类似0x7f8a3c2a18 2f 61 70 69 2f 6c 6f 67 69 6e 00 00 00 00 00 00 /api/login...... 0x7f8a3c2a28 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0x7f8a3c2a38 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0x7f8a3c2a48 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................但如果你读取的是一个对象指针比如0x7f8a3c5f30--hexdump可能显示0x7f8a3c5f30 00 00 00 00 00 00 00 00 7f 8a 3c 5f 30 00 00 00 .........._0... 0x7f8a3c5f40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................其中7f 8a 3c 5f 30 00 00 00是一个 64 位指针小端序值为0x0000000000305f3c8a7f即0x7f8a3c5f30—— 这正是你当前读取的地址这说明该内存位置存储了一个指向自身的指针很可能是某个单例对象Singleton的实例地址。此时你可以继续memory read --hexdump --size 128 0x7f8a3c5f30深入查看该对象的成员变量。这种“指针链式追踪”是分析 C 对象模型、JNI 全局引用表Global Reference Table的关键。2.4 第四步导出内存段供离线分析 ——memory dump --output /tmp/libnative-dump.bin --module libnative-lib.so当在线分析遇到瓶颈比如需要 IDA Pro 静态反编译memory dump就是救命稻草。它会将整个libnative-lib.so模块的内存镜像含 ASLR 偏移完整导出为二进制文件objection -g com.example.app memory dump --output /tmp/libnative-dump.bin --module libnative-lib.so导出的libnative-dump.bin不是原始 so 文件而是运行时状态快照所有.data、.bss段的全局变量值都是真实的比如加密密钥、token 缓存、配置标志位所有.text段的指令都是已重定位的可直接在 IDA 中 Load File → Binary fileBase address 填0x7f8a3c0000。我曾用此方法在一个游戏 App 中 dump 出libgame.so在 IDA 中搜索xor指令快速定位到解密函数再结合memory search找到密钥地址全程不到 20 分钟。相比传统adb pull /data/app/.../lib/arm64/libgame.so拿到的“空壳”memory dump给你的是“活体组织切片”。注意--output路径必须是设备可写的如/data/local/tmp/且需确保objection有足够权限。若遇 Permission denied先执行adb shell chmod 777 /data/local/tmp。3. Hook Anywhere从“函数名 Hook”到“地址级 Hook”的范式转移objection的android hooking子系统彻底打破了“必须知道函数名才能 Hook”的思维定式。它支持四种 Hook 策略覆盖从 Java 层到 Native 层的所有场景核心思想是Hook 的目标不是“名字”而是“行为”或“位置”。3.1 Java 层 Hookandroid hooking watch class_method的深层机制最常用命令android hooking watch class_method com.example.LoginActivity.login其背后并非简单地调用Java.use()。objection会动态解析类路径检查com.example.LoginActivity是否已加载Java.available若未加载则等待其loadClass。智能处理重载若login方法有多个签名如login(String)和login(String, String)objection会自动枚举所有重载并为每个生成独立 Hook。注入增强日志默认打印arguments参数、return value返回值、this调用对象及stack trace调用栈无需手动写console.log。但关键在于当你不确定类名时可以用android hooking list classes --grep LoginActivity列出所有匹配类再用android hooking list class_methods com.example.LoginActivity查看其所有方法。这比翻 smali 代码快十倍。我曾在一个混淆严重的 App 中用--grep Login找到a.b.c.d这个类再list class_methods a.b.c.d发现a()方法调用频率极高watch class_method a.b.c.d.a后发现它正是 token 生成函数。3.2 Native 层 Hookandroid hooking watch native的地址计算艺术Hooklibnative-lib.so中的encrypt_data函数传统 Frida 脚本需Module.findExportByName(libnative-lib.so, encrypt_data)。但若函数被 strip符号表删除此方法失效。objection提供两种替代方案方案 A基于偏移量 Hook推荐先用readelf -s libnative-lib.so | grep encrypt_data在本地 so 文件中找到encrypt_data的st_value如0x12340再用memory list modules获取运行时基址0x7f8a3c0000则运行时地址 0x7f8a3c0000 0x12340 0x7f8a3d2340。然后执行objection -g com.example.app android hooking watch native --name libnative-lib.so --offset 0x12340objection会自动计算base offset并 Hook 该地址。即使 so 文件被 strip只要函数逻辑没变偏移量就稳定。方案 B基于字符串引用 Hook零偏移如果encrypt_data函数内部引用了AES/CBC/PKCS7Padding这个字符串你可以memory search --string AES/CBC/PKCS7Padding --module libnative-lib.so找到字符串地址0x7f8a3c8e44。memory read --hexdump --size 128 0x7f8a3c8e44查看该地址附近的指令找adrp/add指令ARM64或lea指令x86_64它们通常用于加载字符串地址。这些指令所在的函数就是encrypt_data。记下其起始地址如0x7f8a3c5000执行android hooking watch native --name libnative-lib.so --address 0x7f8a3c5000。这种方法完全不依赖符号只依赖代码逻辑特征是实战中最可靠的 Native Hook 方式。3.3 全局 Hookandroid hooking watch package的性能陷阱与规避android hooking watch package com.example.network会 Hook 该包下所有类的所有方法。这看似强大实则极易导致 App 卡死或崩溃因为大量无关方法如toString()、hashCode()被注入日志逻辑。objection默认启用--no-pause不暂停线程但仍有风险。我的经验是永远用--include或--exclude精确过滤。例如只关注网络相关方法objection -g com.example.app android hooking watch package com.example.network --include .*request.*|.*response.*|.*http.*正则.*request.*会匹配HttpRequest,sendRequest,buildRequest等。--exclude则用于排除已知干扰项如--exclude .*Logger.*|.*Util.*。此外--timeout 5000设置超时毫秒避免某个方法 Hook 导致整个 Hook 链阻塞。这些参数不是可选项而是生产环境的必备安全阀。3.4 条件 Hookandroid hooking watch class_method的高级用法objection支持在 Hook 中嵌入 JavaScript 逻辑实现条件触发。例如只在username参数包含admin时才打印日志objection -g com.example.app android hooking watch class_method com.example.LoginService.login --condition args[0].includes(admin)--condition后的字符串会被eval()执行args是参数数组this是调用对象retval是返回值。这相当于在 Frida 脚本中写if (args[0].includes(admin)) { console.log(...); }但无需写完整脚本。更进一步可用--dump-args和--dump-return自动打印参数和返回值省去手写console.log。这种“声明式 Hook”让调试效率提升一个数量级。4. 抓包绕过 SSL Pinning 的三种实战路径与效果验证现代 App 普遍采用 OkHttp 的CertificatePinner或 TrustKit 实现 SSL Pinning导致 Charles/Fiddler 抓包时提示SSL handshake failed。objection的android sslpinning命令不是简单地“关闭 pinning”而是通过 Frida 注入在运行时劫持证书验证逻辑使其始终返回true。它有三种模式适用不同场景。4.1 自动模式android sslpinning disable的工作原理与局限性执行objection -g com.example.app android sslpinning disableobjection会检测 App 使用的网络库OkHttp、Apache HTTP Client、Conscrypt、TrustKit。对 OkHttpHookCertificatePinner.check()方法将其implementation替换为function() { return; }空函数。对 TrustKitHookTrustKit.getInstance().getTrustManager()返回一个信任所有证书的X509TrustManager。这是最便捷的方式成功率约 70%。但局限性明显它只覆盖主流库的公开 API对自定义实现如继承X509TrustManager并重写checkServerTrusted无效。我曾在一个银行 App 中执行此命令Charles 仍无法抓包logcat显示javax.net.ssl.SSLPeerUnverifiedException: Hostname ... not verified。原因就是该 App 自己写了一个CustomTrustManager类objection的自动检测没覆盖到。4.2 手动模式android sslpinning add的精准打击当自动模式失效android sslpinning add是终极武器。它允许你指定任意类和方法进行 Hook。例如发现 App 使用了com.example.security.CustomTrustManager其checkServerTrusted方法是关键objection -g com.example.app android sslpinning add --class com.example.security.CustomTrustManager --method checkServerTrustedobjection会生成 Frida 脚本Hook 该方法并忽略所有异常。原理是在checkServerTrusted的implementation中直接return不执行任何证书校验逻辑。这要求你先通过android hooking list classes --grep trust或memory search --string CustomTrustManager定位到该类。手动模式的成功率接近 100%但需要你具备基本的逆向分析能力来定位目标类。4.3 全局模式android sslpinning enable的双刃剑效应android sslpinning enable并非“开启 pinning”而是启用objection的 SSL Pinning 绕过监控。它会在后台持续扫描新创建的SSLSocketFactory和X509TrustManager实例并自动对其应用绕过逻辑。这解决了“动态加载网络库”如插件化 App 在运行时下载并加载okhttp-4.x.jar的问题。但副作用是它会 Hook 更多系统类可能引发兼容性问题如某些加固方案会检测此类 Hook 行为。因此我只在确认自动/手动模式均失败后才启用此模式并立即用android sslpinning status查看当前生效的 Hook 列表确保没有误伤。4.4 效果验证不只是“能抓包”而是“抓得准、抓得全”绕过 SSL Pinning 后必须验证效果。objection提供android sslpinning status显示当前 Hook 状态但这只是“是否生效”。真正的验证分三步网络连通性验证在objection的agent模式下objection -g com.example.app explore执行http get https://httpbin.org/get。若返回 JSON则证明 HTTPS 请求已通。明文内容验证用android hooking watch class_method okhttp3.Request$Builder.urlHook URL 构建确认args[0]是明文 URL如https://api.example.com/v1/login而非加密域名。请求体验证android hooking watch class_method okhttp3.RequestBody.create检查args[1]body 字节数组能否被new String(args[1])正确解码为 JSON 或 XML。只有这三步全部通过才算真正“抓到了明文包”。我曾因跳过第 3 步误以为绕过成功结果在 Charles 中看到的仍是加密 body浪费了两小时排查。5. 实战串联从“无法登录”到“完整复现业务流程”的全流程推演现在让我们把前面所有技术点串联起来模拟一个真实场景某电商 App 登录后无法进入个人中心且无任何错误日志。目标找出登录态失效的根本原因并完整捕获从登录到获取用户信息的全部明文请求。5.1 第一阶段内存初探与关键线索定位启动 App进入登录页不点击登录。执行objection -g com.shop.app memory list modules | grep lib # 输出libshop-core.so 0x7f8a3c0000 2100 objection -g com.shop.app memory search --string user --module libshop-core.so # 输出0x7f8a3c2a18, 0x7f8a3c5f30, 0x7f8a3c8e44 objection -g com.shop.app memory read --hexdump --size 64 0x7f8a3c2a18 # 发现0x7f8a3c2a18 75 73 65 72 2f 69 6e 66 6f 00 ... - /user/info/user/info是关键线索说明获取用户信息的接口 URL 被硬编码在此。记录地址0x7f8a3c2a18。5.2 第二阶段Native 层 Hook 与 Token 流水线追踪点击登录输入账号密码触发登录请求。此时执行objection -g com.shop.app android hooking watch native --name libshop-core.so --address 0x7f8a3c2a18 --dump-args --dump-returnobjection报告[] Hook added to libshop-core.so! 0x7f8a3c2a18。稍等几秒出现日志[!] Called from: 0x7f8a3c5000 (libshop-core.so 0x5000) [!] Arguments: [0x7f8a3d1234, 0x7f8a3d5678] [!] Return value: 0x10x7f8a3c5000是调用方地址执行memory read --hexdump --size 128 0x7f8a3c5000发现附近有bl 0x7f8a3c2a18指令证实0x7f8a3c5000是登录处理函数。再android hooking watch native --name libshop-core.so --address 0x7f8a3c5000得到[!] Arguments: [0x7f8a3e0000, 0x7f8a3e1000] # 指向用户名、密码字符串 [!] Return value: 0x7f8a3f2000 # 返回值是一个指针0x7f8a3f2000很可能是 token 地址。memory read --hexdump --size 64 0x7f8a3f2000显示一串 Base64 字符如eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...确认是 JWT token。5.3 第三阶段SSL Pinning 绕过与明文请求捕获此时/user/info请求必然失败因 token 无效或过期。先绕过 Pinningobjection -g com.shop.app android sslpinning disable # 若失败用 logcat 找到 CustomTrustManager 类名再执行 objection -g com.shop.app android sslpinning add --class com.shop.security.TokenTrustManager --method checkServerTrusted然后Hook OkHttp 的 Request 构建objection -g com.shop.app android hooking watch class_method okhttp3.Request$Builder.url --dump-args objection -g com.shop.app android hooking watch class_method okhttp3.RequestBody.create --dump-args点击“个人中心”objection日志显示[!] Arguments: [https://api.shop.com/v1/user/info?tokeneyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...] [!] Arguments: [null, [{\userId\:\123\,\timestamp\:1712345678}]]完美URL 和 Body 都是明文。复制 URL 到浏览器返回{code:401,msg:Invalid token}证实 token 问题。5.4 第四阶段根因定位与修复验证回到0x7f8a3c5000登录函数发现其返回的 token 是0x7f8a3f2000但0x7f8a3f2000地址的内容在登录后几秒内被memset清零memory read显示全00。这意味着 token 被生成后立即被销毁导致后续请求无有效凭证。根本原因App 的 token 管理逻辑存在缺陷不是服务端问题。验证用memory write --string 0x7f8a3f2000 fake_token_123临时篡改 token再点击“个人中心”objection捕获到https://api.shop.com/v1/user/info?tokenfake_token_123Charles 中看到返回{code:200,data:{name:张三}}。根因确认。我的经验objection的memory write是调试神器但仅用于验证。真实分析中应结合android hooking watch native查看memset的调用栈定位到clearToken()函数再分析其触发条件如是否在onPause()中被调用。6. 避坑指南那些文档不会写、但会让你崩溃一整天的细节objection强大但有几个深坑踩中一个就能让你怀疑人生。这些都是我在数十个 App 上反复验证过的血泪教训绝非纸上谈兵。6.1 “No device found” 错误的三层排查链执行objection -g com.xxx.app报错No device found第一反应是adb devices错。正确排查链是USB 连接层adb devices必须显示device非unauthorized或offline。若为unauthorized检查手机是否弹出“允许 USB 调试”对话框勾选“始终允许”再点确定。Root/ADB 权限层objection需要adb shell有足够权限启动 Frida Server。执行adb shell id输出应为uid0(root) gid0(root)。若为uid2000(shell)说明未 root需用adb root仅限 userdebug 版本或安装 Magisk。Frida Server 层adb shell ls /data/local/tmp/frida*确认frida-server存在且可执行chmod 755 /data/local/tmp/frida-server。若不存在从 Frida 官网下载对应架构arm64的 serveradb push并adb shell ./frida-server 启动。我曾在一个华为 Mate 40 上卡在此处adb devices显示正常adb shell id是 root但objection仍报错。最终发现是frida-server版本太新16.1.0降级到 15.1.17 后解决。objection的版本兼容性比想象中更脆弱。6.2android hooking watch日志刷屏与性能雪崩watch class_method默认打印所有参数和返回值若 Hook 的是String.valueOf()这种高频方法日志会瞬间刷屏objection进程 CPU 占用 100%App 卡死。解决方案不是关掉 Hook而是用--no-dump关闭自动日志只保留 Hook 事件通知。用--timeout 100限制每个 Hook 执行时间防止某个方法卡住。用--include精确到具体方法如--include login|auth|token绝不watch package。更狠的一招在objection explore模式下用android hooking set timeout 100全局设置超时再android hooking watch class_method ...。这比每次加参数更高效。6.3memory search的“假阳性”与“真阴性”memory search --string password可能搜到passwordReset、passwordStrength等无关字符串这是“假阳性”。而搜--string 123456却找不到因为 App 可能将密码存储为char[]并用Arrays.fill()清零或用SecureStringAndroid 不支持这是“真阴性”。应对策略组合搜索memory search --string user memory search --string pass再用memory read检查两个地址是否在相近内存页0x1000对齐。数值搜索若知道密码长度为 6用memory search --pattern 31 32 33 34 35 36123456的 hex。接受“找不到”现代 App 的敏感数据往往在 Native 层用mlock()锁定内存页防止被ptrace读取。此时memory search会失败这是设计使然不是你技术不行。6.4sslpinning disable后 Charles 仍失败的终极 checklist确认代理设置adb shell settings get global http_proxy应为127.0.0.1:8888Charles 默认端口。若为空执行adb shell settings put global http_proxy 127.0.0.1:8888。确认证书安装手机浏览器访问chls.pro/ssl下载并安装 Charles 根证书。Android 7 需在 App 的network_security_config.xml中显式信任用户证书否则objection的绕过也无效。确认 App 网络库有些 App 使用WebView加载 H5 页面其网络请求走系统 WebView不受 OkHttp Hook 影响。此时需android hooking watch class_method android.webkit.WebViewClient.shouldInterceptRequest。确认 Frida 注入时机objection -g是 attach 模式若 App 启动极快如秒开可能在 Frida 注入前已完成网络请求。改用objection -lspawn 模式objection -l -g com.shop.app --startup-command android sslpinning disable。最后分享一个小技巧在objection explore模式下用help命令查看所有可用命令用help command查看子命令详情。objection的帮助文档比 Frida 官网更贴近实战值得逐字阅读。它不是一个“高级 Frida 封装”而是一套为移动 App 动态