JS逆向之瀏覽器補(bǔ)環(huán)境詳解

# JS逆向之瀏覽器補(bǔ)環(huán)境詳解

**“補(bǔ)瀏覽器環(huán)境**”是JS逆向者升職加薪的必備技能,也是工作中不可避免的操作。

為了讓大家徹底搞懂 **“補(bǔ)瀏覽器環(huán)境**”的緣由及原理,本文將從以下四個(gè)部分進(jìn)行描述:

1. **什么是補(bǔ)環(huán)境?**

1. **為什么要補(bǔ)環(huán)境?**

1. **怎么補(bǔ)環(huán)境?**

1. **補(bǔ)環(huán)境實(shí)戰(zhàn)**

2. *補(bǔ)環(huán)境框架成品源碼*

### 一:什么是 **“補(bǔ)瀏覽器環(huán)境**”?

**瀏覽器環(huán)境**: 是指 JS代碼在瀏覽器中的運(yùn)行時(shí)環(huán)境,它包括V8自動(dòng)構(gòu)建的對象(即ECMAScript的內(nèi)容,如Date、Array),瀏覽器(內(nèi)置)傳遞給V8的操作DOM和BOM的對象(如document、navigator);

**Node環(huán)境**:是基于V8引擎的Js運(yùn)行時(shí)環(huán)境,它包括V8與其自己的內(nèi)置API,如fs,http,path;

**Node環(huán)境** 與 **瀏覽器環(huán)境** 的異同點(diǎn)可以簡單概括如圖:

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

所以我們所說的 **“補(bǔ)瀏覽器環(huán)境**” 其實(shí)是補(bǔ)瀏覽器有 而Node沒有的環(huán)境,即 補(bǔ)BOM和DOM的對象;

### 二:為什么要 **“補(bǔ)瀏覽器環(huán)境**”?

對于逆向老手而言,**“補(bǔ)環(huán)境”** 這個(gè)詞不會(huì)陌生,當(dāng)我們每次把辛辛苦苦扣出來的 **“js加密算法代碼”**,并且放在**瀏覽器環(huán)境**中能正確執(zhí)行后,就需要將它放到**Node環(huán)境** 中去執(zhí)行,而由于**Node環(huán)境**與**瀏覽器環(huán)境**之間存在差異,會(huì)導(dǎo)致部分JS代碼在瀏覽器中運(yùn)行的結(jié)果 與在node中運(yùn)行得到的結(jié)果不一樣,從而影響我們最終逆向成果;eg:

```python

function decrypt() {

? ? document = false;

? ? var flag = document?true:false;

? ? if (flag) {

? ? ? ? return "正確加密"

? ? } else {

? ? ? ? return "錯(cuò)誤加密";

? ? }

}

在瀏覽器環(huán)境運(yùn)行時(shí) flag為true,然后得到正常結(jié)果;

在Node環(huán)境運(yùn)行時(shí) flag為false,然后得到錯(cuò)誤結(jié)果;

```

所以我們需要 **“補(bǔ)瀏覽器環(huán)境**”,使得扣出來的 **“js加密算法代碼”** 在**Node環(huán)境**中運(yùn)行得到的加密值,與其在 **瀏覽器環(huán)境**中運(yùn)行得到的加密值一致。 即對于這段 **“js加密算法代碼”** 而言,我們補(bǔ)出來的環(huán)境與**瀏覽器環(huán)境**一致。

### 三:怎么 **“補(bǔ)瀏覽器環(huán)境**”?

要想 **“補(bǔ)瀏覽器環(huán)境**”,首先我們得知道 **“js加密算法代碼”** 到底使用了哪些瀏覽器環(huán)境API,然后再對應(yīng)去補(bǔ)上這些環(huán)境;

那么我們該如何監(jiān)測 **“js加密算法代碼”**? 對瀏覽器環(huán)境API的使用呢?

毫無爭議:**使用Proxy來監(jiān)測瀏覽器環(huán)境API的使用,輔助補(bǔ)瀏覽器環(huán)境**”

