Skip to content

js逆向-补环境

目录

前言

TIP

在JS逆向中,补环境是一项核心技能,其根本目的是让一段原本依赖浏览器环境才能正确运行的JavaScript代码(例如生成加密参数的算法),在Node.js等服务器端环境中也能被执行,并产生与浏览器环境完全一致的结果。

为何需要补环境🌐

浏览器环境和Node.js环境虽然都基于V8引擎,但它们提供的API(全局对象)存在显著差异

特性浏览器环境Node.js环境
核心引擎V8V8
全局对象window, document, navigator, location, screen 等BOM/DOM对象global, process, module,
exports, require
主要用途与DOM交互,渲染页面服务器端操作,如文件系统、网络

补充:二者都有的API是globalThis

当你在浏览器中成功“扣出”用于加密的JS代码后,直接放在本地Node.js环境中运行,通常会遇到类似 ReferenceError: document、window is not defined的错误。这是因为代码试图访问一个Node.js中根本不存在的对象。

补环境,就是手动在本地Node环境中补充这些缺失的环境,删除浏览器中没有的,尽量模拟这些缺失的浏览器对象(环境) ,“欺骗”代码,让它以为自己仍在浏览器中运行。

补环境的核心思路与方法 💡

补环境遵循“缺什么,补什么”的原则,并非要实现一个完整的浏览器。

其基本流程是一个循环:运行代码 → 分析报错 → 补充缺失的环境 → 再次运行,直到代码顺利执行。

针对补环境的反扒,一般会使用try来"保护"代码,防止吐出真实缺失的环境

js
try {
    window.xxx = xxxx
} catch (error) {
    //
}

结果:尽管环境中仍有缺失,但不会报错,导致程序运行到错误分支,生成错误结果

补环境常用方法

1. 直接手动补环境

这是最直接的方法,根据错误信息逐一修补。

示例

js
// 示例1:代码报错 ReferenceError: navigator is not defined
// 补:定义一个基础的 navigator 对象
navigator = {
    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...'
};

// 示例2:代码报错 TypeError: Cannot read property 'href' of undefined
// 补:定义 location 对象及其属性
location = {
    href: 'https://www.target.com',
    protocol: 'https:',
    host: 'www.target.com'
};

// 示例3:补一个函数,如果代码不真正使用其功能,可返回空对象或空函数
document = {
    createElement: function (tag) {
        console.log(`创建了 ${tag} 元素`);
        return {
            appendChild: () => {
            }
        }; // 返回一个简化对象
    }
};

优缺点

优点: 这种方法简单快捷,适用于环境检测点较少、逻辑简单的情况。

缺点:对于环境检测多切复杂的不合适,还是要大量混淆代码

2. 利用Proxy探测环境(吐环境)

当目标代码经过高度混淆,包含大量难以直接阅读的环境检测点时,手动补环境效率极低。

此时,Proxy​ 就成为了一把利器。 Proxy可以代理一个对象,拦截并记录对该对象的任何操作(如读取属性、设置属性、函数调用等)。通过它,我们可以创建一个** “监控器”让代码自己告诉我们它检查了哪些环境属性**。

下面是一个通用的Proxy自吐环境脚本:

js
function func_proxy(func, name) {
    return new Proxy(func, {
        apply(target, thisArg, arg_list) {
            var ret = Reflect.apply(target, thisArg, arg_list)
            if (typeof ret == 'object') {
                return obj_proxy(ret, name + " 的返回值")
            } else if (typeof ret == 'function') {
                return func_proxy(ret, name + " 的返回值")
            }
        }
    })
}


function obj_proxy(obj, name) {
    return new Proxy(obj, {
        get(target, prop, rece) {
            var val = Reflect.get(target, prop, rece)
            // 判断是否需要补
            if (val === undefined) {
                console.log(`从 ${name} 访问属性: ${prop} ,该属性的值是: `, val, ` ****;`)
            } else {
                console.log(`从 ${name} 访问属性: ${prop} ,该属性的值是: `, val, ";")
            }
            // 判断类型
            if (typeof val == 'object') {
                return obj_proxy(val, prop)
            } else if (typeof val == 'function') {
                return func_proxy(val, prop)
            }
            return val
        },
        set(target, prop, val, rece) {
            Reflect.set(target, prop, val, rece)
            console.log(`从 ${name} 访问属性: ${prop} ,该属性的值是: `, val, ";")
        }
    })
}

window = obj_proxy({}, 'window')
var navigator = {
    userAgent: 'mnk的浏览器',
    plugins: {
        abc: {
            ded: {
                "aaa": 'bbbb'
            }
        },
        demo: function () {
            return {
                name: "这是函数返回的属性"
            }
        }
    }
}
window.navigator = navigator

