ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

拼多多逆向协议实战:rc4加密、sign签名与三端还原思路

拼多多逆向协议实战:rc4加密、sign签名与三端还原思路 一提到拼多多逆向协议做电商数据分析、店铺自动化运营、价格监控的朋友多半会条件反射地皱眉H5端明明就是一个网页偏偏塞了一堆rc4加密参数安卓端抓包看到的全是二进制请求体好不容易定位到加密函数又被滑块验证卡在半路。这篇博文就围绕“拼多多逆向协议”这个话题把我在实际项目中踩坑、梳理、复现过的接口还原思路完整写下来覆盖App端、H5端和微信小程序端也适合刚接触逆向、想找个真实目标练手的新手参考。最近两年无论是做选品调研、价格行情分析还是店铺批量管理都绕不开拼多多这个体量。而拼多多客户端对外暴露的接口几乎每一层都做了加密处理这让一堆正经的开发需求在技术层面先卡住了。我自己从最初在Chrome里瞎翻js到后面能稳定跑通整套协议前后折腾了大半年。这篇内容算是把这些经验做个沉淀能帮你少走一点弯路。1. 拼多多逆向协议到底在逆什么1.1 逆向协议的本质与拼多多的独特难点所谓逆向协议其实就是把客户端与服务端之间通信所用到的接口地址、请求字段、加密算法、签名规则给还原出来。直白点说客户端App访问后端接口之前会先对请求做一层“包装”明文参数加密、加上签名、带上设备信息。如果你直接拿抓包工具去看看到的是一堆乱码和二进制数据。而逆向要做的就是把这层包装解开搞清楚服务端究竟校验了哪些东西然后自己构造出能通过后端校验的请求。可以用一个类比来理解协议就像两个站岗的人之间的口令明文参数是正常问答加密是暗号签名是身份证明。正常用户每一次点击客户端都会按约定生成一套完整的“口令”服务端只认这一套流程。逆向协议做的事就是观察这套口令的生成规律然后在自己的程序里复现它。拼多多是公认的“难啃”目标主要体现在四个层面。第一是请求体加密H5端的很多业务参数会用rc4这类对称加密算法重新编码你改一个参数整段密文都得重算。第二是签名动态变化请求里除了业务参数还有sign、timestamp、scene这类风控字段它们之间有关联关系不是随手拼一下就能过的。第三是风控策略激进同一个设备请求频率稍微高一点、UA异常一点就会弹滑块验证验证参数还会影响后续请求。第四是客户端反调试安卓端so库加壳、反frida检测、反xposed检测几乎成了标配H5端则是js混淆、无限debugger那套流程。1.2 三端入口的差异化特征很多新手在拼多多逆向上有种误解觉得只要搞定App端就等于搞定全平台。其实拼多多有三个主要入口App端、H5端、微信小程序端三者的技术栈和逆向难度完全不同。入口技术栈逆向难度关键点常用工具App端安卓Java/Kotlin so库最高so库加密、反调试、设备指纹jadx、IDA、Frida、objectionH5端JavaScript中等rc4加密、sign签名、js混淆Chrome DevTools、Charles、sekiro、execjs微信小程序端类H5 JS中等偏高小程序环境补全、组件加密小程序反编译工具、Node、jsdomApp端的难度主要在so层很多核心加密函数被编进native库光看Java层代码只能拿到壳得用IDA打开so文件去看汇编逻辑或者用Frida在运行时hook函数把入参和返回值直接打印出来。H5端则相对友好加密逻辑都在js文件里只要能定位到函数就可以用Node把整套环境补起来跑通。小程序端介于两者之间代码结构和H5相近但要处理小程序特有的运行时环境一些组件加密逻辑还依赖微信App提供的能力。我的建议是新手优先从H5端入手跑通一条完整链路后再迁移到App端和小程序端这样能少走很多弯路。H5端练的是“定位js函数、补环境、模拟请求”这套基本功这些能力迁移到其他两个端一样用得上。1.3 先想清楚边界逆向协议用于什么场景动手之前必须承认一个事实逆向协议本身是中性的技术方法它可以用在接口联调、自动化测试、数据分析、竞品公开信息整理等正当场景也可以被用来做超高频抓取、绕过风控、窃取用户隐私等违规行为。我写过不少逆向相关的代码但每次涉及外部平台协议的时候都会先明确用途和边界确认数据来源的合法性遵守网站的robots协议和服务条款。在实际开发中我更推荐优先考虑平台官方提供的开放接口。拼多多开放平台本身就有sdk包php、java版本都有很多商品、订单、售后场景都能覆盖。只有在官方接口满足不了、而技术调研又确实需要的时候才值得投入人力去解析客户端协议。这样既减少法律风险也避免把精力浪费在没意义的对抗上。2. 从抓包到定位还原一次完整的接口交互2.1 抓包环境怎么配才顺手抓包是整个逆向协议的起点环境配不好后面一切白搭。我常用的方案分两种PC端H5页面直接用Chrome DevTools配合Charles做代理抓包手机端则是把手机代理指到电脑上的Charles或Fiddler。先以Charles为例说下安装流程。下载安装后先在Charles里设置SSL Proxying把目标域名加进Include列表否则https流量会被当成加密串直接跳过。然后在电脑上安装Charles根证书手机和电脑连同一个局域网手机WiFi代理指向电脑IP的8888端口手机浏览器访问chls.pro/ssl下载并安装证书。iOS需要在“设置-通用-关于本机-证书信任设置”里手动打开信任开关这一步经常有人漏掉结果一直抓不到https明文。手机上抓包还有一个坑Android 7.0及以上系统默认不信任用户证书即使你把抓包工具的证书装了https请求照样显示明文加密或直接报证书错误。解决办法有两个一是把证书转成系统证书放进/system/etc/security/cacerts/需要root权限二是用Frida或者objection把SSL Pinning和证书校验一起绕过去。后者更省事但需要你手上有能运行Frida的调试环境。2.2 从打开页面到看到商品数据走过哪些请求链路抓包之后不能急着找加密函数先理清请求链路更重要。以H5端搜索商品为例你会看到一串串请求。整个操作序列大致是打开搜索页页面初始化接口下发配置和静态资源地址。输入关键词触发前端预加载和联想词接口。点击搜索前端本地组装参数发出搜索请求。下拉加载分页接口返回下一页数据。点击商品详情接口返回商品详情和推荐列表。这些请求里真正包含核心商品数据的是搜索接口和详情接口而真正难处理的则是它们请求体里的密文和签名。埋点上报接口和页面配置接口虽然也会带一堆参数但通常不影响业务数据获取可以先不关注。很多新手容易在这里卡住看到返回的表单数据不知道怎么对应到页面。建议抓包时把页面操作拆细打开页面、输入内容、点击搜索、下拉加载、进入详情一步一步慢慢点每做一步就回看一次抓包记录标清每个接口对应的操作。这样后续定位加密函数时你能顺着操作一路跟下去不会迷路。2.3 怎么快速识别核心加密封装层接口链路清完之后就要给参数分类。拼多多的请求参数大致可以分成三类参数类型典型字段特征处理方法明文业务参数keyword、page、size直接可读和页面操作直接相关可以直接修改加密载荷请求体里的长字符串base64或hex肉眼不可读需解密或重新加密风控签名参数sign、timestamp、scene、device_id与业务参数联动动态变化需还原生成逻辑识别的方法是把同一个请求连着抓两次对比哪些字段在变。固定不变的字段多半是固定密钥或固定设备标识每请求都在变的字段要考虑是否与时间戳、随机数有关。请求体里明显是一长串base64或hex的字符串通常就是密文先用base64解码看看开头有没有可读结构如果有明确的标志头基本能推断出加密方式。这一步不需要立即算出算法关键是先摸清参数之间的依赖关系。举个例子如果发现timestamp变化后sign也变了说明sign大概率参与了时间戳如果keyword变化后加密载荷变长说明该载荷确实加密了keyword。把这些依赖关系记下来后面定位函数的时候你就能准确判断自己找到的函数对不对。3. 拼多多加密体系解析sign、rc4与滑块验证3.1 rc4加密的识别和还原拼多多H5端最常被讨论的加密手段就是rc4。rc4是典型的对称流密码同一个密钥既用来加密也用来解密算法本身不复杂但它的一个特点是同样的明文和密钥每次得到的密文完全一样所以很好验证。从抓包数据里识别rc4最直观的信号就是密文长度和明文长度基本一致而且密文没有base64那种可读padding特征。还原rc4最稳妥的方法是直接定位生成密文的js函数然后让这段js自己算。你在代码里搜索关键字的时候可以留意encrypt、rc4、encode、sign这些词同时用Charles的Map Local功能把目标js替换成本地副本加入console.log逐步打印中间结果。定位到加密函数后把它单独拎出来放到Node里执行输入同样的参数看输出是否和抓包里的密文一致。如果一致说明你定位准了如果不一致多半是密钥里混入了其他动态字段需要继续追查密钥的生成逻辑。补充一个我在实践中遇到的细节拼多多的rc4密钥往往不是硬编码在js里的而是由某个接口先下发一段配置前端再基于这段配置和时间戳动态生成密钥。如果你只盯着加密函数本身密钥对不上也没用还得往前找密钥的生成入口。搜代码的时候不妨把“getKey”“secret”“config”这些词一起搜往往能有意外发现。3.2 sign签名与风控参数的产生逻辑签名这类参数在拼多多请求里几乎随处可见最常见的字段是sign它实质上是一段基于请求参数、时间戳、固定盐值计算出来的摘要值。服务端拿到请求之后会按照同样的规则重新计算签名如果和请求里带的sign不一致直接拒绝。所以要还原签名第一步是确定参与签名的参数范围以及拼接顺序第二步是找到那个固定盐值第三步是确认签名算法是MD5、SHA1还是更复杂的变体。实际操作中我习惯用“增量对比法”来锁定参数范围。先构造一个空请求或者最小请求记录sign值然后往请求里加一个参数再看sign怎么变。通过多次对比就能反推出哪个参数参与签名、哪个参数不参与。例如固定keyword和page只改timestamp看sign是否变化再固定timestamp和page只改keyword继续观察。两次对比下来基本能确定签名依赖哪些字段。这个过程中要注意有的接口签名时会把参数key按字典序排列有的会按固定顺序拼接还可能会把时间戳放在拼接结果中间或末尾。直接搜md5算法的标准实现不一定能对上最好还是通过js代码里的调用逻辑去确认。找一个值得信任的参照对象也很重要把浏览器里的真实请求记录下来然后逐步修改参数重放一旦发现sign校验不过就回看代码定位是哪段逻辑在负责签名。3.3 滑块验证的触发机制与相对对策滑块验证是拼多多风控体系里让人最头疼的一块。触发条件一般包括请求频率超过阈值、设备指纹信息缺失或不一致、请求头里UA异常、同一IP短时间发起大量并发请求等等。滑块一旦出现请求会进入验证流程接口返回的riskResult字段会带上验证状态后续正常请求也会受到影响。从技术原理上看滑块验证本身会采集用户的鼠标轨迹、点击位置、耗时等行为特征再把特征合成一段加密参数拼进验证请求。要做技术分析大概思路是捕获滑块的初始上下文参数模拟生成轨迹数据然后带着轨迹数据请求验证接口拿到token后继续走业务请求。但我更要提醒一句不要为了追求高并发而刻意对抗滑块。实践中更合理的做法是降低请求频率、控制并发数、让请求行为更接近真实用户很多项目只要把频率降到合理区间滑块触发率就能大幅下降。我自己的经验是每分钟请求次数控制在10次以内加上随机的鼠标轨迹模拟基本能维持一个较低的验证出现概率。4. 实操环节以H5端“搜索商品”接口为例走通全流程4.1 选定目标并确认关键接口选一个H5端的商品搜索接口当例子最合适因为它链路短、加密点集中、返回数据也清晰非常适合新手完整跑一遍。打开拼多多H5搜索页面手动输入一个固定关键词比如“数据线”在浏览器开发者工具的Network面板里过滤XHR请求找到返回结果里能看到商品列表的那个请求。把这个请求的URL、请求方法、请求头和请求体完整复制出来先存成一个文本后面所有工作都围绕它展开。确认接口的时候留意两点一是别把埋点接口和页面配置接口当核心接口判断标准是看响应体里有没有商品标题、价格、链接这些业务数据二是优先选择GET参数较少的接口有些接口参数多达几十个新手容易在第一步就被绕晕。搜索类的核心接口通常有type、keyword、page、size这类字段拿到之后先手工改一下keyword确认返回数据会随参数变化以此验证你确实找对了接口。4.2 从Network到Sources定位加密参数生成点接口定下来之后下一步是找到请求体里密文和sign的生成位置。回到DevTools的Sources面板在Network里点击那个搜索请求查看Initiator列它会显示是哪个js文件触发了这个请求。点进文件后搜索与请求参数相关的关键字比如请求参数名、接口地址片段、search这个业务词一般能找到请求的组装入口。找到入口后顺着调用栈往上翻重点看请求体里那个密文字段是怎么被赋值的。常见的情况是前端在入口函数里收集明文参数然后调用某个加密函数把加密结果塞给请求对象。这时候在该处打断点刷新页面再触发搜索单步执行盯着变量面板看如果出现了明文的关键词接着往下走看它什么时候变成密文加密函数的位置就暴露了。整个过程就是不断打断点、缩小范围最后定位到那个你觉得“就是它”的函数。这里分享一个调试技巧如果你怀疑某段代码被混淆可以先在Console里执行monitor(functionName)让浏览器在函数被调用时自动打印参数和返回值省得反复打断点。对拼多多这种混淆程度偏高的前端这招往往比手工断点更高效。4.3 补环境、跑通加密函数定位到加密函数后直接把整段函数和相关依赖一起抠出来放进Node里运行。但浏览器里的js依赖window、document、navigator这些环境变量Node里没有所以要先补环境。最简单的做法是逐个把缺失的全局变量定义成mock对象global.window global; global.document { cookie: , getElementById: () null, createElement: () ({ getContext: () null }) }; global.navigator { userAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 };这个阶段比较枯燥但也是最考验耐心的一步。经常碰到的情况是补了一个变量运行后又报另一个变量未定义。解决思路是看报错信息里缺什么就补什么千万别一次性补一大堆用不上的东西反而把环境搞复杂了。如果加密函数里用到了canvas指纹、webgl这类浏览器特有的能力可以考虑用jsdom或者puppeteer起一个真实浏览器环境来执行虽然重一些但稳定性更高。补环境跑通后就可以封装一个本地脚本输入明文参数输出加密后的请求体。把这个脚本接到请求发送逻辑里先拿浏览器抓到的原始请求验证保证输出密文和浏览器一致。之后再用Node直接发起一次请求带上本地生成的加密参数看服务端是否返回同样的业务数据。如果返回成功说明你已经走通了最核心的“逆向—重放”闭环如果返回错误码再回过来排查签名或者其他参数少了什么。4.4 联调稳定性与频率控制单次请求跑通只是第一步真正让逆向协议“可用”的是稳定性。首先要把流程固化成一个函数输入关键词、页码等业务参数输出完整可用的请求参数其次要把签名生成、rc4加密、请求发送、响应解析这几个环节分开方便出错时单独排查最后还要再加上请求间隔、失败重试、滑块出现时的降速策略让整个流程在长时间运行时不容易触发风控。我在实际项目里会把请求频率压制在每分钟10次以内必要时还会让脚本随机sleep几秒再发下一次请求。拼多多的风控看的是综合特征单一指标不一定会触发但多个指标同时异常就很容易出问题。所以除了频率User-Agent、Referer、Cookie保持、设备指纹参数也要尽量模拟成真实浏览器的形态。稳定跑一个小时不出滑块基本就可以认为这套协议对当前业务场景是可用的。5. 常见问题与排查技巧实录5.1 滑块频繁出现怎么办滑块频繁出现多半是请求频率或设备指纹异常。先看日志里每次请求带的device_id是否一致如果每次都变说明你没有正确保存服务端下发的设备标识。再看请求间隔是否太短拼多多对短时间内的连续请求非常敏感把频率降下来往往比任何技术手段都有效。还有一种情况是浏览器环境缺失某些风控参数没有被正确带入比如navigator属性、canvas指纹、webgl信息这些在H5端是可以被采集校验的。如果想要稳定尽量用真实浏览器内核来跑不要用过于精简的请求库。我自己早期贪图速度快直接用requests模拟请求结果十次里有八次触发滑块后来换成浏览器环境模拟情况才好转。5.2 请求被风控拦截的通用排查思路被拦截的时候不要急着改加密逻辑先看返回错误和请求头。拼多多常见的拦截特征有两种一种是返回错误码并提示参数校验失败这种情况基本是sign或时间戳算错了另一种是正常返回但数据缺字段或者返回空列表这种情况往往是被风控降级了可能你已经触发了某个隐蔽的风控策略。排查顺序建议是先校验时间戳是否正确再验证rc4密钥是否动态变化的接着确认sign参数拼接顺序有没有变化最后检查Cookie和device指纹。一次只改一个变量改完立刻测试。为了加快排查可以在代码里加一个debug开关把每次请求的完整参数、签名结果、服务端响应都打印到日志文件里问题复现后直接翻日志比临时加打印效率高得多。5.3 同一套参数在不同网络下结果不一致这类问题通常出在IP层。同一个账号或设备在短时间切换网络风控系统会做环境一致性校验如果IP归属地和之前差别太大、或者IP本身在风控黑名单里就可能出现数据行为不一致的情况。碰到这种问题先固定网络环境对比测试确认IP是否是变量。如果是就需要控制请求来源的稳定性避免跨地区、跨运营商频繁切换网络。这里还要提一个细节很多办公室或家庭出口IP是共享的别人如果正好在同一IP段内做了高频请求你也可能被连带限制。遇到这种情况不要反复重试先停半小时让IP冷却一下同时检查自己是否有异常的并发请求在跑。5.4 几款常用逆向工具的真实使用感受工具不贪多顺手最重要。这里列一个我在不同阶段经常用的组合工具用途使用感受Charles抓包、断点、Map Local稳定尤其适合H5调试证书安装要花点心思Chrome DevToolsjs调试、打断点H5逆向必备Initiator栈是定位函数的关键FridaApp端hook、so层调用功能强大但对环境要求高容易被检测jadx反编译安卓Java层查看代码结构方便so逻辑看不了objection免root环境下的绕过工具适合快速绕过ssl pinning但别指望它解决所有问题execjs / node vm2本地执行js加密函数补环境跑通后用它把流程自动化工具选型上的经验是H5优先用Chrome加CharlesApp端优先用Frida加jadx。不要一开始就追求最重的方案先用最顺手的工具跑通流程遇到瓶颈再换更底层的工具。比如H5端搞不定时可以再考虑Frida hook一下WebView里的js调用看看是不是某个参数被前端动态注入了。最后分享两点个人体会。第一逆向协议这块思路比工具重要很多问题靠的不是某个牛叉工具而是你花了多少心思去理解请求参数之间的关联。第二稳定的逆向方案一定是尽量靠近真实用户行为而不是一味堆并发。你只要把频率控制住、环境模拟到位、参数还原准确很多项目是可以长时间稳定运行的。
返回列表