js逆向-补环境
目录
前言
TIP
在JS逆向中,补环境是一项核心技能,其根本目的是让一段原本依赖浏览器环境才能正确运行的JavaScript代码(例如生成加密参数的算法),在Node.js等服务器端环境中也能被执行,并产生与浏览器环境完全一致的结果。
为何需要补环境🌐
浏览器环境和Node.js环境虽然都基于V8引擎,但它们提供的API(全局对象)存在显著差异:
| 特性 | 浏览器环境 | Node.js环境 |
|---|---|---|
| 核心引擎 | V8 | V8 |
| 全局对象 | 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来"保护"代码,防止吐出真实缺失的环境
try {
window.xxx = xxxx
} catch (error) {
//
}结果:尽管环境中仍有缺失,但不会报错,导致程序运行到错误分支,生成错误结果
补环境常用方法
1. 直接手动补环境
这是最直接的方法,根据错误信息逐一修补。
示例
// 示例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自吐环境脚本:
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对象及其原型链关系,从而创建一个功能完整的“丐版”浏览器环境。
框架设计特点
🏗️ 模块化架构
- 分离实现:每个浏览器对象(如
Navigator、Document、Window、Location、History等)的实现放在单独的JavaScript文件中 - 便于维护:模块化设计使得代码结构清晰,易于调试和扩展
- 可复用性:模块可以独立测试和使用,提高开发效率
🔗 原型链模拟
- 构造函数模拟:精确实现每个浏览器对象的构造函数
- 原型方法模拟:准确模拟对象的原型链方法和实例属性
- 类型检查一致性:确保
instanceof、typeof等类型检查与真实浏览器环境一致 - 继承关系准确:正确模拟对象之间的继承关系(如
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 # 框架入口重要注意事项
最小化与精准化原则
- 目的导向:补环境的目的是让目标代码跑通,不是模拟整个浏览器
- 按需模拟:只补代码真正用到的属性和方法
- 简化实现:实现可以尽量简单,除非代码逻辑依赖复杂的返回值
属性值真实性 🎯
- 关键检测点:对于navigator.userAgent、window.screen.width等关键属性
- 获取真实值:最好从浏览器控制台中直接复制真实值
- 避免假值:使用空字符串或明显错误的假值可能无法通过检测
异常捕获处理🐛
- 暴露问题:尝试删除异常捕获代码,让错误暴露出来
- 定位根源:通过错误信息准确定位环境缺失点
- 谨慎操作:修改后要注意不影响正常逻辑执行
// 原混淆代码可能包含
try {
// 环境检测逻辑
if (!window.xyzFeature) {
throw new Error('Environment mismatch');
}
} catch (e) {
// 静默失败,但后续逻辑可能出错
return fallbackValue;
}
// 逆向时可能需要修改为
// 删除try-catch,让错误暴露
if (!window.xyzFeature) {
throw new Error('Environment mismatch'); // 现在会暴露问题
}🧬 理解原型链的重要性
- 高级检测:复杂的反爬机制会检查对象的构造函数和原型链
- 完整模拟:需要模拟完整的原型链关系,而不仅仅是简单赋值
- 类型检查:确保Object.getPrototypeOf()、obj.constructor等检查通过
// 错误的简单补法
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;
};