逆向工程入门:从网络请求到Token生成算法的完整解析

逆向工程入门:从网络请求到Token生成算法的完整解析 1. 项目概述从“黑盒”到“白盒”的逆向旅程在移动应用和Web前端开发领域为了保护核心业务逻辑和接口安全开发者们常常会使用代码混淆和参数加密技术。这就像给自家的保险箱加了一把复杂的锁并把它藏在一个不起眼的角落里。而我们今天要聊的“逆向工程”本质上就是扮演一个“开锁匠”和“寻宝者”的角色目标不是破坏而是理解这套安全机制是如何运作的。这个名为“简单混淆和token参数加密”的案例就是一个非常典型的入门场景它几乎涵盖了客户端安全对抗中最基础、也最核心的几个环节。简单来说这个案例模拟了这样一个过程一个应用可能是网页也可能是App在向服务器请求数据时并非直接发送明文参数比如你的用户ID、查询关键词而是会将参数进行某种加密或编码并附上一个名为token或sign的签名值。服务器收到后会用同样的逻辑验签以此判断请求是否合法、是否被篡改。我们的逆向目标就是把这个“某种加密”和生成token的逻辑给还原出来。这听起来像是黑客行为但实际上对于安全研究人员、测试工程师、甚至是希望与第三方接口对接的开发者来说这是一项非常重要的技能。它能帮你进行安全审计、编写自动化测试脚本、或者分析竞品的接口协议。通过这个案例你将能亲手剥开一层看似神秘的技术外壳看到其内部相对简单的运作原理。这对于初学者建立逆向思维、熟悉常用工具链、理解常见加密模式有着不可替代的作用。无论你是刚入门的安全爱好者还是希望提升排错能力的后端开发这篇文章都将带你走完一个完整的、可复现的逆向流程。2. 核心思路与工具选型如何定位关键逻辑面对一个已经混淆和加密的应用盲目地一头扎进代码海是低效的。一个清晰的逆向思路往往比掌握一百个工具更重要。我们的核心思路可以概括为“由外而内动态追踪”。2.1 逆向分析的核心思路拆解首先要明确目标。我们不是要破解整个应用而是找到特定参数尤其是token的生成位置和算法。因此最直接的入口就是网络请求。使用抓包工具如Charles、Fiddler或mitmproxy拦截应用发出的请求你会发现原本应该清晰的参数变得面目全非多出了一堆看似随机的字符串其中很可能就包含我们的目标——token。接下来的关键一步是寻找突破口。一个常见的技巧是“对比分析法”。你可以尝试相同操作不同输入比如搜索“苹果”和“香蕉”对比两次请求中token和相关参数的变化。如果token完全不同说明它很可能与输入参数有关。相同输入不同时间隔几分钟重复同样的搜索如果token变了可能加入了时间戳。清空缓存/重新登录观察token是否与登录态如session或另一个固定token有关。通过对比你可以初步判断token的生成依赖哪些因素是全部请求参数是用户身份还是包含了一个时间变量这个判断将极大地缩小后续静态分析的代码搜索范围。2.2 工具链的选择与搭配工欲善其事必先利其器。根据目标平台的不同工具链的选型也不同。对于Web前端JS逆向抓包/调试浏览器开发者工具Chrome DevTools是首选。它的Network面板可以查看所有请求Sources面板可以调试JavaScriptConsole面板可以执行任意代码进行测试。反混淆/格式化线上JS代码经常被压缩Minify和混淆Obfuscate变量名变成a, b, c逻辑被拆分。浏览器Sources面板自带的美化Pretty Print功能能解决压缩问题。对于混淆虽然有一些反混淆工具但更多时候需要依靠调试技巧人脑还原。Hook与拦截可以使用浏览器插件或Fiddler等工具的脚本功能在JS代码执行前注入调试代码拦截关键函数的输入输出这是定位加密函数的“大杀器”。对于Android应用APK逆向抓包仍然需要Charles/Fiddler并配合在手机或模拟器上安装代理证书。反编译与静态分析Jadx-GUI是目前最友好、强大的开源工具它能将APK文件中的DEX字节码反编译成可读性很高的Java代码。对于简单的混淆如ProGuard它通常能处理得很好变量名可能被混淆成a、b但类和方法结构清晰。动态调试对于核心逻辑在Native层C/C或加固过的应用静态分析可能不够。这时需要用到Frida或Xposed框架。它们可以在应用运行时动态地注入代码、Hook挂钩指定函数打印出函数的参数、返回值甚至修改其逻辑。Frida尤其强大支持全平台是进行深度逆向的必备技能。对于iOS应用工具链类似抓包用Charles静态分析可以用IDA Pro、Hopper Disassembler或Ghidra分析二进制文件动态调试则常用LLDB或Frida。注意在本案例中我们假设面对的是一个使用了“简单混淆”的Web或Android应用。这意味着代码结构虽然被扰乱但核心算法逻辑依然以高级语言JavaScript/Java的形式存在并未被虚拟化或深度Native化因此我们以静态分析阅读反编译代码为主动态调试Hook验证为辅的策略是可行的。2.3 为什么选择这样的分析路径选择“网络请求切入 - 对比分析 - 静态定位 - 动态验证”这条路径是因为它符合认知规律。网络请求是应用与外界交互的唯一窗口也是加密参数的最终出口。从这里开始是最高效的“顺藤摸瓜”。对比分析能让我们用最少的样本归纳出加密逻辑的依赖关系形成假设。静态分析则是根据假设在代码中寻找证据。最后用动态调试验证形成闭环。这个过程本质上就是一次科学的“侦查”与“实验”。3. 实战拆解定位并还原Token生成逻辑现在我们进入实战环节。假设我们通过抓包发现了一个向api.example.com/search发送的POST请求其请求体如下{ keyword: e4b88be4b880e697a5e5a587e891a9, page: 1, timestamp: 1712345678, token: a1b2c3d4e5f67890fedcba9876543210 }很明显keyword被编码了看起来像Hex编码timestamp是Unix时间戳而token是一串32位的十六进制字符串很像MD5或SHA-1的结果。3.1 第一步初步分析与假设解码keyword将e4b88be4b880e697a5e5a587e891a9进行Hex解码得到字节数据再尝试用UTF-8解码结果可能是中文。例如解码后得到“下一日奇遇”。这验证了参数被简单编码过。对比请求我们再次搜索“测试”抓包得到keyword为e6b58be8af95“测试”的Hextimestamp更新了token也完全不同。提出假设token很可能是一个签名由keyword解码后、page、timestamp等参数按照一定顺序拼接后再经过某种哈希算法如MD5计算得出。可能还混合了一个固定的密钥salt。3.2 第二步静态代码分析寻找线索对于Web应用我们打开浏览器开发者工具的Sources面板搜索这个token值a1b2c3d4e5f67890fedcba9876543210。通常不会直接搜到因为这是计算结果。我们应该搜索更可能出现的常量比如token、sign、md5、sha1、CryptoJS一个常用加密库等。假设我们搜索md5在某个被混淆的JS文件如app.obfuscated.js中找到了如下代码片段function s(t) { var e n.md5; return o(e(t)) } function getToken(e, n, a) { var i c(e) c(n) c(a) key r; return s(i) }虽然变量名被混淆了t, e, n, a, i, r, o, c, s但逻辑依然可辨。getToken函数接收三个参数用c函数处理可能是编码或转字符串然后用连接最后追加一个key和一个变量r。整个拼接后的字符串传给函数s而s内部调用了n.md5最后结果经过o函数处理可能是转小写或Hex。这里的r很可能就是那个固定的密钥salt。我们需要找到r的值。在混淆代码中它可能被定义在文件开头或者从某个全局对象中获取。我们可以尝试在代码中搜索var r 或r 。3.3 第三步动态Hook验证与算法还原静态分析给出了一个清晰的函数调用链和可能的算法MD5。现在需要用动态Hook来验证我们的猜想并获取关键参数如密钥r。我们使用浏览器Console或Fiddler的AutoResponder自定义脚本功能在页面JS加载前注入Hook代码。以Console为例在页面加载前或通过断点暂停时输入以下代码// 保存原始的 md5 函数 var originalMD5 window.CryptoJS ? CryptoJS.MD5 : null; // 假设用了CryptoJS if (originalMD5) { // 重写或Hook console.log([Hook] CryptoJS.MD5 found.); // 更优雅的方式是Hook我们找到的 getToken 函数 } // 直接Hook我们找到的 getToken 函数假设它挂载在 window 的某个对象下 // 我们需要先找到它的引用。如果找不到可以尝试Hook Function.prototype.call var _getToken null; // 通过遍历window对象来寻找这是一种暴力但有时有效的方法 for (var key in window) { try { if (typeof window[key] function window[key].toString().includes(md5)) { console.log([Hook] Potential token function found:, key); _getToken window[key]; // 替换它 window[key] function() { console.log([Hook] getToken called with args:, arguments); var result _getToken.apply(this, arguments); console.log([Hook] getToken result:, result); return result; } } } catch(e) {} }执行搜索操作如果Hook成功Console会打印出getToken被调用时的参数和返回值。通过对比多组参数和结果我们就能100%确认算法逻辑。假设我们Hook后看到一次调用[Hook] getToken called with args: Arguments(3) [下一日奇遇, 1, 1712345678, ...] [Hook] getToken result: a1b2c3d4e5f67890fedcba9876543210同时我们通过断点或搜索确定了密钥r的值为my_secret_key_123。3.4 第四步算法复现与验证现在我们可以在Python或Node.js中复现这个算法import hashlib import time def generate_token(keyword, page, timestamp, secret_key): # 1. 拼接字符串注意顺序和分隔符要与JS中一致 raw_str f{keyword}{page}{timestamp}key{secret_key} # 2. 计算MD5注意JS中可能是先转UTF-8字节 m hashlib.md5() m.update(raw_str.encode(utf-8)) # 3. 获取16进制的哈希值并转为小写如果JS结果是32位小写 token m.hexdigest().lower() return token # 测试 secret my_secret_key_123 keyword_hex e4b88be4b880e697a5e5a587e891a9 keyword_bytes bytes.fromhex(keyword_hex) keyword keyword_bytes.decode(utf-8) # 得到“下一日奇遇” timestamp 1712345678 page 1 my_token generate_token(keyword, page, timestamp, secret) print(fGenerated token: {my_token}) # 输出应该与抓包到的 token: a1b2c3d4e5f67890fedcba9876543210 一致如果输出一致恭喜你你已经成功逆向了这个简单的token生成算法。实操心得在复现时最常遇到的“坑”是字符串编码和哈希输出格式。JS中的字符串到字节的转换UTF-8、MD5计算后输出的是WordArray对象可能需要调用.toString(CryptoJS.enc.Hex)才能得到十六进制字符串。而Python的hashlib.md5需要传入bytes输出也是十六进制字符串。务必确保每一步的中间结果都和Hook打印出来的一致。另一个常见问题是参数顺序和拼接方式还是|有没有额外的空格或换行都需要严格对照。4. 深入混淆代码人脑反混淆的技巧上面的案例中混淆程度较轻我们还能依稀辨认出md5、getToken等关键字。但在实际中你可能会遇到所有变量名都被替换成无意义的单字母控制流被平坦化控制流平坦化是一种混淆技术将原本线性的代码逻辑打散成一个个基本块通过一个调度器来控制执行顺序使代码难以阅读字符串被加密存放的情况。这时就需要一些人脑反混淆的技巧。4.1 识别常见的混淆模式变量名混淆userName-_0x1a2b3c或a。这其实影响不大我们关注的是函数调用和字符串操作。字符串常量加密原本的md5字符串可能被一个函数解密出来如_0xabcd(0x12)。你需要找到这个解密函数通常在代码最前面把它复制到Console里运行就能得到明文字符串映射表。控制流平坦化代码被拆分成很多个switch-case块或if-else链跳来跳去。对付它的诀窍是不要试图通读而是用调试器单步执行观察数据流。找到核心的计算部分比如哈希函数调用、大循环重点分析那一块。死代码注入与代码膨胀插入大量永远不会执行的代码干扰分析。直接忽略即可。4.2 利用调试器进行动态追踪当静态代码难以阅读时动态调试就成了你的眼睛。以浏览器开发者工具为例下断点在可能的关键函数入口如包含encode、hash、sign等字眼的函数或网络请求的发起函数XMLHttpRequest.send/fetch处下断点。观察调用栈Call Stack当断点触发时查看调用栈。它能告诉你这个函数是被谁调用的从而理清函数间的层级关系。监视变量Watch将你关心的变量如拼接中的中间字符串、最终的token值添加到监视面板观察它们每一步的变化。单步执行Step Over/Into一步步执行代码跟踪数据的流向。遇到无关的、混淆的代码块比如巨大的switch可以使用“Step Over”跳过或者直接跳到下一个断点。4.3 提炼核心逻辑忽略干扰信息逆向的核心是理解算法而不是读懂每一行代码。你的大脑应该像一个过滤器自动忽略那些为了混淆而添加的冗余赋值、无用的条件判断和复杂的变量传递。紧紧抓住几个关键点输入是什么哪些参数参与了计算输出是什么最终的token中间经过了哪些关键处理编码A - 拼接B - 加密C - 编码D有没有固定的密钥或常量key后面的值或者一个硬编码的数组只要理清了这四步哪怕代码被混淆得再厉害其核心骨架也已经在你手中了。5. 常见问题与排查技巧实录在实际操作中你几乎一定会遇到下面这些问题。这里记录了我踩过的坑和总结的排查技巧。5.1 问题一Hook不到函数Console里找不到任何线索可能原因1代码在闭包或Web Worker中执行。函数作用域受限没有挂载到window全局对象上。排查技巧在Sources面板中对所有JS文件尤其是大的、混淆的在开头部分设置“Event Listener Breakpoints”中的Script-Script First Statement断点。刷新页面代码会在最开始处暂停。然后单步执行观察函数定义过程找到目标函数被赋值给了哪个变量。可能原因2代码被动态加载或执行eval。排查技巧在Network面板过滤XHR或JS请求查看是否有额外的JS文件在页面加载后请求。或者在开发者工具设置中开启“JavaScript source maps”如果存在。也可以直接搜索eval(或Function(调用。可能原因3目标函数名被高度混淆且不包含任何可搜索的关键字。排查技巧放弃搜索函数名转而搜索常量字符串。比如如果token最终要发送到api.example.com/search那就搜索“/search”或“api.example.com”。找到发送请求的代码附近向上追溯调用栈。5.2 问题二算法复现结果总是和抓包结果对不上这是最令人头疼也最常见的问题。请按照以下清单逐项核对排查项可能原因验证方法1. 参数完整性漏掉了某个参与签名的参数如User-Agent,Cookie中的某个值。仔细对比多个请求找出所有值变化的参数。尝试将整个请求头或URL参数都纳入签名计算。2. 参数顺序拼接顺序错误。JS中可能是abc你写成了cba。通过Hook打印出拼接前的最终字符串与你复现的字符串进行逐字符比对。3. 编码问题JS和Python对字符串的编码处理不一致。例如JS中“中文”参与计算时可能是UTF-16也可能是UTF-8字节。在Hook时不仅打印字符串还打印其字符编码如escape(str)或查看字节数组。在Python复现时使用.encode(‘utf-8’)或.encode(‘utf-16le’)明确指定。4. 哈希算法与格式猜错了哈希算法MD5 vs SHA1 vs SHA256或者输出格式不对Hex大写 vs 小写Base64 vs Hex。尝试常见的几种哈希算法。对比Hook得到的原始结果和你计算结果的长度和字符集。32位Hex是MD540位是SHA-164位是SHA-256。5. 密钥Salt错误密钥找错了或者密钥本身也经过了某种编码。通过Hook确认密钥的明文值。检查密钥是否在代码中被二次处理如反转、截取、再编码。6. 时间戳精度时间戳单位是秒还是毫秒是否经过了取整或格式化Hook获取参与计算的时间戳值与你本地生成的时间戳进行对比和换算。7. 存在随机数签名中混入了一个随机的nonce每次请求都不同。在请求参数或响应头中寻找一个长度固定、每次请求都变化的随机字符串。5.3 问题三面对复杂的加密算法如AES、RSA怎么办如果发现不是简单的哈希而是对称加密AES或非对称加密RSA难度会提升但思路不变。定位算法搜索CryptoJS.AES、encrypt、decrypt、publicKey、RSA等关键字。获取密钥对于AES关键是找到key和iv初始化向量。它们可能是硬编码的也可能是通过某个接口动态获取的。对于RSA前端通常使用公钥加密公钥可能以字符串或模数modulus、指数exponent的形式嵌在代码里。模式与填充AES有多种模式CBC, ECB, GCM等和填充方式PKCS7, ZeroPadding等。这些信息通常在调用加密库的代码中可以看到例如CryptoJS.AES.encrypt(data, key, {mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7})。必须完全一致才能解密成功。工具辅助一旦获取了算法、密钥、模式等所有参数可以使用Python的pycryptodome库或在线工具进行复现和验证。动态Hook获取加密前的明文和加密后的密文是验证复现是否正确的唯一标准。5.4 一个宝贵的调试技巧本地模拟执行当你已经将关键的加密函数定位并大致理解后一个高效的方法是尝试将这段JS代码剥离出来在Node.js或浏览器Console的独立环境中运行。将目标函数及其所有依赖的函数、变量定义复制到一个新的文本文件中。补全缺失的全局对象。例如如果代码中用到了CryptoJS你需要在环境里引入crypto-js库或者用Node.js内置的crypto模块实现对应函数。创建一个简单的测试脚本调用这个函数传入你从Hook中捕获的真实参数。观察输出是否与真实token一致。这个过程能帮你彻底理清函数的所有依赖和内部逻辑是最终复现算法前最可靠的验证步骤。它就像把发动机从汽车里拆下来单独测试排除了整车其他电路的干扰。