在读正文之前,有几个概念值得先了解一下,方便不做逆向的同学跟上节奏。
什么是"签名"? 你每次打开 Shopee App 浏览商品,App 都会向服务器发请求。为了防止有人用脚本冒充真实用户刷数据,Shopee 在每个请求里加了一串"签名"——可以类比为银行的动态口令。这串签名是 App 内部用一套复杂算法实时计算出来的,服务器收到后会验算,对不上就拒绝。我们的目标就是搞懂这套算法,用 Python 自己算出一模一样的签名。
什么是"逆向工程"? 简单说,就是在没有源代码的情况下,通过分析编译后的程序(二进制文件)来推导出它的内部逻辑。类似于拿到一个上锁的保险箱,不知道密码,但可以通过研究锁的结构来理解它怎么工作。
什么是"补环境"和"纯算法还原"? "补环境"是一种偷懒的方法——搭一个虚拟的手机环境,把 App 中的签名函数直接跑起来让它帮你算。好处是快,坏处是不稳定、依赖很重。"纯算法还原"则是把算法本身完全看懂,用 Python 从头实现,不再依赖任何 App 组件。后者才是我们的最终目标。
AI 在这里扮演什么角色? 我们用 AI(Codex + GPT-5.4)作为分析助手。它帮我们阅读海量的二进制反汇编输出、试验各种算法假设、编写和调试代码。但 AI 并非全知全能——它经常搞错方向,需要人类不断纠正。这个"人机协作"的过程,正是本次分享的重点。
在短短四天里,我们完成了一件通常需要一个逆向团队数月才能做到的事情——用纯 Python 还原了 iOS Shopee 应用中签名系统的完整算法。
这个 App 的签名系统有多复杂?它动用了将近十种不同的加密和哈希算法(可以理解为十种不同的"搅拌方式"),而且每次请求会随机选用其中几种进行组合。打个比方:这不是一把固定的锁,而是每次开门都会换一把不同组合的锁。我们最终产出的纯 Python 实现通过了 126 条真实请求的全量验证——也就是说,对照 126 次真实 App 产生的签名,我们的代码每一次都算出了完全相同的结果。
整个过程中,AI 既是核心工具,也是需要被不断"纠偏"的协作者。这份报告将覆盖四个维度:完整使用流程、模型方向纠正案例、核心技术难点、以及如何引导 AI 生成可用的最终成品。

整个项目分为两个大阶段,历时约四天(4月5日至4月8日),期间共发起两轮 Codex 会话(可以理解为两轮独立的 AI 对话任务)。
类比:先用一台模拟的手机把 App 的签名功能跑起来,观察它的输入输出,为后面的纯算法还原积累线索。
起点与目标。 项目起始于一个已经可以运行的脚本 emulate_shopee.py。这个脚本使用 chOmper——一个在电脑上模拟 iOS 手机 CPU 的工具——来加载 Shopee App 的核心文件(162MB,相当大),然后直接调用 App 内部的签名函数来生成签名。启动时给 AI 的指令很简洁:
"在这里有一个 emulate_shopee.py 的文件,这个是用补环境的方式执行获取签名,目前可以正常获取内容,我需要用 Python 纯算法还原这个参数"
一句话就把目标说清楚了:现在能跑,但我要的不是"能跑",而是"完全理解并自己实现"。
AI 在这个阶段做了什么。 主要是三件事:
第一,定位签名入口。App 的二进制文件有 162MB,里面有无数个函数。AI 帮我们从中找到了关键的签名类 ShopeeSecuritySDKManager,以及它的核心方法 genSignWithURL:data:(根据 URL 和数据生成签名)。这一步类似于在一本没有目录的百万字大部头里,找到你要读的那一章。
第二,理解环境依赖。签名函数在运行时需要获取很多手机信息:进程 ID、网络接口、屏幕尺寸、当前时间、设备标识等等。在模拟器里,这些都需要用"假数据"替代(术语叫 Hook),AI 帮我们梳理了所有这些依赖项。
第三,分析签名的内部结构。通过反汇编(把机器码翻译回可读的汇编指令),AI 画出了签名函数的调用链路——它先对 URL 做预处理,然后进入一个签名管道,依次调用哈希函数(数据摘要)、加密函数、自定义编码,最终拼装成 JSON 格式返回。
签名生成的大致流程: URL + 请求数据 → URL 预处理 → 签名管道(哈希 → 加密 → 编码) → 拼装成 JSON → 作为 HTTP 请求头发出
从补环境到纯算法的转折。 4月6日是关键转折点。我给 AI 提供了安卓版 Shopee 的参考实现(同事之前做过安卓端),并明确了分析策略:
"headers 的 key 的算法是随着随机数的变化而变化,长 value 的加密算法也是随着随机数的变化而变化。每一个随机数的生成都会改变后续算法的情况。"
翻译成白话:签名不是固定公式,而是每次都"摇骰子"决定用哪套算法。这大大增加了破解难度。
AI 最初试图在安卓代码基础上修改来适配 iOS——图省事。我立即纠正:
"错了。iOS 的代码应该放在 shopee-ios 里,之前给你的安卓算法是给你用于参考的,不是让你在安卓基础上修改,你应该按照 iOS 的算法逻辑实现 iOS"
这次纠正确立了此后的工作模式:安卓做参考(帮助理解大致架构),iOS 做独立实现(具体参数从 iOS 自己的数据中推导)。

