您好,登錄后才能下訂單哦!
這篇文章主要講解了“JS逆向之怎么補瀏覽器環(huán)境”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“JS逆向之怎么補瀏覽器環(huán)境”吧!
瀏覽器環(huán)境: 是指 JS代碼在瀏覽器中的運行時環(huán)境,它包括V8自動構(gòu)建的對象(即ECMAScript的內(nèi)容,如Date、Array),瀏覽器(內(nèi)置)傳遞給V8的操作DOM和BOM的對象(如document、navigator);
Node環(huán)境:是基于V8引擎的Js運行時環(huán)境,它包括V8與其自己的內(nèi)置API,如fs,http,path;
Node環(huán)境 與 瀏覽器環(huán)境 的異同點可以簡單概括如圖:
所以我們所說的 “補瀏覽器環(huán)境” 其實是補瀏覽器有 而Node沒有的環(huán)境,即 補BOM和DOM的對象;
對于逆向老手而言,“補環(huán)境” 這個詞不會陌生,當(dāng)我們每次把辛辛苦苦扣出來的 “js加密算法代碼”,并且放在瀏覽器環(huán)境中能正確執(zhí)行后,就需要將它放到Node環(huán)境 中去執(zhí)行,而由于Node環(huán)境與瀏覽器環(huán)境之間存在差異,會導(dǎo)致部分JS代碼在瀏覽器中運行的結(jié)果 與在node中運行得到的結(jié)果不一樣,從而影響我們最終逆向成果;eg:
function decrypt() { document = false; var flag = document?true:false; if (flag) { return "正確加密" } else { return "錯誤加密"; } } 在瀏覽器環(huán)境運行時 flag為true,然后得到正常結(jié)果; 在Node環(huán)境運行時 flag為false,然后得到錯誤結(jié)果;
所以我們需要 “補瀏覽器環(huán)境”,使得扣出來的 “js加密算法代碼” 在Node環(huán)境中運行得到的加密值,與其在 瀏覽器環(huán)境中運行得到的加密值一致。 即對于這段 “js加密算法代碼” 而言,我們補出來的環(huán)境與瀏覽器環(huán)境一致。
要想 “補瀏覽器環(huán)境”,首先我們得知道 “js加密算法代碼” 到底使用了哪些瀏覽器環(huán)境API,然后再對應(yīng)去補上這些環(huán)境;
那么我們該如何監(jiān)測 “js加密算法代碼” 對瀏覽器環(huán)境API的使用呢?
毫無爭議:使用Proxy來監(jiān)測瀏覽器環(huán)境API的使用,輔助補瀏覽器環(huán)境”
Proxy是ES6提供的代理器,用于創(chuàng)建一個對象的代理,從而實現(xiàn)基本操作的攔截和自定義(如屬性查找、賦值、枚舉、函數(shù)調(diào)用等)。 它可以代理任何類型的對象,包括原生數(shù)組,函數(shù),甚至另一個代理;擁有遞歸套娃的能力??!
也就是說 我們代理某個對象后,我們就成了它的中間商,任何JS代碼對它的任何操作都可以被我們所攔截!!
# 對navigator對象進行代理,并設(shè)置攔截后的操作 var handler = {set:funcA,get:funcB,deleteProperty:funcC,has:funcD ...}; navigator = new Proxy(navigator,handler); # 對代理后的navigator進行各種操作都會被攔截并觸發(fā)對應(yīng)處理函數(shù) navigator.userAgent 會被攔截并觸發(fā) get funcB navigator.userAgent = "xx" 會被攔截并觸發(fā) set funcA delete navigator; 會被攔截并觸發(fā) deleteProperty funC "userAgent" in navigator 會被攔截并觸發(fā) has funD ... 等等... 任何操作都可以被攔截
基于Proxy的特性,衍生了兩種補環(huán)境思路:
遞歸嵌套Proxy以此來代理瀏覽器所有的BOM、DOM對象及其屬性,再配合node vm2模塊提供的純凈V8環(huán)境,就相當(dāng)于在node中,對整個瀏覽器環(huán)境對象進行了代理,JS代碼使用任何瀏覽器環(huán)境 api都能被我們所攔截。然后我們針對攔截到的環(huán)境檢測點去補。
搭建補環(huán)境框架,用JS模擬瀏覽器基于原型鏈去偽造實現(xiàn)各個BOM、DOM對象,然后將這些JS組織起來,形成一個純JS丐版瀏覽器環(huán)境,我們補的純JS丐版瀏覽器環(huán)境越完善,就越接近真實瀏覽器環(huán)境,能通殺的js環(huán)境檢測就越多。最終完美通殺所有JS環(huán)境檢測?。?/p>
第一種思路雖然實現(xiàn)簡單,主要是對Proxy攔截器的使用 ,但是具備的環(huán)境監(jiān)測能力有限,對較復(fù)雜的原型鏈等難以監(jiān)測,即使是二次開發(fā)也上限不高;并且遇到JS使用了很多環(huán)境時手補也相當(dāng)麻煩;
第二種思路雖然實現(xiàn)較為復(fù)雜,但是上限極高,且可以完美兼容第一種思路,具備可成長的通殺潛質(zhì)。
所以業(yè)內(nèi)補環(huán)境框架幾乎都是基于第二種思路,先搭建一個補環(huán)境框架的骨架,將常見瀏覽器環(huán)境BOM、DOM對象補齊,如:window、location、Document、navigator等,等空閑時或工作遇到其他瀏覽器環(huán)境BOM、DOM對象,再將它補進來。補的越完善,我們能通殺JS環(huán)境檢測越多。
優(yōu)點:
補的越完善,能通殺JS環(huán)境檢測越多。最終完美通殺所有JS環(huán)境檢測?。?;
一鍵運行輸出目標(biāo)JS中所有環(huán)境檢測點;
生成的最終代碼可直接用于生產(chǎn)環(huán)境(可直接供nodejs、v8使用);
告別玄學(xué)補環(huán)境,不再一行行去debugger,極大提高工作效率。
可以在Chrome瀏覽器進行無瀏覽器環(huán)境調(diào)試。
新人彎道超車必備
…
傳統(tǒng)補環(huán)境格式:
// 環(huán)境頭: window = global; navigator= {userAgent:"Mozilla/5.0 (Windows NT 1";} // 扣出來的JS ........ ......
傳統(tǒng)補環(huán)境太簡陋,而且不夠通用,代碼組織混亂,我們最好將其組織為一個項目:
補環(huán)境框架項目整體結(jié)構(gòu):
那么實現(xiàn)這么一個瀏覽器補環(huán)境框架需要哪些步驟哪些考慮呢?
先確定框架運行主流程,即入口文件 。
每個BOM、DOM對象的實現(xiàn)都使用一個單獨的js文件,便于定位及維護。
將這些BOM、DOM文件按照原型鏈的優(yōu)先順序進行讀取,拼接成整個瀏覽器環(huán)境。
思考如何去實現(xiàn)一個BOM、DOM對象使其和瀏覽器一致;(這個是影響框架上限的重要因素,同時也包含大量重復(fù)性人力工作)
事件的處理(對行為驗證碼有幫助)
思考如何保證JS中使用到的所有瀏覽器環(huán)境都能被我們所檢測;(這個是影響框架上限的重要因素)
如何設(shè)計優(yōu)化補環(huán)境框架項目的可擴展、可維護性;(非常必要)
…
還有一些其他細(xì)節(jié)思考,我們的目標(biāo)框架就是 一個易于可擴展與維護、能檢測到JS中所有瀏覽器環(huán)境API的使用、實現(xiàn)了常見瀏覽器環(huán)境方法等,讓我們在之后補環(huán)境中,達到通殺效果。
如果對于原理及實現(xiàn)方向 思考不夠全面、深入,那么實現(xiàn)的框架上限會有限,出現(xiàn)玄學(xué)的概率就大了,我也是經(jīng)歷了很長時間打磨,多次推倒重來、借鑒多個課程,最終實現(xiàn)這個理想的框架。
下面就是一些具體的實現(xiàn):
以下就是主流程入口骨架:
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`) ; // 獲取目標(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`); //真實路徑,瀏覽器打開的就是該緩存文件 const vm = new VM(); // new 一個純凈v8環(huán)境 debugger vm.run(script); // 在V8環(huán)境中運行調(diào)試 debugger
骨架搭好之后我們就要去補對應(yīng)的BOM、DOM對象,比如補Navigator
:
1、先在瀏覽器環(huán)境觀察該對象:Navigator
,
能否進行new Navigator,不能的話則在其構(gòu)造函數(shù)定義中拋出異常,能的話不拋;
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
會拋出異常。
發(fā)現(xiàn) 其原型鏈只有一層,即Navigator.prototype.__proto__ === Object.prototype
3、在瀏覽器環(huán)境觀察其實例對象:navigator
查看其屬性、方法與 原型上的差異,發(fā)現(xiàn)差不多,基本都是繼承原型的。
因此可以簡單補成下面這樣:
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);
注:上面實例只是一種補環(huán)境思路,是基于對象.屬性粒度;我個人用的是另一種思路,基于對象.屬性.特性粒度即Object.getOwnPropertyDescriptor 的value,writable..等,雖然需要補代碼更多,但是模擬的效果更完美,理論上限極高。
瀏覽器對象及屬性實在太多了,我們不可能手動補那么對象屬性,因此要想補出一個完美瀏覽器環(huán)境,我們需要編寫瀏覽器環(huán)境自吐腳本。即在瀏覽器執(zhí)行該腳本,它會將某個瀏覽器環(huán)境對象的所有屬性與方法,拼接成我們框架所需要的補環(huán)境代碼,我們直接粘貼進來,稍微改改即可。
我們可以借助: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 } } ..........太長,略過(下面框架源碼中有)
每補完一個瀏覽器對象之后,可以運行起來與真實瀏覽器進行對比,逐步優(yōu)化,最終達到完美效果。
補環(huán)境框架儼然成為JS逆向人員的大殺器,也是眾多面試官的考察點。我們已經(jīng)了解了 它的原理及實現(xiàn)步驟,接下來我們可以嘗試自己從頭實現(xiàn)一個完善的補環(huán)境框架,但是這會花費很長一段時間來進行開發(fā),而且其中有很多重復(fù)性工作比較無聊(復(fù)制粘貼對比等)。
部分成果展示(以頭條 sign值為例):
監(jiān)測到的檢測點,做過的靚仔可以看看是不是都有
與真實瀏覽器對比
感謝各位的閱讀,以上就是“JS逆向之怎么補瀏覽器環(huán)境”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對JS逆向之怎么補瀏覽器環(huán)境這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。