深入理解JS逆向代理與環境監測

博客文章:深入理解JS逆向代理與環境監測

1. 引言

首先要明確JavaScript(JS)在真實網頁瀏覽器環境和Node.js環境中有很多使用特性的區別。尤其是在環境監測和對象原型鏈的檢測方面。本文將探討如何使用JS的代理(Proxy)模式來手動補充環境,Node環境和瀏覽器this環境,以及如何通過原型鏈檢測來增強代碼的安全性以及在。

2. 代理補環境

代理是ES6引入的一種新特性,它允許你定義對象的行為,例如屬性的讀取和設置。以下是一個簡單的代理補環境的實現:

// 代理補環境(缺啥補啥)
function vmProxy(object, objName) {// 創建代理對象,捕獲對對象的訪問和賦值操作return new Proxy(object, {get: function(target, property, receiver) {// 打印屬性訪問信息console.log(objName, "get: ", property, target[property]);// 返回目標對象的屬性值return target[property];},set: function(target, property, value) {// 打印屬性賦值信息console.log(objName, "set: ", property, value);// 使用Reflect.set實現屬性賦值return Reflect.set(...arguments);}});
}// 模擬瀏覽器環境
navigator = {userAgent: 'qh'
};
document = {};// 使用代理來增強navigator和document對象
navigator = vmProxy(navigator, 'navigator');
document = vmProxy(document, 'document');// 測試代理效果
console.log(navigator.userAgent); // 應輸出代理訪問信息
console.log(navigator.platform); // 將觸發代理的get方法
console.log(document.cookie); // 將觸發代理的get方法
console.log(document.createElement); // 將觸發代理的get方法

3. Node環境與瀏覽器this環境監測

在Node.js中,global對象是全局對象,而在瀏覽器中,window是全局對象。檢測這些環境可以通過以下方式實現:

// 確保window指向全局對象
window = globalThis;
// 補充window對象的方法
window['addEventListener'] = function() {};
// 初始化window的navigator和document屬性
window['navigator'] = {};
window['document'] = {};// 使用代理來增強window對象
// 錯誤寫法1:
// window = vmProxy(window, 'window'); // 代理器會影響對象本身的指向,代理window對象本身并不是錯誤,但關鍵在于不應該覆蓋原有的window對象。代理可以用來增強或監測window對象的行為,但不應該改變其身份。
// 錯誤寫法2:
// window ={'addEventListener': function() {},'navigator':{},'document' {}}; // 直接賦值window為一個新對象會覆蓋原有的window對象,這會丟失所有原有的全局屬性和方法,包括繼承來的屬性。
// 錯誤寫法3 重新賦值會影響指向  將window賦值為一個空對象同樣會覆蓋原有的window對象,導致丟失所有屬性和方法。
// window={}// 測試eval.call方法是否正確指向window
!function (){function test(){// 測試eval.call的this指向console.log(eval["call"](undefined, this) === window); // 應輸出true}test.apply(null);
}();

這段代碼的功能是測試eval.call方法的this指向是否正確。在JavaScript中,eval函數可以計算一個字符串表達式的值。當使用call方法調用eval時,可以指定this的值。在這個例子中,想要測試eval.call是否能夠正確地將this指向window對象得到true的結果證明現在node環境和瀏覽器環境是一致的。

下面是對上面代碼的詳細拆分和解讀,如果已經理解就跳過。

錯誤寫法1解釋:

// 錯誤寫法1:
// window = vmProxy(window, 'window'); // 代理器會影響對象本身的指向

這行代碼是錯誤的,因為window對象是全局對象,其原型鏈上有很多內置屬性和方法。將window重新賦值為vmProxy的返回值會切斷window與原有原型鏈的聯系,導致丟失原有的全局屬性和方法。此外,這行代碼試圖將window對象自身作為代理的目標,這在邏輯上是有問題的,因為window對象本身不應該被代理。如下圖高亮紫色是原有全局屬性是繼承來的,
在瀏覽器中打印window對象時,你可能會注意到屬性顏色的差異,這通常是由于瀏覽器的開發者工具中的顏色編碼。在Chrome的開發者工具中,全局對象window的屬性通常分為兩類:

原有全局屬性:這些屬性是window對象直接定義的,通常是淺色顯示,表示它們是window對象的自有屬性,而不是通過原型鏈繼承的。

繼承來的屬性:這些屬性是window對象通過原型鏈從其原型Window.prototype繼承的,通常會以高亮紫色顯示,以區分自有屬性。

例如,navigator對象是window對象的一個自有屬性,而navigator對象本身繼承自Navigator的屬性。在瀏覽器的控制臺中打印window對象時,你會看到類似下面的結構:

Window {window: Window { ... },self: Window { ... },document: document,name: "name",location: Location { ... },history: History { ... },navigator: Navigator { ... }, // 繼承自 Navigator 的屬性將顯示為高亮紫色// ... 其他自有屬性和繼承屬性
}

在這個例子中,document、location、history等是window對象的自有屬性,而navigator對象的屬性,如userAgent,是繼承自Navigator的屬性。

代碼示例:
以下是如何在控制臺中查看這些屬性的示例代碼:

console.dir(window); // 打印window對象及其屬性

使用console.dir可以打印出對象的詳細信息,包括原型鏈上的屬性。
在這里插入圖片描述

在這里插入圖片描述

錯誤寫法2解釋:

// 錯誤寫法2:
// window ={'addEventListener': function() {},'navigator':{},'document' {}}; // 直接賦值window為一個新對象會覆蓋原有的window對象,這會丟失所有原有的全局屬性和方法,包括繼承來的屬性。

這行代碼同樣是錯誤的,因為它試圖將window重新定義為一個具有單個屬性addEventListener的對象,這個屬性的值是一個空字符串。正確的做法是為window添加方法,而不是重新定義window對象。

錯誤寫法3解釋:

// 錯誤寫法3 重新賦值會影響指向  將window賦值為一個空對象同樣會覆蓋原有的window對象,導致丟失所有屬性和方法。
// window = {};

這行代碼是錯誤的,因為它將window重新賦值為一個空對象,這會覆蓋原有的全局window對象,導致所有原有的全局屬性和方法丟失。

衡量錯誤的標準:

在JS逆向中,衡量錯誤的標準是在Node環境中是否成功模擬了瀏覽器環境。正確的做法應該是在不破壞原有window對象的基礎上,補充或修改其屬性和方法,以模擬瀏覽器環境。

加代理正確的做法1:

// 確保window指向全局對象
window = globalThis;// 補充window對象的方法
if (typeof window.addEventListener === 'undefined') {window['addEventListener'] = function() {};
}// 初始化window的navigator和document屬性,但不要覆蓋原有的屬性
if (!window.navigator) {window['navigator'] = {};
}
if (!window.document) {window['document'] = {};
}// 使用代理來增強window對象,但不要重新賦值window
window = vmProxy(window, 'window');

在這個修正的代碼中,我們首先檢查window對象上是否已經有addEventListener方法,如果沒有,我們才添加它。同樣,我們檢查navigatordocument是否存在,如果不存在,我們才初始化它們。最后,我們使用vmProxy來增強window對象,而不是重新賦值它。

加代理正確的做法2:

// 創建window的代理,而不是重新賦值window
const originalWindow = window;
const proxiedWindow = new Proxy(originalWindow, {get(target, property, receiver) {if (property === 'navigator') {console.log('Accessing navigator');}return Reflect.get(target, property, receiver);},set(target, property, value, receiver) {console.log(`Setting ${property} to ${value}`);return Reflect.set(target, property, value, receiver);}
});// 使用代理對象進行操作,而不是直接操作window
proxiedWindow.navigator.userAgent; // 這將觸發get陷阱,并打印日志

創建了一個window的代理,而不是直接修改window對象。這樣,我們可以在不改變window對象本身的情況下,監測和增強其行為。

測試eval.call方法:

!function (){function test(){// 測試eval.call的this指向console.log(eval["call"](undefined, this) === window); // 應輸出true}test.apply(null);
}();

這個測試函數test使用apply方法將this指向null,然后通過eval.callthis指向window。如果eval.call正確地將this指向了window,那么console.log應該輸出true

4. 原型鏈檢測

原型鏈是JS中實現繼承的關鍵機制。## 4. 原型鏈檢測的深入分析

原型鏈是JavaScript中實現繼承的核心機制。每個JavaScript對象都有一個原型對象,這個原型對象可以是另一個對象或者null。當訪問一個對象的屬性時,如果該屬性在對象上不存在,JavaScript引擎會沿著原型鏈向上查找,直到找到該屬性或到達原型鏈的末端(null)。

代碼拆分

定義構造函數和原型屬性
// 定義構造函數Navigator
Navigator = function Navigator(){};
// 在Navigator.prototype上定義userAgent屬性
Object.defineProperty(Navigator.prototype,'userAgent',{set: undefined, // 不允許賦值enumerable: true, // 可枚舉configurable: true, // 可配置get: function() {return "Custom UserAgent"; // 自定義getter函數} // 自定義getter}
);
// 創建navigator對象,其原型指向Navigator.prototype
navigator = {};
navigator.__proto__ = Navigator.prototype;// 檢測navigator的屬性描述符
console.log(Object.getOwnPropertyDescriptors(navigator)); // 應輸出undefined
console.log(Object.getOwnPropertyDescriptors(Navigator.prototype)); // 顯示實際的屬性描述符

截圖示例:在瀏覽器的控制臺中,可以看到Navigator構造函數和其prototype上的userAgent屬性定義。

創建并鏈接自定義navigator對象
// 創建navigator對象,其原型指向Navigator.prototype
var navigator = {};
Object.setPrototypeOf(navigator, new Navigator()); // 更現代的方法來設置原型

截圖示例:在控制臺中,通過Object.getPrototypeOf(navigator)可以看到navigator的原型現在指向Navigator.prototype

屬性描述符檢測
// 檢測navigator的屬性描述符
console.log(Object.getOwnPropertyDescriptors(navigator)); // 應輸出undefined,因為navigator上沒有直接定義userAgent屬性
console.log(Object.getOwnPropertyDescriptors(Navigator.prototype)); // 顯示實際的屬性描述符,包括userAgent的定義

截圖示例:在控制臺中,Object.getOwnPropertyDescriptors的調用結果展示了Navigator.prototype上的userAgent屬性描述符。

JS逆向原型鏈相關知識

在JavaScript逆向工程中,了解原型鏈對于分析和修改代碼行為至關重要。以下是一些與原型鏈檢測相關的逆向知識點:

  1. 原型鏈遍歷:逆向工程師可以通過遍歷對象的原型鏈來查找對象的來源和構造方式。
  2. 原型鏈污染:通過修改對象的原型鏈,可以引入新的行為或屬性,這在某些情況下可以用于繞過安全限制。
  3. 構造函數欺騙:通過修改構造函數的prototype,可以改變通過該構造函數創建的所有新對象的行為。
  4. 屬性攔截:使用Proxy對象,可以在訪問或設置屬性時進行攔截和自定義行為,這可以用來模擬或監測對象的行為。
  5. 環境檢測:通過檢測對象的原型鏈,可以判斷代碼運行在何種環境中(瀏覽器或Node.js),并據此調整代碼行為。

通過這些技術,逆向工程師可以深入了解和操縱JavaScript代碼的運行時行為,實現代碼審計、安全測試或功能增強。然而,這些技術也應謹慎使用,以避免潛在的安全風險和代碼維護問題。

5. 環境監測點案例

以下是一些Node和瀏覽器環境監測點的案例:

  • 監測全局對象類型typeof global !== 'undefined' ? 'node' : 'browser'
  • 監測Node.js特有的模塊require.main === module
  • 監測瀏覽器特有的對象typeof window !== 'undefined' && !!window.document
  • 監測瀏覽器的BOM和DOMtypeof document !== 'undefined' && !!document.createElement
  • 監測環境支持的ES6特性'startsWith' in String.prototype

7. 高級監測點:使用evalProxy進行環境監測

在JavaScript中,eval函數允許你執行字符串中的代碼。然而,使用eval通常被認為是不安全的,因為它可以執行任意代碼。但是,在某些情況下,我們可以通過修改eval的行為來增強環境監測。以下是一個示例代碼,展示了如何重寫eval.call方法,以監測和區分代碼執行環境:

// 重寫eval.call方法以監測執行環境
eval['call'] = function (){debugger; // 啟動調試模式,便于開發者調試if (arguments[1].toString() === '[object Window]'){debugger; // 再次啟動調試模式,如果this指向Window對象// 如果調用環境是瀏覽器的window對象,則直接返回windowreturn window;} else {// 否則,執行原始的eval函數return eval(arguments);}
};

代碼解釋

  • eval['call']: 我們通過eval['call']訪問eval函數的call方法,并對其進行重寫。
  • debugger: 這是一個調試語句,當代碼執行到這里時,如果正在調試模式下,執行會暫停,允許開發者檢查當前的執行環境和變量狀態。
  • arguments[1].toString(): arguments對象包含了調用函數時傳入的所有參數。在這里,我們檢查arguments[1](即this的值),并使用toString()方法獲取它的類型描述。
  • '[object Window]': 這是一個特定的字符串,用來檢測this是否指向瀏覽器的window對象。
  • return window: 如果檢測到thiswindow對象,我們直接返回window,這樣eval.call調用的結果將總是指向全局的window對象,而不是局部作用域中的this

使用場景

這種技術可以用于確保在執行動態代碼時,this總是指向預期的全局對象,從而避免潛在的作用域問題。此外,它還可以用于調試和測試,幫助開發者更好地理解代碼在不同環境下的行為。

7.與局部加密方法對象導出到全局對象的區別

使用代理補環境和原型鏈檢測是一種在運行時動態地修改對象的行為和結構的方法。與之相比,將局部加密方法對象導出到全局對象是一種靜態的修改,通常在代碼的編寫階段就已經確定。代理補環境提供了一種靈活的方式來監測和修改對象的行為,而局部加密方法對象的導出則是一種更靜態、更難以在運行時改變的方法。

結語

通過本文的探討,我們了解到了JS代理的強大功能以及如何使用它來監測和增強JS運行環境。同時,我們也學習了如何通過原型鏈檢測來提高代碼的安全性。這些技術不僅能夠幫助開發者更好地理解和控制JS代碼的行為,還能夠在開發過程中提供更多的靈活性和安全性。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/41194.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/41194.shtml
英文地址,請注明出處:http://en.pswp.cn/web/41194.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MySQL之備份與恢復(九)

備份與恢復 從備份中恢復 更高級的恢復技術 復制和基于時間點的恢復使用的是相同的技術:服務器的二進制日志。這意味著復制在恢復時會是個非常有幫助的工具,哪怕方式不是很明顯。下面將演示一些可以用到的方法。這里列出來的不是一個完整的列表,但應該…

STM32-USART

本內容基于江協科技STM32視頻學習之后整理而得。 文章目錄 1. 串口通信協議1.1 通信接口1.2 串口通信1.3 硬件電路1.4 電平標準1.5 串口參數及時序1.6 串口時序 2. USART串口通信2.1 USART簡介2.2 USART框圖2.3 USART基本結構2.4 數據幀2.5 數據幀-配置停止位2.6 起始位偵測2.…

DP學習——簡單工廠模式

學而時習之,溫故而知新。 敵人出招(使用場景) 不同的業務場景下要創建不同的對象,但是這些對象又有共同的特點。如何復用代碼呢?你會想到,這些對象可以抽象出一個基類/抽象類就行了,那么隨著業…

【Python】一文向您詳細介紹 argparse中 action=‘store_true’ 的作用

【Python】一文向您詳細介紹 argparse中 action‘store_true’ 的作用 下滑即可查看博客內容 🌈 歡迎蒞臨我的個人主頁 👈這里是我靜心耕耘深度學習領域、真誠分享知識與智慧的小天地!🎇 🎓 博主簡介:98…

pdf怎么轉換成圖片格式文件,pdf文檔怎么轉換成圖片格式

在數字化時代,pdf文件轉換成圖片格式是一種常見的操作,無論是在工作還是日常生活中,我們總會遇到需要將pdf文件轉換為圖片的需求。這可能是因為圖片格式更易于分享、展示或編輯。那么,如何高效地將pdf轉換成圖片呢?本文…

圖神經網絡實戰(16)——經典圖生成算法

圖神經網絡實戰(16)——經典圖生成算法 0. 前言1. 圖生成技術2. Erd?s–Rnyi模型3. 小世界模型小結系列鏈接 0. 前言 圖生成算法是指用于創建模擬圖或網絡結構的算法,這些算法可以根據特定的規則和概率分布生成具有特定屬性的圖&#xff0c…

深度解析:如何利用Python高效挖掘SQLite潛力

Python與SQLite共舞:構建高效輕量級數據庫應用實戰 Python,作為一門優雅且強大的編程語言,搭配輕巧靈活的SQLite數據庫,無疑為我們提供了揮灑創意的完美畫布。今天,咱們就通過一個鮮活的案例,一起探索如何…

leetcode77組合——經典回溯算法

本文主要講解組合的要點與細節,以及回溯算法的解題步驟,按照步驟思考更方便理解 c和java代碼如下,末尾 給定兩個整數 n 和 k,返回范圍 [1, n] 中所有可能的 k 個數的組合。 你可以按 任何順序 返回答案。 具體要點: …

將大型語言模型模塊化打造協作智能體

B UILDING C OOPERATIVE E MBODIED A GENTS MODULARLY WITH L ARGE L ANGUAGE M ODELS 論文鏈接: https://arxiv.org/abs/2307.02485https://arxiv.org/abs/2307.02485 1.概述 在去中心化控制及多任務環境中,多智能體合作問題因原始感官觀察、高昂…

【機器學習】機器學習重塑廣告營銷:精準觸達,高效轉化的未來之路

📝個人主頁🌹:Eternity._ 🌹🌹期待您的關注 🌹🌹 ?目錄 📒1. 引言📙2. 機器學習基礎與廣告營銷的結合🧩機器學習在廣告營銷中的核心應用領域🌹用…

【React】React18 Hooks 之 useReducer

目錄 useReducer案例1:useReducer不帶初始化函數案例2:useReducer帶初始化函數注意事項1:dispatch函數不會改變正在運行的代碼的狀態注意事項2:獲取dispatch函數觸發后 JavaScript 變量的值注意事項3:觸發了reducer&am…

webrtc sfu性能壓測

1. 前言 不少網友最近私信我,咨詢webrtc sfu服務端性能問題,SRS開源服務能支持多少路webrtc流,mediasoup單房間能支持多少個人,推流能接入多少路,拉流能拉取多少路?720p能支持多少路,360p能支持…

Spring Boot集成olingo快速入門demo

1.什么是olingo? Apache Olingo 是個 Java 庫,用來實現 Open Data Protocol (OData)。 Apache Olingo 包括服務客戶端和 OData 服務器方面。 Open Data Protocol (開放數據協議,OData) 是用來查詢和更新數據的一種W…

【吊打面試官系列-MyBatis面試題】MyBatis 實現一對多有幾種方式,怎么操作的?

大家好,我是鋒哥。今天分享關于 【MyBatis 實現一對多有幾種方式,怎么操作的?】面試題,希望對大家有幫助; MyBatis 實現一對多有幾種方式,怎么操作的? 有聯合查詢和嵌套查詢。聯合查詢是幾個表聯合查詢,只查詢一次,通過…

觀察矩陣(View Matrix)、投影矩陣(Projection Matrix)、視口矩陣(Window Matrix)及VPM矩陣及它們之間的關系

V表示攝像機的觀察矩陣(View Matrix),它的作用是把對象從世界坐標系變換到攝像機坐標系。因此,對于世界坐標系下的坐標值worldCoord(x0, y0, z0),如果希望使用觀察矩陣VM將其變換為攝像機坐標系下的坐標值localCoord(x…

【滲透入門】HTTP請求包

文章目錄 前言HTTP GET請求包HTTP POST請求包Content-Type 前言 HTTP(HyperText Transfer Protocol)請求包,是Web通信的基礎。HTTP請求包格式主要由以下幾部分組成: 請求行:包含了請求方法(GET、POST、PUT…

32單片機,C語言與匯編聯合編譯的幾種方式

適用編譯器:Keil5 方式一: 單獨創建一個.s匯編文件,在匯編文件內對函數進行EXPORT聲明 r0寄存器是函數傳入的第一個參數,r1寄存器是函數傳入的第二個參數,以次類推。參數最多不確定是到r4為止,還是到r12…

Node.js-path 模塊

path 模塊 path 模塊提供了 操作路徑 的功能,如下是幾個較為常用的幾個 API: 代碼實例: const path require(path);//獲取路徑分隔符 console.log(path.sep);//拼接絕對路徑 console.log(path.resolve(__dirname, test));//解析路徑 let pa…

Robust Regression

最小二乘回歸受數據中的離群點的影響較大,穩健回歸通過降低離群點的影響緩解此問題。M估計法是穩健回歸的重要方法之一,M 估計法的目標函數為: m i n ∑ ρ ( ? i ) m i n ∑ ρ ( y i ? β ^ ? X i ) min\sum\rho(\epsilon_i) min\sum\…

vulhub-activemq(CVE-2016-3088)

在 Apache ActiveMQ 5.12.x~5.13.x 版本中,默認關閉了 fileserver 這個應用(不過,可以在conf/jetty.xml 中開啟);在 5.14.0 版本后,徹底刪除了 fileserver 應用。【所以在滲透測試過程中要確定好 ActiveMQ …