news 2026/5/24 16:30:29

安卓逆向环境从零搭建:Frida Hook与HTTPS抓包全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
安卓逆向环境从零搭建:Frida Hook与HTTPS抓包全解析

1. 为什么现在还要亲手搭一套安卓逆向环境?——不是为了炫技,而是为了“看得见、控得住、改得准”

你有没有遇到过这种情况:用现成的All-in-One逆向工具包跑个Hook脚本,结果日志里只有一行Failed to load script,连报错堆栈都截不全;或者抓包时发现HTTPS流量全是乱码,换三个代理工具反复配置证书,最后发现是Android 7+系统默认不信任用户证书;又或者在真机上调试Frida时,frida-ps -U死活不显示进程,adb shell进去一看,/data/local/tmp/frida-server权限被自动重置了……这些不是玄学,是环境链路上某个环节没对齐的必然结果。

从零构建安卓逆向分析环境,关键词就三个:Frida Hook抓包工具链全解析。它不是教你怎么点几下鼠标装个APK,而是带你把整个分析链路的每一层“拧开盖子”看清楚——从底层内核模块加载机制,到Java层动态调用拦截原理,再到网络层TLS握手过程中的证书验证逻辑。这套环境的核心价值,不在于“能跑起来”,而在于“出问题时,你能精准定位到是Frida的Gum层注入失败,还是Xposed的Zygote hook时机太晚,抑或是抓包代理的SSL Bypass策略与目标App的Network Security Config冲突”。

适合谁?三类人最需要:一是刚从Web渗透转战移动安全的工程师,对Dalvik/ART运行时陌生,但熟悉Burp和Python;二是做App加固对抗的研究者,需要稳定复现脱壳、绕过签名校验、劫持密钥生成等场景;三是开发自研SDK的安全团队,必须在可控环境中验证自身防护逻辑是否真能拦住Frida注入或证书固定绕过。我带过的十几个逆向新人里,90%卡在环境搭建阶段超过两周,不是因为技术难,而是因为网上教程把“adb root”当成万能钥匙,却没人告诉你Pixel 6出厂固件根本禁用adb root,而Magisk的su模块在Android 13上默认关闭SELinux permissive模式——这些细节,恰恰是真实攻防中决定成败的毛细血管。

下面这整套流程,是我过去三年在27个不同品牌、11个Android大版本(8.0–14)、包括折叠屏/车机/手表等特殊设备上反复验证过的最小可行路径。所有步骤均避开需要刷机、解锁Bootloader等高风险操作,全部基于官方ADB接口和用户可写目录完成。每一步背后都有明确的系统级动因,而不是“照着做就行”的黑盒指令。

2. Frida环境:不止是下载frida-server,而是理解Gum引擎如何在ART上“寄生”

Frida常被误认为是“移动端的Chrome DevTools”,但它的核心其实是Gum——一个轻量级的动态二进制插桩框架。在安卓上,它不依赖Xposed那样的Zygote预加载,而是通过ptrace附加到目标进程后,在内存中动态构造并执行机器码片段。这意味着:Frida能否成功Hook,本质是Gum能否在目标进程的ART运行时上下文中安全地分配可执行内存页。这个前提,直接决定了你选哪个frida-server版本、怎么启动、甚至目标App是否启用了android:debuggable="false"

2.1 版本对齐:为什么frida-server 16.3.12在Android 12上会段错误?

很多人卡在第一步:frida-ps -U返回空列表。查日志发现frida-server启动后立即崩溃,logcat里只有signal 11 (SIGSEGV)。这不是frida-server坏了,而是ABI不匹配。Frida官方发布的frida-server是按CPU架构编译的,但Android 12+开始强制要求64位应用必须同时提供32位兼容库(lib/armlib/arm64),而frida-server的版本号并不体现其内部链接的libc版本。实测数据如下:

Android版本推荐frida-server版本关键原因验证命令
8.0–10 (ARM64)14.2.18基于Bionic libc 2.27,兼容旧版ART GCadb shell /data/local/tmp/frida-server --version
11–12 (ARM64)15.1.17修复了mmapPROT_EXEC标志下的SELinux策略适配adb shell getenforce需为Permissive或Disabled
13–14 (ARM64)16.3.12引入/proc/self/maps解析优化,规避Android 13的memfd_create限制adb shell cat /proc/version确认内核≥5.10

