在进行网站加密算法逆向时,我们常常会遇到一个核心问题:浏览器环境与 Node.js 环境的差异。直接将网站的 JavaScript 加密代码复制到 Node.js 中运行,往往会报出 window is not defineddocument is not defined 等错误。这篇文章将带你深入理解「补环境」的核心思路,并通过 Proxy 代理实现高效的环境伪装。

一、逆向的两种核心思路

在破解网站加密时,通常有两种主流方案:

1.重写加密逻辑

深度分析网站的加密算法原理(如参数加密、签名生成等),用 Python 或 JavaScript 从零实现一套等价逻辑,使其能在本地独立运行。​

✅ 优势:彻底脱离浏览器环境,运行稳定,不受环境依赖影响;​

❌ 劣势:对算法理解要求极高,逆向周期长,适合复杂加密场景。

2.复制加密代码(补环境)

直接将网站中扣取的加密代码片段,在 Node.js 环境中运行。无需深入理解加密算法细节,只需解决环境差异问题即可。​

✅ 优势:开发速度快,上手门槛低,适合快速获取加密结果;​

❌ 劣势:依赖浏览器环境,易出现环境缺失报错,需手动补全环境。

本文将重点讲解第二种方案——补环境

二、环境差异:浏览器 vs Node.js

1. 浏览器环境

浏览器中的 JavaScript 可以访问大量专属 API,这些是网站加密代码的常见依赖:

  • BOM (Browser Object Model):操作浏览器窗口和自身信息,核心对象是 window

window:全局对象,代表浏览器窗口。

navigator:提供浏览器信息(如 userAgent)。

navigator.userAgent;     // 用户代理字符串
navigator.appName;       // 浏览器名称
navigator.appVersion;    // 浏览器版本信息

// 平台信息
navigator.platform;               // 操作系统平台(如 "Win32", "MacIntel")
navigator.language;               // 浏览器的主语言
navigator.languages;              // 用户偏好语言数组

// 硬件和网络
navigator.onLine;                 // 布尔值,表示浏览器是否联网
navigator.hardwareConcurrency;    // 逻辑处理器核心数
navigator.deviceMemory;           // 设备内存(GB)

// 其他功能
navigator.cookieEnabled;          // 浏览器是否启用cookie
navigator.geolocation;            // 地理位置API接口
navigator.clipboard;              // 剪贴板API接口
navigator.mediaDevices;           // 媒体设备API接口(摄像头、麦克风)

location:操作当前页面 URL。

location.href;        // 完整的URL
location.protocol;    // 协议(如 "https:")
location.host;        // 主机名和端口(如 "www.example.com:8080")
location.hostname;    // 主机名(如 "www.example.com")
location.port;        // 端口号
location.pathname;    // 路径部分(如 "/path/page.html")
location.search;      // 查询字符串(如 "?id=123")

history:浏览器历史记录。

history.length;                              // 会话历史中的记录数量
history.state;                               // 当前历史记录条关联的状态对象

history.back();                              // 后退一页
history.forward();                           // 前进一页
history.go(-2);                              // 前进/后退指定页数

history.pushState(state, title, url);        // 向历史记录中添加一条新记录,但不刷新页面
history.replaceState(state, title, url);     // 替换当前这一条历史记录,不产生新的记录

screen:显示器信息。

screen.width;         // 屏幕的总宽度
screen.height;        // 屏幕的总高度
screen.availWidth;    // 屏幕的可用宽度(减去任务栏等)
screen.availHeight;   // 屏幕的可用高度
screen.colorDepth;    // 颜色深度(如 24)
screen.pixelDepth;    // 像素深度(通常与colorDepth相同)
  • DOM (Document Object Model): DOM 是浏览器解析 HTML 后生成的树形结构,JavaScript 可以通过 document 对象访问和操作 HTML 元素。

作用

方法

说明

查询与选择

document.getElementById(id)

通过ID获取唯一元素

document.querySelector(selector)

通过 CSS 选择器获取第一个匹配元素

document.querySelectorAll(selector)

通过 CSS 选择器获取所有匹配元素

document.getElementsByClassName(className)

通过类名获取元素

document.getElementsByTagName(tagName)

通过标签名获取元素

创建和修改

document.createElement(tagName)

创建一个新的 HTML 元素

document.createTextNode(text)

创建一个纯文本节点

document.createDocumentFragment()

创建文档片段,用于批量插入节点

document.write() / writeln()

向文档流直接写HTML

常用的属性如下:

属性

说明

document.body

返回 <body> 元素

document.title

获取或设置文档标题

document.URL

当前文档的完整 URL

document.domain

当前文档的域名

document.referrer

来源页面的 URL

document.cookie

网站的cookie

2. Node.js 环境

Node.js 是一个服务端运行时,不包含任何浏览器专属的 BOM/DOM API。因此,当加密代码尝试访问 windowdocument 等对象时,就会抛出经典的 ReferenceError


三、基础补环境:手动模拟浏览器对象

我们的目标是在 Node.js 中模拟出浏览器环境,让加密代码认为自己正运行在 Chrome 等浏览器中。下面通过一个完整案例来演示。

案例代码分析

假设我们有一段依赖浏览器环境的函数:

function getTime() {
    if (typeof window == 'undefined') {
        return window;
    }

    document.cookie;
    document.createElement('div').tagName;
    const canvas = document.createElement('canvas');
    if (!canvas.getContext) return false;

    if (!navigator.userAgent) {
        return null;
    }

    if (screen.width === undefined || screen.width < 800) {
        return '1925/10/26 01:22:52';
    }

    if (!location.href) {return false};

    return "运行成功:" + new Date().toLocaleString();
}

