真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

【JS 逆向百例】網(wǎng)洛者反爬練習平臺第一題:JS 混淆加密,反 Hook 操作

為惠濟等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計制作服務,及惠濟網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務為成都網(wǎng)站設(shè)計、網(wǎng)站制作、惠濟網(wǎng)站設(shè)計,以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務,秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務。我們深信只要達到每一位用戶的要求,就會得到認可,從而選擇與我們長期合作。這樣,我們也可以走得更遠!

關(guān)注微信公眾號:K哥爬蟲,持續(xù)分享爬蟲進階、JS/安卓逆向等技術(shù)干貨!

聲明

本文章中所有內(nèi)容僅供學習交流,抓包內(nèi)容、敏感網(wǎng)址、數(shù)據(jù)接口均已做脫敏處理,嚴禁用于商業(yè)用途和非法用途,否則由此產(chǎn)生的一切后果均與作者無關(guān),若有侵權(quán),請聯(lián)系我立即刪除!

寫在前面

題目本身不是很難,但是其中有很多坑,主要是反 Hook 操作和本地聯(lián)調(diào)補環(huán)境,本文會詳細介紹每一個坑,并不只是一筆帶過,寫得非常詳細!

通過本文你將學到:

  1. Hook Function 和定時器來消除無限 debugger;
  2. 解決反 Hook,通過 Hook 的方式找到加密參數(shù) _signature;
  3. 分析瀏覽器與本地環(huán)境差異,如何尋找 navigator、document、location 等對象,如何本地補環(huán)境;
  4. 如何利用 PyCharm 進行本地聯(lián)調(diào),定位本地和瀏覽器環(huán)境的差異,從而過掉檢測。

逆向目標

  • 目標:網(wǎng)洛者反反爬蟲練習平臺第一題:JS 混淆加密,反 Hook 操作
  • 鏈接:http://spider.wangluozhe.com/challenge/1
  • 簡介:本題要提交的答案是100頁的所有數(shù)據(jù)并加和,要求以 Hook 的方式完成此題,不要以 AST、扣代碼等方式解決,不要使用 JS 反混淆工具進行解密。(Hook 代碼的寫法和用法,K哥以前文章有,本文不再詳細介紹)

繞過無限 debugger

首先觀察到點擊翻頁,URL 并沒有發(fā)生變化,那么一般就是 Ajax 請求,每一次請求有些參數(shù)會改變,熟練的按下 F12 準備查找加密參數(shù),會發(fā)現(xiàn)立馬斷住,進入無限 debugger 狀態(tài),往上跟一個棧,可以發(fā)現(xiàn) debugger 字樣,如下圖所示:

這種情況在K哥以前的案例中也有,當時我們是直接重寫這個 JS,把 debugger 字樣給替換掉就行了,但是本題很顯然是希望我們以 Hook 的方法來過掉無限 debugger,除了 debugger 以外,我們注意到前面還有個 constructor 字樣,在 JavaScript 中它叫構(gòu)造方法,一般在對象創(chuàng)建或者實例化時候被調(diào)用,它的基本語法是:constructor([arguments]) { ... },詳細介紹可參考 MDN 構(gòu)造方法,在本案例中,很明顯 debugger 就是 constructor 的 arguments 參數(shù),因此我們可以寫出以下 Hook 代碼來過掉無限 debugger:

// 先保留原 constructor
Function.prototype.constructor_ = Function.prototype.constructor;
Function.prototype.constructor = function (a) {
    // 如果參數(shù)為 debugger,就返回空方法
    if(a == "debugger") {
        return function (){};
    }
    // 如果參數(shù)不為 debugger,還是返回原方法
    return Function.prototype.constructor_(a);
};

注入 Hook 代碼的方法也有很多,比如直接在瀏覽器開發(fā)者工具控制臺輸入代碼(刷新網(wǎng)頁會失效)、Fiddler 插件注入、油猴插件注入、自寫瀏覽器插件注入等,這些方法在K哥以前的文章都有介紹,今天就不再贅述。