提示:不要用frida --version查本地版本,它只显示Python binding版本。真正要确认的是设备端frida-server的ABI和libc兼容性。最稳妥的方式是:先用adb shell getprop ro.product.cpu.abi确认CPU架构(如arm64-v8a),再从 Frida Releases 下载对应*-android-arm64.xz包,解压后用file frida-server检查ELF类型:“ELF 64-bit LSB pie executable, ARM aarch64”才正确。

2.2 启动策略:为什么nohup ./frida-server &在Android 12+上必失败?

很多教程让你把frida-server推送到/data/local/tmp/然后后台运行。但在Android 12+上,/data/local/tmp/目录的noexec挂载选项默认启用,任何在此目录下尝试mmap(..., PROT_EXEC)的操作都会被内核拒绝。这不是Frida的bug,而是Android强化内存保护的正常行为。解决方案有两个,且必须二选一:

方案A(推荐):使用-D参数以daemon模式启动

adb push frida-server /data/local/tmp/ adb shell "chmod 755 /data/local/tmp/frida-server" adb shell "/data/local/tmp/frida-server -D"

-D参数让frida-server主动fork子进程并脱离终端控制,同时它会自动检测挂载点属性,若/data/local/tmp/不可执行,则尝试/dev/shm/(如果存在)或/sdcard/Android/data/(需存储权限)。这是Frida 15.0+引入的健壮性改进。

方案B(备用):重挂载tmp目录(需root)

adb shell "su -c 'mount -o remount,exec /data/local/tmp'"

但此操作在Android 13+上会被SELinux策略拦截,除非你已禁用SELinux(不推荐)。

注意:frida-server -D启动后,进程名会变成frida-server而非./frida-server,因此ps | grep frida可能找不到。正确检查方式是adb shell ps | grep -E "(frida|gum)",Gum引擎的线程名通常含gum-js-loop

2.3 Hook时机:为什么Java.perform在Application#onCreate之前就执行失败?

这是新手最常踩的坑。写一个最简单的Hook脚本:

Java.perform(() => { console.log("Java layer ready"); });

运行frida -U -f com.example.app -l hook.js --no-pause,却发现日志里根本没有输出。原因在于:Java.perform的回调函数,是在Frida注入后、ART虚拟机完成初始化(即Runtime::Init执行完毕)时才触发。而-f参数启动App时,Frida注入发生在Zygote fork子进程之后、Application#onCreate之前,但此时ART的JNI环境尚未完全就绪。

正确时机分三层

  • Native层:用Interceptor.attach(Module.findExportByName("libart.so", "art::Runtime::Init"))监听ART初始化完成;
  • Java层准备期Java.performNow()强制立即执行,但仅限于已加载的类(如java.lang.String);
  • Application生命周期:必须用Java.choose("android.app.Application", {onMatch: ...}),等待Application实例创建后再Hook。

我实际调试某金融App时,发现其加固SDK在Application#attachBaseContext中就完成了dex加密解密,若Hook太晚,classloader已被替换,Java.use("com.secure.Class")会抛JavaException: java.lang.ClassNotFoundException。最终解决方案是:先Native Hookdlopen监听libsec.so加载,再在其JNI_OnLoad中注入Java Hook,形成跨层协同。

3. 抓包工具链:从HTTP明文到HTTPS解密,关键不在代理而在证书信任链

抓包的本质,是让自己成为客户端与服务器之间的“中间人”。在安卓上,这比PC端复杂得多,因为Android从7.0开始强制应用遵循network_security_config.xml,默认不信任用户安装的CA证书。所以,不是Burp Suite配错了,而是你的证书根本没进App的信任库

3.1 代理配置:为什么adb shell settings put global http_proxy在Android 9+上失效?

早期安卓允许全局HTTP代理,adb shell settings put global http_proxy 192.168.1.100:8080即可。但从Android 9(Pie)开始,系统级代理仅影响WebView和部分系统服务,App层网络请求(OkHttp、Retrofit等)完全忽略该设置。真正的代理入口,是App自身的网络配置。

验证方法:用adb shell dumpsys connectivity查看当前网络状态,其中Proxy info字段只反映系统代理,不代表App会走它。更可靠的方式是:在Burp中开启Proxy > Options > Proxy Listeners > Edit > Binding,勾选Support invisible proxying (enable only if needed),然后在手机WLAN设置中手动配置代理(IP填电脑局域网IP,端口8080)。注意:此操作需手机与电脑在同一局域网,且电脑防火墙放行8080端口。

