下载使用vm2(有安全漏洞,酌情使用)
如果我们直接使用Node环境来运行js代码的话,有些东西是无法规避环境检测的,所以我们需要一个纯净的V8环境来进行补环境的操作。vm2可以理解为一个纯净的脱离Node的V8环境,相当于重新启动了一个进程,然后在进程中不允许加载Node中的一些东西,禁止了一些Node中的特性,相当于V8的沙箱环境。PS:一些node环境中无法删除的node特征可以在vm2中删除,而不是说vm2=v8环境。
npm install vm2 //下载vm2
const fs = require('fs')
const {VM,VMScript} = require('vm2') //VMScript是用来调试VM环境中的代码的
const file = `${__dirname}/code.js`
const windowfile = `${__dirname}/window.js`
const vm = new VM()
const script = new VMScript(fs.readFileSync(file),'VM2') //第二个参数是指调试名,可以自己任意指定
vm.run(script)
检测环境的例子
- 如node环境中有process,而V8环境或者浏览器环境中没有process。
- 任何canvas指纹都存在一定误杀率,所以厂商不会去对canvas指纹做强校验,因为不同浏览器canvas底层实现代码不同;其次即便是相同浏览器,它的内核版本不同则取出来的canvas指纹也是不同的:
document.createElement('canvas').toDataURL()
。 - toString()
- 基于原型链的检测
- DOM环境的检测
手动补环境的例子
主要可以参考官方文档来:
https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/getContext
- 补
document.createElement
:
// 补 document.createElement('canvas').toDataURL()
document = {
createElement: function(tagName){
var tag = (tagName + '').toLowerCase;
if (tag == 'canvas')
{
return {
toDataURL:function(){
return 'data:image/png;base64,iVBORw0KG...略...RU5ErkJggg==' //可以手动在浏览器中运行生成获取
}
}
}
return {}
}
}
- 下面的代码中:
//在node环境中运行下面代码
var window = this;
navigator = {
userAgent:'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36'
}
const descriptor1 = Object.getOwnPropertyDescriptor(navigator,'userAgent');
此时会返回这样的一个对象:
但如果是在浏览器中,是拿不到descriptor1的(返回undefined
)。
此时我们可以这样补环境:
Object.getOwnPropertyDescriptor_ = Object.getOwnPropertyDescriptor;
Object.getOwnPropertyDescriptor = function(o,p){
if (o.toLocaleString() == '[object Navigator]'){
return undefined;
}
Object.getOwnPropertyDescriptor_.apply(this,arguments);
}
此时再次运行const descriptor1 = Object.getOwnPropertyDescriptor(navigator,'userAgent');
,得到的返回值便是undefined
了。
- 补原型链环境例
var Window_my = function Window_my() {
}
window_my = new Window_my;
Object.defineProperties(window_my.__proto__,{
[Symbol.toStringTag]:{
value:'Window_my',
configurable:true
}
});
var WindowProperties_my = function WindowProperties_my() {
}
Object.defineProperties(WindowProperties_my.prototype,{
[Symbol.toStringTag]:{
value:'WindowProperties_my',
configurable:true
}
});
Window_my.prototype.__proto__ = WindowProperties_my.prototype;
var EventTarget_my = function EventTarget_my() {
}
Object.defineProperties(EventTarget_my.prototype,{
[Symbol.toStringTag]:{
value:'EventTarget_my',
configurable:true
}
});
WindowProperties_my.prototype.__proto__ = EventTarget_my.prototype;
如以上代码,便是一个补原型链的环境例子,效果如下:
4. 补环境例
a = document.createElement('a')
a.href = 'https://www.yuanrenxue.com'
console.log(a.host) // 输出www.yuanrenxue.com
可见我们修改了href的同时也修改了host,要如何实现这一目的?
// 注意自己伪造的对象应该如何避免被new(在浏览器中也无法被new)
HTMLAnchorElement = function HTMLAnchorElement(val){
debugger;
if(val === "此处可以填入一个密钥,当密钥对的时候该对象才会被new出来"){
}else if(this instanceof HTMLAnchorElement){ //new的时候,this指向实例化对象
throw TypeError('Illegal constructor')
}else{ //不使用new来创建对象的时候,this指向的是window
throw TypeError(`Failed to construct 'HTMLAnchorElement': Please use the 'new' operator, this DOM object constructor cannot be called as a function`)
}
};
Object.defineProperty(HTMLAnchorElement.prototype, 'href', {
set: function(){
debugger;
this.host = arguments[0].split('/').at(-1);
this._HTMLAnchorElement_href = arguments[0];
// return arguments[0] // 这里不用return 等号表达式在解析的时候会自动向前赋值的,这里不需要return
},
get: function(){
debugger;
return this._HTMLAnchorElement_href; //这里不用 return this.href 的原因在于避免无限循环调用(栈溢出),所以我们需要一个中间值_HTMLAnchorElement_href
},
})
HTMLAnchorElement.prototype.host = '';
HTMLAnchorElement.prototype.href = '';
HTMLAnchorElement.prototype._HTMLAnchorElement_href = '';
document = {
createElement: function(val){
if(val === 'a'){
return new HTMLAnchorElement("此处可以填入一个密钥,当密钥对的时候该对象才会被new出来")
}
}
}
- form标签对全局变量影响的检测:
备注:
window.__proto__.__proto__[tag.id] = tag这个地方完整的写法是依次去浏览器的window的原型上去找,
发现当window.__proto__.__proto__.__proto__.__yrxform__时为undefined,
然后window.__proto__.__proto__是一个WindowProperties,那么我们考虑从WindowProperties上去补,下面的补法仅仅是个示例,针对以下几行检测的代码所写。
window = global;
let document = {};
let formTag = {
tagName: "FORM"
};
document.createElement = function (tagName){
if(tagName === "form"){
return formTag
}
};
document.body = {};
document.body.appendChild = function (tag){
if(tag.tagName === "FORM"){
window.__proto__.__proto__[tag.id] = tag
}
};
form = document.createElement("form");
form.id = "__yrxform__";
form.action = "https://yuanrenxue.com";
document.body.appendChild(form);
if (window["__yrxform__"] !== form){console.log("你被检测了1")}
if (!("__yrxform__" in window) || window.hasOwnProperty("__yrxform__")){console.log("你被检测了2")}
if (!(delete window["__yrxform__"])){console.log("你被检测了3")}
if (window["__yrxform__"] !== form){console.log("你被检测了4")}
if (Object.getOwnPropertyDescriptor(window, "__yrxform__")){console.log("你被检测了5")}
window["__yrxform__"] = 1;
if (window["__yrxform__"] === form){console.log("你被检测了6")}
if (!Object.getOwnPropertyDescriptor(window, "__yrxform__")){console.log("你被检测了7")}
delete window["__yrxform__"];
if (window["__yrxform__"] !== form){console.log("你被检测了8")}
- navigator.plugins的检测
分析:
- navigator.plugins 是一个类数组对象,是PluginArray的实例
- navigator.plugins[0] 是一个类数组对象,是Plugin的实例
- 取Plugin下的属性的时候,每次取到的属性都是新的(也就是,每次取都new 了一下),结果是MimeType的实例
- MimeType的实例下面有一个enabledPlugin属性,指向了它的"祖宗" Plugin
function MimeType(_ts){
this["enabledPlugin"] = _ts
}
function Plugin(){}
Plugin = new Proxy(Plugin, {
construct: function (){
let ob = {}
Object.defineProperty(ob, "0", {
get: function (){
return new MimeType(this)
}
})
Object.defineProperty(ob, "1", {
get: function (){
return new MimeType(this)
}
})
return ob
}
})
function PluginArray(){
this["0"] = new Plugin()
}
navigator = {}
navigator.plugins = new PluginArray()
var dd=navigator.plugins[0]
console.log(dd[0]==dd[0])
console.log(navigator.plugins[0][0]==navigator.plugins[0][0])
console.log(dd[0].enabledPlugin[0]==dd[0])
console.log(navigator.plugins[0][0].enabledPlugin==dd)
console.log(navigator.plugins[0][0].enabledPlugin==dd[1].enabledPlugin)
代理是什么?
参考文章:https://www.jianshu.com/p/63507d282dfe
- 使用一个简易封装的代理例:
function vmProxy(o)
{
return new Proxy(o,{
set(target,property,value)
{
console.log('set->',target,property,value);
return Reflect.set(...arguments);
},
get(target,property,receiver)
{
console.log('get->',target,property,receiver);
return target[property];
}
})
}
//测试一下↓
var navigator = vmProxy({})
navigator.test = 111
console.log(navigator.test)
- 对于Proxy,有一个要点是
后代理的检测不到先代理的
。所以我们自己伪造的框架中我们是先代理的,无法被检测代码检测到。
一些零碎的知识点
- 当var window = global时,可以用下面这个方法修改window的名字
Object.defineProperties(window,{
[Symbol.toStringTag]:{
value:'window',
configurable:true
}
});
- 创建对象的几种方式:
- {}
- Object.create({})
- class f{}
- function f(){}
- document.all的检测与绕过
- document.all 是类数组对象。也有属性和方法,怎么看都应该是个数组,但typeof document.all = undefined;
- 它具备以下特性:1.有length 2.隐式转换结果为 ‘[object HTMLAllCollection]’ 3.typeof document.all 结果是 ‘undefined’ 4.可以取值
- 以上这些特性可以用来检测环境,但typeof目前无法在Js层面解决,可以通过魔改底层nodejs代码实现。
- 现阶段纯js只能通过“对付”绕过,例如:
// 先通过调试分析的手段,找到每一次调用document.all进行了什么操作,然后针对性进行hook处理
// 例如下面的代码,调用了三次document.all,依次返回undefined,undefined,6就可以通过检测,那么就Hook它,针对性绕过该检测
document = {}
_all_number = 0
Object.defineProperty(document, "all", {
get: function (){
return [undefined, undefined, [1,2,3,4,5,6]][_all_number++]
}
})
console.log(typeof document.all);
console.log(typeof document.all);
a = document.all.length;
console.log(a);
- 纯净的V8环境中在this下没有setTimeout、setinterval,但在浏览器里又被放在了this(window)下。
- 使用webRTC获取本机真实IP
注意事项
:因为浏览器本身是不被授权获取本机IP的,所以我们需要手动打开授权设置。拿谷歌举例:在导航栏里输入 chrome://flags/ 进入设置页面 搜索“Anonymize local IPs exposed by WebRTC” 按钮点击修改为 disabled。 - Navigator.getBattery()获取设备电池状态:
navigator.getBattery().then((res)=>{console.log(res)});
- 蜜罐如何处理: 1.笨方法:浏览器和nodejs联调,一步一步跟栈排查;2.取巧方法,有时候能很快定位到:搜索条件分支(a. if else b. switch case c. 三目表达式 d. try catch e. && ||)
- 使用谷歌浏览器调试nodejs:
node --inspect-brk .\demo.js
- 在jsvmp中插桩时,或者平时逆向时可能会遇到console被重写的情况,这时候我们使用日志断点就没法打印出想要的东西,这时候我们可以使用
Object.assign()
,对一个对象或多个对象进行深拷贝,彻底切断两个对象间的内存之间的联系。比如针对console.log被重写的情况,我们直接在代码开头处进行hook,var _console = Object.assign(console);
即可 。 - 在Nodejs里global.__proto__为空对象:{}。
- 要注意this指向的问题:
let a = {a: 100}
a.__proto__ = location
console.log(a.href)
这段代码在浏览器中会报错,但在nodejs中不会报错,可以类似于下面这样使用proxy处理:
Object.defineProperty(location,'href',{
get: function(){
if (this != location){
throw TypeError("Illegal invocation")
}
}
})
参考
志远开源二期 付费课程
vm2实现原理分析
JS Proxy(代理)
猿人学sec4