“补浏览器环境”是JS逆向者升职加薪的必备技能,也是工作中不可避免的操作。
为了让大家彻底搞懂 “补浏览器环境”的缘由及原理,本文将从以下四个部分进行描述:
浏览器环境: 是指 JS代码在浏览器中的运行时环境,它包括V8自动构建的对象(即ECMAScript的内容,如Date、Array),浏览器(内置)传递给V8的操作DOM和BOM的对象(如document、navigator);
Node环境:是基于V8引擎的Js运行时环境,它包括V8与其自己的内置API,如fs,http,path;
Node环境 与 浏览器环境 的异同点可以简单概括如图:

所以我们所说的 “补浏览器环境” 其实是补浏览器有 而Node没有的环境,即 补BOM和DOM的对象;
对于逆向老手而言,“补环境” 这个词不会陌生,当我们每次把辛辛苦苦扣出来的 “js加密算法代码”,并且放在浏览器环境中能正确执行后,就需要将它放到Node环境 中去执行,而由于Node环境与浏览器环境之间存在差异,会导致部分JS代码在浏览器中运行的结果 与在node中运行得到的结果不一样,从而影响我们最终逆向成果;eg:
function decrypt() {
document = false;
var flag = document?true:false;
if (flag) {
return "正确加密"
} else {
return "错误加密";
}
}
在浏览器环境运行时 flag为true,然后得到正常结果;
在Node环境运行时 flag为false,然后得到错误结果;
所以我们需要 “补浏览器环境”,使得扣出来的 “js加密算法代码” 在Node环境中运行得到的加密值,与其在 浏览器环境中运行得到的加密值一致。 即对于这段 “js加密算法代码” 而言,我们补出来的环境与浏览器环境一致。
要想 “补浏览器环境”,首先我们得知道 “js加密算法代码” 到底使用了哪些浏览器环境API,然后再对应去补上这些环境;
那么我们该如何监测 “js加密算法代码” 对浏览器环境API的使用呢?
毫无争议:使用Proxy来监测浏览器环境API的使用,辅助补浏览器环境”
Proxy是ES6提供的代理器,用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。 它可以代理任何类型的对象,包括原生数组,函数,甚至另一个代理;拥有递归套娃的能力!!
也就是说 我们代理某个对象后,我们就成了它的中间商,任何JS代码对它的任何操作都可以被我们所拦截!!
# 对navigator对象进行代理,并设置拦截后的操作
var handler = {set:funcA,get:funcB,deleteProperty:funcC,has:funcD ...};
navigator = new Proxy(navigator,handler);
# 对代理后的navigator进行各种操作都会被拦截并触发对应处理函数
navigator.userAgent 会被拦截并触发 get funcB
navigator.userAgent = "xx" 会被拦截并触发 set funcA
delete navigator; 会被拦截并触发 deleteProperty funC
"userAgent" in navigator 会被拦截并触发 has funD ...
等等... 任何操作都可以被拦截
基于Proxy的特性,衍生了两种补环境思路:
第一种思路虽然实现简单,主要是对Proxy拦截器的使用 ,但是具备的环境监测能力有限,对较复杂的原型链等难以监测,即使是二次开发也上限不高;并且遇到JS使用了很多环境时手补也相当麻烦;
第二种思路虽然实现较为复杂,但是上限极高,且可以完美兼容第一种思路,具备可成长的通杀潜质。
所以业内补环境框架几乎都是基于第二种思路,先搭建一个补环境框架的骨架,将常见浏览器环境BOM、DOM对象补齐,如:window、location、Document、navigator等,等空闲时或工作遇到其他浏览器环境BOM、DOM对象,再将它补进来。补的越完善,我们能通杀JS环境检测越多。
优点:
传统补环境格式:
// 环境头:
window = global;
navigator= {userAgent:"Mozilla/5.0 (Windows NT 1";}
// 扣出来的JS
........
......
传统补环境太简陋,而且不够通用,代码组织混乱,我们最好将其组织为一个项目:
补环境框架项目整体结构:

那么实现这么一个浏览器补环境框架需要哪些步骤哪些考虑呢?
如果对于原理及实现方向 思考不够全面、深入,那么实现的框架上限会有限,出现玄学的概率就大了,我也是经历了很长时间打磨,多次推倒重来、借鉴多个课程,最终实现这个理想的框架。
下面就是一些具体的实现:
以下就是主流程入口骨架:
var fs = require('fs');
var catvm2 = require('./CatVm2/catvm2.node.js');
const {VM,VMScript} = require('vm2'); //看作纯净V8
var catvm2_code = catvm2.GetCode(); //获取所有代码(工具代码、补的所有BOM、DOM对象)
var web_js_code = fs.readFileSync(`${__dirname}/target/get_b_fz.js`) ; // 获取目标网站js代码
var log_code = "\r\ncatvm.print.getAll();debugger;\r\r";
var all_code = catvm2_code+web_js_code+log_code;
fs.writeFileSync(`${__dirname}/debugger_bak.js`,all_code);
const script = new VMScript(all_code,`${__dirname}/debugger.js`); //真实路径,浏览器打开的就是该缓存文件
const vm = new VM(); // new 一个纯净v8环境
debugger
vm.run(script); // 在V8环境中运行调试
debugger
骨架搭好之后我们就要去补对应的BOM、DOM对象,比如补Navigator:
1、先在浏览器环境观察该对象:Navigator,
能否进行new Navigator,不能的话则在其构造函数定义中抛出异常,能的话不抛;
var dsf_tmp_context = catvm.memory.variable.Navigator = {};
var Navigator = function Navigator() { // 构造函数
throw new TypeError("Illegal constructor");
}; catvm.safefunction(Navigator);//13
2、查看其原型Navigator.prototype 的属性、方法、原型链,
发现Navigator原型属性、方法不能通过原型调用,即
Navigator.appVersion 会抛出异常。
发现 其原型链只有一层,即Navigator.prototype.__proto__ === Object.prototype
3、在浏览器环境观察其实例对象:navigator
查看其属性、方法与 原型上的差异,发现差不多,基本都是继承原型的。
因此可以简单补成下面这样:
Object.defineProperties(Navigator.prototype, {
[Symbol.toStringTag]: {
value: "Navigator",
configurable: true
}
});
var navigator = {};
navigator.__proto__ = Navigator.prototype;
Navigator.prototype.plugins = [];
Navigator.prototype.languages = ["zh-CN", "zh"];
Navigator.prototype.userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36';
Navigator.prototype.platform = 'Win32';
Navigator.prototype.maxTouchPoints = 0;
Navigator.prototype.onLine = true;
for (var _prototype in Navigator.prototype) {
navigator[_prototype] = Navigator.prototype[_prototype];
if (typeof (Navigator.prototype[_prototype]) != "function") {
Navigator.prototype.__defineGetter__(_prototype, function () {
debugger;
var e = new Error();
e.name = "TypeError";
e.message = "Illegal constructor";
e.stack = "VM988:1 Uncaught TypeError: Illegal invocation \r\n " +
"at <anonymous>:1:21";
throw e;
// throw new TypeError("Illegal constructor");
});
}
}
// 加上代理
navigator = catvm.proxy(navigator);
注:上面实例只是一种补环境思路,是基于对象.属性粒度;我个人用的是另一种思路,基于对象.属性.特性粒度即Object.getOwnPropertyDescriptor 的value,writable..等,虽然需要补代码更多,但是模拟的效果更完美,理论上限极高。
浏览器对象及属性实在太多了,我们不可能手动补那么对象属性,因此要想补出一个完美浏览器环境,我们需要编写浏览器环境自吐脚本。即在浏览器执行该脚本,它会将某个浏览器环境对象的所有属性与方法,拼接成我们框架所需要的补环境代码,我们直接粘贴进来,稍微改改即可。
我们可以借助:Reflect.ownKeys(real_obj)来获取该对象的所有属性与方法,
然后对其 attr进行各种判断、处理,最终拼接成我们需要的样子。
var all_attrs = Reflect.ownKeys(real_obj);
var continue_attrs = ["prototype", "constructor"];
for (let index = 0; index < all_attrs.length; index++) {
let attr_name = all_attrs[index];
// 暂时不处理在 continue_attrs 中的属性
if (continue_attrs.indexOf(attr_name) != -1) {
console.log(`遇到 ${attr_name},跳过`);
continue
}
if (attr_name == Symbol.toStringTag) {
result_code = `Object.defineProperties(${repair_obj}, {
[Symbol.toStringTag]: {
value: "${real_obj[Symbol.toStringTag]}",
configurable: true
}
});//23\n`;
symbol_code_ls.push(result_code);
continue
}
}
..........太长,略过(下面框架源码中有)
每补完一个浏览器对象之后,可以运行起来与真实浏览器进行对比,逐步优化,最终达到完美效果。
补环境框架俨然成为JS逆向人员的大杀器,也是众多面试官的考察点。我们已经了解了 它的原理及实现步骤,接下来我们可以尝试自己从头实现一个完善的补环境框架,但是这会花费很长一段时间来进行开发,而且其中有很多重复性工作比较无聊(复制粘贴对比等)。
我在这条路已经走的比较久,补了很多环境,如果你想省下大段时间极大提高效率,直接弯道超车的话,可以 微信联系我:dengshengfeng666 付费源码借鉴;
统一固定价 99,付完直接发源码(有readme可直接小白上手),后续有疑问可以直接问我。
或者直接在CSDN私信我。
监测到的检测点,做过的靓仔可以看看是不是都有