提示:某些国产ROM(如MIUI、EMUI)会拦截手动代理设置,表现为保存后自动清空。此时必须用adb shell settings put global http_proxy配合adb shell settings put global global_http_proxy_host双写,或直接修改/data/misc/apexdata/com.android.conscrypt/config.properties(需root)。

3.2 HTTPS解密:绕过Certificate Pinning的三种实战路径

当Burp抓到HTTPS请求但显示ClientHello后无响应,说明App启用了证书固定(Certificate Pinning)。常见实现有三类,应对策略完全不同:

Pinning类型典型实现Frida Hook点绕过难度实测成功率
OkHttp内置PinOkHttpClient.Builder.certificatePinner()okhttp3.CertificatePinner.check()★★☆92%(需Hook所有重载方法)
Conscrypt底层Pinorg.conscrypt.SSLUtils.verifyCertificateChain()org.conscrypt.NativeCrypto.X509_verify_cert()★★★78%(需处理JNI层多签名算法)
自定义JNI Pinlibcrypto.soSSL_CTX_set_cert_verify_callback()dlsym(handle, "SSL_CTX_set_cert_verify_callback")★★★★45%(需逆向so符号表)

最通用的Frida脚本结构(以OkHttp为例):

Java.perform(() => { const CertificatePinner = Java.use("okhttp3.CertificatePinner"); CertificatePinner.check.implementation = function(host, peerCertificates) { console.log(`[PINNING BYPASS] Host: ${host}, Certs: ${peerCertificates.length}`); // 直接返回,不校验 return; }; });

但要注意:OkHttp 4.0+将check方法改为check$okhttp(带$符号),需用Java.use("okhttp3.CertificatePinner").check$okhttp.implementation。这个细节,官方文档从不提,但不改就会Hook失败。

3.3 证书安装:为什么burp.crt拖进手机相册再点安装,App还是不信任?

Android 7+将用户证书存放在/data/misc/user/0/cacerts-added/,但App是否信任它,取决于android:networkSecurityConfig指向的XML文件。典型配置如下:

<?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config> <domain includeSubdomains="true">example.com</domain> <trust-anchors> <certificates src="system" /> <certificates src="user" /> <!-- 关键!必须显式声明信任用户证书 --> </trust-anchors> </domain-config> </network-security-config>

如果App未声明<certificates src="user"/>,即使你安装了Burp证书,App的OkHttp client也会在TrustManagerImpl.checkServerTrusted()中直接抛CertificateException

终极解决方案:用Frida动态修改TrustManager。以下脚本可通杀90%的App(包括未声明user证书的):

Java.perform(() => { const X509TrustManager = Java.use("javax.net.ssl.X509TrustManager"); const SSLContext = Java.use("javax.net.ssl.SSLContext"); // Hook TrustManager的checkServerTrusted方法 X509TrustManager.checkServerTrusted.implementation = function(chain, authType) { console.log("[TRUSTMANAGER BYPASS] Ignoring certificate validation"); return; }; // 强制SSLContext使用我们的TrustManager SSLContext.init.overload( "java.security.KeyManager[]", "javax.net.ssl.TrustManager[]", "java.security.SecureRandom" ).implementation = function(keyManagers, trustManagers, secureRandom) { console.log("[SSLCONTEXT INIT] Replacing TrustManager"); this.init(keyManagers, [X509TrustManager.$new()], secureRandom); }; });

此脚本在SSLContext.init时注入自定义TrustManager,完全绕过XML配置限制。但要注意:某些加固App会校验TrustManager类名,此时需用Java.openClassFile动态加载伪造类。

4. 工具链协同:当Frida Hook与抓包同时进行,如何避免“互相干扰”?

真实分析中,你往往需要一边Hook关键Java方法获取密钥,一边抓包查看加密后的请求体。但Frida和抓包工具会争夺同一资源——网络I/O和SSL上下文,导致frida-trace日志混乱、Burp出现Connection reset、甚至App闪退。这不是工具冲突,而是资源调度失序。

4.1 端口与进程隔离:为什么Burp和Frida不能共用8080端口?

表面看,Burp监听8080,Frida监听27042(默认),互不干扰。但问题出在adb reverse。当你执行adb reverse tcp:27042 tcp:27042时,ADB会在设备端创建一个反向代理,将设备上的27042端口映射到电脑的27042。而某些国产ROM(如ColorOS)的ADB daemon会错误地将所有reverse请求路由到同一端口池,导致Burp的8080被意外覆盖。

验证方法:执行adb reverse --list,若输出包含tcp:8080 tcp:8080,说明已被占用。解决办法是:为Frida指定非标准端口,并确保Burp也换端口:

# Frida用27043 adb reverse tcp:27043 tcp:27043 frida -U -f com.example.app -l hook.js --no-pause -H 127.0.0.1:27043 # Burp改用8081 # 手机WLAN代理设为 192.168.1.100:8081

4.2 日志污染:为什么console.log输出会混入Burp的HTTP头?

Frida的console.log默认输出到frida-server的stdout,而frida-server -D以daemon模式运行时,stdout被重定向到/dev/null。但如果你用frida -U -l hook.js --no-pause,Frida CLI会捕获frida-server的stdout并打印到终端。而Burp的HTTP响应头(如HTTP/1.1 200 OK)有时会通过frida-server的IPC通道被误读为日志——这是因为Frida的JS引擎与Burp的Java进程共享同一ADB socket缓冲区,当网络拥塞时发生字节粘包。

根治方案:禁用Frida的console输出,改用send()发送结构化数据到Python端:

// hook.js Java.perform(() => { const SecretKey = Java.use("javax.crypto.spec.SecretKeySpec"); SecretKey.$init.implementation = function(keyBytes, algorithm) { send({type: "SECRET_KEY", key: keyBytes, algo: algorithm}); return this.$init(keyBytes, algorithm); }; });
# recv.py import frida import sys def on_message(message, data): if message['type'] == 'send': print(f"[KEY FOUND] {message['payload']}") device = frida.get_usb_device() pid = device.spawn(["com.example.app"]) session = device.attach(pid) script = session.create_script(open("hook.js").read()) script.on('message', on_message) script.load() device.resume(pid) sys.stdin.read()

这样,密钥信息走独立IPC通道,与Burp的HTTP流物理隔离。

4.3 时间戳同步:为什么Frida Hook到的时间比Burp抓包慢300ms?

这是ART JIT编译的副作用。Frida注入后,首次调用Java.use("xxx").method.implementation时,ART会触发JIT编译该方法的HotSpot代码,耗时约200–500ms。而Burp抓包发生在Socket write阶段,远早于此。结果就是:你在Burp看到请求发出,300ms后Frida才Log出onCreate被调用。

解决方案:预热JIT。在Java.perform中提前调用一次目标方法(不Hook):

Java.perform(() => { // 预热:强制JIT编译Activity.onCreate const Activity = Java.use("android.app.Activity"); try { Activity.onCreate(null); // 传null会抛异常,但JIT已触发 } catch (e) {} // 此时再Hook,延迟降至20ms内 Activity.onCreate.implementation = function(savedInstanceState) { console.log("Activity created"); return this.onCreate(savedInstanceState); }; });

实测在Pixel 4a上,预热后Hook延迟从320ms降至18ms,与Burp时间戳误差小于50ms,满足密钥-请求体关联分析需求。

5. 真机调试避坑指南:从Pixel到Redmi,那些官网绝不会告诉你的硬件差异

模拟器永远无法替代真机。但不同厂商的ROM对逆向工具的支持度天差地别。以下是我在17台真机上踩出的血泪经验:

5.1 小米/Redmi系列:MIUI的“安全中心”如何静默杀死frida-server

MIUI 12+的“安全中心”默认开启“应用行为记录”,它会监控/data/local/tmp/下的可执行文件,并在后台静默kill -9frida-server进程。现象是:frida-ps -U偶尔能扫到进程,但frida -U -f xxx必失败。解决方案不是关安全中心(它会自动重启),而是改用/sdcard/Download/目录

adb push frida-server /sdcard/Download/ adb shell "chmod 755 /sdcard/Download/frida-server" adb shell "/sdcard/Download/frida-server -D"

因为MIUI对/sdcard/目录的扫描策略较宽松,且/sdcard/Download/是用户可写目录,无需root。

5.2 华为/Honor系列:EMUI的“纯净模式”如何禁用ADB调试

EMUI 11+的“纯净模式”会彻底禁用ADB的shell权限,adb shell返回error: device unauthorized,即使已授权。这不是USB调试开关问题,而是华为自研的hwselinux策略。解决方法:进入设置 > 系统和更新 > 开发人员选项,关闭“纯净模式”,然后重启手机。注意:关闭后需重新连接USB并点击授权弹窗。

5.3 Samsung系列:One UI的“调试通知”如何干扰Hook执行

One UI 12+在调试时会弹出悬浮通知“正在调试此应用”,该通知由com.samsung.android.app.watchmanagerstub进程管理,它会hook Zygote的fork系统调用,导致Frida的ptrace附加失败。现象是frida -U -f xxx卡在Waiting for process...。解决方案:在设置 > 高级功能 > 开发者选项中,关闭“USB调试(安全设置)”,此选项默认开启,会启用Samsung的调试守护进程。

5.4 Google Pixel系列:原生Android的“Verified Boot”如何阻止Magisk模块

Pixel 6/7的Titan M2安全芯片启用Verified Boot,即使刷入Magisk,su命令也返回Permission denied。此时frida-server -D无法获得CAP_SYS_PTRACE能力。解决方案:不用Magisk,改用adb root+adb remount组合。但Pixel 6+出厂固件禁用adb root,需先用fastboot flashing unlock解锁Bootloader(会清除数据),再刷入aosp_arm64-userdebug镜像。这是唯一官方支持的调试路径。

最后分享一个小技巧:在所有真机上,执行adb shell getprop ro.build.version.release后,立即跟一句adb shell getprop ro.build.type。若返回userdebug,说明是调试友好型固件;若返回user,则需按上述厂商方案处理。这个判断比查型号更可靠,因为同一型号不同批次固件可能不同。

我在实际项目中,曾为某银行App做合规审计,客户指定必须用Redmi K50(MIUI 13)。前三天都在解决frida-server被杀问题,直到发现/sdcard/Download/这个隐藏路径。后来我把这个路径写进公司内部Wiki,标注为“MIUI黄金路径”,现在团队新人搭环境平均耗时从3.2小时降到22分钟。逆向环境的本质,从来不是堆砌工具,而是理解每个字节在操作系统、运行时、应用层之间的真实流向——当你看清了这条链路,所谓“环境”,不过是信手拈来的几行命令而已。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/24 16:28:06

js-reverse-mcp安装

安装 下载解压:https://github.com/zhizhuodemao/js-reverse-mcp 执行 npm install npm run build客户端工具添加配置: {"mcpServers": {"js-reverse": {"command": "node"

作者头像 李华
网站建设 2026/5/24 16:27:28

DeepSeek缓存策略设计(L1/L2/L3三级协同失效预警机制首次公开)

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;DeepSeek缓存策略设计 DeepSeek模型在推理服务中面临高并发、低延迟与显存受限的多重挑战&#xff0c;其缓存策略需兼顾KV缓存复用效率、内存生命周期管理及跨请求上下文共享能力。核心设计围绕“分层缓…

作者头像 李华
网站建设 2026/5/24 16:24:32

IPSEC证书体系构建:从OpenSSL根CA到StrongSwan隧道实战

1. 这不是“配个证书”那么简单&#xff1a;IPSEC CA配置的真实战场很多人看到“IPSEC CA证书配置”这六个字&#xff0c;第一反应是翻出某厂商文档&#xff0c;照着步骤点几下CA服务器界面&#xff0c;导出个.crt、.key&#xff0c;再填进防火墙或路由器的证书栏——完事。我试…

作者头像 李华
网站建设 2026/5/24 16:08:39

别再被GPG签名卡住了!手把手教你修复老版本Kali Linux的apt更新源报错

彻底解决Kali Linux旧系统GPG签名失效&#xff1a;从原理到实战当你面对Kali Linux系统中apt-get update命令抛出的一连串GPG签名错误时&#xff0c;那种挫败感我深有体会。作为一名长期维护渗透测试环境的工程师&#xff0c;我见过太多同行因为这类问题放弃旧系统&#xff0c;…

作者头像 李华
网站建设 2026/5/24 16:07:54

3步搞定Switch游戏安装:Awoo Installer终极兼容性解决方案

3步搞定Switch游戏安装&#xff1a;Awoo Installer终极兼容性解决方案 【免费下载链接】Awoo-Installer A No-Bullshit NSP, NSZ, XCI, and XCZ Installer for Nintendo Switch 项目地址: https://gitcode.com/gh_mirrors/aw/Awoo-Installer 还在为Switch游戏安装的兼容…

作者头像 李华