聲明
本文章中所有內(nèi)容僅供學(xué)習(xí)交流,抓包內(nèi)容规哲、敏感網(wǎng)址跟啤、數(shù)據(jù)接口均已做脫敏處理,嚴(yán)禁用于商業(yè)用途和非法用途,否則由此產(chǎn)生的一切后果均與作者無關(guān)隅肥,若有侵權(quán)竿奏,請聯(lián)系我立即刪除!
逆向目標(biāo)
- 目標(biāo):某航 airasia 航班狀態(tài)查詢腥放,請求頭 Authorization 參數(shù)
- 主頁:
aHR0cHM6Ly93d3cuYWlyYXNpYS5jb20vZmxpZ2h0c3RhdHVzLw==
- 接口:
aHR0cHM6Ly9rLmFwaWFpcmFzaWEuY29tL2ZsaWdodHN0YXR1cy9zdGF0dXMvb2QvdjMv
- 逆向參數(shù):
- Request Headers:
authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI......
- Request Headers:
逆向過程
抓包分析
來到航班狀態(tài)查詢頁面泛啸,隨便輸入出發(fā)地和目的地,點(diǎn)擊查找航班秃症,例如查詢澳門到吉隆坡的航班候址,MFM 和 KUL 分別是澳門和吉隆坡國際機(jī)場的代碼,查詢接口由最基本的 URL + 機(jī)場代碼 + 日期組成种柑,類似于:https://xxxxxxxxxx/MFM/KUL/28/09/2021 岗仑,其中請求頭 Request Headers 里有個 authorization 參數(shù),通過觀察發(fā)現(xiàn)聚请,不管是清除 cookie 還是更換瀏覽器荠雕,此參數(shù)的值是一直不變的,經(jīng)過測試驶赏,直接復(fù)制該參數(shù)到代碼里也是可行的舞虱,但本次我們的目的是通過編寫瀏覽器插件來 Hook 這個參數(shù),找到它生成的地方母市。
有關(guān) Hook 的詳細(xì)知識,在 K 哥前期的文章有詳細(xì)介紹:JS 逆向之 Hook损趋,吃著火鍋唱著歌患久,突然就被麻匪劫了!
瀏覽器插件 Hook
瀏覽器插件事實上叫做瀏覽器擴(kuò)展(extensions)浑槽,它能夠增強(qiáng)瀏覽器功能蒋失,比如屏蔽廣告、管理瀏覽器代理桐玻、更改瀏覽器外觀等篙挽。
既然是通過編寫瀏覽器插件的方式進(jìn)行 Hook,那么首先我們肯定是要簡單了解一下如何編寫瀏覽器插件了镊靴,編寫瀏覽器插件也有對應(yīng)的規(guī)范铣卡,在以前,不同瀏覽器的插件編寫方式都不太一樣偏竟,到現(xiàn)在基本上都和 Google Chrome 插件的編寫方式一樣了煮落,Google Chrome 的插件除了能運(yùn)行在 Chrome 瀏覽器之外,還可以運(yùn)行在所有 webkit 內(nèi)核的國產(chǎn)瀏覽器踊谋,比如 360 極速瀏覽器蝉仇、360 安全瀏覽器、搜狗瀏覽器、QQ 瀏覽器等等轿衔,另外沉迹,F(xiàn)irefox 火狐瀏覽器也有很多人使用,火狐瀏覽器插件的開發(fā)方式變化了很多次害驹,但是從 2017 年 11 月底開始鞭呕,插件必須使用 WebExtensions APIs 進(jìn)行構(gòu)建,其目的也是為了和其他瀏覽器統(tǒng)一裙秋,一般的 Google Chrome 插件也能直接運(yùn)行在火狐瀏覽器上琅拌,但是火狐瀏覽器插件需要要經(jīng)過 Mozilla 簽名后才能安裝,否則只能臨時調(diào)試摘刑,重啟瀏覽器后插件就沒有了进宝,這一點(diǎn)較為不便。
一個瀏覽器插件的開發(fā)說簡單也簡單枷恕,說復(fù)雜也復(fù)雜党晋,不過對于我們做爬蟲逆向的開發(fā)人員來說,我們主要是利用插件對代碼進(jìn)行 Hook徐块,我們只需要知道一個插件是由一個 manifest.json 和一個 JavaScript 腳本文件組成的就夠了未玻,接下來 K 哥以本案例中請求頭的 authorization 參數(shù)為例,帶領(lǐng)大家開發(fā)一個 Hook 插件胡控。當(dāng)然扳剿,如果你想深入研究瀏覽器插件的開發(fā),可以參考 Google Chrome 擴(kuò)展文檔和 Firefox Browser 擴(kuò)展文檔昼激。
按照 Google Chrome 插件的開發(fā)規(guī)范庇绽,首先新建一個文件夾,該文件夾下包含一個 manifest.json 文件和一個 JS Hook 腳本橙困,當(dāng)然瞧掺,如果你想為你的插件配置一個圖標(biāo)的話,也可以將圖標(biāo)放到該文件夾下凡傅,圖標(biāo)格式官方建議 PNG辟狈,也可以是 WebKit 支持的任何格式,包括 BMP夏跷、GIF哼转、ICO 和 JPEG 等,注意:manifest.json 文件名不可更改槽华!正常的插件目錄類似如下結(jié)構(gòu):
JavaScript Hook
├─ manifest.json // 配置文件释簿,文件名不可更改
├─ icons.png // 圖標(biāo)
└─ javascript_hook.js // Hook 腳本,文件名順便取
manifest.json
manifest.json 是一個 Chrome 插件中最重要也是必不可少的文件硼莽,它用來配置所有和插件相關(guān)的配置庶溶,必須放在根目錄煮纵。其中,manifest_version偏螺、name行疏、version 這 3 個參數(shù)是必不可少的,本案例中套像,manifest.json 文件配置如下:(完整配置參考 Chrome manifest file format)
{
"name": "JavaScript Hook", // 插件名稱
"version": "1.0", // 插件版本
"description": "JavaScript Hook", // 插件描述
"manifest_version": 2, // 清單版本酿联,必須是2或者3
"icons": { // 插件圖標(biāo)
"16": "/icons.png", // 圖標(biāo)路徑,插件圖標(biāo)不同尺寸也可以是同一張圖
"48": "/icons.png",
"128": "/icons.png"
},
"content_scripts": [{
"matches": ["<all_urls>"], // 匹配所有地址
"js": ["javascript_hook.js"], // 注入的代碼文件名和路徑夺巩,如果有多個贞让,則依次注入
"all_frames": true, // 允許將內(nèi)容腳本嵌入頁面的所有框架中
"permissions": ["tabs"], // 權(quán)限申請,tabs 表示標(biāo)簽
"run_at": "document_start" // 代碼注入的時間
}]
}
這里需要注意以下幾點(diǎn):
- manifest_version:配置清單版本柳譬,目前支持 2 和 3喳张,2 將會在將來被逐步淘汰,將來也可能推出 4 或者更高版本美澳∠浚可以在官網(wǎng)查看 Manifest V2 和 Manifest V3 的區(qū)別,3 有更高的隱私安全要求制跟,這里推薦使用 2舅桩。
- content_scripts:Chrome 插件中向頁面注入腳本的一種形式,包括地址匹配(支持正則表達(dá)式)雨膨,要注入的 JS擂涛、CSS 腳本,代碼注入的時間(建議 document_start聊记,網(wǎng)頁開始加載時就注入)等歼指。
javascript_hook.js
javascript_hook.js 文件里就是 Hook 代碼了:
var hook = function () {
var org = window.XMLHttpRequest.prototype.setRequestHeader;
window.XMLHttpRequest.prototype.setRequestHeader = function (key, value) {
if (key == 'Authorization') {
debugger;
}
return org.apply(this, arguments);
}
}
var script = document.createElement('script');
script.textContent = '(' + hook + ')()';
(document.head || document.documentElement).appendChild(script);
script.parentNode.removeChild(script);
XMLHttpRequest.setRequestHeader()
是設(shè)置 HTTP 請求頭部的方法,定義了一個變量 org 來保存原始方法甥雕,window.XMLHttpRequest.prototype.setRequestHeader
這里有個原型對象 prototype,所有的 JavaScript 對象都會從一個 prototype 原型對象中繼承屬性和方法胀茵,具體可以參考菜鳥教程 JavaScript prototype 的介紹社露。一旦程序在設(shè)置請求頭中的 Authorization 時,就會進(jìn)入我們的 Hook 代碼琼娘,通過 debugger 斷下峭弟,最后依然將所有參數(shù)返回給 org,也就是 XMLHttpRequest.setRequestHeader()
這個原始方法脱拼,保證數(shù)據(jù)正常傳輸瞒瘸。然后創(chuàng)建 script 標(biāo)簽,script 標(biāo)簽內(nèi)容是將 Hook 函數(shù)變成 IIFE 自執(zhí)行函數(shù)熄浓,然后將其插入到網(wǎng)頁中情臭。
到此我們?yōu)g覽器插件就編寫完成了,接下來介紹如何在 Google Chrome 和 Firefox Browser 中使用。
Google Chrome
在瀏覽器地址欄輸入 chrome://extensions 或者依次點(diǎn)擊右上角【自定義及控制 Google Chrome】—>【更多工具】—>【擴(kuò)展程序】俯在,進(jìn)入擴(kuò)展程序頁面竟秫,再依次選擇開啟【開發(fā)者模式】—>【加載已解壓的擴(kuò)展程序】,選擇整個 Hook 插件文件夾(文件夾里應(yīng)包含 manifest.json跷乐、javascript_hook.js 和圖標(biāo)文件)肥败,如下圖所示:
Firefox Browser
火狐瀏覽器不能直接安裝未經(jīng)過 Mozilla 簽名認(rèn)證的插件,只能通過調(diào)試附加組件的方式進(jìn)行安裝愕提。插件的格式必須是 .xpi馒稍、.jar、.zip 的浅侨,所以需要我們將 manifest.json纽谒、javascript_hook.js 和圖標(biāo)文件一起打包,打包需要注意不要包含頂層目錄仗颈,直接全選右鍵壓縮即可佛舱,否則在安裝時會提示 does not contain a valid manifest。
在瀏覽器地址欄輸入 about:addons
或者依次點(diǎn)擊右上角【打開應(yīng)用程序菜單】—>【擴(kuò)展和主題】挨决,也可以直接使用快捷鍵 Ctrl + Shift + A 來到擴(kuò)展頁面请祖,在管理您的擴(kuò)展目錄旁有個設(shè)置按鈕,點(diǎn)擊選擇【調(diào)試附加組件】脖祈,在臨時擴(kuò)展項目下肆捕,選擇【臨時載入附加組件】,選擇 Hook 插件的壓縮包即可盖高。
也可以直接在瀏覽器地址欄輸入 about:debugging#/runtime/this-firefox
慎陵,直接進(jìn)入到臨時擴(kuò)展頁面,如下圖所示:
自此喻奥,瀏覽器 Hook 插件我們就開發(fā)安裝完畢了席纽,重新來到航班查詢頁面,隨便輸入出發(fā)地和目的地撞蚕,點(diǎn)擊查找航班润梯,就可以看到此時已經(jīng)成功斷下:
TamperMonkey 插件 Hook
前面我們已經(jīng)介紹了如何自己編寫一個瀏覽器插件,但是不同瀏覽器插件的編寫始終是大同小異的甥厦,有可能你編寫的某個插件在其他瀏覽器上運(yùn)行不了纺铭,而 TamperMonkey 就可以幫助我們解決這個問題,TamperMonkey 俗稱油猴插件刀疙,它本身就是一個瀏覽器擴(kuò)展舶赔,是最為流行的用戶腳本管理器,基本上支持所有帶有擴(kuò)展功能的瀏覽器谦秧,實現(xiàn)了腳本的一次編寫竟纳,所有平臺都能運(yùn)行撵溃,用戶可以在 GreasyFork、OpenUserJS 等平臺直接獲取別人發(fā)布的腳本蚁袭,功能眾多且強(qiáng)大征懈,同樣的,我們也可以利用 TamperMonkey 來實現(xiàn) Hook揩悄。
TamperMonkey 可以直接在各大瀏覽器擴(kuò)展商店里面安裝卖哎,也可以去 TamperMonkey 官網(wǎng)進(jìn)行安裝,安裝過程這里不再贅述删性。
安裝完成后點(diǎn)擊圖標(biāo)亏娜,添加新腳本,或者點(diǎn)擊管理面板蹬挺,再點(diǎn)擊加號新建腳本维贺,寫入以下 Hook 代碼:
// ==UserScript==
// @name JavaScript Hook
// @namespace http://tampermonkey.net/
// @version 0.1
// @description JavaScript Hook 腳本
// @author K哥爬蟲
// @include *://*airasia.com/*
// @icon https://profile.csdnimg.cn/1/B/8/3_kdl_csdn
// @grant none
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
var org = window.XMLHttpRequest.prototype.setRequestHeader;
window.XMLHttpRequest.prototype.setRequestHeader = function (key, value) {
if (key == 'Authorization') {
debugger;
}
return org.apply(this, arguments);
};
})();
整個代碼 JavaScript 部分是個 IIFE 立即執(zhí)行函數(shù),具體含義就不解釋了巴帮,前面瀏覽器插件開發(fā)時已經(jīng)講過溯泣,重要的是上面幾行注釋,千萬不要以為這只是簡單的注釋榕茧,可有可無垃沦,在 TamperMonkey 中用押,可以將這部分視為基本的配置選項肢簿,各項都有其具體含義,完整的配置選項參考 TamperMonkey 官方文檔池充,常見配置項如下表所示(其中需要特別注意 @match
收夸、@include
和 @run-at
選項):
選項 | 含義 |
---|---|
@name | 腳本的名稱 |
@namespace | 命名空間功氨,用來區(qū)分相同名稱的腳本,一般寫作者名字或者網(wǎng)址就可以 |
@version | 腳本版本,油猴腳本的更新會讀取這個版本號 |
@description | 描述這個腳本是干什么用的 |
@author | 編寫這個腳本的作者的名字 |
@match |
從字符串的起始位置匹配正則表達(dá)式围来,只有匹配的網(wǎng)址才會執(zhí)行對應(yīng)的腳本跺涤,例如 * 匹配所有匈睁,https://www.baidu.com/* 匹配百度等,可以參考 Python re 模塊里面的 re.match() 方法桶错,允許多個實例 |
@include |
和 @match 類似航唆,只有匹配的網(wǎng)址才會執(zhí)行對應(yīng)的腳本,但是 @include 不會從字符串起始位置匹配院刁,例如 *://*baidu.com/* 匹配百度糯钙,具體區(qū)別可以參考 TamperMonkey 官方文檔
|
@icon | 腳本的 icon 圖標(biāo) |
@grant | 指定腳本運(yùn)行所需權(quán)限双揪,如果腳本擁有相應(yīng)的權(quán)限吏砂,就可以調(diào)用油猴擴(kuò)展提供的 API 與瀏覽器進(jìn)行交互。如果設(shè)置為 none 的話苗胀,則不使用沙箱環(huán)境狡刘,腳本會直接運(yùn)行在網(wǎng)頁的環(huán)境中享潜,這時候無法使用大部分油猴擴(kuò)展的 API。如果不指定的話嗅蔬,油猴會默認(rèn)添加幾個最常用的 API |
@require | 如果腳本依賴其他 JS 庫的話剑按,可以使用 require 指令導(dǎo)入,在運(yùn)行腳本之前先加載其它庫 |
@run-at |
腳本注入時機(jī)澜术,該選項是能不能 hook 到的關(guān)鍵艺蝴,有五個值可選:document-start :網(wǎng)頁開始時;document-body :body出現(xiàn)時瘪板;document-end :載入時或者之后執(zhí)行吴趴;document-idle :載入完成后執(zhí)行,默認(rèn)選項侮攀;context-menu :在瀏覽器上下文菜單中單擊該腳本時锣枝,一般將其設(shè)置為 document-start
|
重新來到航班查詢頁面,啟用 TamperMonkey 腳本兰英,如果配置正確的話撇叁,就可以看到我們編寫的 Hook 腳本已開啟,隨便輸入出發(fā)地和目的地畦贸,點(diǎn)擊查找航班陨闹,就可以看到此時已經(jīng)成功斷下:
參數(shù)逆向
不管你是使用瀏覽器插件還是 TamperMonkey 進(jìn)行 Hook,此時 Hook 到的是設(shè)置請求頭 Authorization 的地方薄坏,也就是說 Authorization 的值是產(chǎn)生肯定經(jīng)過了之前的某個函數(shù)或者方法趋厉,那么我們跟進(jìn)開發(fā)者工具的 Call Stack 調(diào)用棧,就一定能夠找到這個方法胶坠,跟調(diào)用棧是一個考驗?zāi)托牡倪^程君账,花費(fèi)時間也比較多。
通常情況下沈善,我們是挨個函數(shù)查看其傳遞的參數(shù)有沒有包含我們目標(biāo)參數(shù)乡数,如果上一個函數(shù)里沒有而下一個函數(shù)里出現(xiàn)了椭蹄,那么大概率加密過程就在這兩個函數(shù)之間,進(jìn)入上一個函數(shù)再進(jìn)行單步調(diào)試净赴,一般就能找到加密代碼绳矩,在本案例中,我們跟到 t.getData
函數(shù)埋下斷點(diǎn)進(jìn)行單步調(diào)試玖翅,可以看到其實后面在反復(fù)調(diào)用 t.subscribe
和 t.call
翼馆,之所以不在這兩個函數(shù)處埋下斷點(diǎn),是因為循環(huán)過多不好調(diào)試烧栋,而且 t.getData
通過名稱判斷也比較可疑写妥。
重新點(diǎn)擊登陸,來到我們剛剛埋下斷點(diǎn)的地方审姓,F(xiàn)11 或者點(diǎn)擊向下箭頭珍特,進(jìn)入函數(shù)內(nèi)部進(jìn)行單步調(diào)試,調(diào)試大約 7 步后魔吐,來到一個 t.getHttpHeader
函數(shù)扎筒,可以看到 Authorization 的值就是 "Bearer " + r.accessToken
,我們在控制臺打印 r.accessToken
可以看到就是我們想要的值酬姆,如下圖所示:
那么重點(diǎn)是這個 r.accessToken
嗜桌,如果你嘗試直接往上找,你會發(fā)現(xiàn)找了很多行也沒有找到辞色,直接搜索關(guān)鍵字 accessToken骨宠,可以發(fā)現(xiàn)在 zUnb 對象里面是直接定義死了的,直接拿來用即可相满,如下圖所示:
關(guān)于出發(fā)地层亿、目的地的各個地方的代碼,是通過 JSON 傳遞過來的立美,很容易找到匿又,可根據(jù)實際需求靈活處理,如下圖所示:
這個案例本身不難建蹄,直接搜索還能更快定位參數(shù)位置碌更,但是本案例重點(diǎn)在于如何使用瀏覽器插件進(jìn)行 Hook 操作,這對于某些無法經(jīng)過搜索得到的參數(shù)洞慎,或者搜索結(jié)果太多難以定位的情況來說痛单,是一個很好的解決方法。
完整代碼
GitHub 關(guān)注 K 哥爬蟲劲腿,持續(xù)分享爬蟲相關(guān)代碼旭绒!歡迎 star !https://github.com/kgepachong/
以下只演示部分關(guān)鍵代碼,不能直接運(yùn)行快压!完整代碼倉庫地址:https://github.com/kgepachong/crawler/
Python 示例代碼
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import requests
status_url = '脫敏處理,完整代碼關(guān)注 GitHub:https://github.com/kgepachong/crawler'
def get_flight_status(departure, destination, date):
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36',
'authorization': '脫敏處理垃瞧,完整代碼關(guān)注 GitHub:https://github.com/kgepachong/crawler'
}
complete_url = status_url + departure + '/' + destination + '/' + date
response = requests.get(url=complete_url, headers=headers)
print(response.text)
if __name__ == '__main__':
departure = input('請輸入出發(fā)地代碼:')
destination = input('請輸入目的地代碼:')
date = input('請輸入日期(例如:29/09/2021):')
# departure = 'MFM'
# destination = 'KUL'
# date = '29/09/2021'
get_flight_status(departure, destination, date)