2026/10/19 10:23:21

某物App推荐接口逆向分析:抓包与签名还原全流程实战

某物App推荐接口逆向分析:抓包与签名还原全流程实战 相信不少做数据分析、安全研究或者自动化采集的朋友都有过这种经历某款购物App的推荐流数据价值很高你想拿它做点研究结果发现客户端压根没给你留一个“方便之门”。我也是一路踩坑过来的这次专门把“某物App”的推荐商品接口完整拆了一遍从抓包到签名还原再到模拟调用把整条链路走通。这篇博文就是这次分析的全程实录适合对移动端接口协议感兴趣、想搞懂推荐接口背后机制、或者正准备做数据采集但又不知道从哪下手的朋友。先说明一点这篇文章里的所有操作都限定在学习和安全研究的范畴我不会提供任何批量采集或商用破解的方案也不鼓励任何人拿这套技术去做损害平台或用户利益的事。搞清楚边界之后我们再来看技术本身。整个拆解过程我按“业务认知 - 抓包定位 - 签名还原 - 问题排查”四个阶段来做每个阶段都有可以落地的步骤和直接能用的经验。1. 先搞清楚推荐接口的业务形态1.1 推荐接口和搜索接口完全是两码事拿到“某物App”这个目标后我的第一反应不是直接开抓包工具而是先想清楚一个重要问题推荐接口到底属于什么类型的业务接口。推荐接口和搜索接口差别非常大。搜索接口通常有一个明确的query参数比如商品关键词、筛选条件后端根据文本匹配返回结果逻辑相对直观推荐接口则完全不同它背后是一套算法引擎输入的是用户ID、浏览历史、点击序列、当前场景、地理位置、设备型号、时间甚至还有AB实验分组输出是一串经过排序的商品。这也决定了逆向分析的重心你不仅要搞清楚接口的参数格式还要把签名、风控、调度这些“外围机制”全部摸清否则就算拿到了接口地址也很难模拟出一个让服务端“信以为真”的请求。从请求格式上看这类App的推荐接口通常有几个共同特征URL路径里一般带 recommend、rec、feed、home 这类关键字请求方法绝大多数是POST因为参数多且需要携带上下文信息Body格式是JSON或者表单Header里会带一堆客户端信息。这次分析的目标接口形态大致如下POST https://api.某物app.com/api/v1/recommend/home Content-Type: application/json请求体里会包含page、pageSize、scene、last_page_token、device_id、session_id、timestamp、sign等字段其中sign是整个请求的核心后面的章节会重点展开。1.2 为什么不能直接抓网页版接口很多人会问这个App不是有网页版吗直接抓网页端的接口不就行了为什么非要逆向App因为网页端和App端通常是两套独立的接口体系。网页端的接口往往有独立的风控逻辑返回数据经常被“脱敏”处理比如商品价格、库存、推荐理由在网页端不一定给全而且网页端需要登录Cookie部分页面还是服务端渲染的解析成本高。App端接口返回的是纯粹的结构化数据字段最全、更新最及时对做数据分析的人来说显然是更好的数据来源。但App端的问题同样明显请求参数加密、签名校验、设备指纹、证书绑定一层套一层。逆向分析的本质就是把这层壳剥开还原出“客户端到底是怎么构造这个请求的”然后在自己写的程序里复刻这个行为。1.3 拆解目标与合规边界动手之前我会把整个工程拆成四个阶段抓包拿到真实请求的URL、Header、Body定位找到接口对应的代码位置还原搞清楚签名、加密、风控字段怎么生成模拟用自己的程序重放请求验证效果。这里必须强调合规的边界。逆向分析本身是安全研究和开发学习中的合法技术方向很多大厂的安全团队日常做的就是类似的协议分析。但如果你打算绕过风控批量抓数据、做商业化数据倒卖或者把分析过程写成公开的“破解教程”那就越界了。我的态度是只做学习验证、只做小规模个人研究不公开发布可用的采集工具不碰支付、账号、隐私这类敏感接口。下面正式开始技术流程。2. 抓包定位接口的完整实战流程2.1 环境准备手机、模拟器与代理配置抓包是整个分析的地基地基不稳后面全是空中楼阁。先说我的标准配置。硬件或模拟器方面我习惯用Android平台理由很简单Android生态开放Root、Hook、证书安装都方便而且大多数电商App的Android端和iOS端接口基本一致。你可以用一台Android手机也可以直接用模拟器。我在日常开发环境里用模拟器比较多因为启动快、支持快照、可以随时重置环境。抓包工具我用的是代理型工具市面上比较常见的有Charles、Fiddler以及mitmproxy原理大同小异手机流量先经过代理工具做中间人解密HTTPS。如果你是命令行爱好者可以用mitmproxy配合脚本能把请求直接落库但如果想要可视化操作体验Charles这类工具效率更高。搭建步骤其实不复杂手机和电脑连同一个局域网电脑上打开代理工具监听端口比如8888手机WiFi设置里把代理指向电脑IP的8888端口手机浏览器访问代理工具的证书下载地址比如Charles的chls.pro/ssl安装CA证书用adb设置全局代理也可以命令是adb shell settings put global http_proxy 192.168.1.100:8888这里有一个很关键的知识点Android 7.0之后系统默认不信任用户安装的CA证书。如果你抓的是普通App很多App其实直接就能解密但如果是经过安全加固的电商App大概率会做证书校验。到时候有两种解决思路一是把用户证书转成系统证书需要Root后操作或者用Magisk模块把证书挂载进系统分区二是用Hook框架在运行时绕过证书校验。我一般先走“系统证书”路线如果App做了SSL Pinning再上Hook。2.2 捕获推荐接口的真实请求环境搭好后打开App进入首页推荐Tab下拉刷新或者上滑翻几页抓包工具里就会冒出一堆请求。这时候不要急着乱看先学会过滤。我的习惯是先在抓包工具里输入“recommend”关键字或者按域名过滤把目标请求筛出来。找到之后重点看四样东西请求行URL、请求方法、协议版本HeaderUser-Agent、Client-Type、App-Version、Authorization、X-Device-Id等Body业务参数响应返回的JSON结构。下面是我在实际抓包中整理出来的典型参数表具体字段名因版本而异但结构指向基本一致参数类型作用说明pageint页码第几页推荐流page_sizeint每页条数常见值20或30scenestring场景标识比如home、search、detaillast_page_tokenstring上一页返回的滚动令牌翻页必备device_idstring设备唯一标识风控维度之一session_idstring会话标识服务端用它串联用户行为client_typestringandroid / iosapp_versionstring客户端版本号timestampstring毫秒时间戳signstring请求签名核心校验参数有一个字段需要特别留意last_page_token有的App也叫page_token或offset。推荐流是无限滑动的服务端不用传统页码分页而是每次返回一个token下次请求必须带上它服务端才知道“你上次看到哪了”。模拟请求时如果忽略这个字段你会发现翻页翻不过去或者返回大量重复数据。2.3 返回数据的结构解读抓到响应后JSON结构通常长这样{ code: 0, message: success, data: { items: [ { product_id: 100234, title: 某品牌双肩包, price: 299, original_price: 499, reason: 因为你常逛户外装备, trace_info: { item_id: 100234, scene: home, position: 1 } } ], next_page_token: abc123, has_more: true } }这里的reason字段很有意思它是算法给用户看的推荐解释。做逆向分析时这个字段能帮你判断接口到底是“真个性化推荐”还是“运营位固定推荐”。如果每条推荐商品的reason高度雷同比如全是“热销”“特惠”那大概率推荐策略偏运营如果reason里有“猜你喜欢”“你常逛的品牌”说明确实有实时用户画像在参与排序。trace_info是曝光埋点数据客户端会在商品曝光时回传。模拟请求时可以不传它服务端一般不校验但如果要做更深度的行为模拟需要把它存下来并按真实协议回传。3. 核心难点签名与风控参数的还原3.1 先识别签名校验的“长相”抓包只能拿到数据真正的硬骨头在签名还原。拿到请求后你先观察一下有没有这几个特征请求里有一个sign字段值是一串看似随机的十六进制字符串同时有一个timestamp字段几乎是毫秒级的当前时间你改动sign以外的任意字段请求立刻返回“签名错误”。这几点基本说明签名函数是把请求参数、时间戳、加上一个固定的盐值拼在一起再做一次哈希运算。最常见的算法是MD5或SHA-256进阶版本会先用HMAC或者RSA包装一层。识别算法有个简单方法看sign的长度。32位十六进制基本是MD540位是SHA-164位是SHA-256。但这只是第一步关键还得看拼接规则和盐值是什么。这里我分享一个经验很多App的sign长度确实是32位但这并不意味着只有MD5因为MD5有可能只作为整体摘要的一部分前面还可能套了一层自定义的混淆逻辑。所以长度只能作为线索不能作为结论。3.2 从代码层定位签名生成函数拿到APK后我一般用反编译工具直接打开。如果不加固Java层的类基本都能直接看。进入代码后搜索关键字“sign”“timestamp”“salt”很快就能找到疑似函数。但现实往往比较棘手App的代码做了混淆类名和方法名全部变成a、b、c直接搜关键字基本无效。这时候就要上动态分析法也就是用Hook框架。我常用的是Frida它可以在App运行时注入JavaScript脚本Hook Java层的方法打印入参和返回值。举个例子签名生成前通常会经过字符串拼接我可以在运行时Hook StringBuilder的toString方法观察哪个字符串的末尾拼接了盐值Java.perform(function () { var StringBuilder Java.use(java.lang.StringBuilder); StringBuilder.toString.implementation function () { var result this.toString(); if (result.length 30 result.indexOf() 0) { console.log([StringBuilder] result); } return result; }; });这类Hook脚本采取“先粗后精”的策略先打印所有拼接字符串再根据长度和上下文缩小范围。找到拼接串后再Hook MD5、SHA-256这些摘要类的update方法就能看到最终的原始输入。这个定位过程看似简单但实际做起来非常考验耐心因为App可能在好几个地方都在做字符串拼接你需要靠日志上下文把真正的签名链路筛选出来。3.3 处理Native层的加密逻辑现在很多成熟App已经不只停留在Java层加密而是把签名算法下沉到Native层的so文件里。这背后的考量很直接so文件是二进制无法直接看到源码需要反汇编和动态调试方法论完全不同。判断是否走了Native层你可以在Frida里Hook java.lang.System.loadLibrary或者JNI的注册函数然后看so文件被加载的时机。如果签名相关字符串出现在Native调用栈里基本可以确认算法在so中。Native层的分析思路一般是这样的先查看APK的lib目录下有几个so文件找到与业务相关的那个比如名字带sec、core、sign字样用反汇编工具查看导出函数再结合Frida的Interceptor.attach在关键函数上打点观察输入输出。这个过程相对耗时但思路是线性清晰的找到Java层到Native层的JNI桥接函数然后把参数和返回值的对应关系摸清。这里还要补充一个常见情况现在很多App会套一层加固壳。遇到加固的APK直接反编译看到的是壳的入口真实DEX是运行时才释放的。处理思路是先识别加固服务商再用对应的脱壳方案把内存中的DEX dump出来。脱壳工具的具体用法各有不同但通用步骤都是让App运行起来等内存加载真实DEX后抓取并还原最后再用反编译工具分析。3.4 完整构造一个模拟请求签名算法还原之后就可以在本地写代码模拟请求了。下面是我常用的Python模板import hashlib import time import requests SALT 从App里提取到的盐值 def make_sign(params: dict) - str: # 按key排序后拼成字符串 keys sorted(params.keys()) raw .join(f{k}{params[k]} for k in keys) raw raw.rstrip() SALT return hashlib.md5(raw.encode(utf-8)).hexdigest() def fetch_recommend(session_token: str): params { page: 1, page_size: 20, scene: home, last_page_token: session_token, device_id: 你的测试设备ID, session_id: 测试会话ID, client_type: android, app_version: 8.0.0, timestamp: str(int(time.time() * 1000)), } params[sign] make_sign(params) headers { User-Agent: Mozilla/5.0 ... 某物App/8.0.0, Content-Type: application/json; charsetUTF-8 } resp requests.post( https://api.某物app.com/api/v1/recommend/home, jsonparams, headersheaders, timeout10 ) return resp.json()有几个坑必须提醒。第一salt不要写死成固定值不同版本可能不同建议从一个版本稳定后再固化第二时间戳必须是毫秒级秒级时间戳会被服务端直接拒绝第三device_id如果留空或格式不对服务端很可能返回风控提示。第一次跑脚本绝对不要多线程并发先用单请求把链路验证通再考虑其他优化。3.5 风控字段的行为级校验除了签名这种静态校验服务端还会做行为风控。意思是即使你的sign完全正确但如果请求频率、时段、行为轨迹和“正常人”差太多照样会被拦截。常见的风控维度有几个请求频率一个设备短时间内发起几十次请求直接触发限制设备一致性设备ID、机型、操作系统版本组合不合理会被标记为异常行为序列没有先打开App、没有浏览页面、没有点击行为就直接调推荐接口会被判定为脚本。所以如果只做学习验证我建议每次请求间隔至少5到10秒模拟“人翻页”的节奏如果做更深入的接口行为研究还需要补上埋点上报、页面停留时间等前置行为。但说实话这些一旦做全套就接近商业级模拟了我不鼓励在没有授权的情况下做这种深度对抗。4. 常见问题与排查技巧实录4.1 抓不到包或请求全是CONNECT隧道很多新手第一步就卡在抓不到包上App请求在代理工具里只显示CONNECT没有明文内容。这通常不是工具的问题而是App做了SSL Pinning也就是证书绑定。客户端在建立HTTPS连接时校验服务端证书指纹一旦发现证书不是它内置的那张直接断开连接。排查思路按照优先级来说确认代理本身没坏先用浏览器访问一个HTTPS网站如果浏览器能解密说明代理通畅确认证书装到了系统证书目录而不是用户目录Android 7只信任系统证书如果都做了还是不行就需要Hook绕过Pinning。绕过Pinning的通用做法是用Frida找常见的证书校验库比如OkHttp的CertificatePinner、系统TrustManagerImpl的checkTrustedRecursive然后让校验函数直接返回。这类脚本网上有现成的但建议自己理解原理再改不要盲目照抄因为不同版本App的证书校验时机和入口方法可能有差异。4.2 请求一直报“签名错误”签名错误是最常见的重放失败原因。我遇到过的情况里90%不是算法本身搞错了而是某个细节没对齐时间戳格式不对有些App用毫秒有些用秒有些甚至用自定义的混淆时间戳参数排序规则不对有的按ASCII排序有的按参数名长度排有的按自定义顺序排拼接格式不对可能多了一个空格、引号、等号、符号或者换行符少传了某个“不参与签名但服务端会校验”的参数。碰到这种情况最高效的办法不是猜而是回到Frida里把真实的签名输入打印出来。让App自己告诉你答案比自己反复试错要快得多。我自己常用的排查套路是先只盯一个参数的变化比如单独改timestamp看sign的输出怎么变逐步逼近完整规则。4.3 返回“设备异常”或被风控拦截如果签名没有报错但接口返回类似“操作频繁”“设备异常”“需要验证码”之类的提示那就是触发了行为风控。处理建议按顺序尝试降低频率改为单线程、间隔10秒以上换一个干净的设备ID从App新安装后首次启动的包里抓取原始分配值确认请求头完整User-Agent、Accept-Language、X-Device-Id这些尽量与真实请求一致如果是IP维度限制可以考虑更换网络环境但不要用违规手段。我个人的经验是很多风控拦截其实是“数据不一致”导致的比如设备ID是A但设备型号是BUser-Agent里是C几个维度对不上风控就会怀疑是脚本。你从抓包里看到的真实请求是什么样子就尽量把自己的请求做成什么样子一比一还原才是应对风控最稳妥的策略。4.4 接口字段变化时要怎么办推荐接口是动态的今天返回里还有reason字段明天可能换成了recommend_tag今天翻页用next_page_token明天可能改成offset。遇到字段变化不要慌。先抓一次真实请求对比本地脚本的解析逻辑把新增字段加入解析模型同时保留旧的字段映射做兼容处理。如果要长期维护这套东西建议把请求模板做成配置文件字段变化时只改配置不改代码。我自己的工程习惯是维护一份“接口字段映射表”每次抓包后把新旧字段做一个diff这样即使App版本升级也能在最短时间内跟上节奏。5. 一些踩过坑之后的真心话整条链路走下来最难的不是某个函数、某个算法而是“对齐细节”。签名算法哪怕只差一个字符服务端就能识别出来时间戳少算毫秒请求直接被拒绝设备信息里型号和系统版本写得不一致风控立刻盯上你。逆向分析本质上是一场耐心博弈靠的不是什么高深技巧而是一遍一遍抓包、打断点、对比差异。另外想多说一句分寸感。分析接口、学习加密算法、做安全研究这些都是正常的技术活动。但如果你拿这套能力去大规模抓取数据、去搞对抗、去公开售卖爬虫方案那不仅违背了技术分享的初衷也会给自己惹来麻烦。工具是中性的方向得自己把握好。我自己做这类分析时最终都会把研究成果整理成内部的学习笔记和测试用例而不是固化成一整套批量采集系统。如果你也是抱着提升技术、理解客户端服务端协议设计的目的来做这件事那这套流程应该能帮你省下不少弯路。后面如果大家有具体某个环节卡住了比如Native定位、证书绕过或者签名拼接顺序的问题也欢迎留言交流我看到会尽量回复。