引入真实抓包数据作为"答案册"。 4月7日,我将真实的网络请求数据导入了项目。这些数据来自 HAR 文件——浏览器或抓包工具记录的真实网络请求存档,包含了 App 实际发出的签名值。有了这些"标准答案",我们就可以检验自己的算法是否正确。
"我将抓取的请求信息都导入到 .har 里了,里面有大量的签名你可以综合分析。"
同时我设定了一条铁律——绝对不能用真实账号去试:
"不要使用全量 cookie 测试,这样会导致我账号风控。你需要的是根据他们分析算法,而不是盲目发请求。一定不能使用全量 cookie 发请求,发现罚款 100000000000 美元!"
为什么这么严格?因为 Shopee 有风控系统,如果检测到异常请求模式,账号会被封禁。我们的策略是纯离线分析——只看数据,不碰网络。
这一阶段有一个显著的特点:我只说了"继续"两次,AI 几乎全程自主运行了25分钟,自行完成了假设、验证、修正的完整循环。 这并非偶然,而是精心设计的初始指令的结果。启动时的指令包含5条精确约束:
有了这些"护栏",AI 就像在划定好的跑道上全速奔跑,不会跑偏。
AI 在这一阶段完成了三个关键任务。
第一个任务:破解"短密码"的切换规则。 签名中有一个"短 value"(12字节的加密数据)。AI 通过解密分析发现,它遵循一个简单的两态规则:当用户的会话时长不到10秒时走一个分支,超过10秒后走另一个分支。126条真实请求,100% 符合这个规则。同时还发现了 iOS 和 Android 的一个差异点:版本标识字节不同(iOS 是 0200 0300,Android 是 0100 0200)。
第二个任务:推导设备指纹的计算模型。 这是整个项目最精彩的推导过程(后面"技术难点"会详细展开)。简单说,签名中有一段12字节的"设备指纹"数据,记录了用户的使用模式(访问了哪些页面、访问了多久)。AI 最初用整数模型计算,发现大部分请求能匹配但有微小偏差。然后它自行提出假设:App 内部用的是浮点数(带小数的数字)而非整数来记录时间。验证之后发现假设正确,偏差消失了。
第三个任务:分析安全令牌的双状态。 签名中还有一个 af-ac-enc-sz-token 字段,AI 发现它在不同请求中呈现两种不同的值,对应用户的两种安全状态。
AI 很强,但它不是全知的,尤其在逆向这种高度经验驱动的领域。在两轮对话中,我共进行了约10次关键方向纠正。以下是最具代表性的4个案例,也是我认为对大家最有参考价值的部分——如何有效地与 AI 协作。
AI 的本能反应。 拿到抓包数据和 cookie 后,AI 的第一反应是"我来发个请求试试对不对"。这就像拿到别人的银行卡密码后直接去 ATM 试——最"高效"但也最危险。在反爬场景下,异常请求会触发风控,直接导致账号被封。
我的纠正。 这是整个项目中语气最强烈的一条消息(前面引用过的"罚款"那条)。
纠正后的效果。 AI 彻底放弃了在线验证的思路,转向纯离线对比:在本地计算签名,再和 HAR 中的真实签名逐一比对。这个转变奠定了整个项目后续的工作方式。
给所有人的启发: 与 AI 合作时,最有效的引导方式之一是"明确告诉它什么不能做"。一条清晰的禁令,往往比十条正面指令更有效。
AI 的偷懒行为。 当我提供安卓端的参考代码后,AI 直接在安卓代码基础上修改来适配 iOS。它默认两个平台的算法相同。
我的纠正。
"错了。iOS 的代码应该放在 shopee-ios 里,之前给你的安卓算法是给你用于参考的,不是让你在安卓基础上修改"
后续还进一步强化:
"可以参考安卓版本,某些值可能是固定的,但请严格按照历史请求以及二进制分析结果,不要自己瞎猜"
纠正后的效果。 AI 建立了独立的 iOS 工作目录,从头构建实现。事实证明两个平台确实有很多差异——使用的哈希算法不同、密钥生成方式不同、版本常量不同。如果一开始图省事在安卓基础上改,后面发现不对再推翻重来,浪费的时间更多。
给所有人的启发: AI 会"抄近路"。如果你给它参考材料,它倾向于直接复制而非仅仅参考。需要明确告诉它:"这是参考思路,不是正确答案。"
AI 的误判。 AI 在模拟器中成功运行了签名函数,得到了看起来合理的输出,就把这些输出当成"标准答案"来验证后续代码。
我的纠正。
"这个补环境出来的值是不完全正确的,因为有很多信息都没有补到。补环境是不会出完全正确的值的,因为这个和真实机器运行过程不一样。"
纠正后的效果。 验证基准从"模拟器输出"切换到"HAR 真实数据"。这就像考试时发现"参考答案"本身有错,赶紧换成官方标准答案。
给所有人的启发: 要明确告诉 AI 哪些数据源可信、哪些不可信。AI 不会自己判断数据质量。
AI 的遗漏。 AI 在分析加解密算法时,忽略了签名数据中一个叫 fingerprint12 的字段(设备使用模式的指纹数据),简单地把它当成固定值处理。
我的纠正(通过第二阶段的启动指令隐含传达)。 直接在新一轮对话的指令中点明:"重点是短 value 和长 value 中指纹参数的生成逻辑。"
纠正后的效果。 AI 将 fingerprint12 从"固定值"升级为"动态计算模型",最终推导出了精确的计算公式。这个指纹模型成为整个项目最核心的突破。
给所有人的启发: 当 AI 遗漏了某个维度时,最好在下一轮对话的起始指令中直接点明,而不是在对话中途补充——起始指令的优先级在 AI 的"心智"中最高。

