我花了两天时间把某安全系列课的第八十五讲啃完这一讲的主题正好落在APP攻防里最让人头疼的一段反证书校验、反代理检测、反模拟器绕过以及Xposed框架的实战用法。整节课最值钱的地方不是教你去下载什么现成工具而是把一条完整链路讲明白了现在App为什么拒绝让你抓包它在检测哪些信号你在测试环境里又该怎么一层层把保护剥掉。这套东西对做渗透测试、漏洞挖掘、加固评估的人都有用哪怕是刚入门移动安全的新手看完至少能弄清楚一个带证书校验的App是怎么被拿下流量口的。移动端不像Web端那样你开个浏览器代理就能舒服地看请求。现在的App普遍做了SSL Pinning、代理指纹、模拟器特征检测甚至检测你有没有挂Hook框架。本讲笔记就是围绕这些点记录我的理解、实操过程以及踩过的坑希望能给同样在啃这一块的人一点参考。1. 从标题拆开聊这一讲到底解决什么痛点1.1 为什么现在的App越测越难受两三年前做App渗透装上Burp、设好代理、把证书导进系统基本就能开始正常抓包。那时候很多App连证书校验都没有代理检测也就象征性地查一下。但现在完全不同稍微像样一点的App至少会做以下几件事中的两三件开启HTTPS证书校验挡掉不信任的CA证书检测系统是否设置了HTTP代理发现代理就拒绝发送敏感请求检测运行环境是不是模拟器在模拟器里直接闪退或者进入假页面甚至检测Root环境发现Xposed、Magisk之类就直接崩溃。这些防护单拿出来都不难绕过难的是组合出现。比如一个App既做证书固定又检测代理又识别模拟器特征你用模拟器开个Burp代理过去还没等到抓包窗口出现App就已经弹窗退出了。这一讲的核心价值就是把这几个绕过程序拆开理清各自对应哪层防护然后组合出适合实战的绕过链。它不是简单的工具科普而是从“为什么会被检测”反推“该从哪里下手”的思考方式。1.2 本讲的内容边界在动手之前先明确一个边界所有操作只适合在自己搭建的测试环境、有授权的目标上做。这点不是套话而是真实经验。我见过太多人拿一套绕过手段对着别人的线上业务瞎测最后既被反制机制记录又可能触犯规定。所以这篇文章里讲的所有方法请自觉限定在实验环境或者授权范围内。从内容上看本讲主要覆盖五块证书校验的原理与绕过、代理检测的原理与绕过、模拟器检测的原理与绕过、Xposed框架的使用方法以及一套完整实验的落地过程。它不涉及App的业务漏洞分析也不讨论脱壳逆向中的高级对抗重点是把“如何让App在测试环境中正常走代理流量”这条主线打穿。后面如果还想深入其实还有很多可扩展我放在最后聊。2. 证书校验、代理检测、模拟器检测背后的实现逻辑2.1 证书从信任到Pinning要绕过证书校验首先得知道证书校验在干什么。Android系统的App请求HTTPS时正常流程是服务器返回证书链客户端让系统CA证书库来验证这个证书链是不是受信任。如果你用Burp抓包Burp会临时签发一张自己的证书替换掉服务器证书App去系统里查这张证书的签发者发现不是常见CA就报错“证书不受信任”。Android 7.0之后有个容易被忽略的变化App默认只信任系统CA证书不信任用户安装的CA证书。你在设置里手动安装的Burp证书属于用户证书普通App根本不会用它来校验所以你会看到装了证书还是抓不到包。绕过思路无非两条一是把你的测试证书塞进系统CA目录让它成为系统级信任证书二是用Hook手段直接让App的验证逻辑放行。第二条就是以Xposed为基础的JustTrustMe这类模块在干的事。再往上走还有一层叫SSL Pinning。很多App不满足于系统CA信任还会在客户端代码里硬编码服务器证书的关键信息比如证书公钥哈希或证书签发者。它用系统校验证书链通过之后还会额外做一次“这个证书是不是我写的那个”的校验。这层校验没法靠装系统证书解决必须去Hook对应的校验方法比如X509TrustManager、OkHttp的CertificatePinner、WebView的onReceivedSslError这些入口。本讲里很大一部分时间就是在跟这类方法打交道。知道这些方法所在的大致位置比背一个现成模块的命令更管用因为不同App的代码结构会让Hook点完全不一样。2.2 代理检测的几种姿势代理检测是最常被低估的一项。你以为只是App检查系统设置里的代理地址实际上它至少有三个层面。第一层是系统代理检测。Android系统里有一个全局HTTP代理配置App可以通过System.getProperty(http.proxyHost)或者Settings.Global里的http_proxy来读取。很多抓包教程会教你打开WLAN的高级选项设置代理结果App一发现这个代理地址就认为环境不安全刻意不走这个代理或者直接提示无法连接。这就是所谓的“反代理”中的基础形态。第二层是连接行为检测。App会主动往某个服务器地址发一个极短的Socket连接或者用Socket直连的方式绕开系统代理直接和真实服务器通信。你在Burp里看到这个连接但那不一定是业务流量。更麻烦的是App如果发现当前网络环境与内置的安全策略不符可能直接切换到加密DNS、自定义CDN、或长连接通道让你的抓包工具变成瞎子。第三层是应用自身实现的网络栈。很多App不用系统默认的HttpURLConnection而是用OkHttp、HttpClient甚至自研网络框架。这些网络栈读代理配置的方式不一样有的会自己拼接请求头有的根本不读系统代理。测试时你设好系统代理它对某些数据走了Burp另一些数据直接走Socket出去了表现得就像代理被反制了一样。绕过代理检测的通用思路是不要依赖系统代理设置。最可靠的方式是在设备上做透明代理让所有流量经过某个端口再转发到BurpApp从系统层面看到的网络环境是没有代理的实际上流量已经被中转。后面实操部分我会给出一套iptables实现方式。2.3 模拟器检测的指纹识别模拟器检测比很多人想象的更细。早期的模拟器好认就是检查Build.MODEL里有没有“sdk_gphone”、Build.FINGERPRINT里有没有“generic”、或者ro.kernel.qemu是不是1。现在的检测逻辑早就不止这些了。常见的检测维度包括设备硬件信息比如Build.BOARD、Build.BRAND、Build.MANUFACTURER传感器列表模拟器里通常没有真实的光线传感器、陀螺仪电话状态比如TelephonyManager读到的IMEI、IMSI、运营商信息是不是空或固定值SIM卡状态没有SIM卡可能被标记文件系统特征比如/system/bin/qemu-props、/dev/socket/qemud这类模拟器专属路径运行参数比如QEMU相关的内核参数以及CPU指令集中出现的模拟器特征。还有更高级的检测是检查渲染层面比如OpenGL的GPU渲染器字符串模拟器会显示类似“Android Emulator OpenGL ES Translator”这样的内容。这一类用纯Hook还不一定好解决因为有些字段不是Java层直接返回的而是从Native层读取。本讲里Xposed主要帮我们解决Java层的Build字段、TelephonyManager返回值、SensorManager设备列表这些问题Native层检测留在后面做延伸思考。2.4 Xposed框架在绕过体系中的定位Xposed框架是一种Android运行时Hook方案。它在App进程启动早期修改虚拟机方法入口让Java方法在执行前先走我们自定义的逻辑。我们的自定义逻辑可以修改参数、改写返回值、跳过原方法从而在不动App安装包的情况下改变它的运行时行为。在绕过证书校验、代理检测、模拟器检测时它就是用来“篡改App眼中的世界”。Xposed有个特点它作用在Java层适合处理上面这些纯Java层校验逻辑。它改的是进程内的行为而不是磁盘文件因此不会破坏App签名这对于抓包测试来说很方便。但要注意Xposed需要Root权限如果你用的是Android版本较高的模拟器传统Xposed框架不一定支持这时可以选择LSPosed这类兼容方案。LSPosed的功能和模块接口和Xposed一致只是加载方式和兼容性更好很多老的Xposed模块也能直接用。本讲说的“XP框架”其实是一类框架而不是某个单一的软件理解了这一点后面用起来就不会被具体版本卡住。3. 工具选择和Xposed框架的实际用法3.1 抓包工具还是老三样移动端抓包我习惯的搭配是Burp Suite为主Charles为辅Fiddler偶尔备用。Burp对测试链路更顺手尤其在做重放、改包、配合插件时比较好用Charles在iOS端看HTTPS请求排序更直观Fiddler则适合快速跑一些脚本过滤规则。说实话工具本身差距不大关键差别在于证书和代理怎么配置。Burp这边需要在Proxy Listener里加一个监听地址监听0.0.0.0:8080然后让模拟器把代理指向宿主机的IP和端口。证书要从Burp导出DER格式再转成PEM最终以系统证书的命名方式放入模拟器的系统CA目录里。这一步我一会儿详细写因为很多新手卡在这里。Charles其实是类似的思路只是证书名字和导入方式不一样。除了这些常规抓包工具还要准备好一些辅助类工具比如adb、openssl、一个文件管理器能访问/system/etc/security/cacerts以及一个能挂载系统分区可读写的模拟器或Root设备。对于普通内部测试真机Root设备比模拟器更接近真实环境但模拟器更方便反复折腾本讲的实验我直接用的模拟器。3.2 Xposed模块的开发与加载流程先说明一个容易混淆的点Xposed本身不是一个“绕过App防护”的工具它是一个平台。真正干活的是基于它的模块。模块通过Xposed的API去Hook目标方法然后改变方法行为。开发一个简单模块的流程大概是创建一个Android工程不包含Activity只在assets/xposed_init文件里写明入口类路径然后在入口类的handleLoadPackage方法里判断当前进程是不是目标App的包名再通过XposedHelpers.findAndHookMethod去Hook目标方法。比如我要Hook掉App读系统证书库时的异常逻辑我会找到验证证书的方法然后把方法实现直接替换成“什么都不做直接返回成功”。这就是JustTrustMe这类模块的基本原理只不过它会把这些Hook点做得更全。加载模块也有讲究。传统Xposed是在“模块管理”里勾选模块然后重启生效。LSPosed则是在它的管理界面里勾选模块然后通过重启对应App或重启系统让模块生效。有些模块只对特定App启用所以要正确设置“适用作用域”否则模块代码根本没进目标进程。实操里我建议先下载现成的成熟模块来验证思路比如JustTrustMe用来绕过大部分证书固定点然后自己写一个简单模块去Hook模拟器Build字段。等对这个流程熟悉了再考虑自己写绕过代理检测和模拟器检测的专用模块。这样比较循序渐进不容易一上来就被环境问题难住。3.3 Frida和Xposed该怎么选Xposed之外还有一个绕不开的工具叫Frida。Frida是动态插桩框架它也可以在运行时Hook Java层方法还可以直接Hook Native层函数。这一点比Xposed要强因为很多高级App的校验逻辑已经放到Native层执行Xposed只看得到Java层的壳。但Frida也有劣势。首先是检测对抗不少App都内置了对Frida的检测通过扫描进程端口、检查/data/local/tmp下的frida-server文件、或者检测内存特征来发现它。你要先绕过Frida检测才能继续用Frida做Hook。其次Frida每次运行都需要一个注入过程这个过程本身也会留下痕迹对于有加固、完整性校验的App而言容错率更低。Xposed的优势在于它在进程启动时就被系统框架加载注入方式更“底层”很多App的Java层自检发现不了它。但Xposed能做的范围基本限定在Java层如果目标把关键逻辑放在Native层你还是要回到Frida或直接静态分析。实际测试中我的习惯是优先用Xposed处理Java层证书和代理检测因为稳定、可反复重启遇到Native层逻辑再切换到Frida或做Native层分析。两个工具不是替代关系而是互补关系。本讲既然把重点放在Xposed上那我们就先用它打通整条Java层绕过链路再去想Native层的事。4. 实操记录从装证书到完整抓包4.1 搭建实验环境我这次实验选了一个Android 9的模拟器原因是它同时支持LSPosed和大部分Xposed模块比Android 11以上的兼容性好不少而且系统证书目录的读写规则相对简单。模拟器启用了Root系统分区切换为可读写。目标App是内部自己写的“模拟项目X”它做了三件事校验服务器证书检查系统代理检测模拟器特征并闪退。这个App的防护不算复杂但足够走通整条链路。如果你手边没有类似App可以直接拿一个带HTTPS的普通App再配合一个模拟器检测模块来测试效果也接近。准备工具清单Burp Suite、Xposed/LSPosed框架、JustTrustMe模块以及一个自己写的Build信息修改模块。模拟器里要先安装LSPosed然后在LSPosed管理界面里启用JustTrustMe模块作用域选目标App。这里有个小细节LSPosed的模块作用域要包含“系统框架”否则部分Hook点不生效。4.2 把Burp证书导入系统证书目录这一步是新手最容易卡住的地方。Burp默认导出的证书是DER格式Android系统信任目录要求的是PEM格式而且文件名必须按照证书的subject_hash_old来命名。网上很多教程只讲了“把证书放进去”没讲清楚命名规则结果放了但没用。我按这个流程操作# 从Burp导出证书保存为 cacert.der # 先转成PEM openssl x509 -inform DER -in cacert.der -out cacert.pem # 查看证书的 subject_hash_old openssl x509 -inform PEM -subject_hash_old -in cacert.pem命令输出的结果类似9a5b5757。这个值就是文件名的主体部分。然后我用adb连接模拟器把证书复制到系统目录adb push cacert.pem /data/local/tmp/ adb shell su mount -o remount,rw /system cp /data/local/tmp/cacert.pem /system/etc/security/cacerts/9a5b5757.0 chmod 644 /system/etc/security/cacerts/9a5b5757.0到这里Burp的证书就成了系统受信任CA那些只信任系统CA的App在证书校验环节就会被蒙混过关。要注意的是不同Android版本系统证书目录可能略有区别Android 14以后已经改成了/apex/com.android.conscrypt/cacerts不过模拟器上一般用不到那么新。4.3 加载模块绕证书校验证书装进系统区之后理论上系统信任关已经过了。但目标App如果做了SSL Pinning它会在自己的代码里再比一次证书信息。为了验证这一层我在LSPosed里开启了现有模块JustTrustMe然后把作用域勾上目标App重启目标App。重启后Burp里开始出现HTTPS请求的明文流量这说明App自己代码里的证书固定校验被模块绕过了。JustTrustMe的原理不难理解它Hook了OkHttp、HttpURLConnection、WebView、TrustManager中的常见校验方法让所有证书校验都返回“合法”。但一旦App自己写了非标准的证书校验入口JustTrustMe就够不着了。这时候就得打开Xposed的日志用XposedBridge.log输出调试信息动态确认哪个方法被调用再去Hook对应方法。我遇到的一个具体例子是模拟项目X额外检查了证书签发者的国别字段普通模块绕过了系统校验但没管这个自定义检查。后来我在日志里发现它调用了X509TrustManager.checkServerTrusted于是自己写了一个模块去Hook那个方法直接构造一个合法的空信任链返回问题就解决了。这个过程说明现成模块能解决共性自定义模块才是应对个性的手段。4.4 代理检测和模拟器检测的绕过证书关过后App开始“老实”发送请求了吗不一定。很多App会在发送业务请求前先做代理检测发现当前系统代理非空就拒绝发送。我在实验里把模拟器WLAN代理手动设置为宿主机IP的8080端口结果目标App直接卡在登录页Burp里没有任何新请求。这就是代理检测生效了。最简单的绕过思路是关闭系统代理改用透明代理转发。我在模拟器里执行了下面这条iptables规则adb shell su iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to-destination 192.168.x.x:8080这条规则把本机发出的所有目标端口为443的流量直接转发到宿主机Burp监听的8080端口而系统网络设置里并没有代理App从代理检测逻辑里看不到任何异常。Burp端口上需要对宿主机同网段连接做监听同时关闭代理的透明模式选项或按需配置确保收到的请求和正常代理一致。实测下来目标App的登录、查询接口全部被Burp截获。接着是模拟器检测。我用的模拟器在Build.FINGERPRINT和Build.MODEL上有明显的模拟器特征目标App一启动就闪退。解决的办法是Hook掉这些Java层的字段读取入口。我写了一个小模块在目标App启动时把Build.MODEL改成真机常见型号、Build.MANUFACTURER改成常见厂商、Build.FINGERPRINT改成某真机ROM的指纹。同时我还Hook了TelephonyManager.getDeviceId和getSimState让App以为它跑在一台有SIM卡的真机上。这里有几个容易出错的地方有些字段不是通过Build.MODEL直接读而是通过反射调用Build.getRadioVersion或者SystemProperties.get。所以写模块时要多看几条日志确认它到底读的哪个字段。还有传感器检测目标App如果调用SensorManager.getSensorList发现只有一两个传感器也会判定为模拟器。我直接Hook通通返回一组正常传感器列表算是把常见检测点全覆盖了一遍。4.5 验证抓包结果所有模块加载完毕将模拟器WLAN代理先关掉依靠iptables透明转发LSPosed里保持目标App的Hook模块启用。重启目标App确认它能正常启动不闪退能完成登录请求列表在Burp里能够看到。我在Burp的HTTP History里验证了一下请求里用的还是目标App自己的HOST域名没有出现连接失败或强制下线情况说明透明代理这条路走得通。这一步是整个实验的终点也是后续漏洞测试的起点。拿到完整请求流量之后就可以继续做越权测试、敏感信息检测、参数篡改这些常规Web层测试了。到这时本讲标题里的几个关键词才算真正形成闭环。5. 踩坑记录和排查思路5.1 我实际踩过的坑先说一个最隐蔽的坑模拟器的系统分区挂载只读。你按照教程把证书复制到/system/etc/security/cacerts/但系统会静默还原或直接报“Read-only file system”。很多模拟器就算开了Root默认也是只读挂载。解决办法是先执行mount -o remount,rw /system如果这条命令报错还需要重启模拟器并在Root模式下开启系统分区可写。第二个坑是关于透明代理的。iptables规则如果只加了DNATBurp收到的HTTPS请求可能会出现“请求头缺失”或者UnknownHostException因为有时它转发的是原始IP而不是域名Burp拿不到正确的Host头。解决方式是在Burp的代理监听里开启“Support invisible proxying”并对透明代理场景做相应配置。如果不开启这个选项你只能看到IP地址没法按域名过滤请求排查业务接口时会很难受。第三个坑是模块启用后不生效。常见原因是LSPosed的作用域没选对。安装模块后必须回到LSPosed界面找到模块勾选目标App再完全重启一次目标App进程。有些模块还需要勾选系统框架我刚接触时经常漏掉这一步导致模块日志没有任何输出。另外要注意如果修改了模块代码或配置也需要清掉目标App的后台进程否则Hook仍停留在旧状态。5.2 快速排查清单以下是我在测试中用来排查整套链路是否正常的检查顺序问题现象大概率原因排查动作证书装了但HTTPS抓不到证书没有放进系统CA目录或文件名不对用openssl确认subject_hash_old核验系统证书文件名App提示证书错误做的是SSL Pinning系统证书没用启用JustTrustMe查看日志确认Hook点设了代理后App无响应App检测到系统代理关掉系统代理改用透明代理转发启用模块后没有任何效果Xposed/LSPosed作用域没勾目标App回到模块管理界面重设作用域并重启App闪退或强退模拟器特征被检测Hook Build字段和TelephonyManager并查看日志确认检测点Frida方式用不了App做了Frida检测换Xposed模块或者对Frida隐藏端口/文件后续研究接口只能看到IP看不到域名透明代理没有正确转发CONNECT请求检查Burp是否开启隐形代理调整iptables规则这个表基本覆盖了普通测试者会遇到的大部分问题。多数情况不是方案错了而是某一个前置条件没满足。6. 绕过之后防御方视角和后续进阶6.1 如果我是防守方会在哪里加强做完一次完整的绕过链路之后再回头看App自己的防护很多漏洞其实一清二楚。如果我是App开发者或安全工程师我不会只依赖一层证书校验而是会把防护组合起来并且不只做Java层检测。首先证书固定要做但更重要的是做证书动态校验和防Hook检测。比如在使用OkHttp时不只校验公钥还要定期更新固定证书信息。其次代理检测不能只看系统代理设置还要在Native层做Socket连接行为分析凡是发现流量出现异常转发就切断关键接口。再次模拟器检测要综合硬件、传感器、CPU指令集、电池状态做多维判断只查Build字段在面对Hook时等于送分。更关键的强化点是加Native层的Frida检测和Xposed检测。检查进程空间有没有Xposed相关类、检查内存中是否出现hook特征的字符串、检测动态库是否被注入。这一层做好很多靠现成模块打天下的新手就卡住了。但作为测试者我也只能说这种防护也是可以被绕过的只是成本更高。所谓攻防就是双方不断抬高对方的时间成本。6.2 往后还能学什么学完这一讲我最大的感受是APP攻防的深度远比想象中大。这一讲还停留在Java层、停留在“让流量正常出来”的阶段。再往后至少要掌握三类内容一是脱壳遇到加固App先把加固壳去掉才能找到真正的入口和校验方法二是Native层分析学会用Frida跟读Native函数、看汇编指令很多反调试、反Hook逻辑都在这一层三是协议分析App如果走私有协议、加密字段、双向认证你需要静态分析动态调试结合才能还原协议格式。如果你和我一样还在学习阶段建议不要急着上乱七八糟的“全自动绕过框架”先把这一讲里的手动链路走通至少三遍一遍跟着文档做一遍自己不看文档做一遍换一个不同App做。等你对证书、代理、模拟器这三类检测的绕过逻辑形成条件反射之后再进Frida和Native层会顺很多。最后分享一个我个人的操作习惯每次实验都保留一份完整的环境快照和模块启停信息这样一旦踩坑可以快速倒回状态而不是反复从零配置。这个习惯帮我省了大量时间对特别依赖环境状态的安全测试来说很值得养成。
阅读完成 · 觉得有帮助?