前言
最近收到粉絲的私信,其在逆向某個站點時遇到了些問題,在查閱資料未果後,來詢問K哥,K哥一向會盡力滿足粉絲的需求。網上大多數分析該站點的教程已經不再適用,本文K哥將提供 3 種解決方案
,對於 webpack 不太熟練的小夥伴來說,這是一個很好的練手案例:
逆向目標
- 目標:某點資料,排行榜
- 地址:aHR0cHM6Ly9hcHAuZGlhbmRpYW4uY29tL3JhbmsvaW9zLw==
逆向過程
抓包分析
開啟開發者人員工具,隨便開啟一個區域排行榜,在 Network 中即會抓包到相應的排行榜資料介面,即
/pc/app/v1/rank
,響應內容如下:
請求引數如下,其中主要引數為 K
引數,需要透過演算法生成,其他引數 time,country_id 等,是時間戳以及一些固定的 id 值,k 值每次都會發生變化,需要進行分析研究:
本文將會用多種方法實現資料的採集,適合不同的技術群體。
協議採集
逆向分析
該介面是透過 XHR 進行請求的,我們直接下一個 XHR 斷點 api.diandian.com/pc/app/v1/rank
,重新整理排行榜,成功斷了下來:
並沒有發現任何 k 值,所以我們找網路攔截器,透過堆疊找到 m.request 的地方,在此處下一個斷點,斷下來以後,檢視 m 變數,裡面儲存了很多回撥方法:
透過檢視 m 方法,我們在 onRequest
方法上下一個斷點,同時在方法末尾也下斷點,同時將上圖的斷點進行方向,再次檢視排行榜,發現在 onRequest 上成功斷了下來。同時我們發現,在呼叫 t 函式之前並沒有 k 引數的生成:
我們繼續執行,在方法結束末尾斷點斷了下來,發現右側已經有 k 引數的生成。所以,由此判斷,k 引數是透過 t 函式生成的:
我們進入 t 函式,在函式的開頭和結尾分別下一個斷點,發現透過 M 函式以後,生成了 k 值:
繼續跟進 M 函式,我們檢視 k 值是如何生成的:
透過分析得到 k 值的生成邏輯為:
var r = h()(t.params, !1)
, o = Object(y.a)(r, path, {
s: n.s,
k: n.k,
l: n.l,
d: n.d,
sort: n.sort,
num: n.num
}, "get");
t.params.k = o
所以他是透過 y.a 傳入 r、path 以及大陣列生成的,我們可以透過將 y.a 函式扣出來實現 k 引數的生成。
第一種思路我們可以看看 y 函式是如何被定義,可以看到它是 webpack 打包,呼叫 2294 模組來實現的:
第二種思路,我們進入 y.a 函式,進行演算法的還原,其生成邏輯如下:
發現是透過 Object(l.b) 函式加密成位元組集,然後透過 t.from 方法編碼成 base64 進行展示,所以我們只需將這兩部分進行演算法還原即可復現 k 引數的生成,當然本文將會用不同的方法進行分析。
手動 webpack + 補環境
在 y=n(2294) 下斷點,重新整理排行榜,在該行成功斷下來:
進入分發器 n 中,將 runtime.js 全部拿下,放到我們本地:
拿到本地以後,將分發器匯出,window.kk=r
。
控制檯透過 n.m[模組名] 將所需的模組進行查詢:
拿到對應模組後,將 js 裡的模組複製放到我們剛剛扣的分發器中:
透過模組,呼叫加密函式,檢視報錯資訊:
a = window.kk(2294)
r = {
"start_time": 1717776000,
"end_time": 1718345618
}
n = {
"proxy": "/app",
"target": "",
"sort": "dd",
"num": 10,
"s": "d044bec62c1c9f9eee1ebd567e501719",
"k": "93086c0e7c41cf46",
"l": "091043cf5d1393af",
"d": 0
}
path = "/v2/user/monitor/msg"
o = Object(a.a)(r, path, {
s: n.s,
k: n.k,
l: n.l,
d: n.d,
sort: n.sort,
num: n.num
}, "get");
console.log(o)
這種錯誤就是提示缺少對應模組,我們只需根據呼叫堆疊向上檢視,補上缺失的模組即可:
然後在控制檯用 n.m[模組名] 進行模組查詢,然後重複上述操作將找到的模組放入分發器即可,它這個站模組分佈在幾百個 js 中,也算是一種程式碼混淆了:
最後板凳坐穿,全部模組找完大概 6w 多行程式碼吧,結果如下:
自動扣 webpack 模組
網上自動扣 webpack 的方法很多,但是對於幾百個 js 檔案的模組來說,可能就不太適用,上部分手動透過 n.m[模組名] 進行模組查詢的方法是最通用的,但是對於多個 js 檔案模組就略顯繁瑣。所以我們可以透過重寫分發器的方法,將載入的模組自動儲存然後匯出。
首先方法同上,先找到分發器的位置,在 r.e 及它之前下個斷點:
重新整理頁面,發現在 r.e 的地方成功斷住,我們將以下 js 程式碼在控制檯進行注入:
window.code = '';
r = function (e) {
if (r[e])
return r[e].exports;
var d = r[e] = {
i: e,
l: !1,
exports: {}
};
console.log(e)
window.code += e + ':' + o[e] + ',\r\n'
return o[e].call(d.exports, d, d.exports, r),
d.l = !0,
d.exports
}
然後回車重新整理瀏覽器,進行一遍檢視排行榜的操作,發現控制檯就會自動列印載入的模組:
控制檯輸入 copy(window.code)
將模組匯出,然後同上述方法一樣放到分發器中即可,再掛上代理,將常規的 document、navigator 補一下即可呼叫。最後結果如下:
演算法還原
分析完 webpack 與補環境以後,我們最後來講講如何用演算法生成。上文提到,我們進入 y.a 函式後,發現他主要是透過 Object(l.b) 和 t.from 這兩個函式生成的,進入 Object(l.b) 發現是一個 AES 方法:
其中又發現了這個 t.from 方法,這個方法其實就是一個 utf8 編碼,復現如下:
t=[]
t.from=function (hexString, encoding) {
if (encoding !== "utf8") {
throw new Error("Unsupported encoding");
}
// 將每個字元轉換為對應的 UTF-8 編碼的數值
let byteArray = new Uint8Array(hexString.split('').map(char => char.charCodeAt(0)));
return byteArray;
}
剩餘的加密方法,我們引庫復現即可:
const crypto = require('crypto');
var c = crypto.createDecipheriv("aes-128-cbc", n, o);
return d += c.update(e, "hex", "utf8"),
d += c.final("utf8")
同時將 c 與 _ 這倆個函式也補一下(補函式的話,遵循和原函式一致即可,如果讀不懂原函式,可能就會卡在某一部分):
function c(a) {
return function(t) {
return t;
};
}
var n = c()(t);
function _(n) {
return typeof n === 'object' && n !== null;
}
最後全部函式實現完畢以後,結果如下:
只需 70 行即可完成 k 引數的生成,至此全部流程分析完畢。
八爪魚採集
對於新手來說,0 程式碼實現資料採集是不二選擇,同時他還可以設定代理 IP 進行免封操作。使用教程也非常簡單,進入官網選擇對應的系統版本進行下載安裝:
安裝完成以後開啟軟體,首先看到的是他擁有一個模板採集,裡面內建了很多已經配置好的採集任務,點選即可一鍵應用:
我們點選“模板”或者“更多”,搜尋點點資料,發現搜尋未果:
那我們只能手動去新建一個任務,選擇左側新建,然後輸入你要採集的網址:
進去以後,等待網頁載入完畢,然後選擇自動識別,它會根據頁面的佈局自動生成幾套採集模板:
同時你也點選切換識別結果來自由切換識別模板:
很多時候識別的結果不盡如意,你可以選擇刪除某些多餘的欄位,同時也可以點選頁面元素進行文字提取或者滑鼠點選等操作:
在所有需要採集的東西都配置完畢以後,我們可以點選設定,進行代理的設定:
這裡代理選擇,我們採用快代理的私密代理或者獨享代理進行配置即可:
所有任務都準備完成以後,即可儲存進行任務的採集:
關於八爪魚進階的玩法還有自動打碼與點選翻頁等,特殊場景需要進行更多的實際應用。