本次我們使用 Fiddler 插件注入,注入以上 Hook 代碼后,會發(fā)現(xiàn)會再次進入無限 debugger,setInterval,很明顯的定時器,他有兩個必須的參數(shù),第一個是要執(zhí)行的方法,第二個是時間參數(shù),即周期性調(diào)用方法的時間間隔,以毫秒為單位,詳細介紹可參考菜鳥教程 Window setInterval(),同樣我們也可以將其 Hook 掉:

// 先保留原定時器
var setInterval_ = setInterval
setInterval = function (func, time){
    // 如果時間參數(shù)為 0x7d0,就返回空方法
    // 當然也可以不判斷,直接返回空,有很多種寫法
    if(time == 0x7d0)
    {
        return function () {};
    }
    // 如果時間參數(shù)不為 0x7d0,還是返回原方法
    return setInterval_(func, time)
}

將兩段 Hook 代碼粘貼到瀏覽器插件里,開啟 Hook,重新刷新頁面就會發(fā)現(xiàn)已經(jīng)過掉了無限 debugger。

Hook 參數(shù)

過掉無限 debugger 后,我們隨便點擊一頁,抓包可以看到是個 POST 請求,F(xiàn)orm Data 里,page 是頁數(shù),count 是每一頁數(shù)據(jù)量,_signature 是我們要逆向的參數(shù),如下圖所示:

我們直接搜索 _signature,只有一個結(jié)果,其中有個 window.get_sign() 方法就是設(shè)置 _signature 的函數(shù),如下圖所示:

這里問題來了?。?!我們再看看本題的題目,JS 混淆加密,反 Hook 操作,作者也再三強調(diào)本題是考驗 Hook 能力!并且到目前為止,我們好像還沒有遇到什么反 Hook 手段,所以,這樣直接搜索 _signature 很顯然太簡單了,肯定是要通過 Hook 的方式來獲取 _signature,并且后續(xù)的 Hook 操作肯定不會一帆風順!

話不多說,我們直接寫一個 Hook window._signature 的代碼,如下所示:

(function() {
    //嚴謹模式 檢查所有錯誤
    'use strict';
    //window 為要 hook 的對象,這里是 hook 的 _signature
	var _signatureTemp = "";
    Object.defineProperty(window, '_signature', {
		//hook set 方法也就是賦值的方法 
		set: function(val) {
				console.log('Hook 捕獲到 _signature 設(shè)置->', val);
                debugger;
				_signatureTemp = val;
				return val;
		},
		//hook get 方法也就是取值的方法 
		get: function()
		{
			return _signatureTemp;
		}
    });
})();

將兩個繞過無限 debugger 的 Hook 代碼,和這個 Hook _signature 的代碼一起,使用 Fiddler 插件一同注入(這里注意要把繞過 debugger 的代碼放在 Hook _signature 代碼的后面,否則有可能不起作用,這可能是插件的 BUG),重新刷新網(wǎng)頁,可以發(fā)現(xiàn)前端的一排頁面的按鈕不見了,打開開發(fā)者工具,可以看到右上角提示有兩個錯誤,點擊可跳轉(zhuǎn)到出錯的代碼,在控制臺也可以看到報錯信息,如下圖所示:

整個 1.js 代碼是經(jīng)過了 sojson jsjiami v6 版本混淆了的,我們將里面的一些混淆代碼在控制臺輸出一下,然后手動還原一下這段代碼,有兩個變量 i1I1i1liillllli1,看起來費勁,直接用 ab 代替,如下所示:

(function() {
    'use strict';
    var a = '';
    Object["defineProperty"](window, "_signature", {
        set: function(b) {
            a = b;
            return b;
        },
        get: function() {
            return a;
        }
    });
}());

是不是很熟悉?有 get 和 set 方法,這不就是在進行 Hook window._signature 操作嗎?整個邏輯就是當 set 方法設(shè)置 _signature 時,將其賦值給 a,get 方法獲取 _signature 時,返回 a,這么操作一番,實際上對于 _signature 沒有任何影響,那這段代碼存在的意義是啥?為什么我們添加了自己的 Hook 代碼就會報錯?

