JS逆向之浏览器补环境详解

# JS逆向之浏览器补环境详解

**“补浏览器环境**”是JS逆向者升职加薪的必备技能,也是工作中不可避免的操作。

为了让大家彻底搞懂 **“补浏览器环境**”的缘由及原理,本文将从以下四个部分进行描述:

1. **什么是补环境?**

1. **为什么要补环境?**

1. **怎么补环境?**

1. **补环境实战**

2. *补环境框架成品源码*

### 一:什么是 **“补浏览器环境**”?

**浏览器环境**: 是指 JS代码在浏览器中的运行时环境,它包括V8自动构建的对象(即ECMAScript的内容,如Date、Array),浏览器(内置)传递给V8的操作DOM和BOM的对象(如document、navigator);

**Node环境**:是基于V8引擎的Js运行时环境,它包括V8与其自己的内置API,如fs,http,path;

**Node环境** 与 **浏览器环境** 的异同点可以简单概括如图:

![在这里插入图片描述](https://upload-images.jianshu.io/upload_images/28746388-bd324433570adc05.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

所以我们所说的 **“补浏览器环境**” 其实是补浏览器有 而Node没有的环境,即 补BOM和DOM的对象;

### 二:为什么要 **“补浏览器环境**”?

对于逆向老手而言,**“补环境”** 这个词不会陌生,当我们每次把辛辛苦苦扣出来的 **“js加密算法代码”**,并且放在**浏览器环境**中能正确执行后,就需要将它放到**Node环境** 中去执行,而由于**Node环境**与**浏览器环境**之间存在差异,会导致部分JS代码在浏览器中运行的结果 与在node中运行得到的结果不一样,从而影响我们最终逆向成果;eg:

```python

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代码对它的任何操作都可以被我们所拦截!!**

```python

# 对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的特性,衍生了两种补环境思路:

1. 递归嵌套**Proxy**以此来代理浏览器所有的**BOM、DOM对象及其属性**,再配合**node vm2模块**提供的**纯净V8环境**,就相当于在node中,对整个浏览器环境对象进行了代理,JS代码使用任何**浏览器环境** api都能被我们所拦截。然后我们针对拦截到的环境检测点去补。

2. 搭建补环境框架,用JS模拟浏览器基于原型链去伪造实现各个BOM、DOM对象,然后将这些JS组织起来,形成一个**纯JS丐版浏览器环境**,我们补的**纯JS丐版浏览器环境**越完善,就越接近真实浏览器环境,能通杀的js环境检测就越多。最终完美**通杀所有JS环境检测!!**;示例:b站搜 "志远补环境"

第一种思路虽然实现简单,主要是对Proxy拦截器的使用 ,但是具备的环境监测能力有限,对较复杂的原型链等难以监测,即使是二次开发也上限不高;并且遇到JS使用了很多环境时手补也相当麻烦;

第二种思路虽然实现较为复杂,但是上限极高,且可以完美兼容第一种思路,具备可成长的通杀潜质。

所以业内补环境框架几乎都是基于第二种思路,先搭建一个补环境框架的骨架,将常见浏览器环境BOM、DOM对象补齐,如:window、location、Document、navigator等,等空闲时或工作遇到其他浏览器环境BOM、DOM对象,再将它补进来。补的越完善,我们能**通杀JS环境检测**越多。

优点:

- 补的越完善,能**通杀JS环境检测**越多。最终完美**通杀所有JS环境检测!!**;

- 一键运行输出目标JS中所有环境检测点;

- 生成的最终代码可直接用于生产环境(可直接供nodejs、v8使用);

- 告别玄学补环境,不再一行行去debugger,极大提高工作效率。

- 可以在Chrome浏览器进行无浏览器环境调试。

- 新人弯道超车必备

- .....

### 四:**“补环境框架**”实战

传统补环境格式:

```

// 环境头:

window = global;

navigator= {userAgent:"Mozilla/5.0 (Windows NT 1";}

// 扣出来的JS

........

......

```

传统补环境太简陋,而且不够通用,代码组织混乱,我们最好将其组织为一个项目:

补环境框架项目整体结构:

![框架结构](https://upload-images.jianshu.io/upload_images/28746388-151f737f15c27983.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

那么实现这么一个浏览器补环境框架需要哪些步骤哪些考虑呢?

- 先确定框架运行主流程,即入口文件 。

- 每个BOM、DOM对象的实现都使用一个单独的js文件,便于定位及维护。

- 将这些BOM、DOM文件按照原型链的优先顺序进行读取,拼接成整个浏览器环境。

- 思考如何去实现一个BOM、DOM对象使其和浏览器一致;(这个是影响框架上限的重要因素,同时也包含大量重复性人力工作)

- 事件的处理(对行为验证码有帮助)

- 思考如何保证JS中使用到的所有浏览器环境都能被我们所检测;(这个是影响框架上限的重要因素)

- 如何设计优化补环境框架项目的可扩展、可维护性;(非常必要)

...

还有一些其他细节思考,我们的目标框架就是 一个易于可扩展与维护、能检测到JS中所有浏览器环境API的使用、实现了常见浏览器环境方法等,让我们在之后补环境中,达到通杀效果。

*如果对于原理及实现方向 思考不够全面、深入,那么实现的框架上限会有限,出现玄学的概率就大了,我也是经历了很长时间打磨,多次推倒重来、借鉴多个课程,最终实现这个理想的框架。*

**下面就是一些具体的实现:**

以下就是主流程入口骨架:

```java

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,不能的话则在其构造函数定义中抛出异常,能的话不抛;

```java

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`

查看其属性、方法与 原型上的差异,发现差不多,基本都是继承原型的。

因此可以**简单**补成下面这样:

```java

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`进行各种判断、处理,最终拼接成我们需要的样子。

```java

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逆向人员的大杀器,也是众多面试官的考察点。我们已经了解了 它的原理及实现步骤,接下来我们可以尝试自己从头实现一个完善的补环境框架,但是这会花费很长一段时间来进行开发,而且其中有很多重复性工作比较无聊(复制粘贴对比等)。

###### 走快车道:

我在这条路已经走的比较久,补了很多环境,如果你想省下大段时间极大提高效率,直接`弯道超车`的话,可以 v联系我:**dengshengfeng666**  付费源码借鉴;

统一固定价 `99`,付完直接发源码(有readme可直接小白上手),后续有疑问可以直接问我。

###### 部分成果展示(以头条 sign值为例):

监测到的检测点,做过的靓仔可以看看是不是都有

![检测点打印](https://upload-images.jianshu.io/upload_images/28746388-de8a3614a50235dc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

与真实浏览器对比

![与真实浏览器对比](https://upload-images.jianshu.io/upload_images/28746388-06f18cc3644e13f7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

弯道超车,从我做起

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容