1. Hook 技術(shù)原理
Hook 是一種鉤子技術(shù),在系統(tǒng)沒有調(diào)用函數(shù)之前,鉤子程序就先得到控制權(quán),這時鉤子函數(shù)既可以加工處理(改變)該函數(shù)的執(zhí)行行為莉掂,也可以強制結(jié)束消息的傳遞。
簡單來說千扔,修改原有的 JS 代碼就是 Hook憎妙。
Hook 技術(shù)之所以能夠?qū)崿F(xiàn)有兩個條件:
- 客戶端擁有 JS 的最高解釋權(quán),可以決定在任何時候注入 JS昏鹃,而服務(wù)器無法阻止或干預(yù)。服務(wù)端只能通過檢測和混淆的手段诀诊,另 Hook 難度加大洞渤,但是無法直接阻止。
- 除了上面的必要條件之外属瓣,還有一個條件载迄。就是 JS 是一種弱類型語言,同一個變量可以多次定義抡蛙、根據(jù)需要進行不同的賦值护昧,而這種情況如果在其他強類型語言中則可能會報錯,導(dǎo)致代碼無法執(zhí)行粗截。js 的這種特性惋耙,為我們 Hook 代碼提供了便利。
JS 作用域問題 1:自執(zhí)行函數(shù)的 Hook 問題
JS 變量是有作用域的,只有當(dāng)被 hook 函數(shù)和 debugger 斷點在同一個作用域的時候绽榛,才能 hook 成功湿酸。
對于下面的自執(zhí)行函數(shù),在執(zhí)行完之后我們實際上是無法 hook test 函數(shù)的灭美。因為 test 是在自執(zhí)行函數(shù)的作用域推溃,而不是在全局作用域。而此時届腐,自執(zhí)行函數(shù)已經(jīng)執(zhí)行完了铁坎,test 函數(shù)已經(jīng)被內(nèi)存清空無法 hook。
!(function(){
var arg = 1;
var test = function(){
console.log(arg);
}
})()
debugger犁苏;
在此處 hook test()硬萍;
爭取的寫法應(yīng)該是下面這樣,這樣當(dāng)程序被斷下來以后才能 hook test 函數(shù)傀顾。
!(function(){
var arg = 1;
var test = function(){
console.log(arg);
}
debugger襟铭;
})()
JS 作用域問題 2:局部變量污染全局變量
在 Hook 的時候要注意一個 JS 代碼的特性,就是 JS 在函數(shù)賦值的時候短曾,會遵循一個原則:
當(dāng)前作用域有變量則賦值該變量寒砖,當(dāng)前作用域沒有該變量則賦值在全局作用域定義該變量并賦值。
比如:
var arg1 = null;
function test1(){
arg1 = 1; // 注意這里沒有 var
};
function test2(){
console.log(arg1)
};
test1();
test2();
上述代碼的執(zhí)行結(jié)果 test2();
打印出 1
嫉拐,而不是 null
哩都。只有當(dāng) test1
函數(shù)中得賦值語句是 var arg1 = 1;
的時候,才會 返回 null
而不是 1
婉徘。
JS 作用域問題 3:this 的指向問題
在不同的作用域中漠嵌,同樣的變量指向是不一樣的。每個函數(shù)在定義被解析器解析時盖呼,都會創(chuàng)建兩個特殊變量: this 和 arguments 。 每個函數(shù)都有屬于自己的 this
對象几晤, 這個 this
對象是在運行時基于函數(shù)的執(zhí)行環(huán)境綁定的。
在 Hook 的時候圾浅,this
的指向遵循下面的原則:
全局作用域中果复,
this = window
;
方法作用域中乐设,this = 調(diào)用者
灸拍;
在淺灘函數(shù)中做祝,this = 調(diào)用外部函數(shù)或內(nèi)部函數(shù)的執(zhí)行環(huán)境對象
;
在類方法里面株搔,this = 類自己
;
總結(jié)來說就是:誰調(diào)用這個函數(shù)對象纤房,this就指向誰(this指向的是調(diào)用執(zhí)行函數(shù)的那個對象)。
在 JS 中捌刮,如果需要改變 this 的指向舒岸,可以通過使用 call()
和 apply()
改變函數(shù)執(zhí)行環(huán)境的情況,以改變this 指向蛾派。
具體可以參考文章《 call/apply以及this指向的理解》,問占中有詳細(xì)的解釋眯杏。
2. Hook 的實現(xiàn)
Hook 實現(xiàn)有兩種方式壳澳,一種是直接替換函數(shù),另一種是 Object.defineProperty
通過為對象的屬性賦值的方式進行 Hook萎津。
兩種方式的區(qū)別:
- 函數(shù) hook抹镊,一般不會 hook 失敗,除非 proto 模擬的不好會被檢測到垮耳。
- 屬性 hook,當(dāng)網(wǎng)站所有的邏輯都采用
Object.defineProperty
綁定時儡炼,屬性 hook 就會失效查蓉。同時榜贴,Object.defineProperty
無法進行二次 Hook妹田。
從日常的實際應(yīng)用層面來說鬼佣,上面的問題并不需要過度關(guān)注霜浴。需要注意的是,第一種方式簡單阴孟、但是太粗暴,容易影響原有代碼的正常執(zhí)行锹漱,也容易被檢測到慕嚷,而第二種方式會更優(yōu)雅一些,具體需要結(jié)合具體需求選擇合適的 Hook 方式嗅辣。
- 方法一:直接替換原有函數(shù)蛇耀。
old_func = 被 hook 函數(shù)
被 hook 函數(shù) = function(arguments){
if 判斷條件:
my_task;
return old_func.apply(arguments)
}
func.prototype.xxx = xxxx
- 方法二:Object.defineProperty 為對象的屬性賦值。
odl_attr = obj.attr
Object.defineProperty(obj, 'attr', {
get: function(){
debugger;
if 判斷條件:
my_task;
return old_attr
},
set: function(val){
debugger;
if 判斷條件:
my_task;
return 自定義內(nèi)容
}
})
- 方法三:JS Proxy
TODO - 方法四:瀏覽器插件油猴
油猴译暂,TODO
3. Hook 的應(yīng)用
Hook http請求
http請求包括 ajax撩炊、src、href伯顶、表單等骆膝。
// 代碼來源:https://www.cnblogs.com/amiezhang/p/9984690.html
function hookAJAX() {
XMLHttpRequest.prototype.nativeOpen = XMLHttpRequest.prototype.open;
var customizeOpen = function (method, url, async, user, password) {
// do something
this.nativeOpen(method, url, async, user, password);
};
XMLHttpRequest.prototype.open = customizeOpen;
}
/**
*全局?jǐn)r截Image的圖片請求添加token
*
*/
function hookImg() {
const property = Object.getOwnPropertyDescriptor(Image.prototype, 'src');
const nativeSet = property.set;
function customiseSrcSet(url) {
// do something
nativeSet.call(this, url);
}
Object.defineProperty(Image.prototype, 'src', {
set: customiseSrcSet,
});
}
/**
* 攔截全局open的url添加token
*
*/
function hookOpen() {
const nativeOpen = window.open;
window.open = function (url) {
// do something
nativeOpen.call(this, url);
};
}
function hookFetch() {
var fet = Object.getOwnPropertyDescriptor(window, 'fetch')
Object.defineProperty(window, 'fetch', {
value: function (a, b, c) {
// do something
return fet.value.apply(this, args)
}
})
}
// 代碼來源:https://www.cnblogs.com/amiezhang/p/9984690.html
修改返回的 response阅签,干擾 JS 原有代碼的運行。
在處理無限反 debugger 的時候政钟,常用的一種方法就是 Fiddler + AutoResponse樟结,然后刪除 js 代碼中的 debugger精算,或者直接修改成本地代碼,都是在瀏覽器接收服務(wù)器返回的資源的時候驮履,基于 Hook 思想完成的廉嚼。
詳細(xì)參見:《JS逆向:Fiddler + AutoResponder 篡改 js 破解企查查無限 debugger 問題》。
修改常用變量前鹅、函數(shù),方便進行加密函數(shù)定位蹂喻。
比如捂寿,如果推測加密過程中用到了 md5 加密,那么通過 Hook md5
函數(shù)秦陋,在其中打 debugger 斷點的方式,就可以很快定位加密函數(shù)的位置赤嚼。
導(dǎo)出加密函數(shù)的參數(shù)顺又、結(jié)果值。
通過定義全局變量蹂空、window屬性等方式,導(dǎo)出加密函數(shù)中的參數(shù)或者結(jié)果值上枕。
cookie鉤子:查找 cookie 生成入口
打 script
斷點弱恒,在 js
剛運行時就把網(wǎng)頁斷住。
在 console 中輸入下面的代碼分瘦。
document.cookie_bak = document.cookie
Object.defineProperty(document, 'doockie',{
get: function(){
debugger;
return document.cookie_bak;
},
set: function(val){
debugger;
return;
}
})
cookie鉤子:用于定位cookie中關(guān)鍵參數(shù)生成位置
當(dāng)cookie中匹配到了 目標(biāo)cookie字符串
琉苇, 則插入斷點。
(function () {
'use strict';
var cookieTemp = '';
Object.defineProperty(document, 'cookie', {
set: function (val) {
if (val.indexOf('目標(biāo)cookie字符串') != -1) {
debugger;
}
console.log('Hook捕獲到cookie設(shè)置->', val);
cookieTemp = val;
return val;
},
get: function () {
return cookieTemp;
},
});
})();
請求鉤子
用于定位請求中關(guān)鍵參數(shù)生成位置去团。
(function () {
'use strict';
var cookieTemp = '';
Object.defineProperty(document, 'cookie', {
set: function (val) {
if (val.indexOf('關(guān)鍵參數(shù)') != -1) {
debugger;
}
console.log('Hook捕獲到關(guān)鍵參數(shù)設(shè)置->', val);
cookieTemp = val;
return val;
},
get: function () {
return cookieTemp;
},
});
})();
header鉤子:用于定位header中關(guān)鍵參數(shù)生成位置
TODO
自己寫插件
插件 js 文件穷蛹,inject.js
// hook 代碼
// 略
插件的配置文件:manifest.json
{
"name": "Injection",
"version": "2.0",
"description": "RequestHeader鉤子",
"manifest_version": 2,
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"inject.js"
],
"all_frames": true,
"permissions": [
"tabs"
],
"run_at": "document_start"
}
]
}
將無限 debugger 方法直接置空
debugger函數(shù) = function(){};
Hook eval 繞過無限 debugger
eval_bak = eval
eval = function(val){
debugger;
return eval_bak(val);
}
eval.toString = function(){
return "function eval() { [native code] }"
}
干掉定時器類觸發(fā)的無限 debugger
for (var i = 1; i < 99999; i++)window.clearInterval(i);
利用 Hook 將 eval 函數(shù)替換成 console.log
當(dāng) js 代碼中有 eval
函數(shù)執(zhí)行了某種操作肴熏,逆向過程中需要分析操作的具體內(nèi)容時,可以通過 Hook eval 替換成 console.log 的方式蛙吏,將 eval
執(zhí)行的代碼打印出來。
eval_bak = eval;
eval = console.log;
4. 風(fēng)控如何檢測 Hook
4.1. toString 檢測識別 Hook
toString 檢測励烦,指的是風(fēng)控通過檢測被 Hook 的函數(shù) toString()
結(jié)果是否變化泼诱,來判斷該函數(shù)是否被 Hook 的一種檢測方法。當(dāng)風(fēng)控監(jiān)測到 Hook 以后治筒,可以返回假數(shù)據(jù)誤導(dǎo)逆向工程師,也可以配合內(nèi)存爆破進行反 debugger系瓢。
比如我們 Hook 了 eval
函數(shù)句灌,這時風(fēng)控就可以通過檢測 eval.toString() 的返回值是否是 "function eval() { [native code] }"
來識別該函數(shù)是否被 Hook 了。
如何解決 toString 檢測
如何解決 toString
檢測呢胰锌?其實方法很簡單,仍然需要使用 Hook 技術(shù)酬土。我們只需要修改目標(biāo)函數(shù)的 toString
方法格带。
eval.toString = function(){
return "function eval() { [native code] }"
}
5.2. 檢測原型鏈
TODO