简介小红书x-s参数逆向分析资料面向安全研究人员、逆向工程师及爬虫开发者旨在拆解小红书客户端x-s参数生成逻辑并给出可落地的补环境实现。整体为zip压缩包共1511个文件压缩后仅3.34MB文件类型以js、ts、map、json为主还包含cjs、mjs、yml、md、license等其中js与cjs构成主要运行模块ts与map辅助静态分析和源码映射json与yml承担配置管理md用于记录说明文档目录结构贴近Node.js工程习惯便于按模块检索。目前已有404人学习下载。资料保留了一个相对完整的补环境源码工程覆盖网络通信抓包、协议解析、签名参数还原、静态代码分析和动态调试等关键环节既适合从零理解小红书签名机制也可作为二次开发兼容第三方服务的基础工具能帮助研究者缩短踩坑周期提升对现代Web端加密参数的分析效率。1. 小红书x-s参数逆向分析为什么这是采集路上绕不开的第一道坎做过小红书数据采集的人几乎都遇到过同一个场景脚本明明按照抓包结果把请求头、Cookie、Body都填齐了一跑还是被弹回来服务端返回一串403或406提示里带着签名校验失败。这个拦住你的东西就是x-s参数。它不是单独存在的而是跟x-t、x-mini-gid等一组参数配合参与了每个请求的签名校验。想继续往下做采集、做数据分析、做内容监控第一步就得把这个参数生成的逻辑拆明白。这份逆向分析的资源针对性很强从抓包定位x-s入手到反编译、动态Hook、算法还原再到请求回放验证完整的路径都覆盖了。适合两类人一类是刚接触签名逆向想系统走一遍流程的开发者另一类是写过简单爬虫但被x-s卡住想补上这块短板的工程师。接下来我按自己拆项目的习惯把整条链路拆开讲。2. 抓包定位x-s从HTTPS流量里找到签名入口2.1 为什么先抓包而不是直接上手反编译拿到这类签名校验的问题我一般不会直接去反编译APK因为x-s参数的本质是一个请求参数它长什么样、放在哪个Header、跟哪些字段联动这些信息从流量里就能拿到八成的信息。先抓包的意义在于建立一个基准你需要在改造代码之前确认目标请求长什么样。x-s的典型特征是一个定长的十六进制哈希字符串每次请求的值都不一样而且同一个请求重放之后服务端依然会校验时间窗口。也就是说你拿到一个静态的x-s是无法持续使用的它跟时间戳、设备指纹、请求路径这些字段组合在一起动态变化。抓包要解决的核心问题有三个x-s出现在哪些请求里所有业务请求都带还是仅部分接口带x-s的形成是否依赖请求体Body内容改变Body后x-s是否还成立x-s与相邻参数x-t、x-sign等的变化规律2.2 Android端抓包的完整步骤与关键参数常见做法是用mitmproxy或Charles做中间人代理Android手机通过Wi-Fi代理接到PC然后安装CA证书解密HTTPS流量。我用mitmproxy比较多一个原因是它可以命令行启动方便二次处理流量另一个原因是它的Python接口能直接对请求做过滤和导出。# 安装mitmproxy pip install mitmproxy # 启动代理监听8080端口同时导出所有流量到文件 mitmweb --listen-port 8080 --set web_open_browsertrue参数说明--listen-port指定代理端口手机端配置代理时要用这个端口--set web_open_browsertrue会启动一个Web界面方便实时查看请求。启动之后把手机Wi-Fi代理指向PC的IP和8080端口手机浏览器访问http://mitm.it下载安装CA证书这里有一个关键点Android 7及以上版本默认不信任用户安装的证书需要把证书移到系统证书目录或者用调试包绕过这一限制。完成证书配置后打开小红书App随便刷几个页面回到mitmweb界面过滤条件输入cdn.xhscdn.com或edith.xiaohongshu.com就能看到业务请求。我习惯把抓包重点放在笔记详情、搜索、用户主页这三个接口因为它们足够简单又覆盖了GET和POST两种请求方式能帮助判断x-s的生成规则是否随请求类型变化。2.3 从抓包结果里识别x-s及其兄弟参数找到一条请求记录之后展开Headers面板重点看请求头的Custom Header区域。x-s就在那里通常还伴随以下参数参数名典型值特征作用推测x-s64位十六进制字符串签名主体本次逆向的核心目标x-t10位或13位数字时间戳通常跟随系统当前时间变化x-mini-gid一段较长的混淆字符串设备指纹标识x-sign变长字符串部分接口出现可能对Body内容单独做签名把一条完整的请求头复制下来保存成JSON文件后面验证签名时要对照它逐字段做替换。我之前在拆这个参数时走了不少弯路一开始盯着x-s本身死磕忽略了对x-t的观察后来才发现x-s的生成跟x-t是强绑定的x-t的值会被纳入签名计算。换句话说x-s不是一个孤立参数它和x-t是成对出现的改一个另一个也要跟着变。3. 还原x-s生成逻辑算法定位与签名流程拆解3.1 反编译APK定位字符串入口抓包确认了参数位置接下来要回答的问题是x-s在客户端哪段代码里生成。常见做法是把小红书APK拖进Jadx直接基于字符串交叉引用找入口。# 用jadx反编译APK输出为java源码 jadx -d output_dir xiaohongshu.apk # 进入反编译目录全局搜索x-s字符串 grep -r x-s output_dir/ --include*.java -l参数说明-d指定输出目录后面直接跟APK路径。搜索时Windows环境用findstr /s /r x-s也可以。需要注意这一步搜出来的代码引用位置很多因为x-s只是字典里的一个Key真正有价值的不是插入点而是取出点。搜索到代码后定位到某个类里出现headers.put(x-s, ...)或者buildXSec(...)这类方法名跟进方法实现往往能看到一个单独的类在做签名计算类名通常含xsec、XSSign、Security这样的关键字。3.2 用Frida做动态Hook拿到输入输出静态代码分析只能看到算法轮廓比如它调用了哪个类、传入了哪些参数但具体运行时的值还需要动态观察。这里我一般用Frida辅助定位它的价值在于不去猜直接看函数在真实环境下的入参和返回值。# 安装frida工具链 pip install frida-tools # 启动frida server并注入目标App frida -U -f com.xingin.xhs -l hook_xs.js注入用的hook_xs.js脚本如下逻辑是Hook住类com.xingin.xhs.xsec.XSec的签名方法打印入参和返回值// hook_xs.js Java.perform(function () { // 定位XSec签名类 var XSec Java.use(com.xingin.xhs.xsec.XSec); // Hook签名的核心方法假设名为sign XSec.sign.implementation function (request) { var result this.sign(request); // 打印request的URL与请求体 console.log([*] URL: request.toString()); console.log([*] Result x-s: result); // 进一步打印调用栈帮助确认调用来源 console.log(Java.use(android.util.Log).getStackTraceString( Java.use(java.lang.Throwable).$new() )); return result; }; });脚本逻辑并不复杂Java.perform确保代码在Java运行时环境执行Java.use定位到目标类implementation替换原方法实现在调用前后追加我们想要的输出。需要说明的是这里的类名和方法名是我用过的路径不同App版本可能不同具体类名要以Jadx反编译的结果为准。参数层面request对象包含了请求链接、请求体等信息日志打印出来后把URL、Body、结果x-s对照起来看就能初步判断算法与哪些字段有关。3.3 常见的x-s生成流程拆解把几次Hook结果放在一起对比x-s的生成逻辑基本能定型。以我拆过的版本为例生成流程大致是取请求路径与查询参数去掉域名部分后拼接成字符串拼上当前时间戳对应x-t的值拼上设备相关的标识对应x-mini-gid或x-device-id如果是POST请求再把Body内容拼进原字符串对拼接结果做哈希计算得到64位hex字符串写入请求Header的x-s字段这里的哈希计算常见做法是SHA256但也可能带一层自定义盐值。如何判断盐值是否存在把拼接结果直接用SHA256算一遍如果和抓包中的x-s不一致说明还有其他字段参与拼接或者做了加盐处理。这个时候可以用Frida在算法串内打点定位到MessageDigest或SecretKeySpec的调用确认到底是哪一步多做了处理。从定位到还原整个流程看似琐碎但每一步都是确定性的抓包确认目标反编译找入口Hook确认入参最后在本地用Python还原算法并验证。真正的麻烦不在算法本身而在四个参数之间的联动关系这个我在第5章里详细写。4. 验证签名是否逆向成功请求回放与风控判定4.1 用Python重放签名请求算法还原出来后第一步要做的不是急着写采集脚本而是拿一个已经不再使用的请求做回放验证。构造同样的URL、Header、Body用本地生成的x-s替换抓包中的值然后原样发送对比返回码。import hashlib import time import requests # 构造签名基础字符串按调试时确认的拼接顺序 path /api/sns/web/v1/feed query num30cursor123456 timestamp str(int(time.time())) body raw_string path ? query timestamp body x_s hashlib.sha256(raw_string.encode(utf-8)).hexdigest() # 构造请求头x-s与x-t成对出现 headers { x-s: x_s, x-t: timestamp, x-mini-gid: 从抓包中复制的设备标识, user-agent: 从抓包中复制的UA, referer: https://www.xiaohongshu.com/ } resp requests.get( https://edith.xiaohongshu.com path ? query, headersheaders, cookies{从抓包中复制的Cookie} ) print(resp.status_code, resp.text[:500])逻辑说明raw_string的拼接顺序是我在调试中确认的这里只示意要以你的实际结果为准x_s就是本地还原的签名x-t用当前时间戳因为签名计算时用到的时间戳就是生成时刻的时间戳服务端会比对两者时间差太大会判定请求无效。代码里的headers对象设置了签名相关参数cookies则是从上一节抓包记录里手动粘贴来的。为什么要手动粘贴Cookie因为x-s只是签名校验的一个环节登录态是另一层校验两者相互独立。4.2 签名可复用的边界条件回放之前必须清楚x-s的边界条件否则会反复踩同一个坑。根据我的验证经验影响签名有效性的主要因素有这些影响因素变化是否影响签名说明x-t时间戳影响x-t变化x-s必须同步重新计算请求路径Path影响换接口必须重新生成查询参数Query影响Query顺序变化也可能影响签名拼接Headers中的UA不一定不同版本App的UA不同以实际校验为准Cookie不影响签名Cookie影响登录态校验不参与签名计算POST Body影响Body内容被纳入签名原始字符串这张表的价值在于它把「哪些变了会本文还有配套的精品资源点击获取