
先聊点题外话。平时总有人问我APP逆向到底做到什么程度才算“会了”是把界面调出来、按钮点一遍还是能把加密参数手算出来我的看法是如果用满分100分来量化一次逆向分析的完整度能稳定做到95分的基本就算真正吃透了这一行。这95分怎么定义功能完整度、协议还原度、加密算法定位的准确度、以及在各种反调试、加固手段围追堵截下的从容度每一环都要扣分。最近复盘了一个电商类APP的完整逆向案例正好卡在这个评分标准上全程走下来细节非常多从头到尾拆解一遍顺便把踩过的坑和沉淀下来的思路一并放出来。1. 案例背景与“95分”目标的具体定义这次的目标是一个带用户积分、支付和订单流转的电商APP客户端做了比较完整的加固网络层走HTTPS且带自定义签名校验。为什么说这个案例能对标“95分”呢因为市面上绝大多数分析文章只停留在“看到明文接口”或“翻出加密算法”这个层面也就是60分合格的水平。真正要冲击95分需要把下面几件事全部做完。第一件事是完整搞清楚一个核心业务的请求链路。比如下单支付这个动作从用户点击按钮到最终拿到订单号前端到底构造了哪些字段、这些字段各自怎么拼接、谁参与签名、签名结果放在Header还是Body里都必须做到心中有数。第二件事是把客户端的核心加密逻辑从代码里“捞”出来不要只停留在Hook到结果这个层面而是要能定位到具体的算法实现位置不管是Java层还是So层能改参数出0也能改参数出1这才叫真正逆向出来。第三件事是绕过客户端的所有反调试和完整性校验用Burp Suite或Charles抓到明文流量并且能做到本地重放、篡改字段后服务端还能认这就拿到了“会话级”的控制权。这三步全跑通我认为就够得上95分。在这个案例里目标APP做了一个很有意思的防逆向手段把加密核心逻辑全部下沉到二进制So库里同时Java层做了一个“轻量上报”机制一旦检测到Frida、Xposed、调试器或者Root环境会静默提交设备信息到服务端服务端再把这个设备拉黑。这种“明面上不阻断暗地里静默标记”的设计特别恶心人因为它不会立刻让你发现问题而是到关键时刻让你莫名其妙收不到数据。这也是这次案例里最值得拿出来说的部分——如何发现并反制这种“软风控”。2. 前期侦察与静态布局这个阶段的核心目标就是两个摸清攻击面、建好作战环境。环境如果搭不对后面每一步都会踩雷。2.1 加固壳识别与脱壳策略拿到APK第一件事不是装到手机上而是先扔进apktool看原始资源和解包后的目录结构。干这一行久了都有肌肉记忆看到lib/armeabi-v7a下面的libDexHelper.so或者libjiagu.so脑子里立马要弹出对应的厂商特征库。这次碰到的明显是某数字公司的加固方案Java层的业务逻辑被抽空了onCreate方法变成了一个Native入口的壳方法。脱壳方案我首选FART脱壳机因为它在ART虚拟机执行ClassLoader时能同步把加载进来的类dump出来。流程是先在一台可以对Pixel设备上刷好指定的Android版本装好Frida然后跑FART的dump命令把内存中的dex文件导出来。这里有个需要注意的细节现在的加固方案普遍有“反内存dump”机制会在某些关键方法执行完以后把类标记为内存垃圾让dump出来的dex缺失部分方法体所以脱壳之后尽量用jadx打开看一眼结构完整性最好能用dex2jar转成jar再对比一下方法数量。如果发现方法为空壳别慌直接上Frida-DEXDump这种基于内存搜索的方案打辅助把内存中的dex重新拉全。脱壳也就是脱掉加固壳的步骤做完以后把dex重新打包成可阅读的源码时我一般不用jadx的默认配置而是会打开“源码模式”并且关掉“反混淆”只要保证jadx能把逻辑结构还原出来就好。这个阶段的目标不是看明白具体业务而是把整个项目的类关系、Activity入口、和管理类都摊开来建立起一个完整的地图。2.2 抓包环境与SSL Pinning绕过准备静态分析还没出成果之前抓包环境就得先跑起来。因为目标APP的通讯层全在HTTPS里而且还是双向校验这种场景下Burp Suite是比Charles更好的选择因为Burp的代理环境能跟夜神模拟器、真机、Frida脚本无缝配合做请求改包、断点、中继转发都流畅得多。环境配置方面推荐用USB连接一台老版本安卓真机把Burp的CA证书帮忙装进用户证书区。现在的APP大多数都会在代码里校验TrustManager单纯把证书装进系统证书区已经不够用了必须配合Frida的ssl_pinning_bypass脚本先跑起来。常用的绕过姿势是利用objection的android sslpinning disable快速试一道如果绕过不生效再手动写Frida脚本去HookTrustManagerImpl的checkServerTrusted方法让它直接返回Null跳过校验。这里有个关键词要留意目标APP如果把证书校验放在一个独立的守护进程里你光Hook主进程的TrustManager是不生效的还需要把Frida的-g参数改成附加到那个守护进程上。这一块非常容易被忽略很多新手抓不到包以为是自己证书有问题实际上是挂错了进程。3. 动态注入与核心算法突破静态地图整理完以后最大的难点浮出水面客户端所有的加密逻辑包含签名生成、时间戳拼接、随机数搅进Header全部封装在底层的一个libcore.so里而且这个So文件没有剥离符号表说明开发者虽然做了加固但没有做汇编级别的混淆这对逆向来说是重大利好。从Java层到So层的调用链很清晰就是一句System.loadLibrary(core)然后定义了一个native方法。3.1 用Unidbg把算法从So里抠出来这里我先说明为什么选择Unidbg而不是直接在IDA里硬啃。真实场景里So文件往往会做环境检测你在手机上有完整的调试器生态它反调试反而更简单。而Unidbg是在PC上模拟安卓进程环境让So自己在一个“看似正常”的环境里执行不需要真机也不需要Root更不用面对反调试的阻挠只需要把So依赖的符号补齐它就能像在真机上一样运行。实践中是这样操作的先用jadx找出native方法的完整签名然后新建一个Java工程引上unidbg-android的依赖包创建好安卓模拟器环境把这个libcore.so丢进去。然后通过vm.callFunction()去主动调用那个native接口传入跟真机上理论上一致的参数观察输出。如果是简单算法直接就能拿到明文和密文的对应关系随后配合IDA的F5反编译很快就能还原出加密逻辑。但这次的So里做了“加盐”处理——它会读取手机的IMEI、时间戳、随机数共同参与哈希计算不把这些环境变量喂进去输出结果就会对不上。这时候必须返回到Frida侧把真机上那几个环境变量的值抓出来再硬编码进Unidbg的模拟环境里。这种“主动调用”的调试思路意思就是让你跳出来用本地环境去解释真机逻辑比单纯跟着断点跑要高效得多。3.2 定位加密算法类型与查找关键字节拿到So的汇编级代码后我习惯先用readelf看动态符号再用radare2或IDA探索逻辑结构。如果看到一个函数内部反复进出内存、调用一大堆位运算基本上可以怀疑是AES或RSA这类分组密码。通过常见特征的匹配方式能快速确认底层调用了EVP_EncryptInit_ex这类OpenSSL接口。顺着指针往回追加密用的Key和IV到底怎么生成的通常会有前面拼接的一段字符串。这段拼接动作往往不只是简单连接里面会有一个专业上的“密钥派生”逻辑是查表还是用固定盐加上上一段签名的哈希值都需要在汇编里一点点还原。例如这个案例里的签名逻辑大致是这样把请求体中的业务参数按照字典序排列去掉空值拼成一个StringA然后再加上appId和一个失效时间戳拼成StringB最后对StringB做HmacSHA256结果转成十六进制字符串。表面上看起来很严密但实际上通过Unidbg动态调用把任意输入的参数喂进去就能反推出这张“拼接规则表”所以它的安全强度并不是靠算法本身而是靠把逻辑埋得很深。还原出这一步正如我之前说的我的“95分”进度条就走到了60分后面要做的才是重头戏——怎么让整个流程可落盘。3.3 Frida主动调用与内存级审计静态还原出算法之后千万别急着写脚本收工还有一道很重要的工序是“校验”。用Frida写一个主动调用脚本直接调度So里的native接口一次跟静态分析结果做比对只要一次对不上说明之前还原的拼接规则里肯定有遗漏字段。我习惯的做法是在Frida脚本里添加一个console.log把传入native方法的参数、返回结果全部打印出来然后拿来跟Unidbg模拟的结果做diff。另外为了确认签名有没有被某些中间层二次加工还要做一次“内存级审计”。具体方法是在手机上通过Frida遍历整个内存空间抓取与目标关键字符串相关的临时缓存。这个方案有一定概率捞到中间变量。虽然现在很多加固会做“抹内存”操作但只要操作得当还是有机会在函数执行过程的中段把指针抓住。不过请注意在出货给前端时某些元数据会被Android系统级定期清理所以做内存审计的时候最好多跑几遍业务操作增加截获概率。这一趟下来签名算法和传输逻辑基本就被钉死了剩下的就是协议和字段的全局串联。4. 抓包与协议篡改表面功夫还是需要真本事的算法还原只是单向的“读懂”要拿到95分还得把请求和响应完整地经Handshake落进Burp里。这阶段要处理两个主要问题一个是证书双向校验一个是明文数据与加密数据字段的映射关系。4.1 双向证书校验的应对思路一般文章讲SSL Pinning都是单向校验也就是客户端校验服务端证书这种用带-f参数的通用Frida脚本把checkServerTrusted置空就能绕过。但双向校验也就是服务端还要校验客户端证书的就麻烦一些。这个案例里APP启动时会从系统读到一份内置的客户端证书然后校验服务端传来的Random数这其实就是mTLS通信的雏形。绕过思路是不需要真正去“构造”一份客户端证书更简单的方案是直接Hook持有该证书的构造函数把明文key和证书链整个dump出来然后作为Burp的Client证书配置进去。配置完以后Burp就可以顶替原来的“系统身份”发起后续请求服务端验完证后也认整个HTTPS链路就变成了Burp作为中间人和服务端的明文通信。操作方式是在Burp的Project Options - Connections - Client SSL Certificates里添加新证书选“PKCS#12”格式并把之前dump出来的client.p12传上去。这样设置后代理抓到的就是可以直接读懂的明文流量协议篡改也就有了地基。4.2 业务字段与加解密参数的映射明文流量到手后要把上一阶段分析出的签名算法跟网络包里的字段一一对应起来。比方说看到一个请求体里有{page:1,pageSize:20,ts:1713177600000,sign:a1b2c3...}马上要想到ts对应的是时间戳参数而sign就是在这基础上拼接方法的完整路径名。此时可以试着用Burp的Repeater工具把pageSize从20改成10000然后在ts不变的情况下从解析算法里把新的sign计算出来重新提交。如果服务端返回正常的业务数据说明连接会话的合法性这一块没有被单独校验整个链路就打通了。这里分享一个容易被忽略的细节很多时候签名不只是对body进行签名还会把请求头中的User-Agent、Accept-Language这些字段一起拉进去参与计算所以在构造重放攻击的请求时不能只改Body而忘了同步修改参与签名的Header名段。我见过不少半吊子案例将Body改完但Header里的原始值没动导致签名验证失败直接返回“验签失败”浪费时间。所以每次做重放之前推荐用Postman或Burp插件先把整包请求导出然后逐字逐句比对参与签名的字段全集。5. 常见问题排查与细节打磨实录这部分内容在正式对外输出的时候属于那种“不写在文档里但实际能救命”的经验沉淀。我按实战中出现频率从高到低排了个序凑成一张避坑清单。5.1 抓包时App直接断网或闪退这种情况十有八九是SSL Pinning没绕干净或者是客户端检测到了代理环境。先用objection跑一次android sslpinning disable如果依然失效就打印堆栈看异常是哪一行抛出的。如果是System.loadLibrary那一步闪退大概率是Anti-Debug机制在作怪。这时候不推荐硬破反调试更优雅的方案是直接Hook掉isDebuggable相关方法的返回值或者在Frida里用Interceptor.replace把ptrace调用替换成返回1。5.2 Frida附加进程时出现“unable to start”出现这个提示优先怀疑是环境权限问题多看看App是否开了hardened_malloc或userfaultfd这类防护。如果是老设备还要注意Frida版本跟Android架构的匹配程度。我建议低版本安卓7.0到10.0就用Frida 15.x高版本安卓12以上再用16.x否则会出现令人崩溃的段错误。另一个经常被忽视的点是APP里如果内置了网络安全配置用network_security_config.xml限制了用户证书安装那么即便手机Root了把证书放到系统证书区也会因为配置不信任导致HTTPS握手失败这时候不要死磕直接用Magisk Trust User Certs这个模块把用户证书同步到系统分区一行命令搞定。5.3 Unidbg模拟出来的Result不准确当Unidbg的运算结果和手机Frida的真实结果对不上时我一般从三个方向入手排查。第一检查So初始化时是否有静态注册表有些算法依赖全局随机种子手机端和PC端的随机种子不同会导致结果天然不一致解决方法是给PC端也注入一个随机种子。第二检查是否缺少Android系统环境的指定依赖比如某些So在计算前会查询系统语言、时区、地区这些参数不完整会直接走默认分支导致算法路径跑偏。第三RSA私钥或公钥如果通过系统密钥链读取在Unidbg里基本读不出来所以必须找到代码里写死的那份内置公钥把它转成Java文件格式之后再喂给Unidbg的密钥管理对象。这三个方向排查完结果基本上能对齐。6. 复盘与扩容思路说到这你觉得一次95分的逆向案例到这里就收尾了还没有。95分跟100分之间的差距在哪里在“稳定性测试”和“业务链闭环”上。很多同行的习惯是拿到一个sign算法就把它当成“破解成功”的充要条件但在真实商战中服务端远比客户端想得多。比如本次案例里就算我把sign和ts都算对了服务端后台还有一层频控和风控同一个设备短时间内高频访问直接触发滑块验证这还是没达到闭环。真正去做安全评估时要把这种对抗当成常态不要追求每一次都能绕过而是能清楚地记录这个APP当前达到的安全水位知道在哪一步会触发哪一层风控这本身就是一份很专业的渗透报告。后续可以继续完善的方向是把这次分析出来的协议字段全部写一套自动化脚本用Python的requests库配合解密算法模拟整个订单流程实现“半自动抢购”式的接口调用。但这里必须提一句做逆向分析永远要记住红线是“授权”不要碰未经授权的系统技术本身没有好坏之分但一个真正成熟的安全从业者首先要学会在合法合规的框架内施展拳脚。这是所有工具书里都不会教你的东西也是我做了十来年安全一线后最想强调的一点。话说到这关于工具链的搭配想再分享一个心得。很多人喜欢盲目追求最新版本的Frida或Jadx其实大可不必。逆向工程是讲究“生态稳定”的领域我见过很多同行因为升级了某个跨平台的运行时库导致老脚本全部失效。真正稳妥的做法是搭一个常驻的虚拟机环境里面固定好你常用的安卓镜像、Frida版本、Jadx版本、还有Burp插件组合一旦验证好这批工具的兼容性就尽量不要频繁变动除非有重大的漏洞导致功能瘫痪。频繁的版本更新耗掉的精力远大于它带来的收益。记住这些细节可能比多读十篇分析文章都要有用。