【JS逆向百例】携某 testab 参数补环境详解
字数 1131 2025-08-20 18:18:23
JS逆向补环境技术详解:携程testab参数逆向分析
一、前言
补环境技术是JS逆向中相对通用且易于实现的方法,相较于直接逆向算法,补环境可以更高效地解决JSVMP等复杂加密问题。本文将以携程testab参数为例,详细讲解补环境的完整流程和技术要点。
二、逆向目标分析
1. 目标参数
- 参数名:testab
- 生成方式:由e函数生成,通过JSVMP代码动态执行
- 接口来源:getHotelScript接口返回的VMP代码
2. 定位关键代码
- 全局搜索"testab"定位到生成函数
- 跟栈发现是VMP代码通过eval执行
- VMP代码动态变化,需固定调试
三、VMP代码调试方法
1. 两种调用方式
// 方法一:动态eval执行
var code = `vmp代码`;
function decode(callback) {
window[callback] = function(e) {
delete window[callback];
var e = e();
testab = e;
return e;
}
window.eval(code);
return testab
}
decode("KLBNxcMKmI");
// 方法二:直接执行
function decode(callback) {
window[callback] = function(e) {
delete window[callback];
var e = e();
testab = e;
return e;
}
// 直接放入vmp代码
return testab
}
decode("KLBNxcMKmI");
2. JSVMP插桩调试
在VMP指令为函数调用的地方下断点,输出关键日志:
- navigator自有属性和external的toString()检测
- document.documentElement的getAttribute检测
- Object.keys对document原型检测
- navigator属性描述符检测及UA检测
- node process检测
- Window toString()检测
- document检测
- createElement检测
- appendChild及报错检测
- vm及其他检测
四、补环境实现
1. 基础环境搭建
创建三个文件:
- main.js:主运行文件
- env.js:环境补全代码
- code.js:VMP执行代码
基础保护代码
!(function(){
"use strict";
const $toString = Function.toString;
const myFunction_toString_symbol = Symbol('('.concat(Math.random()+'').toString(36)));
const mytoString = function(){
return typeof this == 'function' && this[myFunction_toString_symbol] || $toString.call(this);
};
function set_native(func,key,value){
Object.defineProperty(func,key,{
"enumerable": false,
"configurable": true,
"writable": true,
"value": value
})
};
delete Function.prototype['toString'];
set_native(Function.prototype,"toString",mytoString);
set_native(Function.prototype.toString,myFunction_toString_symbol,"function toString() { [native code] }");
this.func_set_native = function(func){
set_native(func,myFunction_toString_symbol,`function ${myFunction_toString_symbol,func.name}(){ [native code] }`)
}
}).call(this);
window = this;
self = window;
self.window = window;
2. 关键环境补全
基础对象补全
Window = function Window(){};
Location = function Location(){};
Navigator = function Navigator(){};
Image = function Image(){ console.log("Image", arguments) };
document = {};
navigator = {};
location = {};
external = {};
// 外部对象补全
Object.defineProperty(external, Symbol.toStringTag, {
value: 'External'
});
External = function External(){ console.log("External",arguments) };
func_set_native(External);
external.__proto__ = External.prototype;
Navigator补全
Object.setPrototypeOf(navigator, Navigator.prototype);
Navigator.prototype.webdriver = false;
Navigator.prototype.platform = 'Win32';
Navigator.prototype.appCodeName = 'Mozilla';
Navigator.prototype.userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36';
Document相关补全
HTMLHtmlElement = function HTMLHtmlElement(){
this.getAttribute = function(){
return null
}
};
document.documentElement = new HTMLHtmlElement();
HTMLBodyElement = function HTMLBodyElement(){
this.appendChild = function(child){
if(child.tagName == "DIV"){
child.offsetHeight = 20
}
}
};
document.body = new HTMLBodyElement();
HTMLDivElement = function HTMLDivElement(){
this.tagName = "DIV";
this.style = { height: "" };
this.offsetHeight = 0;
this.remove = function(){
this.offsetHeight = 0
}
};
document.createElement = function createElement(){
console.log("createElement创建了", arguments);
let tagName = arguments[0];
if(tagName == "div"){
var div = new HTMLDivElement();
return div;
}
// 其他标签处理...
};
3. 对象冻结
Object.freeze(document);
Object.freeze(navigator);
4. 关键检测绕过
Object属性检测
// getOwnPropertyDescriptor检测
_getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
Object.getOwnPropertyDescriptor = function(obj, p){
// 自定义处理逻辑
return _getOwnPropertyDescriptor.apply(this, arguments);
};
// keys检测
_keys = Object.keys;
Object.keys = function(obj){
// 自定义处理逻辑
return _keys.apply(this, arguments);
};
// getOwnPropertyNames检测
_getOwnPropertyNames = Object.getOwnPropertyNames;
Object.getOwnPropertyNames = function(obj){
// 自定义处理逻辑
return _getOwnPropertyNames.apply(this, arguments);
};
正则检测绕过
RegExp = new Proxy(RegExp, {
construct(target, argArray) {
if(argArray[0] && argArray[0].indexOf('vm') !== -1){
return new target(...['bootstrapNodeJSCoretryModuleLoadevalmachinerunInContext','g'])
}
return new target(...argArray)
}
});
5. 代理检测处理
使用高级代理方案避免被检测:
dtavm = {};
dtavm.log = console.log;
function proxy(obj, objname, type) {
// 完整代理实现...
// 包含get/set/has/deleteProperty等处理
// 详细代码见原文
}
// 使用方式
window = proxy(window, "window");
document = proxy(document, "document");
// 其他对象代理...
五、完整流程总结
- 定位testab生成位置,提取VMP代码
- 搭建基础环境框架(main.js, env.js, code.js)
- 实现基础保护代码(Function.toString等)
- 补全关键环境对象(window, document, navigator等)
- 处理DOM相关操作(createElement, appendChild等)
- 绕过关键检测(Object方法、正则检测等)
- 使用高级代理避免被检测
- 冻结关键对象防止修改
- 调试验证结果一致性
六、注意事项
- 补环境需要耐心,逐步验证每个补丁的效果
- 优先处理影响主流程的环境检测
- 注意浏览器异常断点的使用
- 代理实现要足够完善以避免被检测
- 关键对象如document、navigator需要冻结
- 所有函数都需要toString保护
通过以上步骤,可以成功补全环境并正确生成testab参数。这种方法具有通用性,可以应用于其他类似的JSVMP逆向场景。