当前位置: 首页 > news >正文

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

1. 项目概述:从“黑盒”到“白盒”的逆向旅程

在移动应用和Web前端开发领域,为了保护核心业务逻辑和接口安全,开发者们常常会使用代码混淆和参数加密技术。这就像给自家的保险箱加了一把复杂的锁,并把它藏在一个不起眼的角落里。而我们今天要聊的“逆向工程”,本质上就是扮演一个“开锁匠”和“寻宝者”的角色,目标不是破坏,而是理解这套安全机制是如何运作的。这个名为“简单混淆和token参数加密”的案例,就是一个非常典型的入门场景,它几乎涵盖了客户端安全对抗中最基础、也最核心的几个环节。

简单来说,这个案例模拟了这样一个过程:一个应用(可能是网页,也可能是App)在向服务器请求数据时,并非直接发送明文参数(比如你的用户ID、查询关键词),而是会将参数进行某种加密或编码,并附上一个名为tokensign的签名值。服务器收到后,会用同样的逻辑验签,以此判断请求是否合法、是否被篡改。我们的逆向目标,就是把这个“某种加密”和生成token的逻辑给还原出来。这听起来像是黑客行为,但实际上,对于安全研究人员、测试工程师、甚至是希望与第三方接口对接的开发者来说,这是一项非常重要的技能。它能帮你进行安全审计、编写自动化测试脚本、或者分析竞品的接口协议。

通过这个案例,你将能亲手剥开一层看似神秘的技术外壳,看到其内部相对简单的运作原理。这对于初学者建立逆向思维、熟悉常用工具链、理解常见加密模式有着不可替代的作用。无论你是刚入门的安全爱好者,还是希望提升排错能力的后端开发,这篇文章都将带你走完一个完整的、可复现的逆向流程。

2. 核心思路与工具选型:如何定位关键逻辑

面对一个已经混淆和加密的应用,盲目地一头扎进代码海是低效的。一个清晰的逆向思路往往比掌握一百个工具更重要。我们的核心思路可以概括为“由外而内,动态追踪”。

2.1 逆向分析的核心思路拆解

首先,要明确目标。我们不是要破解整个应用,而是找到特定参数(尤其是token)的生成位置和算法。因此,最直接的入口就是网络请求。使用抓包工具(如Charles、Fiddler或mitmproxy)拦截应用发出的请求,你会发现原本应该清晰的参数变得面目全非,多出了一堆看似随机的字符串,其中很可能就包含我们的目标——token

接下来的关键一步是寻找突破口。一个常见的技巧是“对比分析法”。你可以尝试:

  1. 相同操作,不同输入:比如搜索“苹果”和“香蕉”,对比两次请求中token和相关参数的变化。如果token完全不同,说明它很可能与输入参数有关。
  2. 相同输入,不同时间:隔几分钟重复同样的搜索,如果token变了,可能加入了时间戳。
  3. 清空缓存/重新登录:观察token是否与登录态(如session或另一个固定token)有关。

通过对比,你可以初步判断token的生成依赖哪些因素:是全部请求参数?是用户身份?还是包含了一个时间变量?这个判断将极大地缩小后续静态分析的代码搜索范围。

2.2 工具链的选择与搭配

工欲善其事,必先利其器。根据目标平台的不同,工具链的选型也不同。

  • 对于Web前端(JS逆向)

    • 抓包/调试:浏览器开发者工具(Chrome DevTools)是首选。它的Network面板可以查看所有请求,Sources面板可以调试JavaScript,Console面板可以执行任意代码进行测试。
    • 反混淆/格式化:线上JS代码经常被压缩(Minify)和混淆(Obfuscate),变量名变成a, b, c,逻辑被拆分。浏览器Sources面板自带的美化(Pretty Print)功能能解决压缩问题。对于混淆,虽然有一些反混淆工具,但更多时候需要依靠调试技巧人脑还原。
    • Hook与拦截:可以使用浏览器插件或Fiddler等工具的脚本功能,在JS代码执行前注入调试代码,拦截关键函数的输入输出,这是定位加密函数的“大杀器”。
  • 对于Android应用(APK逆向)

    • 抓包:仍然需要Charles/Fiddler,并配合在手机或模拟器上安装代理证书。
    • 反编译与静态分析Jadx-GUI是目前最友好、强大的开源工具,它能将APK文件中的DEX字节码反编译成可读性很高的Java代码。对于简单的混淆(如ProGuard),它通常能处理得很好,变量名可能被混淆成ab,但类和方法结构清晰。
    • 动态调试:对于核心逻辑在Native层(C/C++)或加固过的应用,静态分析可能不够。这时需要用到FridaXposed框架。它们可以在应用运行时,动态地注入代码、Hook(挂钩)指定函数,打印出函数的参数、返回值,甚至修改其逻辑。Frida尤其强大,支持全平台,是进行深度逆向的必备技能。
  • 对于iOS应用

    • 工具链类似,抓包用Charles,静态分析可以用IDA ProHopper DisassemblerGhidra分析二进制文件,动态调试则常用LLDBFrida

