
關注微信公眾號:K哥爬蟲,持續分享爬蟲進階、JS/安卓逆向等技術干貨!
逆向目標
本次逆向的目標來源于吾愛破解 2022 春節解題領紅包之番外篇 Web 中級題,吾愛破解每年都會有派送紅包活動(送吾愛幣),需要大家使出看家逆向本領來分析內容獲得口令紅包,今年一共有五個題,一個送分題,兩個 Windows 題、一個 Android 題和一個 Web 題,本文分析的正是 Web 題,吾愛有規定活動結束前不要外泄口令、討論分享分析程序,所以本文在活動結束后才發出來,
此 Web 題題目是:小 D 最愛看的視頻網站最近關站了,關站前他用 Fiddler 和 Web Archive 保存了一位主播的視頻,但他發現存下來的檔案無法播放,你能幫小 D 找回他的回憶嗎?(.saz 與 .wacz 任選其一即可解題)
為防止吾愛后期關閉解題通道,K哥將 .saz 和 .wacz 檔案保存了一份,可在公眾號后臺回復吾愛破解獲取!
-
活動地址:https://www.52pojie.cn/thread-1582582-1-1.html
-
Web 題地址:https://www.52pojie.cn/home.php?mod=task&do=view&id=17
HLS 流媒體傳輸協議
本題涉及到 HLS 流媒體傳輸協議,先簡單介紹一下,了解的同志可直接跳過,
HLS 全稱 HTTP Live Streaming,即基于 HTTP 的自適應碼率流媒體傳輸協議,是蘋果研發的動態碼率自適應技術,它包括一個 M3U(8) 的索引檔案,若干 TS 視頻流檔案,如果視頻流檔案是加密的,那就還會存在一個 key 加密串檔案,
M3U8 檔案是 M3U 的一種,只不過檔案中存盤的文本使用 UTF-8 字符編碼,在極少數情況下,M3U8 檔案可能會以 M3UP 擴展名保存,M3U8 檔案是各種音頻和視頻播放程式使用的播放串列檔案,它包含了媒體檔案或媒體檔案夾的路徑或 URL,以及有關播放串列的相關資訊,
TS 全稱為 MPEG2-TS,TS 即 Transport Stream 傳輸流,又稱 MPEG-TS、MTS、TP,這種格式的特點就是從視頻流的任一片段開始都是可以獨立解碼的,
針對 TS 格式的檔案,如果是未加密的,一般的播放器就能夠直接播放,也可以使用 FFmpeg 等工具轉換為其他格式,FFmpeg 也可以直接處理 M3U8 檔案,自動解密合并轉換 TS 檔案,當然也有其他大佬寫好的小工具,拖入 M3U8 檔案就直接給你處理好了,

M3U8 檔案內容的大致格式示例如下:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-ALLOW-CACHE:YES
#EXT-X-KEY:METHOD=AES-128,URI="https://www.example.com/m3u8.key"
#EXT-X-TARGETDURATION:5
#EXTINF:4.200000,
https://www.example.com/hls/live_00000.ts
#EXTINF:4.166667,
https://www.example.com/hls/live_00001.ts
#EXTINF:3.600000,
https://www.example.com/hls/live_00002.ts
#EXTINF:2.516667,
https://www.example.com/hls/live_00003.ts
#EXTINF:4.166667,
https://www.example.com/hls/live_00004.ts
#EXTINF:4.166667,
https://www.example.com/hls/live_00005.ts
#EXTINF:4.166667,
https://www.example.com/hls/live_00006.ts
#EXTINF:1.716667,
https://www.example.com/hls/live_00007.ts
#EXT-X-ENDLIST
各標簽含義如下:
#EXTM3U:m3u檔案頭,必須放在第一行,起標示作用;#EXT-X-VERSION:播放串列檔案的兼容版本,若不存在此標記,則默認為協議的第一個版本;#EXT-X-MEDIA-SEQUENCE: 播放串列中的每個媒體 URI 都有一個唯一的整數序列號,URI 的序列號等于它之前的 URI 的序列號加一;#EXT-X-ALLOW-CACHE:指示客戶端是否可以快取下載的媒體片段以供以后重播;#EXT-X-KEY:TS 片段可以被加密,該標簽指定加密方式(METHOD)、密鑰的 URI 以及偏移量 IV 等資訊,沒有此標簽表示未加密;#EXT-X-TARGETDURATION:每一份 TS 媒體檔案的最大持續時間,以秒為單位;#EXTINF:每一份媒體檔案的詳細資訊,包括媒體持續時間、媒體 URL 地址等;#EXT-X-ENDLIST:表示不再將媒體片段添加到播放串列檔案中,一般位于檔案結尾,
完整格式、標準標簽可參考 HLS 標準協議中,對 Playlist file 的介紹:https://datatracker.ietf.org/doc/html/draft-pantos-http-live-streaming-08
SAZ 分析
在 Fiddler 軟體中,使用 SAZ 格式用來保存和讀取 HTTP/HTTPS 請求資訊,打開該檔案可以注意到一些重要的請求:script.bundle.js、live.m3u8、drm 以及八個 ts 視頻流檔案,
先來看看 m3u8 檔案,可以看到是 AES-128 加密,加密的 key 檔案地址為 key://live,如下圖所示:

