Web逆向实战:从412状态码追踪AES加密密钥与算法还原 📅 2026/7/4 14:48:02 1. 项目概述从412状态码到AES密钥的追踪之旅最近在分析一个采用AES加密进行数据交互的网站时遇到了一个典型的现代Web逆向场景。这个网站的接口数据全部被加密前端通过JavaScript执行AES算法对请求参数进行加密服务器端验证通过后才返回解密后的数据。整个过程尤其是那个关键的AES密钥被隐藏在层层混淆的JavaScript代码中。这不仅仅是简单的“找到加密函数”更是一场涉及网络协议、JavaScript逆向工程和密码学应用的综合性挑战。对于从事数据采集、接口测试或安全研究的朋友来说这类问题几乎每天都会遇到。今天我就把自己这次完整的分析思路、踩过的坑以及最终定位关键加密逻辑的全过程记录下来希望能给遇到类似问题的朋友提供一个清晰的参考路径。整个分析的核心目标非常明确逆向出网站用于AES加密的密钥Key和初始化向量IV并理解其完整的加密验证流程。只有掌握了这些我们才能模拟客户端行为构造出能被服务器正确识别和处理的合法加密请求。这个过程就像侦探破案需要从网络请求的蛛丝马迹开始一步步追踪到源代码中的核心逻辑。2. 核心思路与逆向工程方法论面对一个加密网站最忌讳的就是一头扎进海量的混淆代码里。一个系统化的分析思路能极大提升效率。我的整体方法论可以概括为“由外而内动静结合”。2.1 逆向分析的核心路径从现象到本质我的分析通常遵循以下四个步骤这次也不例外网络行为观察这是所有逆向的起点。不关心代码只关心浏览器和服务器之间“发生了什么”。重点关注首次请求的特殊状态码如常见的412、202、响应头中的特殊字段、Cookie的设置以及请求体的格式。这一步的目的是划定战场明确我们要攻击的“堡垒”有哪些外围防御。关键入口定位基于网络行为找到执行加密操作的关键JavaScript文件。通常加密函数会集中在处理请求的代码附近比如在XMLHttpRequest或Fetch的拦截器中或者在提交表单的事件监听器里。通过搜索关键词如“encrypt”、“AES”、“CBC”、“PKCS7”等可以快速缩小范围。代码逻辑分析定位到疑似加密函数后需要静下心来分析其逻辑。这包括密钥和IV的来源是硬编码、从服务器动态获取、还是由其他参数计算得出、加密模式如CBC、ECB、填充方式如PKCS7。此时可能需要使用浏览器开发者工具的“代码美化”功能或者借助AST抽象语法树解析工具来反混淆。算法还原验证将分析得到的算法逻辑密钥、IV、模式、填充用另一种编程语言如Python、Node.js重新实现。然后截取一个真实的网络请求数据用自己的代码加密后与浏览器发送的密文进行比对。完全一致才算大功告成。2.2 针对AES加密的专项分析要点AES加密本身是标准算法但网站的实现千奇百怪。我们需要重点关注以下几个变体它们直接影响到最终还原的准确性加密模式最常见的是CBC模式它需要一个IV。也可能会遇到ECB、GCM等模式。模式不同代码实现和调用方式差异很大。填充方案由于AES是块加密需要对不足块长度的数据进行填充。PKCS7或PKCS5是最常见的填充方式。需要确认代码中是否显式指定了填充或者使用了某个库的默认填充。密钥和IV的格式它们通常是字符串或Buffer。关键要看它们是如何生成的。可能是固定的字符串硬编码也可能是对某个时间戳、随机数或特定Cookie值进行MD5、SHA256等哈希运算后的结果。输出格式加密后的输出可能是Base64编码的字符串也可能是十六进制Hex字符串。这关系到我们模拟请求时该如何构造数据。注意很多前端加密库如CryptoJS的默认行为是模式为CBC填充为PKCS7。但这绝不能想当然必须在代码中找到确凿证据。3. 实战拆解网络层行为分析与关键点捕获理论说再多不如一次实战。我们直接进入正题看看这个网站的具体表现。3.1 首次请求的“下马威”412状态码用浏览器Chrome/Edge打开目标网站按F12打开开发者工具切换到Network网络面板并勾选“Preserve log”保留日志。刷新页面立刻就能看到不寻常的地方。第一个请求通常是HTML文档请求返回了状态码412 Precondition Failed。这是一个非常重要的信号。412状态码意味着服务器对客户端提出了一些前提条件客户端未满足因此拒绝处理。在Web逆向中这常常是反爬机制或加密验证流程开始的标志。查看这个412响应的响应头和响应体响应头可能会包含一个或多个Set-Cookie字段设置了一些看起来是随机字符串的Cookie。这些Cookie很可能在后续的加密计算中被用到。响应体通常是一段JavaScript代码或者一个包含JS的HTML片段。这段代码就是我们要分析的起点。它可能是一个自执行的函数负责收集浏览器环境信息、计算一些初始参数并设置新的Cookie为下一次请求做准备。3.2 第二次请求与加密负载的现身在浏览器自动执行了412响应中的JS代码后通常会立即发起第二个请求可能是重新请求页面或是请求一个关键的JS文件。这次请求状态码变成了200。此时我们需要重点关注这个成功请求的请求参数。切换到“Payload”或“Request”标签页查看。如果请求方法是POST那么Form Data或Request Payload很可能是一串看似无规律的、经过Base64编码的字符串。这就是AES加密后的密文。此外请求URL本身也可能包含加密参数体现在查询字符串?paramxxx中。同时检查这次请求的请求头。除了常规头信息要特别注意是否有自定义的头部比如X-Encrypt-Data、X-Sign之类的这些也常常是加密或签名的结果。记录关键信息在这个阶段你需要像取证一样记录下所有可能相关的信息412响应设置的Cookie名称和值。成功请求的URL、方法GET/POST。成功请求携带的加密参数密文完整复制下来。成功请求的请求头信息。这些信息将是后续代码分析和算法验证的“标准答案”。4. 深入核心JavaScript逆向与加密逻辑定位网络行为清楚了接下来就是最核心也最耗时的部分——逆向JavaScript代码。4.1 定位加密入口的多种技巧网站的所有前端代码都在Sources源代码面板中。面对成百上千个JS文件如何快速定位基于网络请求定位在Network面板中找到那个返回了加密参数的成功请求。点击这个请求在右侧的“Initiator”发起者标签页可以看到是哪个JS文件发起了这个请求。点击对应的行号可以直接跳转到Sources面板中的具体代码位置。这通常是最快、最准的方法。全局搜索关键词在Sources面板中按CtrlShiftFWindows进行全局搜索。尝试搜索以下关键词encryptencodeAESCBCECBCryptoJS一个非常常用的前端加密库你之前记录下的加密参数名如dataencryptedData等一些常见的加密函数名如btoaBase64编码、atobBase64解码虽然不直接是AES但可能相关。XHR/Fetch断点在Sources面板的“XHR/Fetch Breakpoints”中可以添加一个断点URL包含你之前记录的成功请求的URL的一部分。这样当JS代码发起这个特定请求时执行会自动暂停你就能看到在发送请求前代码对数据做了哪些处理。4.2 分析混淆代码与还原加密函数找到疑似加密的代码块后你很可能看到的是被混淆过的代码变量名都是a, b, c, _0xabc123逻辑被拆分成无数个小函数可读性极差。应对策略使用Pretty Print在Sources面板中点击代码区域左下角的{}图标可以美化格式化代码使其结构清晰。动态调试在关键函数入口或可疑的赋值语句处打上断点然后重新触发请求如点击页面按钮。当断点触发时你可以在Console面板中直接查看和修改当前作用域的变量值。使用console.log()在代码中插入日志输出关键变量的值如密钥、明文、加密后的结果。单步执行F10和步入函数F11跟踪数据的流向。关键逻辑追踪重点关注以下几个地方密钥Key的来源它可能是一个字符串常量也可能是通过一个函数计算出来的。沿着调用栈向上找看这个值最初是从哪里来的。可能是从window对象的某个属性、从特定的Cookie、或者从第一次412请求的响应中解析出来的。IV的生成对于CBC模式IV同样关键。它可能是一个固定值也可能是一个随机数或者是基于时间戳生成的。加密库的调用如果使用了CryptoJS代码可能长这样CryptoJS.AES.encrypt(plainText, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 })。你需要确认mode和padding的具体值。输出处理加密结果一个CipherParams对象通常会被转换成字符串cipherText.toString()或者cipherText.toString(CryptoJS.enc.Base64)。实操心得在本次分析中我通过“发起者”追踪定位到了一个名为security.js的模块。在其中发现了一个名为_doEncrypt的函数。通过断点调试我观察到它接收一个明文对象将其JSON序列化后调用了一个内部的_aesEncrypt方法。继续步入_aesEncrypt终于看到了核心逻辑密钥是从document.cookie中一个名为__s的字段值经过substring操作截取部分字符得到的而IV则是一个硬编码的16字节字符串“0123456789abcdef”。加密模式为CBC填充为PKCS7输出格式为Base64。5. 算法还原与Python实现验证分析出逻辑后必须用其他语言复现一遍来验证。我选择Python因为它有强大的pycryptodome库。5.1 环境准备与依赖安装首先确保安装了必要的库pip install pycryptodomepycryptodome是Crypto库的维护版本功能更强大和稳定。5.2 根据逆向结果编写加密函数根据之前的分析我们已知Key: 来自Cookie__s的第2到第18个字符共16字节。IV: 固定字符串“0123456789abcdef”。模式: AES-CBC。填充: PKCS7。输入: 需要加密的字典JSON格式。输出: Base64编码的字符串。下面是用Python实现的加密函数import json import base64 from Crypto.Cipher import AES from Crypto.Util.Padding import pad def aes_encrypt(data_dict, cookie_s_value): 模拟前端AES加密 :param data_dict: 需要加密的字典数据 :param cookie_s_value: 从Cookie中获取的 __s 的值 :return: Base64编码的加密字符串 # 1. 获取Key从cookie_s_value的第2个字符开始取16个字符索引1到16 key cookie_s_value[1:17].encode(utf-8) # 确保是16字节 if len(key) ! 16: raise ValueError(Key length must be 16 bytes after extraction.) # 2. 固定IV iv b0123456789abcdef # 16字节 # 3. 准备明文将字典转为JSON字符串再编码为bytes plaintext json.dumps(data_dict, separators(,, :), ensure_asciiFalse).encode(utf-8) # 4. 使用PKCS7进行填充 padded_plaintext pad(plaintext, AES.block_size) # 5. 创建AES-CBC加密器并加密 cipher AES.new(key, AES.MODE_CBC, iv) ciphertext cipher.encrypt(padded_plaintext) # 6. 将加密结果进行Base64编码 encrypted_b64 base64.b64encode(ciphertext).decode(utf-8) return encrypted_b64 # 示例用法 if __name__ __main__: # 假设这是我们要加密的请求参数 request_data { page: 1, size: 20, timestamp: 1687851234567 } # 假设这是从浏览器Cookie中获取的 __s 值模拟 # 实际分析中这个值需要从首次请求的响应Cookie中获取 mock_cookie_s f5a8e1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8 encrypted_result aes_encrypt(request_data, mock_cookie_s) print(加密后的Base64字符串:, encrypted_result)5.3 验证与对比这是最关键的一步。你需要从浏览器中捕获一个真实的、成功的网络请求。复制该请求的Request Payload密文。在Python脚本中使用相同的cookie_s_value即触发该真实请求时浏览器中__sCookie的值和相同的request_data你需要推断或解密出原始的明文参数有时可以通过观察页面表单或分析其他未加密请求来推测。运行你的Python加密函数。将你的输出与浏览器捕获的密文进行对比。如果两者完全一致恭喜你逆向成功如果不一致请按以下顺序排查Key/IV提取逻辑确认字符串截取的位置、长度、编码是否完全一致。前端JS的字符串索引和Python是否一致明文格式确认JSON字符串的格式是否有空格、缩进。JS的JSON.stringify和Python的json.dumps默认输出可能不同。我上面代码中使用了separators(‘,’ ‘:’)来生成最紧凑的JSON以匹配前端常见行为。填充方式确认是PKCS7。有些实现可能使用ZeroPadding或其他方式。加密模式确认是CBC。输出编码确认是Base64。有些可能是Base64URL需要替换/为-_或Hex。6. 常见问题、排查技巧与经验总结逆向过程中总会遇到各种问题这里记录下我踩过的坑和解决方法。6.1 高频问题排查清单问题现象可能原因排查思路Python加密结果与浏览器密文前若干位相同后面不同填充Padding不一致。当明文长度恰好是块整数倍时PKCS7会额外填充一个完整的块而其他填充方式可能不填充。检查前端代码使用的填充库和配置。用不同长度的明文测试观察规律。在Python中尝试pad(plaintext AES.block_size style‘pkcs7’)显式指定。加密结果完全不一样1.Key/IV错误。2.明文根本不对。3.加密模式错误如误用ECB。1. 在JS加密函数入口打断点直接Console输出Key和IV的字节值Hex或Array与Python中获取的进行逐字节比对。2. 在JS加密前打点输出即将加密的明文字符串确保与Python准备的完全一致。3. 确认代码中AES的模式参数。能加密但服务器返回“解密失败”1.请求结构不符服务器期望的可能是{“data”: “密文”}格式而你只发了密文。2.缺少签名或时间戳等其他验证参数。3.Cookie过期或无效。1. 仔细比对浏览器成功请求的完整请求体结构包括所有字段。2. 检查成功请求是否携带了sign、nonce、timestamp等额外参数并分析其生成逻辑。3. 确保你使用的Cookie__s值是新鲜有效的它可能随会话变化。找不到加密函数入口代码混淆严重关键词搜索无果。1. 在Network面板对加密请求的URL设置XHR/Fetch断点。2. 使用“Initiator”调用栈反向追踪。3. 搜索更通用的词如encrypt、encode、param或特定库名CryptoJS。6.2 核心经验与避坑指南动态调试优于静态分析面对混淆代码不要试图完全读懂它。善用断点、单步执行和Console日志让程序自己告诉你关键变量的值。这是最高效的手段。“抠代码”要完整不要只把加密函数抠出来。如果Key是通过一个复杂的函数getSecretKey()生成的你必须把这个函数及其所有依赖也一并抠出来直到你能独立运行出正确的Key为止。注意编码问题JavaScript字符串是UTF-16而Python默认是UTF-8。但在网络传输和CryptoJS中通常处理的是UTF-8编码的字节。确保在Python中将字符串转换为字节时.encode(‘utf-8’)与前端行为一致。一个常见的坑是前端用CryptoJS.enc.Utf8.parse(“中文”)在Python中就要用“中文”.encode(‘utf-8’)。验证环境一致性用于验证的浏览器请求和你的Python脚本必须使用同一时刻、同一会话下的Cookie值。因为密钥可能动态变化。最好在浏览器触发加密后立即从Application面板的Cookies里复制值到Python脚本中测试。理解整体流程加密只是验证的一环。很多网站还有**签名Signature**机制对“明文参数密钥时间戳”等进行哈希。务必确认你逆向的是否是最终用于data字段加密的密钥而不是用于签名的密钥。逆向分析是一个需要耐心和细心的过程。每一次成功破解都是对网络协议、编程语言和加密知识的一次综合运用。当你用自己的代码成功模拟出浏览器的加密请求并收到服务器返回的正确数据时那种成就感是无与伦比的。希望这篇详细的记录能帮你少走弯路更高效地应对Web逆向中的AES加密挑战。记住思路永远比工具更重要。