【2022 年】Python3 爬蟲教程 - JavaScript 逆向調試常用技巧
前面一節我們了解了 JavaScript 的壓縮、混淆等技術,現在越來越多的網站也已經應用了這些技術對其數據接口進行了保護,在做爬蟲時如果我們遇到了這種情況,我們可能就不得不硬著頭皮來去想方設法找出其中隱含的關鍵邏輯了,這個過程我們可以稱之為 JavaScript 逆向。
既然我們要做 JavaScript 逆向,那少不了要用到瀏覽器的開發者工具,因為網頁是在瀏覽器中加載的,所以多數的調試過程也是在瀏覽器中完成的。
工欲善其事,必先利其器。本節我們先來基于 Chrome 瀏覽器介紹一下瀏覽器開發者工具的使用。但由于開發者工具功能十分復雜,本節主要介紹對 JavaScript 逆向有一些幫助的功能,學會了這些,我們在做 JavaScript 逆向調試的過程會更加得心應手。
本節我們以一個示例網站 https://spa2.scrape.center/ 來做演示,用這個示例來介紹瀏覽器開發者工具各個面版的用法。
1. 面板介紹
首先我們用 Chrome 瀏覽器打開示例網站,頁面如圖所示:
接下來打開開發者工具,我們會看到類似圖 xx 所示的結果。
這里可以看到多個面板標簽,如 Elements、Console、Sources 等,這就是開發者工具的一個個面板,功能豐富而又強大,先對面板作下簡單的介紹:
Elements:元素面板,用于查看或修改當前網頁 HTML 節點的屬性、CSS 屬性、監聽事件等等,HTML 和 CSS 都可以即時修改和即時顯示。
Console:控制臺面板,用于查看調試日志或異常信息。另外我們還可以在控制臺輸入 JavaScript 代碼,方便調試。
Sources:源代碼面板,用于查看頁面的 HTML 文件源代碼、JavaScript 源代碼、CSS 源代碼,還可以在此面板對 JavaScript 代碼進行調試,比如添加和修改 JavaScript 斷點,觀察 JavaScript 變量變化等。
Network:網絡面板,用于查看頁面加載過程中的各個網絡請求,包括請求、響應等各個詳情。
Performance:性能面板,用于記錄和分析頁面在運行時的所有活動,比如 CPU 占用情況,呈現頁面性能分析結果,
Memory:內存面板,用于記錄和分析頁面占用內存情況,如查看內存占用變化,查看 JavaScript 對象和 HTML 節點的內存分配。
Application:應用面板,用于記錄網站加載的所有資源信息,如存儲、緩存、字體、圖片等,同時也可以對一些資源進行修改和刪除。
Lighthouse:審核面板,用于分析網絡應用和網頁,收集現代性能指標并提供對開發人員最佳實踐的意見。
了解了這些面板之后,我們來深入了解幾個面板中對 JavaScript 調試很有幫助的功能。
2. 查看節點事件
之前我們是用 Elements 面板來審查頁面的節點信息的,我們可以查看當前頁面的 HTML 源代碼及其在網頁中對應的位置,查看某個條目的標題對應的頁面源代碼,如圖所示。
點擊右側的 Styles 選項卡,可以看到對應節點的 CSS 樣式,我們可以自行在這里增刪樣式,實時預覽效果,這對網頁開發十分有幫助。
在 Computed 選項卡中還可以看到當前節點的盒子模型,比如外邊距、內邊距等,還可以看到當前節點最終計算出的 CSS 的樣式,如圖所示。
接下來切換到右側的 Event Listeners 選項卡,這里可以顯示各個節點當前已經綁定的事件,都是 JavaScript 原生支持的,下面簡單列舉幾個事件。
change:HTML 元素改變時會觸發的事件。
click:用戶點擊 HTML 元素時會觸發的事件。
mouseover:用戶在一個 HTML 元素上移動鼠標會觸發的事件。
mouseout:用戶從一個 HTML 元素上移開鼠標會觸發的事件。
keydown:用戶按下鍵盤按鍵會觸發的事件。
load:瀏覽器完成頁面加載時會觸發的事件。
通常,我們會給按鈕綁定一個點擊事件,它的處理邏輯一般是由 JavaScript 定義的,這樣在我們點擊按鈕的時候,對應的 JavaScript 代碼便會執行。比如在圖 xx 中,我們選中切換到第 2 頁的節點,右側 Event Listeners 選項卡下會看到它綁定的事件。
這里有對應事件的代碼位置,內容為一個 JavaScript 文件名稱 chunk-vendors.77daf991.js,然后緊跟一個冒號,然后再跟了一個數字 7。所以對應的事件處理函數是定義在 chunk-vendors.77daf991.js 這個文件的第 7 行。點擊這個代碼位置,便會自動跳轉 Sources 面板,打開對應的 chunk-vendors.77daf991.js 文件并跳轉到對應的位置,如圖所示。
所以,利用好 Event Listeners,我們可以輕松地找到各個節點綁定事件的處理方法所在的位置,幫我們在 JavaScript 逆向過程中找到一些突破口。
3. 代碼美化
剛才我們已經通過 Event Listeners 找到了對應的事件處理方法所在的位置并成功跳轉到了代碼所在的位置。
但是,這部分代碼似乎被壓縮過了,可讀性很差,根本沒法閱讀,這時候應該怎么辦呢?
不用擔心,Sources 面板提供了一個便捷好用的代碼美化功能。我們點擊代碼面板左下角的格式化按鈕,代碼就會變成如圖所示的樣子。
此時會新出現一個叫作 chunk-vendors.77daf991.js:formatted 的選項卡,文件名后面加了 formatted 標識,代表這是被格式化的結果。我們會發現,原來代碼在第 7 行,現在自動對應到了第 4445 行,而且對應的代碼位置會高亮顯示,代碼可讀性大大增強!
這個功能在調試過程中非常常用,用好這個功能會給我們的 JavaScript 調試過程帶來極大的便利。
4. 斷點調試
接下來介紹一個非常重要的功能 —— 斷點調試。在調試代碼的時候,我們可以在需要的位置上打斷點,當對應事件觸發時,瀏覽器就會自動停在斷點的位置等待調試,此時我們可以選擇單步調試,在面板中觀察調用棧、變量值,以更好地追蹤對應位置的執行邏輯。
那么斷點怎么打呢?我們接著以上面的例子來說。首先單擊如圖所示的代碼行號。
這時候行號處就出現了一個藍色的箭頭,這就證明斷點已經添加好了,同時在右側的 Breakpoints 選項卡下會出現我們添加的斷點的列表。
由于我們知道這個斷點是用來處理翻頁按鈕的點擊事件的,所以可以在網頁里面點擊按鈕試一下,比如點擊第 2 頁的按鈕,這時候就會發現斷點被觸發了,如圖所示。
這時候我們可以看到頁面中顯示了一個叫作 Paused in debugger 的提示,這說明瀏覽器執行到剛才我們設置斷點的位置處就不再繼續執行了,等待我們發號施令執行調試。
此時代碼停在了第 4446 行,回調參數 e 就是對應的點擊事件 MouseEvent 。在右側的 Scope 面板處,可以觀察到各個變量的值,比如在 Local 域下有當前方法的局部變量,我們可以在這里看到 MouseEvent 的各個屬性,如圖所示。
另外我們關注到有一個方法 o,它在 Jr 方法下面,所以切換到 Closure(Jr) 域可以查看它的定義及其接收的參數,如圖所示。
我們可以看到,FunctionLocation 又指向了方法 o ,點擊之后便又可以跳到指定位置,用同樣的方式進行斷點調試即可。
在 Scope 面板還有多個域,這里就不再展開介紹了。總之,通過 Scope 面板,我們可以看到當前執行環境下的變量的值和方法的定義,知道當前代碼究竟執行了怎樣的邏輯。
接下來切換到 Watch 面板,在這里可以自行添加想要查看的變量和方法,點擊右上角的 + 號按鈕,我們可以任意添加想要監聽的對象,如圖所示。
比如這里我們比較關注 o.apply 是一個怎樣的方法,于是點擊添加 o.apply,這里就會把對應的方法定義呈現出來,展開之后可以再點擊 FunctionLocation 定位其源碼位置。
我們還可以切換到 Console 面板,輸入任意的 JavaScript 代碼,便會執行、輸出對應的結果,如圖所示。
如果我們想看看變量 arguments 的第一個元素是什么,那么可以直接敲入 arguments[0],便會輸出對應的結果 MouseEvent,只要在當前上下文能訪問到的變量都可以直接引用并輸出。
此時我們還可以選擇單步調試,這里有 3 個重要的按鈕,如圖所示。
這 3 個按鈕都可以做單步調試,但功能不同。
Step Over Next Function Call:逐語句執行
Step Into Next Function Call:進入方法內部執行
Step Out of Current Function:跳出當前方法
用得較多的是第一個,相當于逐行調試,比如點擊 Step Over Next Function Call 這個按鈕,就運行到了 4447 行,高亮的位置就變成了這一行,如圖所示。
5. 觀察調用棧
在調試的過程中,我們可能會跳到一個新的位置,比如點擊上述 Step Over Next Function Call 幾下,可能會跳到一個叫作 ct 的方法中,這時候我們也不知道發生了什么,如圖所示。
那究竟是怎么跳過來的呢?我們可以觀察一下右側的 Call Stack 面板,就可以看到全部的調用過程了。比如它的上一步是 ot 方法,再上一步是 pt 方法,點擊對應的位置也可以跳轉到對應的代碼位置,如圖所示。
有時候調用棧是非常有用的,利用它我們可以回溯某個邏輯的執行流程,從而快速找到突破口。
6. 恢復 JavaScript 執行
在調試過程中,如果想快速跳到下一個斷點或者讓 JavaScript 代碼運行下去,可以點擊 Resume script execution 按鈕,如圖所示。
這時瀏覽器會直接執行到下一個斷點的位置,從而避免陷入無窮無盡的調試中。
當然,如果沒有其他斷點了,瀏覽器就會恢復正常狀態。比如這里我們就沒有再設置其他斷點了,瀏覽器直接運行并加載了下一頁的數據,同時頁面恢復正常,如圖所示。
7. Ajax 斷點
上面我們介紹了一些 DOM 節點的 Listener,通過 Listener 我們可以手動設置斷點并進行調試。但其實針對這個例子,通過翻頁的點擊事件 Listener 是不太容易找到突破口的。
接下來我們再介紹一個方法 —— Ajax 斷點,它可以在發生 Ajax 請求的時候觸發斷點。對于這個例子,我們的目標其實就是找到 Ajax 請求的那一部分邏輯,找出加密參數是怎么構造的。可以想到,通過 Ajax 斷點,使頁面在獲取數據的時候停下來,我們就可以順著找到構造 Ajax 請求的邏輯了。
怎么設置呢?
我們把之前的斷點全部取消,切換到 Sources 面板下,然后展開 XHR/fetch Breakpoints,這里就可以設置 Ajax 斷點,如圖所示。
要設置斷點,就要先觀察 Ajax 請求。和之前一樣,我們點擊翻頁按鈕 2,在 Network 面板里面觀察 Ajax 請求是怎樣的,請求的 URL 如圖所示。
可以看到 URL 里面包含 /api/movie 這樣的內容,所以我們可以在剛才的 XHR/fetch Breakpoints 面板中添加攔截規則。點擊 + 號,可以看到一行 Break when URL contains: 的提示,意思是當 Ajax 請求的 URL 包含填寫的內容時,會進入斷點停止,這里可以填寫 /api/movie,如圖所示。
這時候我們再點擊翻頁按鈕 3,觸發第 3 頁的 Ajax 請求。會發現點擊之后頁面走到斷點停下來了,如圖所示。
格式化代碼看一下,發現它停到了 Ajax 最后發送的那個時候,即底層的 XMLHttpRequest 的 send 方法,可是似乎還是找不到 Ajax 請求是怎么構造的。前面我們講過調用棧 Call Stack,通過調用棧是可以順著找到前序調用邏輯的,所以順著調用棧一層層找,也可以找到構造 Ajax 請求的邏輯,最后會找到一個叫作 onFetchData 的方法,如圖所示。
接下來切換到 onFetchData 方法并將代碼格式化,可以看到如圖所示的調用方法。
可以發現,可能使用了 axios 庫發起了一個 Ajax 請求,還有 limit、offset、token 這 3 個參數,基本就能確定了,順利找到了突破口!我們就不在此展開分析了,后文會有完整的分析實戰。
因此在某些情況下,我們可以在比較容易地通過 Ajax 斷點找到分析的突破口,這是一個常見的尋找 JavaScript 逆向突破口的方法。
要取消斷點也很簡單,只需要在 XHR/fetch Breakpoints 面板取消勾選即可,如圖所示。
8. 改寫 JavaScript 文件
我們知道,一個網頁里面的 JavaScript 是從對應服務器上下載下來并在瀏覽器執行的。有時候,我們可能想要在調試的過程中對 JavaScript 做一些更改,比如說有以下需求:
發現 JavaScript 文件中包含很多阻撓調試的代碼或者無效代碼、干擾代碼,想要將其刪除。
調試到某處,想要加一行 console.log 輸出一些內容,以便觀察某個變量或方法在頁面加載過程中的調用情況。在某些情況下,這種方法比打斷點調試更方便。
調試過程遇到某個局部變量或方法,想要把它賦值給 window 對象以便全局可以訪問或調用。
在調試的時候,得到的某個變量中可能包含一些關鍵的結果,想要加一些邏輯將這些結果轉發到對應的目標服務器。
這時候我們可以試著在 Sources 面板中對 JavaScript 進行更改,但這種更改并不能長久生效,一旦刷新頁面,更改就全都沒有了。比如我們在 JavaScript 文件中寫入一行 JavaScript 代碼,然后保存,如圖所示。
這時候可以發現 JavaScript 文件上出現了一個感嘆號標志,提示我們做的更改是不會保存的。這時候重新刷新頁面,再看一下更改的這個文件,如圖所示。
有什么方法可以修改呢?其實有一些瀏覽器插件可以實現,比如 ReRes。在插件中,我們可以添加自定義的 JavaScript 文件,并配置 URL 映射規則,這樣瀏覽器在加載某個在線 JavaScript 文件的時候就可以將內容替換成自定義的 JavaScript 文件了。另外,還有一些代理服務器也可以實現,比如 Charles、Fiddler,借助它們可以在加載 JavaScript 文件時修改對應 URL 的響應內容,以實現對 JavaScript 文件的修改。
其實瀏覽器的開發者工具已經原生支持這個功能了,即瀏覽器的 Overrides 功能,它在 Sources 面板左側,如圖所示。
我們可以在 Overrides 面板上選定一個本地的文件夾,用于保存需要更改的 JavaScript 文件,我們來實際操作一下。
首先,根據上文設置 Ajax 斷點的方法,找到對應的構造 Ajax 請求的位置,根據一些網頁開發知識,我們可以大體判斷出 then 后面的回調方法接收的參數 a 中就包含了 Ajax 請求的結果,如圖所示。
我們打算在 Ajax 請求成功獲得 Response 的時候,在控制臺輸出 Response 的結果,也就是通過 console.log 輸出變量 a。
再切回 Overrides 面板,點擊 + 按鈕,這時候瀏覽器會提示我們選擇一個本地文件夾,用于存儲要替換的 JavaScript 文件。這里我選定了一個我任意新建的文件夾 ChromeOverrides,注意,這時候可能會遇到如圖所示的提示,如果沒有問題,直接點擊 “允許” 即可。
這時,在 Overrides 面板下就多了一個 ChromeOverrides 文件夾,用于存儲所有我們想要更改的 JavaScript 文件,如圖所示。
我們可以看到,現在所在的 JavaScript 選項卡是 chunk-19c920f8.012555a2.js:formatted,代碼已經被格式化了。因為格式化后的代碼是無法直接在瀏覽器中修改的,所以為了方便,我們可以將格式化后的文件復制到文本編輯器中,然后添加一行代碼,修改如下:
1
2
3
4
5
6
7
8
...
}).then((function(a) {
console.log('response', a) // 添加一行代碼
var e = a.data
, s = e.results
, n = e.count;
t.loading = !1,
...
接著把修改后的內容替換到原來的 JavaScript 文件中。這里要注意,切換到 chunk-19c920f8.012555a2.js 文件才能修改,直接替換 JavaScript 文件的所有內容即可,如圖所示。
替換完畢之后保存,這時候再切換回 Overrides 面板,就可以發現成功生成了新的 JavaScript 文件,它用于替換原有的 JavaScript 文件,如圖所示。
好,此時我們取消所有斷點,然后刷新頁面,就可以在控制臺看到輸出的 Reponse 結果了,如圖所示。
正如我們所料,我們成功將變量 a 輸出,其中的 data 字段就是 Ajax 的 Response 結果,證明改寫 JavaScript 成功!而且刷新頁面也不會丟失了。
我們還可以增加一些 JavaScript 邏輯,比如直接將變量 a 的結果通過 API 發送到遠程服務器,并通過服務器將數據保存下來,也就完成了直接攔截 Ajax 請求并保存數據的過程了。
修改 JavaScript 文件有很多用途,此方案可以為我們進行 JavaScript 的逆向帶來極大的便利。
9. 總結
本節總結了一些瀏覽器開發者工具中對 JavaScript 逆向非常有幫助的功能,熟練掌握了這些功能會對后續 JavaScript 逆向分析打下堅實的基礎,請大家好好研究。
JavaScript Python
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。