來看看報錯信息:Uncaught TypeError: Cannot redefine property: _signature,不能重新定義 _signature?我們的 Hook 代碼在頁面一加載就運行了 Object.defineProperty(window, '_signature', {}),等到網(wǎng)站的 JS 再次 defineProperty 時就會報錯,那很簡單嘛,既然不讓重新定義,而且網(wǎng)站自己的 JS Hook 代碼不會影響 _signature,直接將其刪掉不就行了嘛!這個地方大概就是反 Hook 操作了。

保存原 1.js 到本地,刪除其 Hook 代碼,使用 Fiddler 的 AutoResponder 功能替換響應(替換方法有很多,K哥以前的文章同樣有介紹),再次刷新發(fā)現(xiàn)異常解除,并且成功 Hook 到了 _signature。

逆向參數(shù)

成功 Hook 之后,直接跟棧,直接把方法暴露出來了:window._signature = window.byted_acrawler(window.sign())

先來看看 window.sign(),選中它其實就可以看到是 13 位毫秒級時間戳,我們跟進 1.js 去看看他的實現(xiàn)代碼:

我們將部分混淆代碼手動還原一下:

window["sign"] = function sign() {
    try {
        div = document["createElement"];
        return Date["parse"](new Date())["toString"]();
    } catch (IIl1lI1i) {
        return "abcdefghigklmnopqrstuvwxyz";
    }
}

這里就要注意了,有個坑給我們埋下了,如果直接略過,覺得就一個時間戳沒啥好看的,那你就大錯特錯了!注意這是一個 try-catch 語句,其中有一句 div = document["createElement"];,有一個 HTML DOM Document 對象,創(chuàng)建了 div 標簽,這段代碼如果放到瀏覽器執(zhí)行,沒有任何問題,直接走 try 語句,返回時間戳,如果在我們本地 node 執(zhí)行,就會捕獲到 document is not defined,然后走 catch 語句,返回的是那一串數(shù)字加字母,最后的結(jié)果肯定是不正確的!

解決方法也很簡單,在本地代碼里,要么去掉 try-catch 語句,直接 return 時間戳,要么在開頭定義一下 document,再或者直接注釋掉創(chuàng)建 div 標簽的這行代碼,但是K哥在這里推薦直接定義一下 document,因為誰能保證在其他地方也有類似的坑呢?萬一隱藏得很深,沒發(fā)現(xiàn),豈不是白費力氣了?

然后再來看看 window.byted_acrawler(),return 語句里主要用到了 sign() 也就是 window.sign() 方法和 IIl1llI1() 方法,我們跟進 IIl1llI1() 方法可以看到同樣使用了 try-catch 語句,nav = navigator[liIIIi11('2b')]; 和前面 div 的情況如出一轍,同樣的這里也建議直接定義一下 navigator,如下圖所示:

到這里用到的方法基本上分析完畢,我們將 window、document、navigator 都定義一下后,本地運行一下,會提示 window[liIIIi11(...)] is not a function

我們?nèi)ゾW(wǎng)頁里看看,會發(fā)現(xiàn)這個方法其實就是一個定時器,沒有太大作用,直接注釋掉即可:

PyCharm 本地聯(lián)調(diào)

經(jīng)過以上操作以后,再次本地運行,會提示 window.signs is not a function,出錯的地方是一個 eval 語句,我們?nèi)g覽器看一下這個 eval 語句,發(fā)現(xiàn)明明是 window.sign(),為什么本地就變成了 window.signs(),平白無故多了個 s 呢?

造成這種情況的原因只有一個,那就是本地與瀏覽器的環(huán)境差異,混淆的代碼里肯定有環(huán)境檢測,如果不是瀏覽器環(huán)境的話,就會修改 eval 里的代碼,多加了一個 s,這里如果你直接刪掉包含 eval 語句的整個函數(shù)和上面的 setInterval 定時器,代碼也能正常運行,但是,K哥一向是追求細節(jié)的!多加個 s 的原因咱必須得搞清楚呀!