这一节技术细节较多,非技术同学可以关注每个难点开头的"通俗版"说明,跳过代码块也完全没问题。
通俗版: 签名的核心部分是一个 744 字节的大数据包,里面塞满了手机信息——iOS 版本号、App 版本号、设备标识、使用统计等二十多项数据,每一项都占据固定的位置。我们的工作就像拿到一封加密信件解密后,发现里面是一张填满了格子的表格,要搞清楚每个格子代表什么。
这个数据包解密后的结构被建模为 IosBigProfile744,包含了超过20个字段。还原过程的关键技巧是差分分析——把多条请求的数据包解密后放在一起对比:哪些字节在请求之间有变化(动态字段),哪些始终不变(设备信息),哪些在递增(计数器)。通过这种"找不同"的方式,逐步搞清了每个字段的含义和位置。
python# 744 字节数据包中的关键字段(简化示意)
@dataclass
class IosBigProfile744:
os_ver: str # iOS 版本,比如 "26.4"
app_version: str # App 版本,比如 "3.71.33"
bundle_id: str # App 标识 "com.beeasy.shopee.sg"
u78: int # 会话持续时间(秒)—— 每次请求都在增长
u90: int # 全局请求计数 —— 每次请求 +1
fingerprint12: bytes # 12字节设备指纹 —— 最难搞的部分
# ... 还有十几个字段
通俗版: 大多数签名系统用固定算法——搞懂一次就行。Shopee 不一样,它每次签名都随机选用不同的算法组合,就像一个保险箱每次开门都换一套不同的密码锁。
具体来说,系统有一个包含 9 种哈希函数的"工具箱"(MurmurHash3、SipHash、xxHash 等等,都是业界知名的数据摘要算法)。每次签名时,系统会用一个伪随机数生成器(MT19937,一种常用的随机数发生器)生成随机序列,然后通过 Fisher-Yates 洗牌算法(就像洗扑克牌一样)从 9 个中随机挑 3 个来用。
加密环节也是动态的:根据随机数的值,从 ChaCha20、RC6、Threefish-512、Salsa20 这四种加密算法中选一种。这意味着同一个接口的不同请求可能走完全不同的加密路径。
签名的"动态组合"示意: 随机数生成器 (MT19937) │ Fisher-Yates 洗牌 │ ┌──────┼──────┐ ▼ ▼ ▼ 哈希A 哈希B 哈希C ← 从9种里随机选3种 │ │ │ ▼ ▼ ▼ key1 key2 key3 │ ▼ 加密算法(四选一) ← ChaCha20 / RC6 / Threefish / Salsa20 │ ▼ 最终签名输出
这种设计大大增加了逆向的难度——不能只破解一条请求就宣告成功,需要理解整个选择机制,才能应对所有可能的组合。

