宇哥JS逆向入门实战课程资源 百度网盘

168 阅读8分钟

从网页到逻辑:JS逆向入门实战与核心代码解析

在当今的Web应用生态中,前端JavaScript(JS)承担着数据交互、逻辑运算甚至核心业务处理的重任。当我们试图通过自动化工具(如Python爬虫)获取网页背后的动态数据时,常常会遇到一道“隐形屏障”——​​加密参数​​(如token、sign、timestamp的复杂组合)、​​动态加载的接口​​(URL随请求动态生成)、甚至​​前端代码混淆​​(变量名变成a/b/c,逻辑被拆分成碎片)。这些设计的本质,是开发者为了保护数据安全与接口稳定而设置的“防护墙”。但对于合法的数据采集需求(如学术研究、竞品分析),我们需要掌握一门关键技术:​​JS逆向工程​​。 本文将通过一个真实的电商网站价格接口逆向案例,带你从零开始理解JS逆向的核心逻辑,并提供可直接运行的Python与JS代码,帮助你建立“从现象到本质”的逆向思维。


一、什么是JS逆向?为什么要学它?

基础概念

JS逆向工程(JavaScript Reverse Engineering)是指通过分析网页前端JavaScript代码,还原其隐藏的业务逻辑(如参数生成规则、加密算法),从而绕过前端限制,直接构造合法请求的过程。它的核心目标不是破坏系统安全,而是理解前端与后端之间的“通信暗号”。

典型应用场景

  • 爬取动态渲染的网页数据(如商品价格、用户评论,这些内容通常由JS异步加载)。
  • 解析加密的API参数(如某些网站的sign参数由时间戳、用户ID等通过特定算法生成)。
  • 自动化测试或合法数据采集(需遵守网站Robots协议与法律法规)。

二、实战案例:某电商网站商品价格接口逆向

场景描述

假设我们要爬取某电商网站(如“示例商城”)的商品详情页价格信息。通过浏览器开发者工具(F12)观察发现:

  1. 商品价格并非直接嵌入HTML,而是通过调用接口https://api.example.com/product/price获取。
  2. 该接口需要传递三个参数:productId(商品ID,可直接从URL获取)、timestamp(当前时间戳)、sign(加密签名,格式如a1b2c3d4e5)。
  3. 直接请求接口(不带sign或带错误sign)会返回{"code":403,"msg":"签名验证失败"}

显然,sign是接口的“通行证”,而它的生成逻辑隐藏在前端JS代码中——这就是我们需要逆向的目标。


三、逆向步骤分解与代码实现

步骤1:定位关键JS代码(找到“sign”生成的位置)

操作指南(浏览器开发者工具使用)
  1. 打开目标商品页(如https://www.example.com/product/12345),按F12打开开发者工具。
  2. 切换到“Network”(网络)标签页,筛选“XHR”或“Fetch”请求,找到调用/product/price的接口请求。
  3. 点击该请求,查看“Headers”中的请求参数,确认sign的存在。
  4. 切换到“Sources”(源代码)标签页,在“Page”栏中找到网站的主JS文件(通常位于static/js/main.xxxx.js或类似路径)。
  5. 使用全局搜索(Ctrl+F)输入关键词:signtimestampproductIdencryptmd5(常见的加密算法名),快速定位生成sign的代码片段。
关键发现(模拟代码片段)

通过搜索,我们可能在某个JS文件中找到如下逻辑(已做脱敏处理,保留核心结构):

// 原始JS代码(经过简化与格式化,实际可能被混淆)
function generateSign(productId, timestamp) {
    var secretKey = "example_key_2024";  // 固定密钥(可能藏在更深层的闭包中)
    var rawStr = productId + "_" + timestamp + "_" + secretKey;
    var sign = md5(rawStr);  // 使用MD5算法生成签名
    return sign;
}

// 调用示例(可能是某个Ajax请求前的逻辑)
var productId = "12345";  // 商品ID
var timestamp = Date.now();  // 当前时间戳(毫秒)
var sign = generateSign(productId, timestamp);
// 发送请求:fetch(`https://api.example.com/product/price?productId=${productId}&timestamp=${timestamp}&sign=${sign}`)
逆向结论

通过分析,我们得知sign的生成规则是: ​sign = MD5(productId + "_" + timestamp + "_" + secretKey)​ 其中secretKey是前端硬编码的固定字符串(如example_key_2024),timestamp是当前时间的毫秒级时间戳。


步骤2:用Python复现JS的sign生成逻辑

既然我们已经知道规则,接下来用Python实现相同的MD5加密逻辑。需要注意的是,JS中的Date.now()返回毫秒级时间戳,而Python的time.time()返回秒级,需乘以1000转换。

Python代码实现
import hashlib
import time

def generate_sign_js_logic(product_id, timestamp):
    secret_key = "example_key_2024"  # 从JS代码中提取的固定密钥
    raw_str = f"{product_id}_{timestamp}_{secret_key}"  # 拼接原始字符串
    # 使用MD5加密(注意:JS的MD5通常是小写,Python的hexdigest()也是小写)
    sign = hashlib.md5(raw_str.encode('utf-8')).hexdigest()
    return sign

# 测试用例
product_id = "12345"  # 替换为实际商品ID
timestamp = int(time.time() * 1000)  # 当前毫秒级时间戳
sign = generate_sign_js_logic(product_id, timestamp)

print(f"商品ID: {product_id}")
print(f"时间戳: {timestamp}")
print(f"生成的sign: {sign}")

# 构造完整请求URL(示例)
api_url = f"https://api.example.com/product/price?productId={product_id}&timestamp={timestamp}&sign={sign}"
print(f"请求接口: {api_url}")  # 可直接用requests.get(api_url)测试
代码说明
  • hashlib.md5():Python内置的MD5加密库,需将字符串编码为UTF-8字节流(.encode('utf-8'))后再加密。
  • hexdigest():返回32位小写的十六进制字符串(与JS的MD5结果格式一致)。
  • 时间戳处理:JS的Date.now()是毫秒级,Python需用int(time.time() * 1000)转换。

步骤3:验证逆向结果(用Python发送合法请求)

通过上述代码生成的sign,我们可以直接构造请求并验证是否能获取正确数据。

完整请求代码(使用requests库)
import requests
import hashlib
import time

def get_product_price(product_id):
    # 生成时间戳和sign
    timestamp = int(time.time() * 1000)
    secret_key = "example_key_2024"
    raw_str = f"{product_id}_{timestamp}_{secret_key}"
    sign = hashlib.md5(raw_str.encode('utf-8')).hexdigest()

    # 构造请求参数
    params = {
        "productId": product_id,
        "timestamp": timestamp,
        "sign": sign
    }

    # 发送GET请求(实际接口可能是POST,需根据Network面板调整)
    api_url = "https://api.example.com/product/price"
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        "Referer": f"https://www.example.com/product/{product_id}"  # 模拟浏览器来源
    }

    response = requests.get(api_url, params=params, headers=headers)
    
    if response.status_code == 200:
        data = response.json()
        if data.get("code") == 200:
            print(f"商品 {product_id} 的价格是: {data['data']['price']} 元")
        else:
            print(f"接口返回错误: {data.get('msg')}")
    else:
        print(f"请求失败,状态码: {response.status_code}")