我們在本地使用 PyCharm 進行調(diào)試,看看到底是哪里給加了個 s,出錯的地方是這個 eval 語句,我們點擊這一行,下個斷點,右鍵 debug 運行,進入調(diào)試界面(PS:原代碼有無限 debugger,如果不做處理,PyCharm 里調(diào)試同樣也會進入無限 debugger,可以直接把前面的 Hook 代碼加到本地代碼前面,也可以直接刪除對應的函數(shù)或變量):

左側(cè)是調(diào)用棧,右側(cè)是變量值,整體上和 Chrome 里面的開發(fā)者工具差不多,詳細用法可參考 JetBrains 官方文檔,主要介紹一下圖中的 8 個按鈕:

  1. Show Execution Point (Alt + F10):如果你的光標在其它行或其它頁面,點擊這個按鈕可跳轉(zhuǎn)到當前斷點所在的行;
  2. Step Over (F8):步過,一行一行地往下走,如果這一行上有方法也不會進入方法;
  3. Step Into (F7):步入,如果當前行有方法,可以進入方法內(nèi)部,一般用于進入用戶編寫的自定義方法內(nèi),不會進入官方類庫的方法;
  4. Force Step Into (Alt + Shift + F7):強制步入,能進入任何方法,查看底層源碼的時候可以用這個進入官方類庫的方法;
  5. Step Out (Shift + F8):步出,從步入的方法內(nèi)退出到方法調(diào)用處,此時方法已執(zhí)行完畢,只是還沒有完成賦值;
  6. Restart Frame:放棄當前斷點,重新執(zhí)行斷點;
  7. Run to Cursor (Alt + F9):運行到光標處,代碼會運行至光標行,不需要打斷點;
  8. Evaluate Expression (Alt + F8):計算表達式,可以直接運行表達式,不需要在命令行輸入。

我們點擊步入按鈕(Step Into),會進入到 function IIlIliii(),這里同樣使用了 try-catch 語句,繼續(xù)下一步,會發(fā)現(xiàn)捕獲到了異常,提示 Cannot read property 'location' of undefined,如下圖所示:

我們輸出一下各個變量的值,手動還原一下代碼,如下:

function IIlIliii(II1, iIIiIIi1) {
    try {
        href = window["document"]["location"]["href"];
        check_screen = screen["availHeight"];
        window["code"] = "gnature = window.byted_acrawler(window.sign())";
        return '';
    } catch (I1IiI1il) {
        window["code"] = "gnature = window.byted_acrawlers(window.signs())";
        return '';
    }
}

這么一來,就發(fā)現(xiàn)了端倪,在本地我們并沒有 document、location、href、availHeight 對象,所以就會走 catch 語句,變成了 window.signs(),就會報錯,這里解決方法也很簡單,可以直接刪掉多余代碼,直接定義為不帶 s 的那串語句,或者也可以選擇補一下環(huán)境,在瀏覽器里看一下 href 和 screen 的值,定義一下即可:

var window = {
    "document": {
        "location": {
            "href": "http://spider.wangluozhe.com/challenge/1"
        }
    },
}

var screen = {
    "availHeight": 1040
}

然后再次運行,又會提示 sign is not defined,這里的 sign() 其實就是 window.sign(),也就是下面的 window[liIIIi11('a')] 方法,任意改一種寫法即可:

再次運行,沒有錯誤了,我們可以自己寫一個方法來獲取 _signature:以下寫法二選一,都可以:

function getSign(){
    return window[liIIIi11('9')](window[liIIIi11('a')]())
}

function getSign(){
    return window.byted_acrawler(window.sign())
}

// 測試輸出
console.log(getSign())

我們運行一下,發(fā)現(xiàn)在 Pycharm 里并沒有任何輸出,同樣的我們在題目頁面的控制臺輸出一下 console.log,發(fā)現(xiàn)被置空了,如下圖所示:

看來他還對 console.log 做了處理,其實這種情況問題不大,我們直接使用 Python 腳本來調(diào)用前面我們寫的 getSign() 方法就能得到 _signature 的值了,但是,再次重申,K哥一向是追求細節(jié)的!我就得找到處理 console.log 的地方,把它變?yōu)檎#?/p>

這里我們?nèi)匀皇褂?Pycharm 來調(diào)試,進一步熟悉本地聯(lián)調(diào),在 console.log(getSign()) 語句處下個斷點,一步一步跟進,會發(fā)現(xiàn)進到了語句 var IlII1li1 = function() {};,查看此時變量值,發(fā)現(xiàn) console.log、console.warn 等方法都被置空了,如下圖所示:

再往下一步跟進,發(fā)現(xiàn)直接返回了,這里有可能第一次運行 JS 時就會對 console 相關(guān)命令進行方法置空處理,所以先在疑似對 console 處理的方法里面下幾個斷點,再重新調(diào)試,會發(fā)現(xiàn)會走到 else 語句,然后直接將 IlII1li1 也就是空方法,賦值給 console 相關(guān)命令,如下圖所示:

定位到了問題所在,我們直接把 if-else 語句注釋掉,不讓它置空即可,然后再次調(diào)試,發(fā)現(xiàn)就可以直接輸出結(jié)果了:

調(diào)用 Python 攜帶 _signature 挨個計算每一頁的數(shù)據(jù),最終提交成功:

完整代碼

GitHub 關(guān)注 K 哥爬蟲,持續(xù)分享爬蟲相關(guān)代碼!歡迎 star !https://github.com/kgepachong/

以下只演示部分關(guān)鍵代碼,不能直接運行!完整代碼倉庫地址:https://github.com/kgepachong/crawler/

JavaScript 加密關(guān)鍵代碼架構(gòu)

var window = {
    "document": {
        "location": {
            "href": "http://spider.wangluozhe.com/challenge/1"
        }
    },
}

var screen = {
    "availHeight": 1040
}
var document = {}
var navigator = {}
var location = {}

// 先保留原 constructor
Function.prototype.constructor_ = Function.prototype.constructor;
Function.prototype.constructor = function (a) {
    // 如果參數(shù)為 debugger,就返回空方法
    if(a == "debugger") {
        return function (){};
    }
    // 如果參數(shù)不為 debugger,還是返回原方法
    return Function.prototype.constructor_(a);
};

// 先保留原定時器
var setInterval_ = setInterval
setInterval = function (func, time){
    // 如果時間參數(shù)為 0x7d0,就返回空方法
    // 當然也可以不判斷,直接返回空,有很多種寫法
    if(time == 0x7d0)
    {
        return function () {};
    }
    // 如果時間參數(shù)不為 0x7d0,還是返回原方法
    return setInterval_(func, time)
}

var iil = 'jsjiami.com.v6'
  , iiIIilii = [iil, '\x73\x65\x74\x49\x6e\x74\x65\x72\x76\x61\x6c', '\x6a\x73\x6a', ...];
