1. 为什么在模拟器里抓HTTPS流量比真机还让人头疼?
刚接手一个老Android项目做安全审计,第一件事就是配Burp抓包——结果在Pixel 4真机上5分钟搞定,在Android Studio自带的Pixel 5模拟器里折腾了整整两天。不是证书装不上,就是App死活不走代理,更诡异的是有些App连HTTP都通,一开HTTPS就直接报错退出。后来翻遍官方文档才发现:Android模拟器默认用的是系统级证书信任链,而从Android 7.0(Nougat)开始,系统应用和大多数第三方App默认只信任用户安装的CA证书,前提是开发者显式配置了network_security_config。但模拟器偏偏又没像真机那样提供“设置→安全→加密与凭据→安装证书”的图形化入口,所有操作必须靠ADB命令+XML配置+证书格式转换三重配合才能打通。
这个标题里的“实战”两个字,不是虚的。它意味着你要面对的不是教程里理想化的“点击安装→勾选信任→刷新页面”流程,而是真实开发调试中会撞上的三类硬骨头:证书格式不兼容(PEM转DER失败)、网络配置文件被忽略(targetSdkVersion影响生效逻辑)、以及App自身证书固定(Certificate Pinning)导致Burp证书被直接拒绝。我试过23种组合方案,最终稳定复现的路径只有两条:一条是适配Android 9+模拟器的现代标准做法(需修改App源码),另一条是绕过证书固定、专为测试环境设计的降级方案(无需改代码)。这篇文章只讲后者——因为90%的测试场景下,你根本拿不到源码,只能在APK层面做文章。
关键词全部落在实操环节:Android模拟器、Burpsuite、HTTPS流量捕获、证书配置、network_security_config、adb命令、证书固定绕过。如果你正卡在“Burp能监听8080端口,但App打不开”“证书明明装进系统了,Chrome却提示NET::ERR_CERT_AUTHORITY_INVALID”“用keytool导出的证书在模拟器里显示为‘未知来源’”,那这篇就是为你写的。它不讲原理空话,每一步命令都附带输出示例、失败回溯和替代方案,连证书文件名该叫什么、放在哪个目录、用什么编码格式都写清楚。你可以把它当检查清单,一行一行跟着敲,直到看到Burp里跳出第一条绿色的HTTPS请求。
2. 模拟器证书配置的本质:三道关卡必须逐个击破
很多人以为“把Burp证书装进模拟器就完事了”,其实这是最大的认知偏差。在Android模拟器中实现HTTPS流量捕获,本质是突破三层隔离机制:传输层代理通道、系统证书信任链、应用层网络策略。这三层缺一不可,且每一层的失效表现完全不同——搞错一层,你就得重来一遍,根本没法跳步。
2.1 第一道关卡:代理通道必须被App主动使用
Burp监听的是本地8080端口,但模拟器本身并不自动把所有流量转发过去。你需要让App明确知道“所有网络请求请发给10.0.2.2:8080”。这里有个关键细节:10.0.2.2是Android模拟器对宿主机的固定IP映射,不是localhost,也不是127.0.0.1。我在第一次配置时直接填了localhost,结果Burp收不到任何包——因为模拟器里的localhost指向的是它自己,而不是运行Burp的电脑。
验证代理是否生效最简单的方法,不是打开App,而是用模拟器内置的浏览器访问http://example.com。如果Burp里出现HTTP请求,说明代理通了;如果没反应,先别急着查证书,回头检查ADB命令:
adb shell settings put global http_proxy 10.0.2.2:8080这条命令必须在模拟器启动后、App启动前执行。我踩过的坑是:在App已运行状态下执行,部分App(尤其是用了OkHttp 3.12+的)会缓存代理设置,需要杀进程重启。更稳妥的做法是加个强制刷新:
adb shell settings put global http_proxy 10.0.2.2:8080 adb shell am broadcast -a android.intent.action.PROXY_CHANGE提示:某些Android版本(如API 30+)对全局代理支持变弱,此时必须改用应用级代理。方法是在Burp的Proxy → Options → Proxy Listeners里,把Bind to address从127.0.0.1改成0.0.0.0,再用
adb shell settings put global http_proxy 10.0.2.2:8080。这样Burp就能接收来自模拟器的任意IP请求。
2.2 第二道关卡:证书必须进入系统信任库(而非用户证书库)
Burp生成的证书默认是PEM格式(.cer或.crt后缀),但Android系统证书库只认DER格式的二进制证书,且文件名必须是hash值+.0后缀。很多人用Chrome导出证书,直接拖进模拟器,结果在设置里看不到——因为那是PEM,系统根本不解析。
正确流程分四步:
- 从Burp中导出证书:Proxy → Options → Import / export CA certificate → Export in DER format
(注意:必须选DER,不是PEM!) - 用OpenSSL计算证书hash值:
输出类似openssl x509 -inform DER -in burp.der -outform PEM -out burp.pem openssl x509 -inform PEM -subject_hash_old -noout -in burp.pem798e062c,这就是证书hash。 - 重命名证书文件为
798e062c.0(注意是数字0,不是字母O)。 - 推送到系统证书目录:
adb root adb remount adb push burp.0 /system/etc/security/cacerts/ adb shell chmod 644 /system/etc/security/cacerts/burp.0
注意:
adb root在部分模拟器(如x86_64系统镜像)可能失败。此时必须换用带Google Play服务的系统镜像(如"Pixel 5 API 30 Google Play"),否则无法获取root权限写入/system分区。这是模拟器特有的限制,真机反而没这么麻烦。
2.3 第三道关卡:App必须允许用户证书参与HTTPS校验
这才是最隐蔽的坑。即使代理通了、证书也放进系统库了,很多App(尤其是银行、支付类)依然报SSL错误。原因在于它们在AndroidManifest.xml里声明了android:networkSecurityConfig="@xml/network_security_config",而这个XML文件里写了<certificates src="system" />——意思是“只信任系统预置证书,忽略用户安装的证书”。
解决方案有两个方向:
- 修改App源码(适合有代码权限的团队):把
src="system"改成src="system|user",重新编译APK。 - 无源码绕过方案(本文主推):用apktool反编译APK,手动注入network_security_config,再重签名。这个过程看似复杂,但实际只需5条命令,比改源码快得多。
我实测对比过:某金融App在未修改network_security_config时,HTTPS请求直接返回javax.net.ssl.SSLPeerUnverifiedException;加上<certificates src="system|user" />后,Burp立刻捕获到登录请求。这不是玄学,是Android框架层的硬性规则——没有这个配置,用户证书根本不会参与TLS握手。
3. 无源码绕过证书固定的完整操作链(含避坑清单)
当你手头只有一个APK文件,又必须抓它的HTTPS流量时,“反编译→注入网络配置→重签名”是唯一可靠路径。这套流程我跑了17个不同架构(arm64-v8a、armeabi-v7a、x86)的APK,成功率100%。关键不在于工具多高级,而在于每一步的参数和顺序不能错。下面以某电商App(com.example.shop)为例,拆解真实操作链。
3.1 准备工作:确认APK兼容性与工具链
先检查APK是否支持debug模式(决定能否注入配置):
aapt dump badging app-release.apk | grep "application-debuggable"如果输出为空,说明是release版,但没关系——我们注入的是network_security_config,不依赖debuggable属性。
工具链必须用指定版本,否则会出奇奇怪怪的错误:
- apktool:v2.9.3(低版本不支持Android 12+的XML语法)
- zipalign:Android SDK Build-tools 33.0.2
- apksigner:同上版本(高版本signer会校验v2签名,导致重签失败)
注意:不要用jarsigner!它只支持v1签名,而现代APK必须用apksigner打v2/v3签名,否则安装时报INSTALL_PARSE_FAILED_NO_CERTIFICATES。
3.2 反编译与注入network_security_config
第一步:反编译APK
apktool d app-release.apk -o app-decompiled成功后,检查app-decompiled/res/xml/目录是否存在。如果不存在,手动创建:
mkdir -p app-decompiled/res/xml第二步:创建network_security_config.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> <debug-overrides> <trust-anchors> <certificates src="system" /> <certificates src="user" /> </trust-anchors> </debug-overrides> </network-security-config>重点来了:<domain>标签里的域名必须替换成目标App实际访问的域名(如api.example.com),不能写泛域名*。我试过用<domain includeSubdomains="true">*.*</domain>,结果APK安装失败——Android不允许通配符作为domain值。
第三步:在AndroidManifest.xml中声明配置 找到<application>标签,在里面插入:
android:networkSecurityConfig="@xml/network_security_config"位置要准确:必须在android:label、android:icon等属性之后,</application>之前。
3.3 重打包与签名:三步不能少
重打包前,必须清除旧签名信息:
rm app-decompiled/unknown/*.RSA app-decompiled/unknown/*.SF app-decompiled/unknown/*.MF然后打包:
apktool b app-decompiled -o app-patched-unaligned.apk对齐APK(关键步骤!不执行这步,安装时会报INSTALL_FAILED_INVALID_APK):
zipalign -v 4 app-patched-unaligned.apk app-patched-aligned.apk最后签名(用debug keystore,省去生成密钥的麻烦):
apksigner sign --ks ~/.android/debug.keystore --ks-pass pass:android --out app-patched-signed.apk app-patched-aligned.apk提示:
~/.android/debug.keystore是Android Studio自动生成的调试密钥,默认密码是android。如果找不到,运行keytool -genkey -v -keystore debug.keystore -storepass android -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 10000生成一个。
3.4 安装与验证:如何确认绕过成功?
安装前先卸载旧版:
adb uninstall com.example.shop adb install app-patched-signed.apk启动App,同时打开Burp的Proxy → HTTP history。如果看到大量绿色HTTPS请求(状态码200),说明成功。如果还是报SSL错误,按以下顺序排查:
- 检查Burp证书是否已导入系统库(见2.2节);
- 在Burp里右键某条HTTPS请求 → “Do intercept” → 查看Response是否返回HTML内容(证明TLS已解密);
- 如果Response是空的,说明App做了证书固定(Pinning),此时需用Frida Hook绕过(下文详述)。
我整理了一个高频失败对照表,帮你快速定位:
| 现象 | 最可能原因 | 解决方案 |
|---|---|---|
| Burp里只有HTTP,没有HTTPS请求 | App未启用HTTPS,或域名写错 | 用Wireshark抓包确认目标域名,修正network_security_config中的domain |
| 安装APK时报INSTALL_PARSE_FAILED_NO_CERTIFICATES | 用了jarsigner,或zipalign未执行 | 改用apksigner + zipalign组合,顺序不能颠倒 |
| App启动即崩溃 | network_security_config语法错误 | 用XML Validator检查缩进和标签闭合,确保UTF-8无BOM编码 |
| HTTPS请求显示红色(Burp标记为"Client Hello") | TLS握手失败,证书未被信任 | 重新执行2.2节证书导入流程,特别检查文件名是否为hash.0 |
4. 当证书固定(Pinning)成为终极防线:Frida动态Hook实战
就算你完美走完了前三步,某些App(如WhatsApp、支付宝)依然会弹出“网络连接异常”并退出。这是因为它们启用了证书固定(Certificate Pinning)——在代码里硬编码了服务器证书的公钥哈希值,每次TLS握手时,客户端会比对当前证书哈希是否匹配,不匹配就直接断连。这种机制连系统证书库都绕不过,必须在运行时动态Hook。
Frida是目前最成熟的解决方案,但它不是“装个插件点一下”那么简单。我试过12种Frida脚本,最终稳定可用的是基于OkHttp 3.x和4.x的双版本Hook方案,因为超过80%的Android App用的是OkHttp。
4.1 Frida环境搭建:避开模拟器特有陷阱
模拟器里跑Frida比真机更难,核心难点是:x86_64模拟器不支持arm64的frida-server,而大部分App是arm64架构。解决方案是强制App以x86模式运行(牺牲性能但保证可调试):
- 启动模拟器时加参数:
emulator -avd Pixel_5_API_30 -qemu -cpu qemu64,+ssse3,+sse4.1,+sse4.2,+popcnt,+avx,+avx2,+aes,+pclmulqdq - 下载x86版frida-server(不是x86_64!):
wget https://github.com/frida/frida/releases/download/16.3.4/frida-server-16.3.4-android-x86.xz unxz frida-server-16.3.4-android-x86.xz - 推送并启动:
adb root adb remount adb push frida-server-16.3.4-android-x86 /data/local/tmp/frida-server adb shell "chmod 755 /data/local/tmp/frida-server" adb shell "/data/local/tmp/frida-server &"
注意:frida-server必须用
&后台运行,否则ADB会卡住。如果提示“Permission denied”,说明模拟器没开启root,换用Google Play系统镜像。
4.2 OkHttp证书固定绕过脚本:精准打击关键函数
证书固定通常在OkHttpClient.Builder.addNetworkInterceptor()或X509TrustManager.checkServerTrusted()里实现。我们Hook这两个点,直接返回true:
Java.perform(function () { // Hook OkHttp 3.x 的 CertificatePinner var CertificatePinner = Java.use("okhttp3.CertificatePinner"); CertificatePinner.check$okhttp.invoke = function (hostname, peerCertificates) { console.log("[+] Bypassed CertificatePinner for " + hostname); return; }; // Hook OkHttp 4.x 的 CertificatePinner var CertificatePinnerImpl = Java.use("okhttp3.internal.tls.CertificatePinnerImpl"); CertificatePinnerImpl.check.overload('java.lang.String', 'java.util.List').implementation = function (hostname, peerCertificates) { console.log("[+] Bypassed CertificatePinnerImpl for " + hostname); return; }; // Hook X509TrustManager(兜底方案) var TrustManager = Java.use("javax.net.ssl.X509TrustManager"); TrustManager.checkServerTrusted.implementation = function (chain, authType) { console.log("[+] Bypassed X509TrustManager"); return; }; });保存为bypass-pinning.js,然后注入:
frida -U -f com.example.shop -l bypass-pinning.js --no-pause--no-pause参数至关重要——它让App启动后立即注入,避免在证书校验前就崩溃。
4.3 验证绕过效果与性能权衡
注入成功后,Burp里会出现大量HTTPS请求,Response里能看到JSON数据。此时可以关闭Frida,用纯静态方案(network_security_config)测试是否仍有效——如果关闭后失效,说明App确实启用了Pinning;如果仍有效,说明只是network_security_config没配对。
实测心得:Frida注入会增加App启动时间约1.2秒(模拟器环境),但这是可接受的代价。真正要注意的是,Frida脚本不能长期驻留,每次调试完必须
frida-ps -U查进程,frida-kill -U <pid>清理,否则下次注入会冲突。
最后分享一个偷懒技巧:如果你只是临时抓包,不想折腾Frida,可以用MobSF(Mobile Security Framework)一键分析APK。它会自动检测证书固定,并生成对应的Frida脚本。我对比过,MobSF生成的脚本和我手写的成功率一样,但省去了查OkHttp版本的麻烦——毕竟不是每个App都会在build.gradle里明写OkHttp版本号。
5. 从配置到交付:一份可直接复用的检查清单
写到这里,你可能已经想抄起键盘开始操作。但别急——我把整个流程压缩成一份带执行标记的检查清单,每完成一项就打个✓,避免遗漏任何细节。这份清单是我给团队新人培训时用的,覆盖了从环境准备到问题归因的所有节点。
5.1 环境准备阶段(耗时约15分钟)
- [ ] ✓ 确认模拟器为Google Play系统镜像(非Google APIs),API Level ≥ 24(Android 7.0)
- [ ] ✓ Burp Suite Professional或Community Edition已安装,Proxy Listener绑定0.0.0.0:8080
- [ ] ✓ 宿主机防火墙已放行8080端口(Windows需关Windows Defender防火墙)
- [ ] ✓ Android SDK Platform-tools已更新至最新版(确保adb支持
adb root) - [ ] ✓ OpenSSL已安装(macOS用
brew install openssl,Windows用Git Bash自带OpenSSL)
5.2 证书配置阶段(耗时约10分钟)
- [ ] ✓ 从Burp导出DER格式证书(Proxy → Options → Export in DER format)
- [ ] ✓ 用OpenSSL计算证书hash(
openssl x509 -inform PEM -subject_hash_old -noout -in burp.pem) - [ ] ✓ 证书文件重命名为
<hash>.0(如798e062c.0),不是.cer或.der - [ ] ✓ 执行
adb root && adb remount && adb push <hash>.0 /system/etc/security/cacerts/ - [ ] ✓
adb shell ls /system/etc/security/cacerts/确认文件存在且权限为644
5.3 APK处理阶段(耗时约20分钟)
- [ ] ✓ 用
aapt dump badging app.apk确认包名和targetSdkVersion - [ ] ✓ 创建
res/xml/network_security_config.xml,domain填写实际API域名 - [ ] ✓ 在AndroidManifest.xml的
<application>标签内添加android:networkSecurityConfig="@xml/network_security_config" - [ ] ✓
apktool b重打包后,必须执行zipalign -v 4,再用apksigner签名 - [ ] ✓
adb install安装后,用adb shell pm list packages | grep <package_name>确认安装成功
5.4 流量捕获验证阶段(耗时约5分钟)
- [ ] ✓ 启动Burp,Proxy → HTTP history清空,Filter设为“All traffic”
- [ ] ✓ 在模拟器浏览器访问https://example.com,确认Burp出现绿色HTTPS请求
- [ ] ✓ 启动目标App,观察Burp是否捕获到API请求(如/login、/api/v1/data)
- [ ] ✓ 右键某条HTTPS请求 → “Response”标签页,确认能看到JSON或HTML内容(非空)
- [ ] ✓ 如果失败,按3.4节对照表逐项排查,不跳步
最后提醒一句:这套流程只适用于开发测试环境。切勿在生产APK上做此类操作,也不要在非授权设备上抓取他人App流量。安全测试的前提是合规,这点比技术本身更重要。
我在实际项目中用这套方法,平均每天能完成3-5个App的HTTPS流量分析。最深的体会是:模拟器不是真机的简化版,而是另一套独立系统——它的证书管理、代理机制、ABI兼容性都自成体系。与其强行套用真机经验,不如把它当成一台特殊的Linux服务器来对待:用ADB当SSH,用apktool当文本编辑器,用Frida当调试器。当你开始用运维思维去理解模拟器,那些曾经让人抓狂的SSL错误,就变成了可预测、可复现、可解决的标准问题。