# 测试
get_product_price("12345")
注意事项
  • ​请求头​​:部分接口会校验User-Agent(模拟浏览器)或Referer(来源页面),需从浏览器Network面板中复制真实请求的头信息。
  • ​反爬升级​​:如果网站进一步校验了IP频率、Cookie或WebSocket握手信息,则需要更复杂的逆向(如Hook技术、动态调试)。

四、进阶技巧:当JS代码被混淆时怎么办?

在实际项目中,前端JS代码常被工具(如Webpack、UglifyJS)混淆,变量名变成abc,逻辑被拆分成多个函数嵌套。此时逆向难度会上升,但核心思路不变:

1. 动态调试法(推荐新手)

使用浏览器开发者工具的“Debugger”功能:

  • 在“Sources”标签页找到目标JS文件,设置断点(在疑似生成sign的代码行点击左侧)。
  • 触发接口请求(如刷新页面或点击加载更多),程序会在断点处暂停。
  • 逐步执行代码(F10单步跳过,F11单步进入),观察变量的实时值变化,定位关键逻辑。

2. Hook技术(针对动态生成)

通过注入自定义JS代码,劫持关键函数(如md5encodeURIComponent),直接打印输入输出参数。例如:

// 在Console中执行以下代码,劫持MD5函数
const originalMd5 = window.md5;  // 保存原始函数
window.md5 = function(str) {
    console.log("MD5输入:", str);  // 打印原始字符串
    const result = originalMd5(str);
    console.log("MD5输出:", result);  // 打印加密结果
    return result;
};

五、总结与学习建议

逆向的核心思维

  • ​现象驱动​​:从接口报错(如403签名失败)、参数缺失(如缺少sign)出发,定位问题根源。
  • ​逻辑还原​​:通过分析JS代码,将前端隐藏的算法(如字符串拼接+MD5)转化为后端可复现的逻辑。
  • ​工具辅助​​:浏览器开发者工具(Network/Sources/Debugger)、Python请求库(requests)、加密库(hashlib)是必备武器。

学习路径推荐

  1. ​基础阶段​​:掌握MD5/SHA1/Base64等常见加密算法的原理与Python实现,学会用开发者工具定位JS代码。
  2. ​进阶阶段​​:学习动态调试(断点、单步执行)、Hook技术,应对混淆代码。
  3. ​实战阶段​​:从简单的静态参数接口开始,逐步挑战动态Token、加密请求体(如AES/RSA加密的POST数据)。

通过本文的案例,你会发现JS逆向并非“黑魔法”,而是逻辑分析与代码实现的结合。它教会我们的不仅是获取数据的技巧,更是对前端与后端协作机制的深度理解。下次当你面对一个加密接口时,不妨打开开发者工具,用逆向思维揭开它的神秘面纱——技术的本质,永远是为了解决问题而存在。