一般情況下,要想解密 ts,必然會去請求 key 的地址,拿到 key 后再解密 ts,很顯然此題的 key 地址不是一個合法的 URL 地址,當然此題的抓包記錄可能是出題人偽造的,因為這個 Host 是 52tube.mmxxii,也不是一個合法的域名,最主要的是,抓包記錄里沒有 key://live 這條請求,那么很大概率真實的地址隱藏在 JS 里,從另一個方面來思考,如果這是完整的抓包記錄,不管真實的 key 地址是啥,必然會在記錄里出現!
有經驗的朋友應該一眼就能看出來 drm 這條請求最有可能是拿 key 的操作了,第一是 drm 這個關鍵詞在 ts 解密里經常會出現,搞得多的朋友應該見過不少,第二 ping 請求回傳的 success,通過其名稱和回傳值來看也不像 key,剩下就只有 drm 了,查看回傳值是亂碼的,查看 Hex 值,32 位 16 進制資料,而正常的 key 應該是 16 位 16 進制資料,所以你如果直接拿這個資料當作 key 去解密,肯定也是失敗的,
到這里我們應該有如下猜想:drm 回傳的資料,經過了 script.bundle.js 二次處理就能得到正確的 key,

JS 逆向
我們把抓包記錄的 script.bundle.js,右鍵,save - response - response body,保存到本地,
格式化之后有 15000+ 行代碼,又不能動態除錯,從哪里找加密入口呢?可以大膽嘗試一下:
- JS 里可能會檢測到 m3u8 里存在 key 的 URI 之后,發送 /api/drm/ 這個請求,可以直接搜索
/api/drm/或者key://live定位; - drm 是一個 post 請求,帶有 h 和 id 兩個引數,可以直接搜索
post、id、h定位到大致位置,

通過搜索可以發現如下可疑代碼片段:

將關鍵代碼提煉一下:
function n(t) {
return [...new Uint8Array(t)].map((t => t.toString(16).padStart(2, "0"))).join("")
}
function s(t, e) {
let r = new Uint8Array(t.length);
for (let i = 0; i < t.length; i++) r[i] = t[i] ^ e[i];
return r
}
let e = "/api/ping/",
i = "/api/drm/";
class a extends t.DefaultConfig.loader {
let e = await async function() {
let t = new Uint8Array(16);
crypto.getRandomValues(t);
let e = n(t.buffer) + Date.now() + Math.random();
return new Uint8Array((await async function(t) {
const e = (new TextEncoder).encode(t);
return await crypto.subtle.digest("SHA-256", e)
} (e)).slice(0, 16))
}();
var r = new URLSearchParams;
r.append("h", n(e.buffer)),
r.append("id", t);
var a = {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: r
};
let o = await fetch(i, a),
l = await o.arrayBuffer();
if (32 !== l.byteLength) throw new Error("Invalid response");
let u = new Uint8Array(l.slice(0, 16)),
c = new Uint8Array(l.slice(16, 32));
return s(s(u, e), c)
}
可以看到事實上在發送 /api/drm/ 請求拿到結果后,先后取前后 16 位資料,然后經過了 s 方法的處理,最后回傳的 s(s(u, e), c) 應該才是正確的 key,這里的重點在于 e 的值,上面有個方法,取了當前時間+隨機值,經過 SHA-256 加密,再取前 16 位,
這里可以思考一下,這個 e 的值是不固定的,那么最后的 key 應該也是不固定的,同一個 TS 對應有無數個 key,我反正是沒見過,不信的話嘗試就用那個方法生成 e,你會發現最終的 key 是錯誤的,
仔細看一下,發送 post 請求對 h 值賦值的地方:r.append("h", n(e.buffer)),n 方法是轉 16 進制,那么我們直接將 h 值倒推,從16進制轉為10進制,這才是正確的 e 的值!然后 l 的值是 /api/drm/ 請求回傳的 32 位 16 進制資料轉為 10 進制,剩下的就好說了,直接改寫一下 JS 代碼拿到正確的 key:
function s(t, e) {
let r = new Uint8Array(t.length);
for (let i = 0; i < t.length; i++)
r[i] = t[i] ^ e[i];
return r
}
function getKey(){
// /api/drm/ 請求表單的 h 值,16進制資料
const h = ["7b", "10", "31", "1e", "6e", "31", "0f", "0d", "f0", "68", "d9", "ed", "e1", "04", "75", "a8"];
// /api/drm/ 請求回傳的32位16進制資料
const drm = ["08", "A5", "E6", "C2", "C2", "61", "A8", "AC", "B4", "D7", "9C", "49", "AF", "16", "0A", "3A", "DA", "4E", "5C", "EA", "E1", "6F", "ED", "46", "EB", "6F", "49", "8C", "9B", "63", "D5", "3B"]
// 轉換為10進制資料,為 e 和 l 賦值
const e = [];
const l = [];
for (let i=0; i<h.length; i++)
{
e.push(parseInt(h[i],16))
}
for (let i=0; i<drm.length; i++)
{
l.push(parseInt(drm[i],16))
}
const u = new Uint8Array(l.slice(0, 16));
const c = new Uint8Array(l.slice(16, 32));
const keyArray = s(s(u, e), c);
const keyHex = new Buffer.from(keyArray).toString('hex');
const keyBase64 = new Buffer.from(keyArray).toString('base64');
console.log("keyArray: ", keyArray)
console.log("keyHex: ", keyHex)
console.log("keyBase64: ", keyBase64)
}
getKey()
// 輸出
// keyArray: Uint8Array(16) [
// 169, 251, 139, 54, 77,
// 63, 74, 231, 175, 208,
// 12, 40, 213, 113, 170,
// 169
// ]
// keyHex: a9fb8b364d3f4ae7afd00c28d571aaa9
// keyBase64: qfuLNk0/Suev0Awo1XGqqQ==
TS 解密合并轉換
通過 JS 逆向我們拿到了 16進制和 base64 形式的 key,不管什么形式都可以拿來解密,這里介紹兩種對 TS 媒體流解密、合并、轉換的方法,
第一種方法是使用 FFmpeg 工具,FFmpeg 是一套可以用來記錄、轉換數字音頻、視頻,并能將其轉化為流的開源計算機程式,官網地址:https://ffmpeg.org/ ,下載編譯好的程式,將 bin 目錄添加到環境變數即可,該工具也可以直接在K哥爬蟲公眾號后臺回復 M3U8 獲取,
首先我們要把 m3u8 檔案和 ts 媒體流保存到同一個檔案夾,由于是虛假的 Host,所以不能直接瀏覽器訪問保存,可以直接在 Fiddler 里,右鍵,save - response - response body,保存到本地,如下圖所示:

然后就是保存密鑰檔案,這里要求密鑰檔案必須是16進制的資料,如果你直接將 key 以字串形式保存的話,解密也是失敗的,編輯 16 進制檔案有專門的工具,比如 HxD、010 editor、winhex 等,以 HxD 為例,新建檔案,寫入我們前面通過 JS 逆向得到的 key 的 16 進制資料,存為 .key 檔案,如下圖所示:

然后修改 m3u8 檔案里 key 的地址、名稱,建議將 key、m3u8、ts 檔案都放同一個檔案夾,這樣 m3u8 檔案里就不用添加資源路徑了,不容易出錯,

然后在當前檔案夾,打開命令列輸入命令:ffmpeg -allowed_extensions ALL -i live.m3u8 -c copy live.mp4,即可自動解密 ts,并合并轉換為 .mp4 格式:


第二種方法就是使用大佬寫的第三方小工具,這里推薦吾愛大佬逍遙一仙寫的 M3U8 批量下載器,下載地址、使用方法見原貼:https://www.52pojie.cn/thread-1374045-1-1.html ,也可以在K哥爬蟲公眾號后臺回復 M3U8 獲取,
我們可以直接拖入處理好的 M3U8 檔案,自動處理:

也可以選擇其他 - 工具 - 合并助手,添加所有 TS 檔案,輸入 key 后自動處理:

處理完畢后的 mp4 檔案默認在軟體目錄的 output 檔案夾里面,解密后是一段影片,往后看會找到 flag:flag{like_sub_52tube} 為正確答案,




轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/424906.html
標籤:其他
