|
閱讀本文大概需要 25 分鐘。 ”在上一節(jié)總結(jié)一些網(wǎng)站加密和混淆技術(shù)中,我們介紹了網(wǎng)頁(yè)防護(hù)技術(shù),包括接口加密和 JavaScript 壓縮、加密和混淆。這就引出了一個(gè)問(wèn)題,如果我們碰到了這樣的網(wǎng)站,那該怎么去分析和爬取呢? 本節(jié)我們就通過(guò)一個(gè)案例來(lái)介紹一下這種網(wǎng)站的分析思路,本節(jié)介紹的這個(gè)案例網(wǎng)站不僅在 API 接口層有加密,而且前端 JavaScript 也帶有壓縮和混淆,其前端壓縮打包工具是使用了現(xiàn)在流行的 Webpack,混淆工具是使用了 javascript-obfuscator,這二者結(jié)合結(jié)合起來(lái),前端的代碼會(huì)變得難以閱讀和分析。 如果我們不使用 Selenium 或 Pyppeteer 等工具來(lái)模擬瀏覽器的形式爬取的話(huà),要想直接從接口層面上獲取數(shù)據(jù),基本上我們就要一點(diǎn)點(diǎn)調(diào)試分析 JavaScript 的調(diào)用邏輯、堆棧調(diào)用關(guān)系來(lái)整個(gè)弄清楚網(wǎng)站加密的實(shí)現(xiàn)方法了,我們可以稱(chēng)之為這個(gè)過(guò)程叫 JavaScript 逆向。這些接口的加密參數(shù)往往都是一些加密算法或編碼的組合,完全搞明白其中的邏輯之后,我們就能把這個(gè)算法用 Python 模擬出來(lái),從而實(shí)現(xiàn)接口的請(qǐng)求了。 案例介紹案例的地址為:https://dynamic6.scrape./,頁(yè)面如圖所示。 初看之下并沒(méi)有什么特殊的,但仔細(xì)觀察可以發(fā)現(xiàn)其 Ajax 請(qǐng)求接口和每部電影的 URL 都包含了加密參數(shù)。 比如我們點(diǎn)擊任意一部電影,觀察一下 URL 的變化,如圖所示。 這里我們可以看到詳情頁(yè)的 URL 和包含了一個(gè)長(zhǎng)字符串,看似是一個(gè) Base64 編碼的內(nèi)容。 那么接下來(lái)直接看看 Ajax 的請(qǐng)求,我們從列表頁(yè)的第 1 頁(yè)到第 10 頁(yè)依次點(diǎn)一下,觀察一下 Ajax 請(qǐng)求是怎樣的,如圖所示。 可以看到 Ajax 接口的 URL 里面多了一個(gè) token,而且不同的頁(yè)碼這個(gè) token 還是不一樣的,這個(gè) token 同樣看似是一個(gè) Base64 編碼的字符串。 另外更困難的是,這個(gè)接口還是有時(shí)效性的,如果我們把 Ajax 接口 URL 直接復(fù)制下來(lái),短期內(nèi)是可以訪問(wèn)的,但是過(guò)段時(shí)間之后就無(wú)法訪問(wèn)了,會(huì)直接返回 401 狀態(tài)碼。 接下來(lái)我們?cè)倏聪铝斜眄?yè)的返回結(jié)果,比如我們打開(kāi)第一個(gè)請(qǐng)求,看看第一部電影數(shù)據(jù)的返回結(jié)果,如圖所示。 這里我們把看似是第一部電影的返回結(jié)果全展開(kāi)了,但是剛才我們觀察到第一步電影的 URL 的鏈接卻為 https://dynamic6.scrape./detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx,看起來(lái)是 Base64 編碼,我們解碼一下,結(jié)果為 再然后,這僅僅是某一個(gè)詳情頁(yè)頁(yè)面的 URL,其真實(shí)數(shù)據(jù)是通過(guò) Ajax 加載的,那么 Ajax 請(qǐng)求又是怎樣的呢,我們?cè)儆^察下,如圖所示。 那么總結(jié)下來(lái)這個(gè)網(wǎng)站就有如下特點(diǎn): ·列表頁(yè)的 Ajax 接口參數(shù)帶有加密的 token·詳情頁(yè)的 URL 帶有加密 id·詳情頁(yè)的 Ajax 接口參數(shù)帶有加密 id 和加密 token 那如果我們要想通過(guò)接口的形式來(lái)爬取,必須要把這些加密 id 和 token 構(gòu)造出來(lái)才行,而且必須要一步步來(lái),首先我們要構(gòu)造出列表頁(yè) Ajax 接口的 token 參數(shù),然后才能獲取每部電影的數(shù)據(jù)信息,然后根據(jù)數(shù)據(jù)信息構(gòu)造出加密 id 和 token。 OK,那到現(xiàn)在為止我們就知道了這個(gè)網(wǎng)站接口的加密情況了,我們下一步就是去找這個(gè)加密實(shí)現(xiàn)邏輯了。 由于是網(wǎng)頁(yè),所以其加密邏輯一定藏在前端代碼里面,但上節(jié)課我們也說(shuō)了,前端為了保護(hù)其接口加密邏輯不被輕易分析出來(lái),會(huì)采取壓縮、混淆的一些方式來(lái)加大分析的難度。 好,那么我們 就來(lái)看看這個(gè)網(wǎng)站的源代碼和 JavaScript 文件是怎樣的吧。 首先看看網(wǎng)站源代碼,我們?cè)诰W(wǎng)站上點(diǎn)擊右鍵,彈出選項(xiàng)菜單,然后點(diǎn)擊「查看源代碼」,可以看到結(jié)果如圖所示。 內(nèi)容如下: <!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content='IE=edge'><meta name=viewport content='width=device-width,initial-scale=1'><link rel=icon href=/favicon.ico><title>Scrape | Movie</title><link href=/css/chunk-19c920f8.2a6496e0.css rel=prefetch><link href=/css/chunk-2f73b8f3.5b462e16.css rel=prefetch><link href=/js/chunk-19c920f8.c3a1129d.js rel=prefetch><link href=/js/chunk-2f73b8f3.8f2fc3cd.js rel=prefetch><link href=/js/chunk-4dec7ef0.e4c2b130.js rel=prefetch><link href=/css/app.ea9d802a.css rel=preload as=style><link href=/js/app.5ef0d454.js rel=preload as=script><link href=/js/chunk-vendors.77daf991.js rel=preload as=script><link href=/css/app.ea9d802a.css rel=stylesheet></head><body><noscript><strong>We're sorry but portal doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.77daf991.js></script><script src=/js/app.5ef0d454.js></script></body></html>這是一個(gè)典型的 SPA (單頁(yè) Web 應(yīng)用)的頁(yè)面, 其 JavaScript 文件名帶有編碼字符、chunk、vendors 等關(guān)鍵字,整個(gè)這就是經(jīng)過(guò) Webpack 打包壓縮后的源代碼,目前主流的前端開(kāi)發(fā),如 Vue.js、React.js 的輸出結(jié)果都是類(lèi)似這樣的結(jié)果。 好,那么我們?cè)倏聪缕?JavaScript 代碼是什么樣子的,我們?cè)陂_(kāi)發(fā)者工具中打開(kāi) Sources 選項(xiàng)卡下的 Page 選項(xiàng)卡,然后打開(kāi) js 文件夾,這里我們就能看到 JavaScript 的源代碼,如圖所示。 我們隨便復(fù)制一些出來(lái),看看是什么樣子的,結(jié)果如下: 嗯,就是這種感覺(jué),可以看到一些變量都是一些十六進(jìn)制字符串,而且代碼全被壓縮了。 沒(méi)錯(cuò),我們就是要從這里面找出 token 和 id 的構(gòu)造邏輯,看起來(lái)是不是很崩潰? 要完全分析出整個(gè)網(wǎng)站的加密邏輯還是有一定的難度的,不過(guò)不用擔(dān)心,我們本節(jié)會(huì)一步步地講解逆向的思路、方法和技巧,如果你能跟著這個(gè)過(guò)程走完,相信還是能學(xué)會(huì)一定的 JavaScript 逆向技巧的。
列表頁(yè) Ajax 入口尋找好,那么接下來(lái)我們就開(kāi)始第一步入口的尋找吧,這里就簡(jiǎn)單介紹兩種尋找入口的方式: ·全局搜索標(biāo)志字符串·設(shè)置 Ajax 斷點(diǎn) 全局搜索標(biāo)志字符串一些關(guān)鍵的字符串通常會(huì)作為找尋 JavaScript 混淆入口的依據(jù),我們可以通過(guò)全局搜索的方式來(lái)查找,然后根據(jù)搜索到的結(jié)果大體觀察是否是我們想找的入口。 好,我們重新打開(kāi)列表頁(yè)的 Ajax 接口,看下請(qǐng)求的 Ajax 接口,如圖所示。 這里的 Ajax 接口的 URL 為 https://dynamic6.scrape./api/movie/?limit=10&offset=0&token=NTRhYWJhNzAyYTZiMTc0ZThkZTExNzBiNTMyMDJkN2UxZWYyMmNiZCwxNTg4MTc4NTYz,可以看到帶有 offset、limit、token 三個(gè)參數(shù),那關(guān)鍵就是找 token,我們就全局搜索下 token 是不是存在吧,我們可以點(diǎn)擊開(kāi)發(fā)者工具右上角的下拉選項(xiàng)卡,然后點(diǎn)擊 Search,如圖所示。 ![]() 這時(shí)候可以看到整個(gè)代碼都是壓縮過(guò)后的,只有一行,不好看,我們可以點(diǎn)擊左下角的 ![]() 美化后的結(jié)果就是這樣子了,如圖所示。 ![]() 這時(shí)候可以看到這里彈出來(lái)了一個(gè)新的選項(xiàng)卡,其名稱(chēng)是 JavaScript 文件名加上了 那可以看到這里有 limit、offset、token 然后觀察下其他的邏輯,基本上能夠確定這就是構(gòu)造 Ajax 請(qǐng)求的地方了,如果不是的話(huà)可以繼續(xù)搜索其他的文件觀察下。 那現(xiàn)在,混淆的入口點(diǎn)我們就成功找到了,這是一個(gè)首選的找入口的方法。 XHR 斷點(diǎn)由于這里 token 這個(gè)字符串并沒(méi)有被混淆,所以上面的這個(gè)方法是奏效的。之前我們也講過(guò),這種字符串由于非常容易成為找尋入口點(diǎn)的依據(jù),所以這樣的字符串也會(huì)被混淆成類(lèi)似 Unicode、Base64、RC4 的一些編碼形式,這樣我們就沒(méi)法輕松搜索到了。 那如果遇到這種情況,我們可以怎么辦呢?這里再介紹一種通過(guò)打 XHR 斷點(diǎn)的方式來(lái)尋找入口。 XHR 斷點(diǎn),顧名思義,就是在發(fā)起 XHR 的時(shí)候進(jìn)入斷點(diǎn)調(diào)試模式,JavaScript 會(huì)在發(fā)起 Ajax 請(qǐng)求的時(shí)候挺住,這時(shí)候我們可以通過(guò)當(dāng)前的調(diào)用棧的邏輯順著找到入口。 怎么設(shè)置呢?我們可以在 Sources 選項(xiàng)卡的右側(cè),XHR/fetch Breakpoints 處添加一個(gè)斷點(diǎn)選項(xiàng)。首先點(diǎn)擊 ![]() 添加完畢之后重新刷新下頁(yè)面,可以發(fā)現(xiàn)就進(jìn)入了斷點(diǎn)模式,如圖所示。 ![]() 好,接下來(lái)我們重新點(diǎn)下 ![]() 那這里看到有個(gè) send 的字符,我們可以初步猜測(cè)這就是相當(dāng)于發(fā)送 Ajax 請(qǐng)求的一瞬間。 那到了這里感覺(jué) Ajax 都馬上要發(fā)出去了,是不是有點(diǎn)太晚了,我們想找的是構(gòu)造 Ajax 的那個(gè)時(shí)候來(lái)分析 Ajax 參數(shù)啊?不用擔(dān)心,這里我們通過(guò)調(diào)用棧就可以找回去。我們點(diǎn)擊右側(cè)的 Call Stack,這里記錄了 JavaScript 的方法逐層調(diào)用過(guò)程,如圖所示。 ![]() 這里當(dāng)前指向的是一個(gè)名字為 anonymouns,也就是匿名的調(diào)用,在它的下方就顯示了調(diào)用這個(gè) anonymouns 的方法,名字叫做 這里我們可以逐個(gè)往下找下去,然后通過(guò)一些觀察看看有沒(méi)有 token 這樣的信息,就能找到對(duì)應(yīng)的位置了,最后我們就可以找到 onFetchData 這個(gè)方法里面實(shí)現(xiàn)了這個(gè) token 的構(gòu)造邏輯,這樣我們也成功找到 token 的參數(shù)構(gòu)造的位置了,如圖所示。 好,那到現(xiàn)在為止我們就通過(guò)兩個(gè)方法找到入口點(diǎn)了。其實(shí)還有其他的尋找入口的方式,比如 Hook 關(guān)鍵函數(shù)的方式,稍后后文我們會(huì)講到,這里就暫時(shí)不講了。 列表頁(yè)加密邏輯尋找好,那么接下來(lái)我們已經(jīng)找到 token 的位置了,可以觀察一下這個(gè) token 對(duì)應(yīng)的變量叫做 怎么找呢?我們打個(gè)斷點(diǎn)就好了。 看下這個(gè)變量是在哪里生成的,我們?cè)趯?duì)應(yīng)的行打一個(gè)斷點(diǎn),如果打了剛才的 XHR 斷點(diǎn)的話(huà)可以先取消掉,如圖所示。 ![]() 這時(shí)候我們就設(shè)置了一個(gè)新的斷點(diǎn)了。由于只有一個(gè)斷點(diǎn),可以重新刷新下網(wǎng)頁(yè),這時(shí)候我們會(huì)發(fā)現(xiàn)網(wǎng)頁(yè)停在了新的斷點(diǎn)上面。 ![]() 那這里我們就可以觀察下運(yùn)行的一些變量了,比如我們把鼠標(biāo)放在各個(gè)變量上面去,可以看到變量的一些值和類(lèi)型,比如我們看 ![]() 另外我們還可以通過(guò)在右側(cè)的 Watch 面板添加想要查看的變量名稱(chēng),如這行代碼的內(nèi)容為: , _0xa70fc9 = Object(_0x18b11a['a'])(this['$store']['state']['url']['index']);我們比較感興趣的可能就是 ![]() 觀察下可以發(fā)現(xiàn) 那么下面一步就是去尋找這個(gè) function 在哪里了,我們可以把 Watch 面板的 ![]() 點(diǎn)擊進(jìn)入之后發(fā)現(xiàn)其仍然是未格式化的代碼,再次點(diǎn)擊 這時(shí)候我們就進(jìn)入到了一個(gè)新的名字為 ![]() 這時(shí)候就發(fā)現(xiàn)我們就單步執(zhí)行到這個(gè)位置了。 接下來(lái)我們不斷進(jìn)行單步調(diào)試,觀察一下這里面的執(zhí)行邏輯和每一步調(diào)試過(guò)程中結(jié)果都有什么變化,如圖所示。 ![]() 在每步的執(zhí)行過(guò)程中,我們可以發(fā)現(xiàn)一些運(yùn)行值會(huì)被打到代碼的右側(cè)并帶有高亮表示,同時(shí)在 watch 面板還能看到每步的變量具體結(jié)果。 最后我們總結(jié)出這個(gè) token 的構(gòu)造邏輯如下: ·傳入的 以上的一些邏輯經(jīng)過(guò)反復(fù)的觀察就可以比較輕松地總結(jié)出來(lái)了,其中有些變量可以實(shí)時(shí)查看,同時(shí)也可以自己輸入到控制臺(tái)上進(jìn)行反復(fù)驗(yàn)證,相信總結(jié)出這個(gè)結(jié)果并不難。 好,那現(xiàn)在加密邏輯我們就分析出來(lái)啦,基本的思路就是: ·先將 驗(yàn)證下邏輯沒(méi)問(wèn)題的話(huà),我們就可以用 Python 來(lái)實(shí)現(xiàn)出來(lái)啦。 Python 實(shí)現(xiàn)列表頁(yè)的爬取要 Python 實(shí)現(xiàn)這個(gè)邏輯,我們需要借助于兩個(gè)庫(kù),一個(gè)是 hashlib,它提供了 sha1 方法;另外一個(gè)是 base64 庫(kù),它提供了 b64encode 方法對(duì)結(jié)果進(jìn)行 Base64 編碼。 代碼實(shí)現(xiàn)如下:
這里我們就根據(jù)上面的邏輯把加密流程實(shí)現(xiàn)出來(lái)了,這里我們先模擬爬取了第一頁(yè)的內(nèi)容,最后運(yùn)行一下就可以得到最終的輸出結(jié)果了。 另外,還有一些 ·Hook 原理·Hook 注入·Overrides 功能·Tampermonkey 插件·Python 模擬實(shí)現(xiàn) 的內(nèi)容,我放在了下一節(jié),下一節(jié)會(huì)于 5.3 號(hào)發(fā)出,我們不見(jiàn)不散~ 崔慶才 靜覓博客博主,《Python3網(wǎng)絡(luò)爬蟲(chóng)開(kāi)發(fā)實(shí)戰(zhàn)》作者 隱形字 個(gè)人公眾號(hào):進(jìn)擊的Coder ![]() |
|
|
來(lái)自: liqualife > 《爬蟲(chóng)》