注意:在本案例中,我们假设面对的是一个使用了“简单混淆”的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 第一步:初步分析与假设

  1. 解码keyword:将e4b88be4b880e697a5e5a587e891a9进行Hex解码,得到字节数据,再尝试用UTF-8解码,结果可能是中文。例如,解码后得到“下一日奇遇”。这验证了参数被简单编码过。
  2. 对比请求:我们再次搜索“测试”,抓包得到keyworde6b58be8af95(“测试”的Hex),timestamp更新了,token也完全不同。
  3. 提出假设token很可能是一个签名,由keyword(解码后)、pagetimestamp等参数,按照一定顺序拼接后,再经过某种哈希算法(如MD5)计算得出。可能还混合了一个固定的密钥(salt)。

3.2 第二步:静态代码分析寻找线索

对于Web应用,我们打开浏览器开发者工具的Sources面板,搜索这个tokena1b2c3d4e5f67890fedcba9876543210。通常不会直接搜到,因为这是计算结果。我们应该搜索更可能出现的常量,比如tokensignmd5sha1CryptoJS(一个常用加密库)等。

假设我们搜索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(f"Generated token: {my_token}") # 输出应该与抓包到的 token: a1b2c3d4e5f67890fedcba9876543210 一致

如果输出一致,恭喜你,你已经成功逆向了这个简单的token生成算法。

实操心得:在复现时,最常遇到的“坑”是字符串编码和哈希输出格式。JS中的字符串到字节的转换(UTF-8)、MD5计算后输出的是WordArray对象,可能需要调用.toString(CryptoJS.enc.Hex)才能得到十六进制字符串。而Python的hashlib.md5需要传入bytes,输出也是十六进制字符串。务必确保每一步的中间结果都和Hook打印出来的一致。另一个常见问题是参数顺序和拼接方式&还是|,有没有额外的空格或换行,都需要严格对照。

4. 深入混淆代码:人脑反混淆的技巧

上面的案例中,混淆程度较轻,我们还能依稀辨认出md5getToken等关键字。但在实际中,你可能会遇到所有变量名都被替换成无意义的单字母,控制流被平坦化(控制流平坦化是一种混淆技术,将原本线性的代码逻辑打散成一个个基本块,通过一个调度器来控制执行顺序,使代码难以阅读),字符串被加密存放的情况。这时,就需要一些人脑反混淆的技巧。

4.1 识别常见的混淆模式

  1. 变量名混淆userName->_0x1a2b3ca。这其实影响不大,我们关注的是函数调用和字符串操作。
  2. 字符串常量加密:原本的"md5"字符串可能被一个函数解密出来,如_0xabcd('0x12')。你需要找到这个解密函数,通常在代码最前面,把它复制到Console里运行,就能得到明文字符串映射表。
  3. 控制流平坦化:代码被拆分成很多个switch-case块或if-else链,跳来跳去。对付它的诀窍是不要试图通读,而是用调试器单步执行,观察数据流。找到核心的计算部分(比如哈希函数调用、大循环),重点分析那一块。
  4. 死代码注入与代码膨胀:插入大量永远不会执行的代码,干扰分析。直接忽略即可。

4.2 利用调试器进行动态追踪