在 Node.js 中直接运行,会依次报出以下错误:

  1. ReferenceError: window is not defined

  2. ReferenceError: document is not defined

  3. TypeError: document.createElement is not a function

  4. ReferenceError: navigator is not defined

  5. ReferenceError: screen is not defined

  6. ReferenceError: location is not defined

逐步补全环境

我们需要手动创建这些缺失的全局对象:

1. 模拟 window 对象

在 Node.js 中,global 是全局对象,我们可以将其赋值给 window

global.window = global;

2. 模拟 document 对象

global.document = {
    createElement: function(tagName) {
        return {
            tagName: tagName,
            getContext: function() {} // 模拟 canvas.getContext 方法
        };
    }
};

3. 模拟 navigator 对象

global.navigator = {
    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
};

4. 模拟 screen 对象

global.screen = {
    width: 1920 // 模拟屏幕宽度,绕过小于 800 的检测
};

5. 模拟 location 对象

global.location = {
    href: 'https://www.example.com' // 模拟 URL,绕过空值检测
};

完整可运行代码

将以上补全代码整合后,getTime() 函数就能在 Node.js 中正常运行并返回正确结果:

global.window = global;
global.document = {
    createElement: function(tagName){
        return {
            tagName: tagName,
            getContext:function(){}
        }
    }
};
global.navigator = {
    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
};
global.screen = {
    width: 1920
};
global.location = {
    href: "https://www.example.com"
};

function getTime() {
    // ... 原函数代码 ...
}

const result = getTime();
console.log(result); // 输出:运行成功:2026/03/25 17:00:00

四、进阶补环境:用 Proxy 代理高效伪装

手动补环境虽然有效,但面对复杂的网站代码时,需要模拟的对象和方法会非常多,工作量巨大。这时,Proxy 代理就成了更高效的解决方案。

1. 什么是 Proxy?

Proxy 是 ES6 引入的特性,它可以拦截并自定义对象的基本操作(如读取属性、设置属性、调用方法等)。我们可以用它来批量代理 windowdocument 等对象,自动处理所有未定义的属性访问,避免报错。

2. Proxy 基本用法

const target = { name: '张三' };
const handler = {
  get(target, property) {
    console.log(`正在读取属性: ${property}`);
    return target[property];
  }
};
const proxy = new Proxy(target, handler);

console.log(proxy.name); // 输出:正在读取属性: name → 张三

3. 批量代理环境对象

我们可以编写一个通用函数,批量代理所有需要的浏览器对象:

global.window = global;

function getEnv(proxy_array) {
    for (let i = 0; i < proxy_array.length; i++) {
        const objName = proxy_array[i];
        // 定义代理处理器,拦截 get 和 set 操作
        const handler = {
            get(target, property, receiver) {
                console.log(`[get] ${objName}.${property}`);
                // 如果属性不存在,返回一个空函数,避免继续报错
                return target[property] || function() {};
            },
            set(target, property, value, receiver) {
                console.log(`[set] ${objName}.${property} = ${value}`);
                return Reflect.set(...arguments);
            }
        };

        try {
            // 如果对象已存在,直接代理
            global[objName] = new Proxy(global[objName] || {}, handler);
        } catch (e) {
            // 如果对象不存在,创建一个空对象再代理
            global[objName] = new Proxy({}, handler);
        }
    }
}

// 需要代理的环境对象列表
const proxy_array = ['document', 'location', 'navigator', 'history', 'screen', 'Object', 'window'];
getEnv(proxy_array);

4. 日志增强版(便于调试)

在逆向调试时,我们需要清晰地知道代码访问了哪些属性。可以将处理器改造为日志版:

function getEnv(proxy_array) {
    for (let i = 0; i < proxy_array.length; i++) {
        const handler = `{
            get: function(target, property, receiver) {
                console.log('方法:get',' 对象:${proxy_array[i]}',' 属性:',property);
                return target[property] || function() {};
            },
            set: function(target, property, value, receiver){
                console.log('方法:set',' 对象:${proxy_array[i]}',' 属性:',property,' 值:',value);
                return Reflect.set(...arguments);
            }
        }`;
        // 使用 eval 动态创建代理
        eval(`
            try {
                ${proxy_array[i]} = new Proxy(${proxy_array[i]} || {}, ${handler});
            } catch (e) {
                ${proxy_array[i]} = new Proxy({}, ${handler});
            }
        `);
    }
}

运行后,访问任何对象属性都会输出详细日志,例如:

方法:get  对象:document  属性: createElement
方法:get  对象:navigator  属性: userAgent

五、总结与实践建议

核心思想

补环境的本质是:在 Node.js 中模拟出网站代码所依赖的最小浏览器 API 集合,让代码误以为自己在浏览器中运行。

实践步骤

  1. 运行代码,定位缺失对象:根据报错信息,逐个补全 windowdocument 等对象。

  2. 模拟最小可用接口:不需要完整实现所有 API,只需模拟代码实际用到的属性和方法。

  3. 使用 Proxy 批量处理:面对复杂代码时,用 Proxy 代理所有对象,自动处理未定义属性,大幅提高效率。

  4. 结合日志调试:通过 Proxy 的日志功能,清晰追踪代码对环境的访问路径,快速定位问题。

注意事项

  • 环境检测:很多网站会通过 navigator.userAgentscreen.width 等信息检测运行环境,模拟时要尽量贴近真实浏览器。

  • 性能考量:过度模拟会增加运行开销,只模拟必要的部分即可。

  • 版本兼容:不同网站的环境依赖不同,需要根据具体代码灵活调整。

通过以上方法,你可以高效地在 Node.js 中运行网站的加密代码,为后续的逆向分析和算法还原打下坚实基础。