当这段代码运行时,控制台会输出所有被访问和设置的属性。你只需要根据输出,集中修补那些值为 undefined的属性即可,极大地提高了补环境的效率和针对性。

3. 补环境框架

这是更高级、更彻底的方案,也是难度最高的方案,但对于经常进行JS逆向的专业人员来说,是一项极具价值的长期投资,但一旦建成,能够显著提高逆向效率和成功率,可以一劳永逸地解决大量类似网站的逆向问题,是专业JS逆向人员的“大杀器”。!!!

核心思想

是用JavaScript完整模拟浏览器环境的所有主要BOM/DOM对象及其原型链关系,构建一个丐版的纯JS浏览器环境。

在JS逆向中,构建一个完整的纯JavaScript浏览器环境框架是一种高级补环境策略,其核心是用JavaScript完整模拟浏览器环境的所有主要BOM/DOM对象及其原型链关系,从而创建一个功能完整的“丐版”浏览器环境。

框架设计特点

🏗️ 模块化架构
  • 分离实现:每个浏览器对象(如NavigatorDocumentWindowLocationHistory等)的实现放在单独的JavaScript文件中
  • 便于维护:模块化设计使得代码结构清晰,易于调试和扩展
  • 可复用性:模块可以独立测试和使用,提高开发效率
🔗 原型链模拟
  • 构造函数模拟:精确实现每个浏览器对象的构造函数
  • 原型方法模拟:准确模拟对象的原型链方法和实例属性
  • 类型检查一致性:确保instanceoftypeof等类型检查与真实浏览器环境一致
  • 继承关系准确:正确模拟对象之间的继承关系(如HTMLDivElement继承自HTMLElement
📈 可扩展性
  • 渐进式增强:可以从基本的核心对象开始,逐步添加更多浏览器API
  • 插件化机制:支持通过插件方式扩展功能
  • 持续完善:随着逆向经验的积累,框架可以不断强化,最终达到“通杀”大多数环境检测的目的

技术实现示例

// 示例:模块化浏览器环境框架结构
browser-env-framework/
├── core/
│   ├── Window.js      # Window对象实现
│   ├── Document.js    # Document对象实现
│   ├── Navigator.js   # Navigator对象实现
│   └── Location.js    # Location对象实现
├── dom/
│   ├── Element.js     # 元素基类
│   ├── HTMLElement.js # HTML元素
│   └── Event.js       # 事件系统
├── bom/
│   ├── Screen.js      # 屏幕信息
│   └── History.js     # 历史记录
├── utils/
│   └── polyfill.js    # 兼容性填充
└── index.js          # 框架入口

重要注意事项

最小化与精准化原则

  1. 目的导向:补环境的目的是让目标代码跑通,不是模拟整个浏览器
  2. 按需模拟:只补代码真正用到的属性和方法
  3. 简化实现:实现可以尽量简单,除非代码逻辑依赖复杂的返回值

属性值真实性 🎯

  1. 关键检测点:对于navigator.userAgent、window.screen.width等关键属性
  2. 获取真实值:最好从浏览器控制台中直接复制真实值
  3. 避免假值:使用空字符串或明显错误的假值可能无法通过检测

异常捕获处理🐛

  1. 暴露问题:尝试删除异常捕获代码,让错误暴露出来
  2. 定位根源:通过错误信息准确定位环境缺失点
  3. 谨慎操作:修改后要注意不影响正常逻辑执行
js
// 原混淆代码可能包含
try {
// 环境检测逻辑
    if (!window.xyzFeature) {
        throw new Error('Environment mismatch');
    }
} catch (e) {
    // 静默失败,但后续逻辑可能出错
    return fallbackValue;
}

// 逆向时可能需要修改为
// 删除try-catch,让错误暴露
if (!window.xyzFeature) {
    throw new Error('Environment mismatch'); // 现在会暴露问题
}

🧬 理解原型链的重要性

  1. 高级检测:复杂的反爬机制会检查对象的构造函数和原型链
  2. 完整模拟:需要模拟完整的原型链关系,而不仅仅是简单赋值
  3. 类型检查:确保Object.getPrototypeOf()、obj.constructor等检查通过
js
// 错误的简单补法
document.createElement = function () {
    return {};
};

// 正确的原型链模拟
function MockElement() {
// 构造函数逻辑
}

MockElement.prototype.appendChild = function () {
};
MockElement.prototype.querySelector = function () {
};
// ... 其他方法

document.createElement = function (tagName) {
    const element = new MockElement();
    element.tagName = tagName.toUpperCase();
    return element;
};