<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          JavaScript 逆向爬取實戰(zhàn)(下)

          共 9285字,需瀏覽 19分鐘

           ·

          2020-05-03 23:20

          閱讀本文大概需要 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.meituan.net/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.cuiqingcai.com/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 了,如圖所示。

          eb2e2b8b2f13db7f6885220702ee0239.webp

          由此我們可以確定,這個加密 id 是在 Ajax 請求完成之后生成的,而且肯定也是由 JavaScript 生成的了。

          那怎么再去找 Ajax 完成之后的事件呢?是否應(yīng)該去找 Ajax 完成之后的事件呢?

          可以是可以,可以試試,我們可以在 Sources 面板的右側(cè),有一個 Event Listener Breakpoints,這里有一個 XHR 的監(jiān)聽,包括發(fā)起時、成功后、發(fā)生錯誤時的一些監(jiān)聽,這里我們勾選上 readystatechange 事件,代表 Ajax 得到響應(yīng)時的事件,其他的斷點可以都刪除了,然后刷新下頁面看下,如圖所示。

          214b88875a94715c9c0c678c81166dfc.webp

          這里我們可以看到就停在了 Ajax 得到響應(yīng)時的位置了。

          那這里我們怎么再去找這個 id 怎么加密的呢?這里可以選擇一點點斷點找下去,但估計找的過程會崩潰掉,因為這里可能會會逐漸調(diào)用到頁面 UI 渲染的一些底層實現(xiàn),甚至可能找著找著都不知道找到哪里去了。

          那怎么辦呢?這里我們再介紹一種定位的方法,那就是 Hook。

          Hook 技術(shù)中文又叫做鉤子技術(shù),它就是在程序運(yùn)行的過程中,對其中的某個方法進(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í)行邏輯和運(yùn)行效果,但是我們通過這種改寫就順利在原來的 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)行改寫即可。

          改寫的邏輯如下:

          (function(){'use strict'function hook(object, attr){var func =object[attr]object[attr]=function(){            console.log('hooked',object, attr, arguments)var ret = func.apply(object, arguments)debugger            console.log('result', ret)return ret}}    hook(window,'btoa')})()

          我們定義了一個 hook 方法,傳入 object 和 attr 參數(shù),意思就是 Hook object 對象的 attr 參數(shù)。例如我們?nèi)绻?Hook 一個 alert 方法,那就把 object 設(shè)置為 window,把 attr 設(shè)置為 alert 字符串。這里我們想要 Hook Base64 的編碼方法,那么這里我們就只需要 Hook window 對象的 btoa 方法就好了。

          我們來看下,首先一句?var func = object[attr],相當(dāng)于我們先把它賦值為一個變量,我們調(diào)用 func 方法就可以實現(xiàn)和原來相同的功能。接著,我們再直接改寫這個方法的定義,直接改寫?object[attr],將其改寫成一個新的方法,在新的方法中,通過?func.apply?方法又重新調(diào)用了原來的方法。這樣我們就可以保證,前后方法的執(zhí)行效果是不受什么影響的,之前這個方法該干啥就還是干啥的。但是和之前不同的是,我們自定義方法之后,現(xiàn)在可以在?func?方法執(zhí)行的前后,再加入自己的代碼,如?console.log?將信息輸出到控制臺,如?debugger?進(jìn)入斷點等等。這個過程中,我們先臨時保存下來了?func?方法,然后定義一個新的方法,接管程序控制權(quán),在其中自定義我們想要的實現(xiàn),同時在新的方法里面再重新調(diào)回?func?方法,保證前后結(jié)果是不受影響的。所以,我們達(dá)到了在不影響原有方法效果的前提下,可以實現(xiàn)在方法的前后實現(xiàn)自定義的功能,就是 Hook 的完整實現(xiàn)過程。

          最后,我們調(diào)用 hook 方法,傳入 window 對象和 btoa 字符串即可。

          那這樣,怎么去注入這個代碼呢呢?這里我們介紹三種注入方法。

          ?直接控制臺注入?復(fù)寫 JavaScript 代碼?Tampermonkey 注入

          控制臺注入

          對于我們這個場景,控制臺注入其實就夠了,我們先來介紹這個方法。

          這個其實很簡單了,就是直接在控制臺輸入這行代碼運(yùn)行,如圖所示。

          7ce982b166e1f98295028012e821716d.webp

          執(zhí)行完這段代碼之后,相當(dāng)于我們就已經(jīng)把 window 的 btoa 方法改寫了,可以控制臺調(diào)用下 btoa 方法試試,如:

          btoa('germey')

          回車之后就可以看到它進(jìn)入了我們自定義的 debugger 的位置停下了,如圖所示。

          58a16d89371f839f9fce75b99b1bb5c5.webp

          我們把斷點向下執(zhí)行,點擊 Resume 按鈕,然后看看控制臺的輸出,可以看到也輸出了一些對應(yīng)的結(jié)果,如被 Hook 的對象,Hook 的屬性,調(diào)用的參數(shù),調(diào)用后的結(jié)果等,如圖所示。

          db83cab73f405a9e9a7d44eb347a55ae.webp

          那這里我們就可以看到,我們通過 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é)果是:

          ef34#teuq0btua#(-57w1q5o5--j@98xygimlyfxs*-!i-0-mb1

          編碼后的結(jié)果是:

          ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx

          如圖所示。

          b7761a452108d841a32da8b7c948408d.webp

          這里很明顯。

          那么核心問題就來了,編碼前的結(jié)果?ef34#teuq0btua#(-57w1q5o5--j@98xygimlyfxs*-!i-0-mb1又是怎么來的呢?我們展開棧的調(diào)用信息,一層層看看這個字符串的變化情況。如果不變那就看下一層,如果變了那就停下來仔細(xì)看看。

          最后我們可以在第五層找到它的變化過程,如圖所示。

          f668deac3f34633eb8e6880d6650ba42.webp

          那這里我們就一目了然了,看到了?_0x135c4d?是一個寫死的字符串?ef34#teuq0btua#(-57w1q5o5--j@98xygimlyfxs*-!i-0-mb,然后和傳入的這個?_0x565f18?拼接起來就形成了最后的字符串。

          那這個?_0x565f18?又是怎么來的呢?再往下追一層,那就一目了然了,其實就是 Ajax 返回結(jié)果的單個電影信息的 id。

          所以,這個加密邏輯的就清楚了,其實非常非常簡單,就是?ef34#teuq0btua#(-57w1q5o5--j@98xygimlyfxs*-!i-0-mb1?加上電影 id,然后 Base64 編碼即可。

          到此,我們就成功用 Hook 的方式找到加密的 id 生成邏輯了。

          但是想想有什么不太科學(xué)的地方嗎?剛才其實也說了,我們的 Hook 代碼是在控制臺手動輸入的,一旦刷新頁面就不生效了,這的確是個問題。而且它必須是在頁面加載完了才注入的,所以它并不能在一開始就生效。

          下面我們再介紹幾種 Hook 注入方式

          重寫 JavaScript

          我們可以借助于 Chrome 瀏覽器的 Overrides 功能實現(xiàn)某些 JavaScript 文件的重寫和和保存,它會在本地生成一個 JavaScript 文件副本,以后每次刷新的時候會使用副本的內(nèi)容。

          這里我們需要切換到 Sources 選項卡的 Overrides 選項卡,然后選擇一個文件夾,比如這里我自定了一個文件夾名字叫做 modify,如圖所示。

          75a9ef82aa14f401e23760e60bdc16aa.webp

          然后我們隨便選一個 JavaScript 腳本,后面貼上這段注入腳本,如圖所示。

          3fefc72a3647c1464ebdbbdd3d01878f.webp

          保存文件。

          此時可能提示頁面崩潰,但是不用擔(dān)心,重新刷新頁面就好了,這時候我們就發(fā)現(xiàn)現(xiàn)在瀏覽器加載的 JavaScript 文件就是我們修改過后的了,文件的下方會有一個標(biāo)識符,如圖所示。

          c93658a594542cfb69f91ccb264dc922.webp

          同時我們還注意到這時候它就直接進(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.tampermonkey.net/ 下載安裝即可。

          安裝完成之后,在 Chrome 瀏覽器的右上角會出現(xiàn) Tampermonkey 的圖標(biāo),這就代表安裝成功了。

          cb08b8d20056f5c6da404c9b7334012d.webp

          我們也可以自己編寫腳本來實現(xiàn)想要的功能。編寫腳本難不難呢?其實就是寫 JavaScript 代碼,只要懂一些 JavaScript 的語法就好了。另外除了懂 JavaScript 語法,我們還需要遵循腳本的一些寫作規(guī)范,這其中就包括一些參數(shù)的設(shè)置。

          下面我們就簡單實現(xiàn)一個小的腳本,實現(xiàn)某個功能。

          首先我們可以點擊 Tampermonkey 插件圖標(biāo),點擊「管理面板」按鈕,打開腳本管理頁面。

          b936f4d3900c8d7f6b1f9ae1d4e5771a.webp

          界面類似顯示如下圖所示。

          4b4efa7df769ce161064034398d141b0.webp

          在這里顯示了我們已經(jīng)有的一些 Tampermonkey 腳本,包括我們自行創(chuàng)建的,也包括從第三方網(wǎng)站下載安裝的。

          另外這里也提供了編輯、調(diào)試、刪除等管理功能,我們可以方便地對腳本進(jìn)行管理。

          接下來我們來創(chuàng)建一個新的腳本來試試,點擊左側(cè)的「+」號,會顯示如圖所示的頁面。

          f4c0896007103570d41536afb142f552.webp

          初始化的代碼如下:

          // ==UserScript==// @name         New Userscript// @namespace    http://tampermonkey.net/// @version      0.1// @description  try to take over the world!// @author       You// @match        https://www.tampermonkey.net/documentation.php?ext=dhdg// @grant        none// ==/UserScript==
          (function(){'use strict';
          // Your code here...})();

          這里最上面是一些注釋,但這些注釋是非常有用的,這部分內(nèi)容叫做?UserScript Header?,我們可以在里面配置一些腳本的信息,如名稱、版本、描述、生效站點等等。

          在?UserScript Header?下方是 JavaScript 函數(shù)和調(diào)用的代碼,其中?'use strict'?標(biāo)明代碼使用 JavaScript 的嚴(yán)格模式,在嚴(yán)格模式下可以消除 Javascript 語法的一些不合理、不嚴(yán)謹(jǐn)之處,減少一些怪異行為,如不能直接使用未聲明的變量,這樣可以保證代碼的運(yùn)行安全,同時提高編譯器的效率,提高運(yùn)行速度。在下方?// Your code here...?這里我們就可以編寫自己的代碼了。

          我們可以將腳本改寫為如下內(nèi)容:

          // ==UserScript==// @name         HookBase64// @namespace    https://scrape.cuiqingcai.com/// @version      0.1// @description  Hook Base64 encode function// @author       Germey// @match       https://dynamic6.scrape.cuiqingcai.com/// @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)debuggerreturn ret}}    hook(window,'btoa')})()

          這時候啟動腳本,重新刷新頁面,可以發(fā)現(xiàn)也可以成功 Hook 住 btoa 方法,如圖所示。

          5237d442707b6f0f0287ee27d2c307bf.webp

          然后我們再順著找調(diào)用邏輯就好啦。

          以上,我們就成功通過 Hook 的方式找到加密 id 的實現(xiàn)了。

          詳情頁 Ajax 的 token 尋找

          現(xiàn)在我們已經(jīng)找到詳情頁的加密 id 了,但是還差一步,其 Ajax 請求也有一個 token,如圖所示。

          8b3e6e55f8ce0fe81e2ed69845bf5809.webp

          其實這個 token 和詳情頁的 token 構(gòu)造邏輯是一樣的了。

          這里就不再展開說了,可以運(yùn)用上文的幾種找入口的方法來找到對應(yīng)的加密邏輯。

          Python 實現(xiàn)詳情頁爬取

          現(xiàn)在我們已經(jīng)成功把詳情頁的加密 id 和 Ajax 請求的 token 找出來了,下一步就能使用 Python 完成爬取了,這里我就只實現(xiàn)第一頁的爬取了,代碼示例如下:

          import hashlibimport timeimport base64from typing importList,Anyimport requests
          INDEX_URL ='https://dynamic6.scrape.cuiqingcai.com/api/movie?limit={limit}&offset={offset}&token={token}'DETAIL_URL ='https://dynamic6.scrape.cuiqingcai.com/api/movie/{id}?token={token}'LIMIT =10OFFSET =0SECRET ='ef34#teuq0btua#(-57w1q5o5--j@98xygimlyfxs*-!i-0-mb'

          def get_token(args:List[Any]): timestamp = str(int(time.time())) args.append(timestamp) sign = hashlib.sha1(','.join(args).encode('utf-8')).hexdigest()return base64.b64encode(','.join([sign, timestamp]).encode('utf-8')).decode('utf-8')
          args =['/api/movie']token = get_token(args=args)index_url = INDEX_URL.format(limit=LIMIT, offset=OFFSET, token=token)response = requests.get(index_url)print('response', response.json())
          result = response.json()for item in result['results']: id = item['id'] encrypt_id = base64.b64encode((SECRET + str(id)).encode('utf-8')).decode('utf-8') args =[f'/api/movie/{encrypt_id}'] token = get_token(args=args) detail_url = DETAIL_URL.format(id=encrypt_id, token=token) response = requests.get(detail_url)print('response', response.json())

          這里模擬了詳情頁的加密 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 逆向分析。

          本節(jié)代碼:https://github.com/Python3WebSpider/ScrapeDynamic6

          瀏覽 64
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  AA黄色电影| 3P九九蜜芽 | 日本无码北条麻妃 | 一级黄色毛片免费 | 在线黄色毛片 |