JS逆向:Hook 技術(shù)原理以及在 JS 逆向中的相關(guān)應(yīng)用

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市屈呕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌虎眨,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岳守,死亡現(xiàn)場離奇詭異湿痢,居然都是意外死亡扑庞,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門害幅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人以现,你說我怎么就攤上這事约啊。” “怎么了恰矩?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵外傅,是天一觀的道長。 經(jīng)常有香客問我萎胰,道長,這世上最難降的妖魔是什么冰肴? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任熙尉,我火速辦了婚禮,結(jié)果婚禮上检痰,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好爱态,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布锦担。 她就那樣靜靜地躺著,像睡著了一般洞渔。 火紅的嫁衣襯著肌膚如雪磁椒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天本辐,我揣著相機與錄音医增,去河邊找鬼。 笑死叶骨,一個胖子當(dāng)著我的面吹牛忽刽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播跪帝,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼歉甚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起腰素,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤雪营,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后洋访,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谴餐,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年汁展,在試婚紗的時候發(fā)現(xiàn)自己被綠了食绿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片公罕。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖品洛,靈堂內(nèi)的尸體忽然破棺而出摩桶,到底是詐尸還是另有隱情,我是刑警寧澤硝清,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布芦拿,位于F島的核電站,受9級特大地震影響蔗崎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜芳撒,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望芥备。 院中可真熱鬧舌菜,春花似錦、人聲如沸日月。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽台颠。三九已至串前,卻和暖如春实蔽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背局装。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工铐尚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宣增。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓爹脾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親灵妨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容