当静态代码难以阅读时,动态调试就成了你的眼睛。以浏览器开发者工具为例:

  1. 下断点:在可能的关键函数入口(如包含encodehashsign等字眼的函数,或网络请求的发起函数XMLHttpRequest.send/fetch)处下断点。
  2. 观察调用栈(Call Stack):当断点触发时,查看调用栈。它能告诉你这个函数是被谁调用的,从而理清函数间的层级关系。
  3. 监视变量(Watch):将你关心的变量(如拼接中的中间字符串、最终的token值)添加到监视面板,观察它们每一步的变化。
  4. 单步执行(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面板过滤XHRJS请求,查看是否有额外的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中可能是a+b+c,你写成了c+b+a通过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是MD5,40位是SHA-1,64位是SHA-256。
5. 密钥(Salt)错误密钥找错了,或者密钥本身也经过了某种编码。通过Hook确认密钥的明文值。检查密钥是否在代码中被二次处理(如反转、截取、再编码)。
6. 时间戳精度时间戳单位是秒还是毫秒?是否经过了取整或格式化?Hook获取参与计算的时间戳值,与你本地生成的时间戳进行对比和换算。
7. 存在随机数签名中混入了一个随机的nonce,每次请求都不同。在请求参数或响应头中寻找一个长度固定、每次请求都变化的随机字符串。

5.3 问题三:面对复杂的加密算法(如AES、RSA)怎么办?

如果发现不是简单的哈希,而是对称加密(AES)或非对称加密(RSA),难度会提升,但思路不变。

  1. 定位算法:搜索CryptoJS.AESencryptdecryptpublicKeyRSA等关键字。
  2. 获取密钥:对于AES,关键是找到keyiv(初始化向量)。它们可能是硬编码的,也可能是通过某个接口动态获取的。对于RSA,前端通常使用公钥加密,公钥可能以字符串或模数(modulus)、指数(exponent)的形式嵌在代码里。
  3. 模式与填充:AES有多种模式(CBC, ECB, GCM等)和填充方式(PKCS7, ZeroPadding等)。这些信息通常在调用加密库的代码中可以看到,例如CryptoJS.AES.encrypt(data, key, {mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7})必须完全一致才能解密成功。
  4. 工具辅助:一旦获取了算法、密钥、模式等所有参数,可以使用Python的pycryptodome库或在线工具进行复现和验证。动态Hook获取加密前的明文和加密后的密文,是验证复现是否正确的唯一标准。

5.4 一个宝贵的调试技巧:本地模拟执行

当你已经将关键的加密函数定位并大致理解后,一个高效的方法是尝试将这段JS代码剥离出来,在Node.js或浏览器Console的独立环境中运行

  1. 将目标函数及其所有依赖的函数、变量定义,复制到一个新的文本文件中。
  2. 补全缺失的全局对象。例如,如果代码中用到了CryptoJS,你需要在环境里引入crypto-js库,或者用Node.js内置的crypto模块实现对应函数。
  3. 创建一个简单的测试脚本,调用这个函数,传入你从Hook中捕获的真实参数。
  4. 观察输出是否与真实token一致。

这个过程能帮你彻底理清函数的所有依赖和内部逻辑,是最终复现算法前最可靠的验证步骤。它就像把发动机从汽车里拆下来单独测试,排除了整车其他电路的干扰。

http://www.jsqmd.com/news/1134006/

相关文章:

  • Chart.js安全防护指南:防御XSS攻击的纵深防御实践
  • PHP代码混淆实战:构建Adminer安全防护工具对抗静态扫描
  • 道具ai模特换装新玩法,海量模板搭配多种实用工具
  • ai模特脸部真实还原,电商高效生图工具对比
  • AI驱动UI自动化测试:从自然语言到可执行脚本的工程实践
  • AD74413R与PIC32MX764F128L的高精度混合信号系统设计
  • AI Agent开发实战指南:从零构建智能体应用的技术栈与学习路径
  • MATLAB版变压器故障概率识别工具:含PNN模型、数据集与可视化结果
  • Claude in Excel:面向Excel公式的AI语义理解引擎
  • SeleniumBase测试录制神器:5分钟快速生成自动化脚本
  • 微信消息防撤回:3种技术方案解析与自动化备份实战
  • 易语言EXE反编译技术解析:从PE结构到算法还原的逆向实战
  • Pytest+Requests接口自动化框架实战:解决接口关联与工程化难题
  • SM4 ECB跨语言加解密:JavaScript与Java互通实现详解
  • 手把手复现WordPress CVE-2016-10033漏洞:Docker靶场与PHPMailer参数注入实战
  • Web应用安全实战:91个方案构建纵深防御体系
  • Sigmoid函数的工程价值:概率建模、模型校准与边缘部署
  • Python XML解析漏洞实战排查:Ruff安全扫描10大技巧
  • 从OWASP指南到安全代码模板:Semgrep与Cookiecutter实战
  • 基于RPA-Python、pytest与Spark构建企业级分布式测试自动化框架
  • 从V8漏洞到实战利用:解析Electron应用沙箱逃逸与WDAC绕过技术
  • 拉曼光谱定量分析:PLS与PCR 2种数学模型构建对比与Python实现
  • kail_dnn_adapter问题排查与解决方案:常见错误处理与性能瓶颈分析终极指南
  • Java版医院排队叫号系统(SpringBoot+MyBatis+Thymeleaf,含部署文档与可运行源码)
  • 北斗+5G+量子通信融合:构建端到端内生安全防护体系
  • httpx插件开发指南:从响应分析到主动探测的扩展实践
  • 手游漏洞挖掘入门:从数据包分析到逻辑漏洞实战
  • Web应用安全最佳实践:从OWASP Top10防御到AI时代安全左移
  • 微信支付V3退款接口SSL证书错误排查指南
  • WebShell查杀实战:从原理到工具选型与应急响应全流程