
简介小红书客户端x-s参数逆向分析资料包聚焦x-s参数生成机制面向具有一定编程与逆向基础的安全研究者和爬虫开发者旨在通过还原补环境源码剖析应用与服务器之间的加密通信和签名流程。资源包共1511个文件压缩包约3.34MB以JavaScript、TypeScript源码为主体同时包含大量map映射、JSON配置、Markdown说明文档以及eslintrc、yml等工程配置文件覆盖从核心逻辑到构建工具链的完整代码形态便于对照分析调用关系与数据结构。已有404人学习下载适合用于学习逆向工程中动态参数定位、补环境构建以及协议还原的实际案例。通过阅读源码结构读者可理解x-s参数在身份验证与防篡改中的作用掌握从抓包、反编译、断点调试到关键函数定位的完整思路同时结合文档资源梳理加密算法和签名过程为开发合规的数据采集工具或开展移动应用安全评估提供具体参考。1. 小红书 x-s 参数逆向分析从抓包到算法还原的完整路线当你打开小红书 APP 的请求调试面板除了 Cookie 和 Token请求头里还藏着一个 32 位左右的十六进制字符串 x-s。用 requests 直接带上这个值访问接口会返回“请求失败”或“触发风控”。x-s 不是登录态而是每次请求发出前客户端生成的一次性签名用来让服务端校验请求是否来自真实 APP。很多小红书爬虫和数据分析项目卡就卡在 x-s 上算法藏在 native 层每次版本更新都可能变化。这篇文章按一线工程师的动手思路把抓包、hook、算法还原到服务端重放这一路讲透适合想做小红书数据采集、图片批量下载或反爬策略研究的人参考。2. 抓包定位 x-s 参数环境、工具与请求头特征2.1 HTTPS 抓包环境搭建mitmproxy 与 Charles 的取舍x-s 参数本身是 HTTPS 请求的一部分想看到明文就必须先能解密 HTTPS。常见做法是挂代理加安装 CA 证书。我一般用 mitmproxy 做命令行验证用 Charles 做可视化分析。# 安装 mitmproxy pip install mitmproxy # 启动 web 界面监听 8888 端口 mitmweb --listen-port 8888 # 手机连上同一局域网设置代理为电脑 IP:8888 # 手机浏览器打开 http://mitm.it 下载并安装 mitmproxy 的 CA 证书逻辑说明mitmproxy 是中间人代理手机上的请求先经过它再由它转发到小红书服务器。装了它的 CA 证书后代理就能解包 HTTPS 流量。注意 Android 7 及以上系统默认不信任用户安装的 CA所以需要把证书挪到系统证书目录或者改包把网络安全配置里的user信任打开。charles 在 macOS 上的安装类似但图形化展示请求头更直观。如果你是直连 PC 流量调试Fiddler 也可以但在 Linux 服务器上还是 mitmproxy 更方便。参数说明--listen-port指定代理端口默认 8080。如果电脑有几个网卡还要确认手机代理指向的是正确的内网 IP。证书安装完毕后在mitm.it页面能看绿色提示说明 HTTPS 解密生效。如果页面提示你要在系统设置里信任证书描述文件iOS 端记得去“通用—关于本机—证书信任设置”里手动打开开关。在抓包工具的选择上不要盲目从众。日常 debug 我推荐 Charles因为它的 Filter 和断点功能好用能看到完整的请求头层级批量抓取和自动化时用 mitmproxy 的 Python API 更顺手。Fiddler 在 Windows 上也可以但处理 iOS 的 HTTPS 时证书信任步骤略繁琐。2.2 找到携带 x-s 的接口首页 feed 与笔记详情拿到明文流量后刷新小红书首页找到/api/sns/web/v1/homefeed这个接口。它通常在edith.xiaohongshu.com域名下。展开请求头你会看到这么一组关键参数参数名样例值作用x-s33aa2e6d1b1e...请求签名32位hexx-t1712345678901Unix 毫秒时间戳x-common-params%7B%22app_version%22%3A...URL编码的设备/版本信息x-signA2...另一个签名值新版才有x-b3-traceid...全链路追踪ID小红书爬虫项目里x-s 是绕不过去的核心。截取同一接口的几次刷新数据你会发现 x-t 每秒都在变x-s 也跟着变说明 x-s 至少依赖了时间戳。接着你手动改一下请求体里的page_size而不改动 x-s直接提交会得到“签名错误”。这说明 x-s 也覆盖了请求体的内容。通过这种方式你能粗略判断哪些请求字段被签名保护了为后续拼接逻辑做准备。2.3 观察 x-s 的变化规律时间戳、体参、设备指纹在抓到几十组请求样本后可以做一个戏剧性实验固定同一个 x-s只改 x-t 里的毫秒数超出一定范围比如改大 1 秒再发送请求。如果依然返回正常数据说明服务端对时间戳有容忍窗口如果直接拒绝说明它要求严格一致。这个窗口值对你服务端的重放脚本非常重要。另一个观察点把手机恢复出厂设置或登录不同账号重新抓包对比相同接口、相同操作下的 x-s。你会发现即使 path 和 timestamp 相同x-s 也不一样。这是因为 x-common-params 里的 device_id、device_fingerprint 参与了签名。所以当你后期在服务端生成 x-s 时必须保证设备指纹参数稳定不能每次随机生成。最稳妥的方式是把抓包得到的 x-common-params 原样保存作为生成器的输入之一。3. 逆向还原 x-s 生成算法JNI 到底做了什么3.1 静态分析 so从导出符号到 RegisterNativesx-s 的生成逻辑几乎不可能写在 Java 层因为太容易用 jadx 直接看穿。常见做法是把核心算法放进 so 文件Java 层只留一个 native 方法壳。你需要从安装包中提取对应的 so 文件。# 用 apktool 解包 apktool d xiaohongshu.apk -o xhs # 进入原生库目录arm64 架构 cd xhs/lib/arm64-v8a/ # 搜索 sign 相关字符串 strings libsecurity_guard.so | grep -i sign逻辑说明apktool d解包后大部分逻辑会保留在 smali 目录里但 so 文件会被原样释放。strings命令能快速看到 so 内可打印字符串比如sign、md5、sha256这样的单词。如果你的搜索结果显示了很多混淆过的符号可能 so 做了字符串加密。这时候需要用 IDA 或 Ghidra 打开 so看导出函数表中是否有Java_开头的方法。如果导出函数表里没有Java_com_xingin_xxx_sign这类名字说明 native 层用的是动态注册系统会在加载 so 时调用JNI_OnLoad然后通过RegisterNatives将 Java 方法名和 native 函数指针绑定。这时候要从JNI_OnLoad的 disassembly 入手找到RegisterNatives的调用参数再回溯出实际处理函数。这个过程比较费时间但对小红书这种级别的 App 来说是必经之路。3.2 动态 hook用 Frida 抓取 native 入参与返回值静态分析能告诉你函数地址但不知道它的输入输出是不是你想要的。动态 hook 是最快的验证方式。import frida import sys # 从抓包定位到 Java 层签名方法路径 script_code Java.perform(function() { // 这个方法名以真实逆向结果为准 var signClass Java.use(com.xingin.opensdk.sign.SignManager); signClass.sign.implementation function(data) { var result this.sign(data); console.log([x-s] input: data); console.log([x-s] output: result); return result; }; }); def on_message(message, data): if message[type] send: print(message[payload]) else: print(message) device frida.get_usb_device() pid device.spawn([com.xingin.xiaohongshu]) session device.attach(pid) script session.create_script(script_code) script.on(message, on_message) script.load() device.resume(pid) input()逻辑说明这个脚本做的是在 Java 层 hook 签名方法。当你操作 App 触发首页请求时控制台会打印传入的data字符串和返回的 x-s 值。通过对比几十组数据你会发现data是一个由 URL path、query 参数字典、请求体、时间戳、设备指纹拼接起来的文本。这个方法非常直观适合新手入门。参数说明device.spawn用于冷启动 App挂在早期阶段避免错过插件初始化。如果你已经打开 App可以直接用device.attach(com.xingin.xiaohongshu)。implement是 Frida 的替换实现写法执行原函数后再打印返回值。遇到 hook 不到的情况检查进程是否选择正确、类是否被延迟加载或者 so 内部有没有反调试逻辑。3.3 还原拼接逻辑一个可推导的伪代码框架这里不写完整代码因为你会看到网上很多开源仓库里的算法突然“失效”小红书这两年会定期更换 salt 和 seed。但 x-s 生成的骨架通常是这样的import hashlib import time def build_sign_data(path, query, body, timestamp, device_params, salt): # 拼接顺序必须从 hook 到的 data 反推 raw f{path}{query}{body}{timestamp}{device_params}{salt} # 第一轮提取摘要 digest1 hashlib.md5(raw.encode(utf-8)).hexdigest() # 很多版本在摘要后再补一段 key再做一次 md5 digest2 hashlib.md5((digest1 custom_key).encode(utf-8)).hexdigest() # 也可能是 base64 后取前 32 位取决于 hook 结果 return digest2逻辑说明这个伪代码的作用是引导你梳理出拼接顺序。真正的算法可能是 MD5、SHA256、MurmurHash 的组合也可能在中间插入设备唯一 ID。你需要把自己的 hook 结果套进这个模板不断调整顺序和额外 key直到输出与抓包里的 x-s 完全一致。参数说明body不能直接用 dict 转 str因为 dict 的键排序和 URL 编码方式会影响摘要结果。建议用urllib.parse.urlencode去规范化 query。salt一般藏在 so 的.rodata段可以用 Ghidra 搜索 hex 字符串或直接用 Frida 读内存。如果你发现同一时间戳同一 path 的 x-s 每次都不同说明算法里加了随机因子服务端要么能验随机因子要么对随机因子有容忍度处理方式会更复杂。4. 把 x-s 放进请求服务收获小红书图片和笔记内容4.1 封装签名函数与请求库假设你已经从上一章的反推中拿到了可用的签名算法下一步是把它封装成一个独立的签名服务。我一般用 Python 写一个类里面包含get_xs(timestamp, path, query, body)和一个request()包装方法import time import hashlib import requests class XiaoHongShuSigner: def __init__(self, device_params, salt): self.device_params device_params self.salt salt def _sign(self, path, query, body, ts): raw f{path}{query}{body}{ts}{self.device_params}{self.salt} sign hashlib.md5(raw.encode()).hexdigest() return sign def request(self, method, path, query, body): ts int(time.time() * 1000) xs self._sign(path, query, body, ts) headers { x-s: xs, x-t: str(ts), x-common-params: self.device_params, user-agent: discovery/8.67.0 (iPhone; iOS 16.0; Scale/3.00) } url https://edith.xiaohongshu.com path if method.upper() GET: resp requests.get(url, paramsquery, headersheaders) else: resp requests.post(url, databody, headersheaders) return resp.json()逻辑说明这里把签名需要的几个要素作为成员变量固化下来。每次请求前先取毫秒时间戳再对标准化的 path、query、body 做签名。整个请求封装成一个方法后续只需关心业务参数。注意query尽量传字典或原始字符串不要传 Python 的 dict 再让 requests 去编码因为编码顺序不同会导致签名不一致。参数说明device_params是 X-Common-Params 的 URL 编码值不要改动。user-agent必须与设备类型匹配iPhone 的 UA 和 Android 的 UA 不能混用。edith.xiaohongshu.com是小红书 web API 的常见 host如果你抓包抓到的是其他 host就改成自己的。4.2 常见接口的签名调用示例笔记详情与图片列表以“小红书图片提取”为例这是新手最容易上手的方向。请求笔记详情接口signer XiaoHongShuSigner( device_params%7B%22app_version%22%3A%228.67.0%22%2C%22device_id%22%3A%22...%22%7D, saltyour_salt_here ) # 笔记详情接口note_id 从分享链接中解析 note_id from_shortlink path f/api/sns/web/v1/feed query fsource_note_id{note_id}sourceweb data signer.request(GET, path, queryquery) if data.get(code) 0: note_info data[data][items][0][note_card] image_list note_info[image_list] for img in image_list: print(img[url_default]) else: print(请求失败:, data)逻辑说明source_note_id是笔记的唯一 ID。拿到返回的image_list后每个元素里有url_default这是原图地址的 CDN 链接通常带签名参数可以直接下载。注意接口返回的image_list可能嵌套在images字段下不同 App 版本的字段名略有差异。参数说明source参数值要看接口定义有的接口是web有的是discovery。这个值也参与签名不能随便改。如果你的note_id被小红书风控识别为“非正常来源”接口可能返回code -1这是签名以外的 IP 或设备问题。4.3 分享链接解析、url scheme 与批量下载场景很多用户想下载自己收藏的笔记图片但 App 没提供一键导出。常见做法是复制分享链接链接会先重定向到xhslink.cn短链最终落到https://www.xiaohongshu.com/discovery/item/{note_id}这样一个长链接。你需要用requests.head跟随重定向拿 final URL再提取 note_id。这套流程在“小红书分享链接解析 id网站”这类工具里很常见。对于“小红书爬虫”和自动化任务还可以用 x-s 生成器去调用评论接口、用户主页接口、搜索接口。我通常会再写一个download_images()函数把上面拿到的url_default并发下载到本地用hashlib.md5重命名文件避免重复。批量下载时控制并发数在 5 以内因为 CDN 也有风控过快会给你返回 403。注意无论是图片下载还是数据采集请只处理自己有权访问的内容不要无限抓取大量非授权数据。5. 避坑指南小红书 x-s 逆向中高频翻车的五个问题5.1 Hook 不到函数进程选错或时机太早现象Frida 脚本执行后没有红字报错但 App 刷新请求时控制台完全没输出。原因小红书有主进程、子进程、push 进程等多个进程你 attach 的可能不是处理网络请求的进程。另一个常见原因是类加载时机晚于 Hook 时机Java.use在静态 hook 时类还不存在。解决先用frida-ps -U列出所有进程筛选名称里带com.xingin.xiaohongshu的进程挑 PID 最大的那个试试。在脚本里把Java.perform包在setTimeout里延迟 500ms 再执行如果还不行就 HookClassLoader.loadClass拦截目标类的加载后立刻执行替换。5.2 签名一致但被拒x-t、x-common-params 缺一不可现象你用自己的签名函数算出 x-s放在请求头里服务器返回类似STATUS_PARAM_ERROR或sign error。原因小红书案校验签名时不是只拿 x-s 和请求参数比对还会校验 x-t 的时间戳是否在容差范围校验 x-common-params 里的 device_id 是否在黑名单。或者你漏了 x-sign 这个新参数。解决把抓包里所有x-开头的请求头都复制到本地请求里一步一步删减找出哪些是必须的。大多数情况下x-t、x-common-params、x-sign 和 x-s 必须同时存在缺一个都不行。参数与原请求保持一致服务器才认账。5.3 Android 上抓包全是密文证书与 Pinning 的处理现象手机设置了代理并装了证书但 mitmproxy 看到的请求和响应都是二进制乱码或只是连接重置。原因APP 没有把用户 CA 当作信任根或者它在 native 层做了 SSL Pinning。解决把 CA 证书装进系统证书目录而不是用户目录。在 root 的 Android 手机上用 Magisk 模块或者手动 remount 系统分区将 PEM 证书cp到/system/etc/security/cacerts/。如果 Pinning 仍生效就 Frida Hook 掉验证书函数比如 HookSSL_CTX_set_custom_verify让它返回0。这在 iOS 上对应的是 ATS 和证书双向校验需要用SSLKillSwitch这类工具处理。不要指望一台不越狱的 iOS 设备能轻松搞定这个问题。5.4 iOS 与 Android 签名算法不共用现象你在 Android 上还原出 x-s 生成规则放到 iOS 的设备参数和请求头里接口返回签名错误。原因小红书 iOS 和 Android 使用两套 so 逻辑虽然 key 名称相同但内部拼接 salt、迭代次数可能不同。而且 iOS 的x-common-params字段大小写和安卓也不同。解决要么在 iOS 环境单独还原一套要么统一用 Android 端生成签名然后放在任何要访问的请求头里。后端不会校验来自哪个客户端它只看签名是否正确。所以我实际做爬虫时会重点保 Android 这条链路iOS 只用于抓包参考。5.5 请求频率太规律被风控盯上现象每秒固定请求一次连续跑了 50 条之后开始出现滑块验证或接口无数据返回。原因签名本身可能没错但行为特征异常。服务器不仅校验 x-s还会统计同一个 device_id、同 IP 的请求间隔和时间分布。固定间隔的请求跟真实用户滑动行为完全不一样。解决在请求循环里加随机区间比如 1.5~3.5 秒之间浮动准备多个 device_id 轮流使用请求时自带不做重试策略失败就停下来等待足够长的时间。对个人项目而言把并发降到 2 以下远比堆并发更有效。6. 回归验证与工程化让 x-s 生成器成为稳定依赖当你写完签名函数先不要急着跑数据。我习惯拿一组抓包样本做回归验证把样本里的 path、query、body、x-t 原样输入签名函数看输出是否等于样本里的 x-s。如果全部匹配恭喜你算法正确率至少是 90%。接下来做时间戳变化验证把样本里的 x-t 改为当前时间重新生成 x-s 并进行一次真实请求。如果返回正常数据说明服务端允许秒级时间窗口如果返回“签名过期”需要把 x-t 与生成 x-s 时的时间严格绑定。工程化再往下走一步我是建议把签名函数优化成一个独立模块并且把盐值和拼接规则放在配置文件里不允许业务代码直接改。这样当小红书在某天更新 so你的热更新机制能快速切换新算法。我自己的教训是曾在业务线程里直接调用签名函数并加了一个随机退化逻辑导致并发一高就有大量“签名过期”的日志。后来改成只在一个 worker 线程中生成签名其他请求复用结果并把时间戳控制在 2 秒内问题就消失了。x-s 逆向不是一条路走到黑它更接近持续维护的过程——抓包、hook、反推、验证再循环。希望帮到你。本文还有配套的精品资源点击获取