> **Proxy**是ES6提供的代理器,用于創(chuàng)建一個(gè)對象的代理,從而實(shí)現(xiàn)基本操作的攔截和自定義(如屬性查找、賦值、枚舉、函數(shù)調(diào)用等)。 它可以代理任何類型的對象,包括原生數(shù)組,函數(shù),甚至另一個(gè)代理;擁有遞歸套娃的能力!!

也就是說 我們代理某個(gè)對象后,我們就成了它的中間商,**任何JS代碼對它的任何操作都可以被我們所攔截??!**

```python

# 對navigator對象進(jìn)行代理,并設(shè)置攔截后的操作

var handler = {set:funcA,get:funcB,deleteProperty:funcC,has:funcD ...};

navigator = new Proxy(navigator,handler);

# 對代理后的navigator進(jìn)行各種操作都會(huì)被攔截并觸發(fā)對應(yīng)處理函數(shù)

navigator.userAgent 會(huì)被攔截并觸發(fā) get? funcB

navigator.userAgent = "xx" 會(huì)被攔截并觸發(fā) set funcA

delete navigator; 會(huì)被攔截并觸發(fā) deleteProperty funC

"userAgent" in navigator? 會(huì)被攔截并觸發(fā) has funD ...

等等... 任何操作都可以被攔截

```

基于Proxy的特性,衍生了兩種補(bǔ)環(huán)境思路:

1. 遞歸嵌套**Proxy**以此來代理瀏覽器所有的**BOM、DOM對象及其屬性**,再配合**node vm2模塊**提供的**純凈V8環(huán)境**,就相當(dāng)于在node中,對整個(gè)瀏覽器環(huán)境對象進(jìn)行了代理,JS代碼使用任何**瀏覽器環(huán)境** api都能被我們所攔截。然后我們針對攔截到的環(huán)境檢測點(diǎn)去補(bǔ)。

2. 搭建補(bǔ)環(huán)境框架,用JS模擬瀏覽器基于原型鏈去偽造實(shí)現(xiàn)各個(gè)BOM、DOM對象,然后將這些JS組織起來,形成一個(gè)**純JS丐版瀏覽器環(huán)境**,我們補(bǔ)的**純JS丐版瀏覽器環(huán)境**越完善,就越接近真實(shí)瀏覽器環(huán)境,能通殺的js環(huán)境檢測就越多。最終完美**通殺所有JS環(huán)境檢測?。?*;示例:b站搜 "志遠(yuǎn)補(bǔ)環(huán)境"

第一種思路雖然實(shí)現(xiàn)簡單,主要是對Proxy攔截器的使用 ,但是具備的環(huán)境監(jiān)測能力有限,對較復(fù)雜的原型鏈等難以監(jiān)測,即使是二次開發(fā)也上限不高;并且遇到JS使用了很多環(huán)境時(shí)手補(bǔ)也相當(dāng)麻煩;

第二種思路雖然實(shí)現(xiàn)較為復(fù)雜,但是上限極高,且可以完美兼容第一種思路,具備可成長的通殺潛質(zhì)。

所以業(yè)內(nèi)補(bǔ)環(huán)境框架幾乎都是基于第二種思路,先搭建一個(gè)補(bǔ)環(huán)境框架的骨架,將常見瀏覽器環(huán)境BOM、DOM對象補(bǔ)齊,如:window、location、Document、navigator等,等空閑時(shí)或工作遇到其他瀏覽器環(huán)境BOM、DOM對象,再將它補(bǔ)進(jìn)來。補(bǔ)的越完善,我們能**通殺JS環(huán)境檢測**越多。

優(yōu)點(diǎn):

- 補(bǔ)的越完善,能**通殺JS環(huán)境檢測**越多。最終完美**通殺所有JS環(huán)境檢測??!**;

- 一鍵運(yùn)行輸出目標(biāo)JS中所有環(huán)境檢測點(diǎn);

- 生成的最終代碼可直接用于生產(chǎn)環(huán)境(可直接供nodejs、v8使用);

