X-hub

JavaScript逆向工程:环境模拟与补充技巧

详细介绍JavaScript逆向工程中的环境模拟技术,包括浏览器环境补充、API模拟、指纹模拟等实战技巧

JavaScript逆向工程:环境模拟与补充技巧

在JavaScript逆向工程中,环境模拟是一个非常重要的环节。本文将详细介绍如何在Node.js环境下模拟浏览器环境,以及各种环境补充的技巧。

为什么需要补环境?

在将浏览器中的JavaScript代码迁移到Node.js环境时,我们会遇到以下问题:

  1. 缺少浏览器特有的API(如window、document等)
  2. 缺少DOM和BOM对象
  3. 某些特殊对象(如Canvas、WebGL)的缺失
  4. 浏览器指纹相关的环境变量缺失

基础环境搭建

1. 基本框架搭建

// env.js
const jsdom = require('jsdom');
const { JSDOM } = jsdom;

// 创建一个虚拟的DOM环境
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
    url: 'https://example.com',
    referrer: 'https://example.com',
    contentType: 'text/html',
    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
    includeNodeLocations: true,
    storageQuota: 10000000
});

// 全局变量补充
global.window = dom.window;
global.document = window.document;
global.navigator = window.navigator;
global.location = window.location;
global.history = window.history;
global.localStorage = window.localStorage;
global.sessionStorage = window.sessionStorage;

2. 常用对象补充

// 补充XMLHttpRequest
const XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
global.XMLHttpRequest = XMLHttpRequest;

// 补充fetch
const fetch = require('node-fetch');
global.fetch = fetch;

// 补充WebSocket
const WebSocket = require('ws');
global.WebSocket = WebSocket;

高级环境模拟

1. Canvas环境模拟

// canvas.js
const { createCanvas } = require('canvas');

class CanvasRenderingContext2D {
    constructor() {
        const canvas = createCanvas(300, 150);
        return canvas.getContext('2d');
    }
}

// 补充Canvas环境
global.Canvas = createCanvas;
global.CanvasRenderingContext2D = CanvasRenderingContext2D;

// 模拟toDataURL方法
global.Canvas.prototype.toDataURL = function() {
    return 'data:image/png;base64,iVBORw0KGgoAAAANSU...';
};

2. WebGL环境模拟

// webgl.js
const gl = require('gl');

class WebGLRenderingContext {
    constructor(canvas, options) {
        return gl(canvas.width, canvas.height, options);
    }
}

global.WebGLRenderingContext = WebGLRenderingContext;

3. 浏览器指纹模拟

// fingerprint.js
// 模拟navigator对象
const navigator = {
    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...',
    platform: 'Win32',
    language: 'zh-CN',
    languages: ['zh-CN', 'en-US'],
    hardwareConcurrency: 8,
    deviceMemory: 8,
    maxTouchPoints: 0,
    vendor: 'Google Inc.',
    plugins: [],
    mimeTypes: []
};

// 模拟screen对象
const screen = {
    width: 1920,
    height: 1080,
    availWidth: 1920,
    availHeight: 1040,
    colorDepth: 24,
    pixelDepth: 24
};

global.navigator = Object.assign(global.navigator || {}, navigator);
global.screen = screen;

实战技巧

1. 代理监听属性访问

// proxy.js
// 监控对象属性的访问
function createProxyObject(target, name) {
    return new Proxy(target, {
        get: function(target, property) {
            console.log(`[${name}] 访问属性: ${property}`);
            return target[property];
        },
        set: function(target, property, value) {
            console.log(`[${name}] 设置属性: ${property} = ${value}`);
            target[property] = value;
            return true;
        }
    });
}

// 应用到window对象
global.window = createProxyObject(global.window, 'window');

2. 动态补充缺失属性

// dynamic-patch.js
// 动态补充缺失的属性
process.on('uncaughtException', (err) => {
    if (err.message.includes('is not defined')) {
        const property = err.message.split(' ')[0];
        console.log(`自动补充缺失属性: ${property}`);
        global[property] = {};
    }
});

3. 特殊API模拟

// special-apis.js
// 模拟crypto API
const crypto = require('crypto');

global.crypto = {
    getRandomValues: function(buffer) {
        return crypto.randomFillSync(buffer);
    },
    subtle: {
        digest: async function(algorithm, data) {
            const hash = crypto.createHash(algorithm.toLowerCase().replace('-', ''));
            hash.update(data);
            return hash.digest();
        }
    }
};

// 模拟Performance API
global.performance = {
    now: () => Date.now(),
    timing: {
        navigationStart: Date.now()
    }
};

实战案例

案例一:模拟登录加密环境

// login-env.js
// 补充RSA加密所需环境
const NodeRSA = require('node-rsa');

global.window.RSAKey = class {
    constructor() {
        this.key = new NodeRSA({b: 1024});
    }
    
    encrypt(data) {
        return this.key.encrypt(data, 'base64');
    }
};

// 补充MD5环境
const CryptoJS = require('crypto-js');
global.window.md5 = function(str) {
    return CryptoJS.MD5(str).toString();
};

案例二:模拟Cookie环境

// cookie-env.js
// 模拟document.cookie
let cookies = {};

Object.defineProperty(global.document, 'cookie', {
    get: function() {
        return Object.entries(cookies)
            .map(([key, value]) => `${key}=${value}`)
            .join('; ');
    },
    set: function(cookieString) {
        const [keyValue] = cookieString.split(';');
        const [key, value] = keyValue.split('=');
        cookies[key.trim()] = value.trim();
    }
});

注意事项

  1. 性能考虑: 不要过度模拟环境,只补充必要的部分。

  2. 内存管理: 注意及时清理不需要的环境变量。

  3. 异常处理: 做好异常捕获和错误处理。

  4. 版本兼容: 注意不同浏览器版本的API差异。

常见问题解决

1. 处理undefined属性

// 全局代理处理undefined属性
global = new Proxy(global, {
    get: function(target, property) {
        if (!(property in target)) {
            console.warn(`访问未定义属性: ${property}`);
            return {};
        }
        return target[property];
    }
});

2. 处理异步操作

// 处理异步请求
const originalSetTimeout = setTimeout;
global.setTimeout = function(callback, delay) {
    console.log(`设置定时器: ${delay}ms`);
    return originalSetTimeout(callback, delay);
};

总结

环境模拟是JavaScript逆向工程中的重要环节,需要根据具体情况选择合适的模拟方案。本文介绍的方法可以帮助你更好地处理环境模拟问题,但记住要在合法和合规的前提下使用这些技术。

参考资源

如果你在实践中遇到任何问题,欢迎在评论区讨论交流。

评论