通俗版: 计算机表示小数时有精度限制。手机 App(ARM64 芯片)用32位精度算小数,Python 默认用64位精度。同一个公式,两边算出来的结果可能在小数点后第7位出现微小差异。就好比两个人用不同精度的计算器算同一道题,答案在最后一位上可能不一样。
fingerprint12 中有两个字段是浮点数(带小数的数字),记录的是"页面访问时长的衰减值"。AI 最初用 Python 的默认精度计算,发现大部分请求能匹配,但有些后期请求差了一点点(专业术语叫 1-ULP,即"最后一位"的误差)。
AI 为此测试了六种不同的运算顺序——先乘后除、先除后乘、每一步都截断为32位等等——来模拟手机 CPU 的行为。最终通过一个"浮点年龄模型"(用带小数的会话时长而非整数来计算)解决了所有偏差。
这个案例说明了一件事:在逆向工程中,"大致正确"和"完全正确"之间的鸿沟,有时就藏在这种极其微小的精度差异里。
通俗版: 专业的逆向工程师通常会用 IDA Pro、Ghidra 这样的图形化工具来分析程序——它们能把机器码翻译成接近源代码的伪代码,还能画出函数调用图。但我们当时的环境里没有这些工具,只有最基础的命令行工具。
162MB 的程序文件,用基础工具输出的分析结果超过 470 万行。而且大量函数被"剥离了名字"(只显示为无意义的编号),需要通过特征常量来"认出"算法——比如看到特定的数字常量 0xB7E15163,就知道这里用了 RC6 加密;看到特定的位移操作模式,就能认出 MurmurHash3。这种"看特征认算法"的能力,正是 AI 在这个项目中发挥最大价值的地方——它的知识库中包含了大量已知算法的特征签名。
通俗版: Base64 是一种常见的数据编码方式(把二进制数据转成可打印的文字)。标准 Base64 用的字母表是 A-Z、a-z、0-9、+、/。但 Shopee 用了一套自定义字母表:
标准: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ Shopee: shopEeSHOPDFTACGkrIJ45KLBM+NQcdRU1VW89XYwxZabfgijlmntquvyz02/376
如果不知道这个替换关系,用标准工具解码会得到完全错误的结果。AI 在分析过程中识别出了这套自定义字母表,并实现了对应的编解码器。
这一节是我认为对大家最有实操价值的部分——不管你做什么项目,与 AI 协作的这些策略都适用。
从一开始我就告诉 AI 代码应该怎么组织:
"我需要你把算法分开,结构明确,每一个 key 的生成和 value 的生成以及从签名还原内容"
最终形成了清晰的项目结构,每个文件各司其职:
shopee-ios/ ├── pure_core.py # 核心算法库(加解密、编码、数据结构定义) ├── generate_sap.py # 签名生成器(状态管理、主流程) ├── recover.py # 签名反向验证(解密已有签名来验算) ├── pure_sign.py # 轻量签名入口(对外接口) └── vendor_hashes/ # 哈希算法独立模块库 ├── murmurhash3.py ├── siphash.py ├── xxhash.py └── ...(共6个文件)
给所有人的启发: 让 AI 写代码时,先规定好文件结构和职责划分。否则它倾向于把所有东西塞进一个文件里。
这是贯穿整个项目的关键认知框架。安卓实现提供了有价值的结构性知识:签名由 4 个 value 组成;随机数驱动算法选择;长 value 包含设备指纹。这些"大框架"是可以跨平台参考的。但具体的实现参数差异显著(注:表格中的 BLAKE3 和 BLAKE2b 是两种不同的密钥生成算法,类似于两种不同的"配钥匙工艺"):
| 维度 | iOS | Android |
|---|---|---|
| 随机数生成器 | C++ 标准实现 | 自定义实现 |
| 密钥推导算法(从主密钥派生子密钥的方式) | BLAKE3 | BLAKE2b |
| URL 映射常量 | 0x200F1 | 0x334B |
| 版本标识 | 02000300 | 01000200 |
| 平台标记位 | 0x20 | 0x10 |