- 告別玄學(xué)補(bǔ)環(huán)境,不再一行行去debugger,極大提高工作效率。

- 可以在Chrome瀏覽器進(jìn)行無瀏覽器環(huán)境調(diào)試。

- 新人彎道超車必備

- .....

### 四:**“補(bǔ)環(huán)境框架**”實(shí)戰(zhàn)

傳統(tǒng)補(bǔ)環(huán)境格式:

```

// 環(huán)境頭:

window = global;

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

// 扣出來的JS

........

......

```

傳統(tǒng)補(bǔ)環(huán)境太簡陋,而且不夠通用,代碼組織混亂,我們最好將其組織為一個(gè)項(xiàng)目:

補(bǔ)環(huán)境框架項(xiàng)目整體結(jié)構(gòu):

![框架結(jié)構(gòu)](https://upload-images.jianshu.io/upload_images/28746388-151f737f15c27983.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

那么實(shí)現(xiàn)這么一個(gè)瀏覽器補(bǔ)環(huán)境框架需要哪些步驟哪些考慮呢?

- 先確定框架運(yùn)行主流程,即入口文件 。

- 每個(gè)BOM、DOM對象的實(shí)現(xiàn)都使用一個(gè)單獨(dú)的js文件,便于定位及維護(hù)。

- 將這些BOM、DOM文件按照原型鏈的優(yōu)先順序進(jìn)行讀取,拼接成整個(gè)瀏覽器環(huán)境。

- 思考如何去實(shí)現(xiàn)一個(gè)BOM、DOM對象使其和瀏覽器一致;(這個(gè)是影響框架上限的重要因素,同時(shí)也包含大量重復(fù)性人力工作)

- 事件的處理(對行為驗(yàn)證碼有幫助)

- 思考如何保證JS中使用到的所有瀏覽器環(huán)境都能被我們所檢測;(這個(gè)是影響框架上限的重要因素)

- 如何設(shè)計(jì)優(yōu)化補(bǔ)環(huán)境框架項(xiàng)目的可擴(kuò)展、可維護(hù)性;(非常必要)

...

還有一些其他細(xì)節(jié)思考,我們的目標(biāo)框架就是 一個(gè)易于可擴(kuò)展與維護(hù)、能檢測到JS中所有瀏覽器環(huán)境API的使用、實(shí)現(xiàn)了常見瀏覽器環(huán)境方法等,讓我們在之后補(bǔ)環(huán)境中,達(dá)到通殺效果。

*如果對于原理及實(shí)現(xiàn)方向 思考不夠全面、深入,那么實(shí)現(xiàn)的框架上限會(huì)有限,出現(xiàn)玄學(xué)的概率就大了,我也是經(jīng)歷了很長時(shí)間打磨,多次推倒重來、借鑒多個(gè)課程,最終實(shí)現(xiàn)這個(gè)理想的框架。*

**下面就是一些具體的實(shí)現(xiàn):**

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

```java

var? fs = require('fs');

var catvm2 = require('./CatVm2/catvm2.node.js');

const {VM,VMScript} = require('vm2'); //看作純凈V8

var catvm2_code = catvm2.GetCode();? //獲取所有代碼(工具代碼、補(bǔ)的所有BOM、DOM對象)

var web_js_code = fs.readFileSync(`${__dirname}/target/get_b_fz.js`) ; // 獲取目標(biāo)網(wǎng)站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`); //真實(shí)路徑,瀏覽器打開的就是該緩存文件

const vm = new VM(); // new 一個(gè)純凈v8環(huán)境

debugger

vm.run(script); // 在V8環(huán)境中運(yùn)行調(diào)試

debugger

```

骨架搭好之后我們就要去補(bǔ)對應(yīng)的BOM、DOM對象,比如補(bǔ)`Navigator`:

1、先在瀏覽器環(huán)境觀察該對象:`Navigator`,

能否進(jìn)行new Navigator,不能的話則在其構(gòu)造函數(shù)定義中拋出異常,能的話不拋;

```java