var liIIIi11 = function(_0xe, _0x3cbe90) {
    _0xe = ~~'0x'['concat'](_0xe);
    var _0x636e4d = iiIIilii[_0xe];
    return _0x636e4d;
};
(function(_0xd, _0xfd26eb) {
    var _0x1bba22 = 0x0;
    for (_0xfd26eb = _0xd['shift'](_0x1bba22 >> 0x2); _0xfd26eb && _0xfd26eb !== (_0xd['pop'](_0x1bba22 >> 0x3) + '')['replace'](/[fnwRwdGKbwKrRFCtSC=]/g, ''); _0x1bba22++) {
        _0x1bba22 = _0x1bba22 ^ 0x661c2;
    }
}(iiIIilii, liIIIi11));
// window[liIIIi11('0')](function() {
//     var l111IlII = liIIIi11('1') + liIIIi11('2');
//     if (typeof iil == liIIIi11('3') + liIIIi11('4') || iil != l111IlII + liIIIi11('5') + l111IlII[liIIIi11('6')]) {
//         var Ilil11iI = [];
//         while (Ilil11iI[liIIIi11('6')] > -0x1) {
//             Ilil11iI[liIIIi11('7')](Ilil11iI[liIIIi11('6')] ^ 0x2);
//         }
//     }
//     iliI1lli();
// }, 0x7d0);
(function() {
    var iiIIiil = function() {}();
    var l1liii11 = function() {}();
    window[liIIIi11('9')] = function byted_acrawler() {};
    window[liIIIi11('a')] = function sign() {};
    (function() {}());
    // (function() {
    //     'use strict';
    //     var i1I1i1li = '';
    //     Object[liIIIi11('1f')](window, liIIIi11('21'), {
    //         '\x73\x65\x74': function(illllli1) {
    //             i1I1i1li = illllli1;
    //             return illllli1;
    //         },
    //         '\x67\x65\x74': function() {
    //             return i1I1i1li;
    //         }
    //     });
    // }());
    var iiil1 = 0x0;
    var l11il1l1 = '';
    var ii1Ii = 0x8;
    function i1Il11i(iiIll1i) {}
    function I1lIIlil(l11l1iIi) {}
    function lllIIiI(IIi1lIil) {}

    // 此處省略 N 個函數(shù)
    
    window[liIIIi11('37')]();
}());

function iliI1lli(lil1I1) {
    function lili11I(l11I11l1) {
        if (typeof l11I11l1 === liIIIi11('38')) {
            return function(lllI11i) {}
            [liIIIi11('39')](liIIIi11('3a'))[liIIIi11('8')](liIIIi11('3b'));
        } else {
            if (('' + l11I11l1 / l11I11l1)[liIIIi11('6')] !== 0x1 || l11I11l1 % 0x14 === 0x0) {
                (function() {
                    return !![];
                }
                [liIIIi11('39')](liIIIi11('3c') + liIIIi11('3d'))[liIIIi11('3e')](liIIIi11('3f')));
            } else {
                (function() {
                    return ![];
                }
                [liIIIi11('39')](liIIIi11('3c') + liIIIi11('3d'))[liIIIi11('8')](liIIIi11('40')));
            }
        }
        lili11I(++l11I11l1);
    }
    try {
        if (lil1I1) {
            return lili11I;
        } else {
            lili11I(0x0);
        }
    } catch (liIlI1il) {}
}
;iil = 'jsjiami.com.v6';

// function getSign(){
//     return window[liIIIi11('9')](window[liIIIi11('a')]())
// }

function getSign(){
    return window.byted_acrawler(window.sign())
}

console.log(getSign())

Python 計算關(guān)鍵代碼

# ==================================
# --*-- coding: utf-8 --*--
# @Time    : 2021-12-01
# @Author  : 微信公眾號:K哥爬蟲
# @FileName: challenge_1.py
# @Software: PyCharm
# ==================================


import execjs
import requests

challenge_api = "http://spider.wangluozhe.com/challenge/api/1"
headers = {
    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
    "Cookie": "將 cookie 值改為你自己的!",
    "Host": "spider.wangluozhe.com",
    "Origin": "http://spider.wangluozhe.com",
    "Referer": "http://spider.wangluozhe.com/challenge/1",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36",
    "X-Requested-With": "XMLHttpRequest"
}


def get_signature():
    with open('challenge_1.js', 'r', encoding='utf-8') as f:
        ppdai_js = execjs.compile(f.read())
    signature = ppdai_js.call("getSign")
    print("signature: ", signature)
    return signature


def main():
    result = 0
    for page in range(1, 101):
        data = {
            "page": page,
            "count": 10,
            "_signature": get_signature()
        }
        response = requests.post(url=challenge_api, headers=headers, data=data).json()
        for d in response["data"]:
            result += d["value"]
    print("結(jié)果為: ", result)


if __name__ == '__main__':
    main()


網(wǎng)頁名稱:【JS 逆向百例】網(wǎng)洛者反爬練習平臺第一題:JS 混淆加密,反 Hook 操作
當前路徑:http://weahome.cn/article/dsojcej.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部