给所有人的启发: 有参考材料时,告诉 AI "参考它的架构思路,但参数要从实际数据中推导"。这比"照着它改"或"完全忽略它"都要高效。
HAR 文件中的 126+ 条真实请求是整个项目的"答案册"。验证流程是双向的:
先做"反向验证"——拿已知的正确签名,解密还原出中间参数,确认我们的解密逻辑正确。再做"正向验证"——用同样的输入参数从头生成签名,检查是否和原始签名完全一致。两个方向都对上,才算通过。
所有代码修改都必须通过这 126 条请求的全量回归测试。任何一条不通过,都说明改出了问题。
给所有人的启发: 在让 AI 做任何需要"正确性"的任务时,一定要给它明确的验证基准和数据集。"对照真实数据验证"远比"让 AI 自己判断对不对"可靠。
每个哈希算法和加密算法都先用业界标准的测试数据独立验证:比如用已知输入输出验证 MurmurHash3,用标准测试向量验证 ChaCha20。只有单个模块验证通过,才接入整体签名流程。
这就像造汽车——先分别测试发动机、变速箱、制动系统各自是否正常,再组装到一起。如果组装后出了问题,也容易判断是哪个零件的毛病。
给所有人的启发: 复杂任务要拆解成可独立验证的小模块。这对 AI 协作尤其重要——AI 在局部修改时经常引入新问题,模块化设计能把"爆炸半径"控制在最小范围。
这可能是最反直觉但最有效的策略——告诉 AI "不要做什么" 比 "要做什么" 更管用。整个项目中设定的禁止项包括:
禁止向服务器发真实请求;禁止使用完整 cookie;禁止把安卓实现当正确答案;禁止自己猜测参数("不要自己瞎猜");禁止使用固定随机数做测试。
每一条都源于实际踩过的坑。没有风控约束,AI 会本能地"试一试";没有独立性约束,AI 会图省事复制安卓代码;没有禁止猜测的约束,AI 会用"听起来合理但实际上错误"的假设填补空白。
给所有人的启发: 与 AI 协作时,明确的"不要做什么"清单是最好的护栏。AI 非常善于在你划定的边界内寻找最优解,但如果没有边界,它会本能地走向最省力的路径——而那往往不是你想要的。
阶段二中,AI 在极少干预下自主工作了 25 分钟。这不是因为运气好,而是建立在两个基础上:
第一,阶段一已经积累了约 4600 行代码框架,AI 不用从零开始,有了可扩展的骨架。第二,阶段一中反复纠正建立的工作模式(离线验证、安卓仅参考、数据驱动)已经被写进了第二阶段的启动指令里——相当于把"经验教训"编码成了"操作手册"。
给所有人的启发: 如果你的 AI 任务需要多轮对话,把前面几轮踩过的坑总结成明确的规则,写进下一轮的初始指令里。这样每轮对话都站在前一轮的"肩膀"上,而不是从零开始。
AI 是放大器,不是替代品。 它能在给定方向上飞速推进——分析百万行输出、测试六种精度模型、编写数千行代码——但方向本身需要人来把控。每一次关键纠正都决定了项目是在正确轨道上前进还是在错误方向上狂奔。
约束是最好的 prompt 工程。 "禁止发请求"一条约束,就把整个验证策略从"在线试错"重定向到了"离线数据驱动"。这个转变的价值超过了任何正面指令。
数据驱动胜过逻辑推理。 126 条真实请求的全量比对,发现了浮点精度问题、状态切换时机等纯推理难以触及的细节。无论做什么项目,真实数据都是最可靠的验证手段。
模块化设计是 AI 协作的基础。 把系统拆成独立可测试的模块,让 AI 可以在局部修改而不破坏全局,也让人类可以局部验证而不需要理解全部。
最终,一个 162MB 的 App 中的签名系统,被还原为约 5000 行纯 Python 代码,通过 126 条真实请求的全量验证。这既是 AI 辅助逆向的能力展示,也是人机协作模式的一次实践范本——希望其中的方法论对大家有所启发,不管你下一个项目是逆向、数据分析、还是其他什么。


本文作者:回锅炒辣椒
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!