从SourceMap逆向到JS源码-以郑州大学超算版deepseek前端为例

从SourceMap逆向到JS源码-以郑州大学超算版deepseek前端为例

什么是SourceMap

Source Map(源映射)是 JavaScript 开发中用于调试压缩/混淆代码的重要技术。以下是核心要点:

一、核心作用

  1. 代码映射:将压缩后的代码(如 main.min.js)映射回原始源代码(.js/.ts
  2. 调试支持:在浏览器开发者工具中直接调试未压缩的原始代码
  3. 错误追踪:生产环境报错时,通过 source map 定位原始代码位置

二、生成方式

通过构建工具配置:

// webpack.config.js
module.exports = {
  devtool: 'source-map' // 常见选项:eval | inline-source-map | hidden-source-map
}

三、文件结构

典型 source map 包含:

{
  "version": 3,
  "file": "app.min.js",
  "mappings": "AAEIA,QECcE...",
  "sources": ["src/app.js"],
  "sourcesContent": ["原始代码内容"],
  "names": ["originalFunction"],
  "sourceRoot": "/src"
}

四、关键特性

  1. 映射算法:使用 VLQ 编码存储位置映射信息
  2. 性能优化:仅在开发者工具打开时加载 source map
  3. 安全策略:可通过 HTTP Header 控制访问权限
    X-SourceMap: /path/to/file.map
    

五、开发建议

  • 开发环境:使用 eval-source-map 获得最佳调试体验
  • 生产环境:建议使用 hidden-source-map 配合错误监控系统
  • 注意事项:避免公开敏感源码的 source map

逆向过程

获取js文件

获取js文件的方法有很多,这里以document.head.querySelectorAll('script[src]')为例,获取所有的js文件。

const fullAnalysis = () => {
  return [
    ...document.head.querySelectorAll(`
      script[src],
      link[rel="modulepreload"][as="script"][href]
    `)
  ].map(el => ({
    url: el.src || el.href,
    type: el.tagName === 'SCRIPT' ? 'classic' : 'modulepreload',
    crossOrigin: el.crossOrigin || null,
    async: el.hasAttribute('async'), // 仅对script有效
    defer: el.hasAttribute('defer')  // 仅对script有效
  }));
}

运行上面的代码,你可以得到一个类似于下面的json,需要将其保存到一个scripts.json文件中。

[
    {
        "url": "http://chat.zzu.edu.cn/static/loader.js",
        "type": "classic",
        "crossOrigin": null,
        "async": false,
        "defer": true
    },
    {
        "url": "http://chat.zzu.edu.cn/_app/immutable/nodes/0.ChEw188q.js",
        "type": "modulepreload",
        "crossOrigin": "anonymous",
        "async": false,
        "defer": false
    }
]

注意:这里只是获取了js文件的url,还没有获取js文件的sourcemap。

随便打开一个js文件,查看其最后一行是否有sourcemap,如果有,那么就可以继续下一步。 alt text

上面的js文件有sourcemap,且sourcemap的url为js文件的URL+.map

获取sourcemap

这里写一个简单的函数,获取sourcemap。

import json
import os
import requests
from urllib.parse import urlparse

def download_file(url, save_path, headers):
    """通用文件下载函数"""
    try:
        response = requests.get(url, headers=headers, timeout=15)
        response.raise_for_status()
        with open(save_path, 'wb') as f:
            f.write(response.content)
        return True
    except Exception as e:
        print(f"❌ 下载失败 [{url}]: {str(e)}")
        return False

def download_assets(json_path, output_dir="downloaded"):
    """
    下载JS文件及其对应的Sourcemap文件
    参数:
        json_path: 输入的JSON文件路径
        output_dir: 保存文件的目录(默认downloaded)
    """
    os.makedirs(output_dir, exist_ok=True)
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Accept-Encoding': 'gzip, deflate'
    }

    with open(json_path, 'r') as f:
        scripts = json.load(f)

    for index, script in enumerate(scripts, 1):
        js_url = script.get('url', '')
        if not js_url:
            continue

        # 解析URL信息
        parsed = urlparse(js_url)
        domain_part = parsed.netloc.replace(':', '_')
        path_part = parsed.path.lstrip('/')
        
        # 构建保存路径
        save_dir = os.path.join(
            output_dir,
            domain_part,
            os.path.dirname(path_part)
        )
        os.makedirs(save_dir, exist_ok=True)

        # 下载原始JS文件
        js_filename = os.path.basename(path_part)
        js_save_path = os.path.join(save_dir, js_filename)
        if download_file(js_url, js_save_path, headers):
            print(f"✅ [{index}/{len(scripts)}] JS下载成功: {js_filename}")

        # 自动添加.map后缀下载sourcemap
        if js_url.endswith('.js'):
            sourcemap_url = f"{js_url}.map"
            map_filename = f"{js_filename}.map"
            map_save_path = os.path.join(save_dir, map_filename)
            if download_file(sourcemap_url, map_save_path, headers):
                print(f"🗺️ [{index}/{len(scripts)}] MAP下载成功: {map_filename}")

if __name__ == '__main__':
    download_assets("scripts.json", "web_assets")

运行上面的代码,你可以得到一个web_assets文件夹,里面存放了js文件及其对应的sourcemap文件。

alt text

逆向工具

这里使用shuji这个工具来逆向js文件。

安装shuji

npm install -g shuji

逆向js文件

shuji web_assets -p

运行之后获得逆向后的js文件。如下图的src和node_modules文件夹。

alt text

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计