赞
踩
如果我们直接使用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)
document.createElement('canvas').toDataURL()
。主要可以参考官方文档来:
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出来") } } }
备注:
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 是一个类数组对象,是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)
后代理的检测不到先代理的
。所以我们自己伪造的框架中我们是先代理的,无法被检测代码检测到。Object.defineProperties(window,{
[Symbol.toStringTag]:{
value:'window',
configurable:true
}
});
// 先通过调试分析的手段,找到每一次调用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);
注意事项
:因为浏览器本身是不被授权获取本机IP的,所以我们需要手动打开授权设置。拿谷歌举例:在导航栏里输入 chrome://flags/ 进入设置页面 搜索“Anonymize local IPs exposed by WebRTC” 按钮点击修改为 disabled。navigator.getBattery().then((res)=>{console.log(res)});
node --inspect-brk .\demo.js
Object.assign()
,对一个对象或多个对象进行深拷贝,彻底切断两个对象间的内存之间的联系。比如针对console.log被重写的情况,我们直接在代码开头处进行hook,var _console = Object.assign(console);
即可 。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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。