JS逆向入门实战:从定位加密函数到Python复现签名参数

发布时间:2026/6/24 19:41:39
JS逆向入门实战:从定位加密函数到Python复现签名参数 1. 项目概述与目标拆解最近在带新人入门JS逆向发现很多朋友卡在了“知道要扣代码但不知道从哪下手”的阶段。正好图灵爬虫练习平台的第三题是一个典型的、用于教学练手的JS逆向案例它模拟了真实环境中一种常见的参数加密场景。这个项目标题“【js逆向入门】图灵爬虫练习平台第三题”本身就指向了一个非常明确的学习路径通过一个结构清晰、难度适中的靶场来掌握JS逆向的核心分析思路和工具链使用。这个平台第三题的核心通常是模拟一个请求其关键参数比如一个叫token或者sign的字段是由前端JavaScript经过一系列计算生成的。我们的目标不是简单地拿到数据而是理解这个生成逻辑并用Python或其他语言复现出来从而让我们的爬虫程序能够自主构造出合法的请求参数实现自动化数据抓取。这对于想从基础爬虫转向处理动态渲染、反爬机制更复杂网站的朋友来说是必须跨过的一道坎。它适合已经了解HTTP协议、会用Python的requests库发起简单请求但对浏览器开发者工具“Sources”和“Debugger”面板还比较陌生的初学者。2. 逆向环境准备与工具链解析工欲善其事必先利其器。在开始逆向之前搭建一个顺手的分析环境至关重要。很多人一上来就对着混淆的代码硬看效率极低且容易放弃。我的习惯是准备一套组合拳工具。2.1 浏览器开发者工具深度使用Chrome或Edge的开发者工具是核心。关键不在于打开它而在于怎么用。Network面板这是起点。清空记录点击页面上的触发按钮如“查询”、“提交”观察产生的XHR/Fetch请求。重点关注请求头Headers和负载Payload。在这个练习中你一定会发现一个携带了加密参数的请求。把该请求的URL、方法、以及所有参数特别是那个看起来乱码的加密参数完整记录下来。右键请求选择“Copy as cURL”是个好习惯方便后续在Python里转换或重放测试。Sources面板这是主战场。不要被众多的JS文件吓到。通常有两种策略找入口事件监听器断点在Sources面板右侧的“Event Listener Breakpoints”里勾选“Mouse”下的“click”事件。然后去点击页面上触发加密请求的按钮代码会自动在对应的点击事件处理函数处断下。这是最直观的找入口方式。搜索关键参数名在Sources面板按CtrlShiftF进行全局搜索。搜索你在Network里看到的那个加密参数的名字比如token、sign、encryptData等。这能快速定位到生成或设置该参数的代码片段。2.2 辅助调试与反混淆工具Pretty Print在Sources面板打开一个压缩过的JS文件点击左下角的{}图标美化代码这能将单行、无格式的代码还原成可读的格式是分析的第一步。Overrides功能这是本地持久化修改JS代码的神器。允许你将在线JS文件映射到本地磁盘的一个副本并在本地修改、保存。刷新页面后浏览器会加载你修改后的本地文件极大方便了代码调试和逻辑验证。具体操作是在Sources面板的“Overrides”标签中添加一个本地文件夹然后右键网络请求中的JS文件选择“Save for overrides”。Node.js环境很多加密算法如MD5、SHA、AES、RSA在Node.js中有原生或稳定的第三方库如crypto-js实现。当我们扣出关键JS函数后可以尝试在Node.js环境中运行和调试验证其功能是否与浏览器一致这比直接在Python里移植更接近原环境。注意不要一开始就尝试使用自动化的反混淆工具如ast解析还原。对于入门练习和大多数商业网站先尝试通过调试和理解逻辑来解决问题这能锻炼最重要的代码跟踪和逻辑分析能力。自动化工具是后续应对极端混淆时的备选方案。3. 针对第三题的具体逆向流程实录假设我们通过Network面板发现点击查询按钮后向/api/getdata发送了一个POST请求其负载中有一个关键参数sign: 80b8c8f6a1e74c4a8c6f5d8b9a1e2f3c示例值。我们的逆向流程就此展开。3.1 定位加密函数入口按照上一节的方法我们首先尝试事件监听器断点。点击按钮后代码在app.js的某一行断下。我们可以在右侧的“Call Stack”调用堆栈中看到函数调用链。逐步向上查看寻找与参数组装或sign赋值相关的代码。或者我们直接在Sources面板全局搜索sign:。可能会找到类似这样的代码片段$.ajax({ url: /api/getdata, type: POST, data: { page: pageNum, timestamp: new Date().getTime(), sign: generateSign(pageNum, new Date().getTime()) }, success: function(res){...} });太好了这里清晰地显示sign是由一个叫generateSign的函数生成的参数是pageNum和当前时间戳。我们的目标立刻聚焦到这个generateSign函数上。3.2 分析与扣取关键函数在源代码中搜索function generateSign找到其定义。假设它看起来是这样的function generateSign(page, timestamp) { var key turing_platform_secret; var str page | timestamp | key; return md5(str).toUpperCase(); }这是一个非常典型的案例将业务参数页码、时间戳和一个固定的密钥key用特定分隔符拼接然后进行MD5哈希最后转为大写。逻辑清晰明了。“扣代码”在这里的含义就是把这个逻辑移植到Python中。但在此之前我们需要验证。在开发者工具的Console面板我们可以直接测试输入generateSign(1, 1646389472000)看看输出是否与Network中捕获的sign值或类似规律一致。如果一致说明我们找对了。3.3 Python代码复现与验证现在在Python中复现这个逻辑。我们需要hashlib库进行MD5计算。import hashlib import time def generate_sign(page, timestamp): key turing_platform_secret # 严格按照JS中的拼接顺序和分隔符 original_str f{page}|{timestamp}|{key} # 创建md5对象注意update需要bytes类型 m hashlib.md5() m.update(original_str.encode(utf-8)) # 获取十六进制哈希值并转为大写 sign m.hexdigest().upper() return sign # 测试使用一个已知的时间戳进行对比 test_timestamp 1646389472000 my_sign generate_sign(1, test_timestamp) print(f生成的sign: {my_sign}) # 将打印的sign与浏览器Network捕获的对应请求的sign进行比对如果输出结果与浏览器捕获的请求中的sign值完全一致那么恭喜逆向成功。你的爬虫现在可以动态生成这个参数了。3.4 处理更复杂的情况当然实际的第三题可能比这个例子复杂。可能会遇到引入外部库函数比如sign的计算用到了CryptoJS.MD5。这时你需要确认CryptoJS这个库在JS环境中是如何实现的。如果是标准用法Python的hashlib或pycryptodome库可以对应。有时需要把CryptoJS的一小部分辅助函数比如字符串到WordArray的转换也扣出来。包含浏览器环境对象比如函数中使用了window.navigator.userAgent的一部分参与计算。这时你需要在Python代码中模拟一个相同的UA字符串。多层函数调用generateSign内部可能又调用了_encrypt、_base64Encode等其它函数。你需要沿着调用链将所有依赖的函数都找到并扣出来直到最底层的标准算法如MD5、SHA256或简单的逻辑运算为止。实操心得在扣取嵌套函数时善用开发者工具的调试功能。在关键函数入口打上断点点击行号即可然后单步执行F10步过F11步入观察每一步的变量值变化。这能帮你精准理解每一行代码的作用以及数据的流转过程。把每一步的输入输出记录下来就是最好的分析笔记。4. 请求构造与数据抓取实现逆向出参数生成算法后爬虫的构造就水到渠成了。但这里依然有一些细节需要注意这些细节往往是爬虫稳定性的关键。4.1 构建完整的请求参数我们需要模拟一个完整的、合法的请求。除了逆向得到的sign还要注意其他参数import requests import time def get_data(page_num): url https://练习平台域名/api/getdata # 替换为实际URL # 1. 获取当前时间戳毫秒级与JS中new Date().getTime()对应 current_timestamp int(time.time() * 1000) # 2. 生成签名 sign generate_sign(page_num, current_timestamp) # 3. 构造请求负载 payload { page: page_num, timestamp: current_timestamp, # 确保这里传递的时间戳与生成sign时用的是同一个 sign: sign } # 4. 构造请求头非常重要 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..., # 模拟浏览器 Content-Type: application/x-www-form-urlencoded, # 根据实际请求的Content-Type调整 Referer: https://练习平台域名/第三题页面地址, # 来源页有时会校验 # X-Requested-With: XMLHttpRequest # 如果是Ajax请求有时需要添加 } # 5. 发送请求 # 注意如果实际请求是Form Data格式用data参数如果是JSON格式用json参数并调整headers中的Content-Type response requests.post(url, datapayload, headersheaders) # 6. 处理响应 if response.status_code 200: data response.json() # 假设返回的是JSON return data else: print(f请求失败状态码{response.status_code}) print(response.text) return None # 抓取第一页数据 data_page_1 get_data(1) print(data_page_1)4.2 时间戳同步问题这是一个常见的坑。sign的生成依赖于时间戳。如果服务器时间与你的本地时间有较大偏差或者服务器对时间戳的有效期有校验例如只接受最近10秒内的请求那么你生成的sign可能会失效。解决方案是从服务器响应中获取时间。很多网站会在接口的响应里返回一个服务器时间戳。首次请求可以是一个不带签名或签名简单的“握手”请求获取服务器时间后后续请求都用这个服务器时间来计算签名。4.3 请求头与会话保持User-Agent使用一个常见的浏览器UA字符串避免使用python-requests这类默认UA。Cookies有些平台虽然主要验证sign但登录状态可能仍依赖Cookie。可以使用requests.Session()来保持会话先模拟登录获取Cookie再用这个session去调用数据接口。其他自定义头仔细检查浏览器中的原始请求看是否有X-Token,X-Signature等自定义头部这些都可能需要模拟。5. 常见问题排查与进阶技巧即使按照流程操作你也可能会遇到各种问题。这里记录一些典型的排查思路和进阶技巧。5.1 问题排查清单问题现象可能原因排查思路生成的sign与浏览器不一致1. 参数拼接顺序或分隔符错误。2. 字符串编码不一致。3. 使用了不同的MD5库某些库会进行额外处理。4. 依赖的某个变量值在Python和浏览器环境中不同。1. 在JS调试器中在generateSign函数入口和return前打上断点精确记录输入的参数和输出的结果。2. 在Python中将准备进行MD5的字符串原样打印出来与JS调试器中记录的字符串进行逐字符对比包括不可见字符。3. 确保在Python中使用encode(utf-8)。请求返回“签名错误”或“无效参数”1. 时间戳问题见4.2。2. 请求头不完整缺少Referer或Content-Type不正确。3. 负载payload格式错误应用data却用了json参数或反之。1. 使用抓包工具如Fiddler、Charles或开发者工具的“Copy as cURL”功能将浏览器成功的请求导出并与你的Python请求代码进行逐行对比。2. 检查服务器返回的错误信息有时会给出更具体的提示。找不到加密函数入口1. 加密逻辑被Webpack等打包工具包裹。2. 使用了全局事件委托监听器不在按钮上。3. 加密是异步的或在Web Worker中执行。1. 尝试搜索整个JS文件中的关键常量如密钥key、加密函数名的一部分如encrypt、sign。2. 在Network面板对疑似加密的请求右键选择“Break on” - “XHR/Fetch Breakpoint”然后重新触发请求代码会在发起请求前断住。3. 查看是否加载了额外的chunk.js文件加密逻辑可能在里面。5.2 进阶技巧Hook技术辅助定位当搜索和断点都难以定位时可以尝试使用“Hook”钩子技术。这相当于在浏览器环境中“埋点”监听特定函数或属性的调用。例如我们可以在Console中执行以下代码来Hookmd5函数假设它挂载在window上(function() { var originalMd5 window.md5; // 先保存原函数 window.md5 function(str) { console.trace(md5被调用参数是:, str); // 打印调用栈和参数 var result originalMd5(str); // 调用原函数 console.log(md5结果是:, result); // 打印结果 return result; // 返回结果 }; })();执行这段代码后再点击页面按钮Console会输出所有对md5的调用信息包括从哪里调用的调用栈以及输入输出这能帮你快速定位加密发生的位置。这种方法对JSON.stringify、Date.getTime、btoa等标准API同样有效。5.3 扣代码的优化与重构直接翻译JS代码到Python有时会很冗长特别是当JS代码涉及复杂的位操作或特定的编码方式时。在理解算法本质后应寻求用Python更优雅的方式实现。识别标准算法如果经过分析发现最终是AES加密、RSA加密或标准的Base64那么应该直接使用Python成熟的库如pycryptodome、rsa、base64来实现而不是去扣JS里那套复杂的模拟代码。关键在于确认算法模式、密钥、填充方式等参数与JS端一致。提取核心逻辑只扣取自定义的业务逻辑部分比如参数拼接顺序、密钥混合方式等。对于标准的加密哈希函数调用Python内置库。逆向的最终目的不是完美复现一段晦涩的JS代码而是理解其生成规则。规则一旦被掌握就可以用任何语言、以最简洁高效的方式实现它。图灵平台的第三题正是训练这种“理解规则”能力的绝佳起点。当你成功跑通第一个案例后那种透过混淆代码看清本质的成就感会支撑你面对更复杂的真实战场。