var dsf_tmp_context = catvm.memory.variable.Navigator = {};

var Navigator = function Navigator() { // 構(gòu)造函數(shù)

throw new TypeError("Illegal constructor");

}; catvm.safefunction(Navigator);//13

```

2、查看其原型`Navigator.prototype`? 的屬性、方法、原型鏈,

發(fā)現(xiàn)`Navigator`原型屬性、方法不能通過原型調(diào)用,即

`Navigator.appVersion` 會(huì)拋出異常。

發(fā)現(xiàn) 其原型鏈只有一層,即`Navigator.prototype.__proto__? === Object.prototype`

3、在瀏覽器環(huán)境觀察其實(shí)例對象:`navigator`

查看其屬性、方法與 原型上的差異,發(fā)現(xiàn)差不多,基本都是繼承原型的。

因此可以**簡單**補(bǔ)成下面這樣:

```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);

```

注:上面實(shí)例只是一種補(bǔ)環(huán)境思路,是基于**對象.屬性粒度**;**我個(gè)人用的是另一種思路**,基于`對象.屬性.特性粒度即Object.getOwnPropertyDescriptor 的value,writable..等`,雖然需要補(bǔ)代碼更多,但是模擬的效果更完美,理論上限極高。

瀏覽器對象及屬性實(shí)在太多了,我們不可能手動(dòng)補(bǔ)那么對象屬性,因此要想補(bǔ)出一個(gè)完美瀏覽器環(huán)境,我們需要編寫`瀏覽器環(huán)境自吐腳本`。即在瀏覽器執(zhí)行該腳本,它會(huì)將某個(gè)瀏覽器環(huán)境對象的所有屬性與方法,拼接成我們框架所需要的補(bǔ)環(huán)境代碼,我們直接粘貼進(jìn)來,稍微改改即可。

我們可以借助:`Reflect.ownKeys(real_obj)`來獲取該對象的所有屬性與方法,

然后對其 `attr`進(jìn)行各種判斷、處理,最終拼接成我們需要的樣子。

```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];

? ? // 暫時(shí)不處理在 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

? ? ? ? }

? ? }

? ? ..........太長,略過(下面框架源碼中有)

```

每補(bǔ)完一個(gè)瀏覽器對象之后,可以運(yùn)行起來與真實(shí)瀏覽器進(jìn)行對比,逐步優(yōu)化,最終達(dá)到完美效果。

### 五:**“補(bǔ)環(huán)境框架**”成品源碼

? `補(bǔ)環(huán)境框架`儼然成為JS逆向人員的大殺器,也是眾多面試官的考察點(diǎn)。我們已經(jīng)了解了 它的原理及實(shí)現(xiàn)步驟,接下來我們可以嘗試自己從頭實(shí)現(xiàn)一個(gè)完善的補(bǔ)環(huán)境框架,但是這會(huì)花費(fèi)很長一段時(shí)間來進(jìn)行開發(fā),而且其中有很多重復(fù)性工作比較無聊(復(fù)制粘貼對比等)。

###### 走快車道:

我在這條路已經(jīng)走的比較久,補(bǔ)了很多環(huán)境,如果你想省下大段時(shí)間極大提高效率,直接`彎道超車`的話,可以 v聯(lián)系我:**dengshengfeng666**? 付費(fèi)源碼借鑒;

統(tǒng)一固定價(jià) `99`,付完直接發(fā)源碼(有readme可直接小白上手),后續(xù)有疑問可以直接問我。

###### 部分成果展示(以頭條 sign值為例):

監(jiān)測到的檢測點(diǎn),做過的靚仔可以看看是不是都有

![檢測點(diǎn)打印](https://upload-images.jianshu.io/upload_images/28746388-de8a3614a50235dc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

與真實(shí)瀏覽器對比

![與真實(shí)瀏覽器對比](https://upload-images.jianshu.io/upload_images/28746388-06f18cc3644e13f7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

彎道超車,從我做起

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容