与真实浏览器对比

弯道超车,从我做起
我正在尝试在Ruby中制作一个cli应用程序,它接受一个给定的数组,然后将其显示为一个列表,我可以使用箭头键浏览它。我觉得我已经在Ruby中看到一个库已经这样做了,但我记不起它的名字了。我正在尝试对soundcloud2000中的代码进行逆向工程做类似的事情,但他的代码与SoundcloudAPI的使用紧密耦合。我知道cursesgem,我正在考虑更抽象的东西。广告有没有人见过可以做到这一点的库或一些概念证明的Ruby代码可以做到这一点? 最佳答案 我不知道这是否是您正在寻找的,但也许您可以使用我的想法。由于我没有关于您要完成的工作
我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru
我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain
之前在培训新生的时候,windows环境下配置opencv环境一直教的都是网上主流的vsstudio配置属性表,但是这个似乎对新生来说难度略高(虽然个人觉得完全是他们自己的问题),加之暑假之后对cmake实在是爱不释手,且这样配置确实十分简单(其实都不需要配置),故斗胆妄言vscode下配置CV之法。其实极为简单,图比较多所以很长。如果你看此文还配不好,你应该思考一下是不是自己的问题。闲话少说,直接开始。0.CMkae简介有的人到大二了都不知道cmake是什么,我不说是谁。CMake是一个开源免费并且跨平台的构建工具,可以用简单的语句来描述所有平台的编译过程。它能够根据当前所在平台输出对应的m
我试图在rails中了解rubygems是如何变得可以自动使用的,而不是在使用required的文件中gem? 最佳答案 这是通过bundler/setup完成的:http://bundler.io/v1.3/bundler_setup.html.它在您的config/boot.rb文件中是必需的。简而言之,它首先将环境变量设置为指向您的Gemfile:ENV['BUNDLE_GEMFILE']||=File.expand_path('../../Gemfile',__FILE__)然后它通过要求bundler/setup将所有ge
从一开始,我就是一个Windows高手。我从MS-DOS开始。我安装了Windows2.1以及此后的所有Windows。现在,我家里有10台不同的Windows机器在运行,从Windows7Ultimate到各种版本的WindowsServer。我还没有完成Windows8,也不想去那里。我在服务器和各种软件方面都有UNIX经验,但它并不是我的首选环境。但是,我想我正在转换。我试图假装使用Cygwin和MSYS在Windows下运行UNIX。我的目的是搭建一个开发环境。两者都让我失望了。我花了比开发更多的时间来解决一系列技术问题。这是NotAcceptable。到目前为止,我的Ruby
我要下载http://foobar.com/song.mp3作为song.mp3,而不是让Chrome在其native中打开它浏览器中的播放器。我怎样才能做到这一点? 最佳答案 您只需要确保发送这些header:Content-Disposition:attachment;filename=song.mp3;Content-Type:application/octet-streamContent-Transfer-Encoding:binarysend_file方法为您完成:get'/:file'do|file|file=File.
我在这方面尝试了很多URL,在我遇到这个特定的之前,它们似乎都很好:require'rubygems'require'nokogiri'require'open-uri'doc=Nokogiri::HTML(open("http://www.moxyst.com/fashion/men-clothing/underwear.html"))putsdoc这是结果:/Users/macbookair/.rvm/rubies/ruby-2.0.0-p481/lib/ruby/2.0.0/open-uri.rb:353:in`open_http':404NotFound(OpenURI::HT
如果特定语言环境中缺少翻译,如何配置i18n以使用en语言环境翻译?当前已插入翻译缺失消息。我正在使用RoR3.1。 最佳答案 找到相似的question这里是答案:#application.rb#railswillfallbacktoconfig.i18n.default_localetranslationconfig.i18n.fallbacks=true#railswillfallbacktoen,nomatterwhatissetasconfig.i18n.default_localeconfig.i18n.fallback
我开始了一个新的Rails3.2.5项目,Assets管道不再工作了。CSS和Javascript文件不再编译。这是尝试生成Assets时日志的输出:StartedGET"/assets/application.css?body=1"for127.0.0.1at2012-06-1623:59:11-0700Servedasset/application.css-200OK(0ms)[2012-06-1623:59:11]ERRORNoMethodError:undefinedmethod`each'fornil:NilClass/Users/greg/.rbenv/versions/1