| 閱讀本文大概需要 20 分鐘。” 這一篇是 JavaScript 逆向爬取的第二篇。那么接下來我為大家縷順一下學(xué)習(xí)順序。 系列文章的第一篇啟于總結(jié)一些網(wǎng)站加密和混淆技術(shù),這篇文章我們介紹了網(wǎng)頁防護(hù)技術(shù),包括接口加密和 JavaScript 壓縮、加密和混淆。能夠為學(xué)習(xí) JavaScript 逆向爬取奠定堅實的基礎(chǔ)。 接下來就是 JavaScript 逆向爬取的第一篇JavaScript 逆向爬取實戰(zhàn)。分為上下章發(fā)出是因為確實寫得太長了(手動狗頭)。 那么話不多說,我們開始今天的學(xué)習(xí)吧~ 詳情頁加密 id 入口的尋找好,那么我們觀察下上一步的輸出結(jié)果,我們把結(jié)果格式化一下,看看部分結(jié)果: {  'count': 100,  'results': [    {      'id': 1,      'name': '霸王別姬',      'alias': 'Farewell My Concubine',      'cover': 'https://p0./movie/ce4da3e03e655b5b88ed31b5cd7896cf62472.jpg@464w_644h_1e_1c',      'categories': [        '劇情',        '愛情'      ],      'published_at': '1993-07-26',      'minute': 171,      'score': 9.5,      'regions': [        '中國大陸',        '中國香港'      ]    },    ...  ]}這里我們看到有個 id 是 1,另外還有一些其他的字段如電影名稱、封面、類別等等,那么這里面一定有什么信息是用來唯一區(qū)分某個電影的。 但是呢,這里我們點擊下第一個部電影的信息,可以看到它跳轉(zhuǎn)到了 URL 為 https://dynamic6.scrape./detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx 的頁面,可以看到這里 URL 里面有一個加密 id 為 ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx,那么這個和電影的這些信息有什么關(guān)系呢? 這里,如果你仔細(xì)觀察規(guī)律其實是可以比較容易地找出規(guī)律來的,但是這總歸是觀察出來的,如果遇到一些觀察不出規(guī)律的那就歇菜了。所以還是需要靠技巧去找到它真正加密的位置。 這時候我們該怎么辦呢? 分析一下,這個加密 id 到底是什么生成的。 我們在點擊詳情頁的時候就看到它訪問的 URL 里面就帶上了 ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx 這個加密 id 了,而且不同的詳情頁的加密 id 是不同的,這說明這個加密 id 的構(gòu)造依賴于列表頁 Ajax 的返回結(jié)果,所以可以確定這個加密 id 的生成是發(fā)生在 Ajax 請求完成后或者點擊詳情頁的一瞬間。 那為了進(jìn)一步確定是發(fā)生在何時,我們看看頁面源碼,可以看到在沒有點擊之前,詳情頁鏈接的 href 里面就已經(jīng)帶有加密 id 了,如圖所示。 由此我們可以確定,這個加密 id 是在 Ajax 請求完成之后生成的,而且肯定也是由 JavaScript 生成的了。 那怎么再去找 Ajax 完成之后的事件呢?是否應(yīng)該去找 Ajax 完成之后的事件呢? 可以是可以,可以試試,我們可以在 Sources 面板的右側(cè),有一個 Event Listener Breakpoints,這里有一個 XHR 的監(jiān)聽,包括發(fā)起時、成功后、發(fā)生錯誤時的一些監(jiān)聽,這里我們勾選上 readystatechange 事件,代表 Ajax 得到響應(yīng)時的事件,其他的斷點可以都刪除了,然后刷新下頁面看下,如圖所示。 這里我們可以看到就停在了 Ajax 得到響應(yīng)時的位置了。 那這里我們怎么再去找這個 id 怎么加密的呢?這里可以選擇一點點斷點找下去,但估計找的過程會崩潰掉,因為這里可能會會逐漸調(diào)用到頁面 UI 渲染的一些底層實現(xiàn),甚至可能找著找著都不知道找到哪里去了。 那怎么辦呢?這里我們再介紹一種定位的方法,那就是 Hook。 Hook 技術(shù)中文又叫做鉤子技術(shù),它就是在程序運行的過程中,對其中的某個方法進(jìn)行重寫,在原先的方法前后加入我們自定義的代碼。相當(dāng)于在系統(tǒng)沒有調(diào)用該函數(shù)之前,鉤子程序就先捕獲該消息,鉤子函數(shù)先得到控制權(quán),這時鉤子函數(shù)既可以加工處理(改變)該函數(shù)的執(zhí)行行為。 通俗點來說呢,比如我要 Hook 一個方法 a,我可以先臨時用一個變量存一下,把它存成 _a,然后呢,我再重新聲明一個方法 a,里面加點自己的邏輯,比如加點調(diào)試語句、輸出語句等等,然后再調(diào)用下 _a,這里調(diào)用的 _a 就是之前的 a。那這樣就相當(dāng)于新的方法 a 里面混入了我們自己定義的邏輯,同時又把原來的方法 a 也執(zhí)行了一遍。所以這不會影響原有的執(zhí)行邏輯和運行效果,但是我們通過這種改寫就順利在原來的 a 方法前后加上了我們自己的邏輯,這就是 Hook。 那么,我們這里怎么用 Hook 的方式來找到加密 id 的加密入口點呢? 想一下,這個加密 id 是一個 Base64 編碼的字符串,那么生成過程中想必就調(diào)用了 JavaScript 的 Base64 編碼的方法,這個方法名叫做 btoa,這個 btoa 方法可以將參數(shù)轉(zhuǎn)化成 Base64 編碼。當(dāng)然 Base64 也有其他的實現(xiàn)方式,比如利用 crypto-js 這個庫實現(xiàn)的,這個可能底層調(diào)用的就不是 btoa 方法了。 所以,我們其實現(xiàn)在并不確定是不是調(diào)用的 btoa 方法實現(xiàn)的 Base64 編碼,那就先試試吧。 要實現(xiàn) Hook,其實關(guān)鍵在于將原來的方法改寫,這里我們其實就是 Hook btoa 這個方法了,btoa 這個方法屬于 window 對象,我們將 window 對象的 btoa 方法進(jìn)行改寫即可。 改寫的邏輯如下: 
 我們定義了一個 hook 方法,傳入 object 和 attr 參數(shù),意思就是 Hook object 對象的 attr 參數(shù)。例如我們?nèi)绻?Hook 一個 alert 方法,那就把 object 設(shè)置為 window,把 attr 設(shè)置為 alert 字符串。這里我們想要 Hook Base64 的編碼方法,那么這里我們就只需要 Hook window 對象的 btoa 方法就好了。 我們來看下,首先一句  最后,我們調(diào)用 hook 方法,傳入 window 對象和 btoa 字符串即可。 那這樣,怎么去注入這個代碼呢呢?這里我們介紹三種注入方法。 ·直接控制臺注入·復(fù)寫 JavaScript 代碼·Tampermonkey 注入 控制臺注入對于我們這個場景,控制臺注入其實就夠了,我們先來介紹這個方法。 這個其實很簡單了,就是直接在控制臺輸入這行代碼運行,如圖所示。 執(zhí)行完這段代碼之后,相當(dāng)于我們就已經(jīng)把 window 的 btoa 方法改寫了,可以控制臺調(diào)用下 btoa 方法試試,如: btoa('germey')回車之后就可以看到它進(jìn)入了我們自定義的 debugger 的位置停下了,如圖所示。 我們把斷點向下執(zhí)行,點擊 Resume 按鈕,然后看看控制臺的輸出,可以看到也輸出了一些對應(yīng)的結(jié)果,如被 Hook 的對象,Hook 的屬性,調(diào)用的參數(shù),調(diào)用后的結(jié)果等,如圖所示。 那這里我們就可以看到,我們通過 Hook 的方式改寫了 btoa 方法,使其每次在調(diào)用的時候都能停到一個斷點,同時還能輸出對應(yīng)的結(jié)果。 好,那接下來怎么用 Hook 找到對應(yīng)的加密 id 的加密入口呢? 由于此時我們是在控制臺直接輸入的 Hook 代碼,所以頁面一旦刷新就無效了,但由于我們這個網(wǎng)站是 SPA 式的頁面,所以在點擊詳情頁的時候頁面是不會整個刷新的,所以這段代碼依然還會生效。但是如果不是 SPA 式的頁面,即每次訪問都需要刷新頁面的網(wǎng)站,這種注入方式就不生效了。 好,那我們的目的是為了 Hook 列表頁 Ajax 加載完成后的的加密 id 的 Base64 編碼的過程,那怎么在不刷新頁面的情況下再次復(fù)現(xiàn)這個操作呢?很簡單,點下一頁就好了。 這時候我們可以點擊第 2 頁的按鈕,這時候可以看到它確實再次停到了 Hook 方法的 debugger 處,由于列表頁的 Ajax 和加密 id 都會帶有 Base64 編碼的操作,因此它每一個都能 Hook 到,通過觀察對應(yīng)的 Arguments 或當(dāng)前網(wǎng)站的行為或者觀察棧信息,我們就能大體知道現(xiàn)在走到了哪個位置了,從而進(jìn)一步通過棧的調(diào)用信息找到調(diào)用 Base64 編碼的位置。 我們可以根據(jù)調(diào)用棧的信息來觀察這些變量在哪一層發(fā)生變化的,比如最后的這一層,我們可以很明顯看到它執(zhí)行了 Base64 編碼,編碼前的結(jié)果是: 編碼后的結(jié)果是: ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx如圖所示。 這里很明顯。 那么核心問題就來了,編碼前的結(jié)果  最后我們可以在第五層找到它的變化過程,如圖所示。 那這里我們就一目了然了,看到了  那這個  所以,這個加密邏輯的就清楚了,其實非常非常簡單,就是  到此,我們就成功用 Hook 的方式找到加密的 id 生成邏輯了。 但是想想有什么不太科學(xué)的地方嗎?剛才其實也說了,我們的 Hook 代碼是在控制臺手動輸入的,一旦刷新頁面就不生效了,這的確是個問題。而且它必須是在頁面加載完了才注入的,所以它并不能在一開始就生效。 下面我們再介紹幾種 Hook 注入方式 重寫 JavaScript我們可以借助于 Chrome 瀏覽器的 Overrides 功能實現(xiàn)某些 JavaScript 文件的重寫和和保存,它會在本地生成一個 JavaScript 文件副本,以后每次刷新的時候會使用副本的內(nèi)容。 這里我們需要切換到 Sources 選項卡的 Overrides 選項卡,然后選擇一個文件夾,比如這里我自定了一個文件夾名字叫做 modify,如圖所示。 然后我們隨便選一個 JavaScript 腳本,后面貼上這段注入腳本,如圖所示。 保存文件。 此時可能提示頁面崩潰,但是不用擔(dān)心,重新刷新頁面就好了,這時候我們就發(fā)現(xiàn)現(xiàn)在瀏覽器加載的 JavaScript 文件就是我們修改過后的了,文件的下方會有一個標(biāo)識符,如圖所示。 同時我們還注意到這時候它就直接進(jìn)入了斷點模式,成功 Hook 到了 btoa 這個方法了。 其實這個 Overrides 這個功能非常有用,有了它我們可以持久化保存我們?nèi)我庑薷牡?JavaScript 代碼,所以我們想在哪里改都可以了,甚至可以直接修改 JavaScript 的原始執(zhí)行邏輯也都是可以的。 Tampermonkey 注入如果我們不想用 Overrides 的方式改寫 JavaScript 的方式注入的話,還可以借助于瀏覽器插件來實現(xiàn)注入,這里推薦的瀏覽器插件叫做 Tampermonkey,中文叫做油猴。它是一款瀏覽器插件,支持 Chrome。利用它我們可以在瀏覽器加載頁面時自動執(zhí)行某些 JavaScript 腳本。由于執(zhí)行的是 JavaScript,所以我們幾乎可以在網(wǎng)頁中完成任何我們想實現(xiàn)的效果,如自動爬蟲、自動修改頁面、自動響應(yīng)事件等等。 首先我們需要安裝 Tampermonkey,這里我們使用的瀏覽器是 Chrome。直接在 Chrome 應(yīng)用商店或者在 Tampermonkey 的官網(wǎng) https://www./ 下載安裝即可。 安裝完成之后,在 Chrome 瀏覽器的右上角會出現(xiàn) Tampermonkey 的圖標(biāo),這就代表安裝成功了。  我們也可以自己編寫腳本來實現(xiàn)想要的功能。編寫腳本難不難呢?其實就是寫 JavaScript 代碼,只要懂一些 JavaScript 的語法就好了。另外除了懂 JavaScript 語法,我們還需要遵循腳本的一些寫作規(guī)范,這其中就包括一些參數(shù)的設(shè)置。 下面我們就簡單實現(xiàn)一個小的腳本,實現(xiàn)某個功能。 首先我們可以點擊 Tampermonkey 插件圖標(biāo),點擊「管理面板」按鈕,打開腳本管理頁面。  界面類似顯示如下圖所示。  在這里顯示了我們已經(jīng)有的一些 Tampermonkey 腳本,包括我們自行創(chuàng)建的,也包括從第三方網(wǎng)站下載安裝的。 另外這里也提供了編輯、調(diào)試、刪除等管理功能,我們可以方便地對腳本進(jìn)行管理。 接下來我們來創(chuàng)建一個新的腳本來試試,點擊左側(cè)的「+」號,會顯示如圖所示的頁面。  初始化的代碼如下: 
 這里最上面是一些注釋,但這些注釋是非常有用的,這部分內(nèi)容叫做  在  我們可以將腳本改寫為如下內(nèi)容: // ==UserScript==// @name         HookBase64// @namespace    https://scrape./// @version      0.1// @description  Hook Base64 encode function// @author       Germey// @match       https://dynamic6.scrape./// @grant        none// @run-at      document-start// ==/UserScript==(function () {    'use strict'    function hook(object, attr) {        var func = object[attr]        console.log('func', func)        object[attr] = function () {            console.log('hooked', object, attr)            var ret = func.apply(object, arguments)            debugger            return ret        }    }    hook(window, 'btoa')})()這時候啟動腳本,重新刷新頁面,可以發(fā)現(xiàn)也可以成功 Hook 住 btoa 方法,如圖所示。  然后我們再順著找調(diào)用邏輯就好啦。 以上,我們就成功通過 Hook 的方式找到加密 id 的實現(xiàn)了。 詳情頁 Ajax 的 token 尋找現(xiàn)在我們已經(jīng)找到詳情頁的加密 id 了,但是還差一步,其 Ajax 請求也有一個 token,如圖所示。  其實這個 token 和詳情頁的 token 構(gòu)造邏輯是一樣的了。 這里就不再展開說了,可以運用上文的幾種找入口的方法來找到對應(yīng)的加密邏輯。 Python 實現(xiàn)詳情頁爬取現(xiàn)在我們已經(jīng)成功把詳情頁的加密 id 和 Ajax 請求的 token 找出來了,下一步就能使用 Python 完成爬取了,這里我就只實現(xiàn)第一頁的爬取了,代碼示例如下: 
 這里模擬了詳情頁的加密 id 和 token 的構(gòu)造過程,然后請求了詳情頁的 Ajax 接口,這樣我們就可以爬取到詳情頁的內(nèi)容了。 總結(jié)本節(jié)內(nèi)容很多,一步步介紹了整個網(wǎng)站的 JavaScript 逆向過程,其中的技巧有: ·全局搜索查找入口·代碼格式化·XHR 斷點·變量監(jiān)聽·斷點設(shè)置和跳過·棧查看·Hook 原理·Hook 注入·Overrides 功能·Tampermonkey 插件·Python 模擬實現(xiàn) 掌握了這些技巧我們就能更加得心應(yīng)手地實現(xiàn) JavaScript 逆向分析。 | 
|  |