再見,米哈遊!原神社群防f12控制檯除錯程式碼全解(年輕人不講武德)

萌信發表於2020-12-05

前言

剛剛逛原神社群,不經意間按到了 f12 開啟了控制檯,突然螢幕暗了,發生甚麼事了!

再見,米哈遊!原神社群防f12控制檯除錯程式碼全解(年輕人不講武德)

我一看,嗷,原來是進到無限 debbuger 除錯了,傳統審查講究點到為止,用了 debbuger 這還了得,馬上開始審查原始碼。

左取原始碼

Fiddler 化勁

再見,米哈遊!原神社群防f12控制檯除錯程式碼全解(年輕人不講武德)

別看 Tab 標題是 VM 開頭,其實右下角可以溯源,我們點進去格式化看看:

再見,米哈遊!原神社群防f12控制檯除錯程式碼全解(年輕人不講武德)

原來是這一行不講武德,馬上到 Fiddler 官網下載一個抓包工具,安裝好後 ctrl + f5 重新整理快取找到該檔案:

再見,米哈遊!原神社群防f12控制檯除錯程式碼全解(年輕人不講武德)

下載一份到本地,然後在 Fiddler 右側皮膚配置本地攔截:

再見,米哈遊!原神社群防f12控制檯除錯程式碼全解(年輕人不講武德)

ok,現在我們在該檔案頭追加一行 console.log('拿下!') 重新整理頁面看一下:

(window.webpackJsonp=window.webpackJsonp||[]).push([[3],[,,,function(t,e,n){"use strict";

console.log('拿下');

n.d(e,"d",(function(){return r})),n.d // .........

控制檯:

再見,米哈遊!原神社群防f12控制檯除錯程式碼全解(年輕人不講武德)

謝謝你,Fiddler 。

右判邏輯

不講 rude

練功房(變數存放處)

先在檔案開頭宣告好 rude ,方便存放我們後續的變數:

(window.webpackJsonp=window.webpackJsonp||[]).push([[3],[,,,function(t,e,n){"use strict";

console.log('拿下');
window.rude = {};

n.d(e,"d",(f   // .......

四兩撥千斤(變數分析)

按之前控制檯提示我們的那一行搜尋 r("q287" 找到該處,之後把分號前相關的一部分邏輯做好全域性賦值,方便我們檢視。

賦值方法:

	// 不破壞原始碼結構的區域性賦值
	(window.key = .....)

同時注意不要把整個檔案格式化,動哪一部分只需要簡單手工格式化一下週邊程式碼即可,因為經過 webpack 打包的程式碼完全格式化可能有上萬行,而且會執行錯誤,所以一定要 手工格式化

再見,米哈遊!原神社群防f12控制檯除錯程式碼全解(年輕人不講武德)

邏輯結構如下,邏輯中,我們使用 ['...'] 代表掛載到 window.rude['...'] 上的值:

	(function (){
		// ......
		if(!['if'](['if_param1'], ['if_param2'])) {
			return !0
		}
	})[['plus1'] + ['plus2'] + 'r']
	(['value1'])(['value2'], ['value3'])
	[['arr']]
	(['end'])

年輕人不講 rude ,重新整理頁面,直接拿下!

再見,米哈遊!原神社群防f12控制檯除錯程式碼全解(年輕人不講武德)

整理得到:

	(function (){
		// ......
		if(!['if'](['if_param1'], ['if_param2'])) {
			return !0
		}
	})['const' + 'ructo' + 'r']
	(['value1'])('debu', 'gger')
	['call']
	('action')

至此為止,我們可以確認的是,通過修改函式的 constructor 實現了 debugger ,我們直接從控制檯列印點開 value1 檢視一下:

再見,米哈遊!原神社群防f12控制檯除錯程式碼全解(年輕人不講武德)

很好,一個單純相加的純函式,現在我們只需要解決 ifif_param1if_param2 的問題,他這個函式好像並沒有執行,於是沒有賦值到 rude 內。

訓練有素(手動執行賦值)

先將該函式掛載到全域性上:

0,215)],d[o("$WL(",0,0,0,-8)])){}else(
    // 找到函式開頭
    window.rude['fun'] = function(){var t=function(t,e,n,o,r){return l(t,0,0,0,n- -9)},e=function(t,e
再見,米哈遊!原神社群防f12控制檯除錯程式碼全解(年輕人不講武德)

看起來 if 函式傳入的兩個引數也是混淆過的,那 if 函式內容是什麼?

再見,米哈遊!原神社群防f12控制檯除錯程式碼全解(年輕人不講武德)

我們可以確定的是,雖然傳入 if 函式的兩個變數被混淆過,但 if 函式只是判斷兩個變數全等,這裡兩個變數不等,於是返回了 !0 ;另外,即使我們不知道該 function 上半部分的邏輯,也可以斷定我們主動執行得到的結果仍然是一個存在的可行解。

從而邏輯整理得到:

	(function (){
		return true
	})['const' + 'ructo' + 'r']
	('debugger')
	['call']
	('action')

也就是:

	(function (){ return true })['constructor']('debugger')['call']('action')
	// 等價於
	(function (){ return true }).constructor.('debugger').call('action')

注:實際上,此處的 call('action') 中的 action 是沒有作用的,只是大家 copy 過來 copy 過去的結果,此處毫無意義(可自行百度,你懂的)。

要講武德

點到為止(不安全的 eval 行為)

我們可以看一下該段程式碼前面輸出什麼:

 < (function (){ return true })['constructor']('debugger')
 
 > ƒ anonymous(
   ) {
   	 debugger
   }

嗷!之前無限 debugger 顯示在 VM 內的這段程式碼出處原來在此,那麼呼叫該函式即可實現 debugger

注:實際上使用 ['constructor']('debugger') 是一種 eval 的變體,在一些嚴格情況下會導致報錯:

再見,米哈遊!原神社群防f12控制檯除錯程式碼全解(年輕人不講武德)

這是一種高危行為。

小聰明(迴圈執行計時器)

最後還有一個問題,無限 debugger 的計時器在哪裡?

搜尋檔案全文,一共有 7 個 setInterval ,根據實際中每次點選跳過 debugger 按鍵後要大概等待 4s 再次進入 debugger ,定位到相應位置:

setInterval((function(){
    var t,e,n,o,r={};r[(t=1407,e="ps6m",v(t-410,e))]=function(t){return t()},
    window.rude.r = r,
    r[(n=1407,o="ps6m",v(n-410,o))](window.rude.k = k);
}),4e3);

這裡 4e3 即為 4000 毫秒,將 rk 掛載後發現,該 k 函式指向整個混淆函式(很長),其中包括了上面我們分析過的程式碼,具體我們不做分析了。

總結

經過一番分析,我們通透了無限 debbuger 的原理,這裡再額外提及幾點。

三維立體混元勁(前端混淆)

為了防止簡單爬蟲,一些站點會使用前端混淆,在原神社群使用的是類似 base64 的字典演算法,本文沒有詳細研究。

其實在 webpack 打包下研究混淆價值並不大,如果有興趣的同學可以學習網易雲前端混淆演算法,典例質量高,大家研究的都很通透(自行百度)。

另外前端混淆計算需要的時間很長,本例的混淆計算足足留有 4s ,實際上也要花費很昂貴秒級的時間,建議不要對核心 api 資料介面進行混淆,可以混淆主動傳送時的 token 等幾條少量關鍵詞資料。

好好反思

目前對於 f12 防審查比較優秀的方案是 偵測控制檯 + 跳轉頁面,是一種很友好的方案(同樣有解)。

對於無限 debbuger 方案,因為是 eval 變體,有時還要附加 CSP 規則,所以不建議使用;另外,快速幹掉無限 debbuger 只需要在控制檯點一下忽略 debugger 按鍵即可:

再見,米哈遊!原神社群防f12控制檯除錯程式碼全解(年輕人不講武德)

或是給 debbuger 手工打上條件斷點為 false 即可:

再見,米哈遊!原神社群防f12控制檯除錯程式碼全解(年輕人不講武德)

以上兩種方法都可以一秒幹掉該方案。

以和為貴

堂堂正正講武德是不怕開控制檯的,辦法總比問題多,建議同學們不要耍小聰明,謝謝朋友們!

相關文章