創新互聯公司專注于企業全網營銷推廣、網站重做改版、青島網站定制設計、自適應品牌網站建設、H5頁面制作、商城系統網站開發、集團公司官網建設、成都外貿網站建設、高端網站制作、響應式網頁設計等建站業務,價格優惠性價比高,為青島等各大城市提供網站開發制作服務。
本文章中所有內容僅供學習交流使用,不用于其他任何目的,不提供完整代碼,抓包內容、敏感網址、數據接口等均已做脫敏處理,嚴禁用于商業用途和非法用途,否則由此產生的一切后果均與作者無關!
本文章未經許可禁止轉載,禁止任何修改后二次傳播,擅自使用本文講解的技術而導致的任何意外,作者均不負責,若有侵權,請在公眾號【K哥爬蟲】聯系作者立即刪除!
瑞數動態安全 Botgate(機器人防火墻)以“動態安全”技術為核心,通過動態封裝、動態驗證、動態混淆、動態令牌等技術對服務器網頁底層代碼持續動態變換,增加服務器行為的“不可預測性”,實現了從用戶端到服務器端的全方位“主動防護”,為各類 Web、HTML5 提供強大的安全保護。
瑞數 Botgate 多用于政企、金融、運營商行業,曾一度被視為反爬天花板,隨著近年來逆向大佬越來越多,相關的逆向文章也層出不窮,真正到了人均瑞數的時代了,這里也感謝諸如 Nanda、懶神等逆向大佬,揭開了瑞數神秘的面紗,總結的經驗讓后來人少走了不少彎路。
過瑞數的方法基本上有以下幾種:自動化工具(要隱藏特征值)、RPC 遠程調用、JS 逆向(硬扣代碼和補環境),本文介紹的是 JS 逆向硬扣代碼,盡可能多的介紹各種細節。
對于絕大多數使用了瑞數的網站來說,有以下幾點特征(可能有特殊版本不一樣,先僅看主流的):
1、打開開發者工具(F12)會依次出現兩個典型的無限 debugger:
2、瑞數的 JS 混淆代碼中,變量、方法名大多類似于 _$xx
,有眾多的 if-else
控制流,新版瑞數還可能會有 jsvmp 以及眾多三目表達式的情況:
3、看請求,會有典型的三次請求,首次請求響應碼是 202(瑞數3、4代)或者 412(瑞數5代),接著單獨請求一個 JS 文件,然后再重新請求頁面,后續的其他 XHR 請求中,都帶有一個后綴,這個后綴的值是由 JS 生成的,每次都會變化,后綴的值第一個數字為瑞數的版本,比如 MmEwMD=4xxxxx
就是4代瑞數,bX3Xf9nD=5xxxxx
就是5代瑞數:
4、看 Cookie,瑞數 3、4 代有以 T 和 S 結尾的兩個 Cookie,其中以 S 開頭的 Cookie 是第一次的 201 那個請求返回的,以 T 開頭的 Cookie 是由 JS 生成的,動態變化的,T 和 S 前面一般會跟 80 或 443 的數字,Cookie 值第一個數字為瑞數的版本(為什么可以通過第一個數字來判斷版本?難道相同版本第一個數字不會變嗎?這些問題我們在分析 JS 的時候可以找到答案),比如:
FSSBBIl1UgzbN7N80T=37Na97B.nWX3....
:數字 80 是 http 協議的默認端口號,對應 http 請求,其值第一位為 3,表示 3 代瑞數;FSSBBIl1UgzbN7N443T=4a.tr1kEXk.....
:數字 443 是 https 協議的默認端口號,對應 https 請求,其值第一位為 4,表示 4 代瑞數。瑞數 5 代也有以 T 和 S 結尾的兩個 Cookie,但有些特殊的 5 代瑞數也有以 O 和 P 結尾的,同樣的,以 O 開頭的是第一次的 412 那個請求返回的,以 P 開頭的是由 JS 生成的,Cookie 值第一個數字同樣為瑞數的版本,和 3、4 代不同的是,5 代沒有加端口號了,比如:
vsKWUwn3HsfIO=57C6DwDUXS.....
:以 O 結尾,其值第一位為 5,表示 5 代瑞數;WvY7XhIMu0fGT=53.9fybty......
:以 T 結尾,其值第一位為 5,表示 5 代瑞數。5、看入口,瑞數有個流程是在虛擬機 VM 中加載 1w+ 行的代碼,加載此代碼的入口,不同版本也不一樣(這個入口具體在哪里?怎么定位?在后續逆向分析中再詳細介紹),示例如下:
_$aW = _$c6[_$l6()](_$wc, _$mo);
,_$c6
實際上是 eval
,_$l6()
實際上是 call
;ret = _$DG.call(_$6a, _$YK);
,_$DG
實際上是 eval
,有關鍵字 ret
,call
是明文;ret = _$Yg.call(_$kc, _$mH);
,有關鍵字 ret,call 是明文,也有沒有 ret 關鍵字的版本,比如 _$ap = _$j5.call(_$_T, _$gp);
,也有像 3 代那樣全部混淆了的,比如:_$x8 = _$mP[_$nU[15]](_$z3, _$Ec);
,_$mP
實際上是 eval
,_$nU[15]
實際上是 call
,混淆的 call
與 3 代的區別就是 5 代是在一個數組里取值得到的;當然要想精準區分不同版本,得各個條件結合起來看,最主要的還是得看看內部的實現邏輯,以及頁面的代碼結構,比如 4 代有一個生成假 Cookie 的步驟,而 5 代沒有,有的特殊版本雖然看起來是 5 代,但是加了 jsvmp 和三目表達式,和傳統的 5 代又有區別,偶爾愚人節啥的突然來個新版本,也會不一樣,各版本在分析一遍之后,就很容易區分了。
本文案例中瑞數 4 代網站為:aHR0cDovL3d3dy5mYW5nZGkuY29tLmNuL25ld19ob3VzZS9uZXdfaG91c2VfZGV0YWlsLmh0bWw=
首先過掉無限 debugger(過不過其實無所謂,后面的分析其實這個基本上沒影響),直接右鍵 Never pause here
永不在此處斷下即可:
定位 Cookie,首選 Hook 來的最快,通過 Fiddler 等抓包工具、油猴腳本、瀏覽器插件等方式注入以下 Hook 代碼:
(function() {
// 嚴謹模式 檢查所有錯誤
'use strict';
// document 為要hook的對象 這里是hook的cookie
var cookieTemp = "";
Object.defineProperty(document, 'cookie', {
// hook set方法也就是賦值的方法
set: function(val) {
// 這樣就可以快速給下面這個代碼行下斷點
// 從而快速定位設置cookie的代碼
console.log('Hook捕獲到cookie設置->', val);
debugger;
cookieTemp = val;
return val;
},
// hook get 方法也就是取值的方法
get: function()
{
return cookieTemp;
}
});
})();
Hook 發現會有生成兩次 Cookie 的情況,斷下之后往上跟棧,可以看到組裝 Cookie 的代碼,類似如下結構:
仔細觀察這兩次 Cookie 生成的地方,分別往上跟棧,你就會發現兩個 Cookie 分別是經過了兩個不同方法得到的,如下圖所示:
這里的代碼存在于 VM 虛擬機中,且是 IIFE 自執行代碼,我們還得往前跟棧看看這些 VM 代碼是從哪里加載出來的,跟棧來到首頁(202頁面)帶有 call 的位置:
我們在文章開頭介紹的這個位置就是這么分析得來的,這個位置通常在分析瑞數的時候作為入口,圖中 _$te
實際上是 eval 方法,傳入的第一個參數 _$fY
是 Window 對象,第二個對象 _$F8
是我們前面看到的 VM 虛擬機中的 IIFE 自執行代碼。
在知道了瑞數大致的入口之后,我們也可以使用事件監聽中的 Script 斷點,一直下一個斷點(F8)就可以走到 202 頁面,然后搜索 call 關鍵字就能快速定位到入口,Script 斷點中的兩個選項,第一個表示運行 JS 腳本的第一條語句時斷下,第二個表示 JS 因為內容安全政策而被屏蔽時斷下,一般選擇第一個就可以了,如下圖所示:
想要后續分析 Cookie 的生成,我們不得不要觀察一下 202 頁面的代碼,meta 標簽有個 content 內容,引用了一個類似于 c.FxJzG50F.dfe1675.js
的 JS 文件,接著跟一個自執行的 JS,如下圖所示:
第1部分 meta 標簽的 content 內容,每次都是變化的,第2部分引用的這個外部 JS 在不同頁面也有所差別,但是同一個網站同一個頁面 JS 里的內容一般是固定不會變的,第3部分自執行代碼每次變化的只是變量名,整體邏輯不變,后續我們在扣代碼的時候,也會用到這里的部分方法。自執行代碼里同樣也是有很多 if-else
控制流,開頭的那個數組,比如上圖中的 _$Dk
就是用來控制后續的控制流的。
引用的 c.FxJzG50F.dfe1675.js
直接打開看是亂碼的,而自執行 JS 的主要作用是將這 JS 亂碼還原成 VM 里的 1w+ 行的正常代碼,并且定義了一個全局變量 window.$_ts
并賦了許多值,這個變量在后續 VM 中作用非常大,meta 標簽的 content 內容同樣也會在 VM 里用到。
由于很多值、變量都是動態變化的,肯定不利于我們的分析,所以我們需要固定一套代碼到本地,打斷點、跟棧都會更加方便,隨便保存一份 202 頁面的代碼,以及該頁面對應的外鏈 JS 文件,如 c.FxJzG50F.dfe1675.js
到本地,使用瀏覽器自帶的 overrides 重寫功能、或者瀏覽器插件 ReRes、或者抓包工具的響應替換功能(如 Fiddler 的 AutoResponder)進行替換。
VM 里面的代碼是生成 Cookie 的主要代碼,包含眾多的 if-else
控制流,無疑增加了我們分析代碼的成本,這里就可以使用 AST 技術做一下反混淆,比如 Nanda 就將 if-else
控制流轉換成了 switch-case
的,同一個控制流下的代碼放在了同一個 case
下,然后在 call
入口那個地方,將 VM 代碼做一下本地替換,具體可以參考 Nanda 的文章:《某數4代邏輯分析》,感興趣的可以試試,不了解 AST 的可以看看以前的文章《逆向進階,利用 AST 技術還原 JavaScript 混淆代碼》,后續有時間 K 哥再寫寫 AST 還原瑞數代碼的實戰,本文咱們選擇硬剛!
前面我們了解了 VM 代碼和 $_ts
的重要性,所以我們第一步是要想辦法拿到他們,至于在什么時候有用到,文章后續再說,復制外鏈 JS,即 c.FxJzG50F.dfe1675.js
的代碼和 202 頁面的自執行代碼到文件,本地直接運行即可,需要輕度補一下環境,缺啥補啥,大致補一下 window、location、document 就行了,補的具體內容可以直接在瀏覽器控制臺使用 copy()
命令復制過來,然后 VM 代碼我們就可以直接 Hook eval 的方式得到,大致的補環境代碼如下:
var eval_js = ""
window = {
$_ts:{},
eval:function (data) {
eval_js = data
}
}
location = {
"ancestorOrigins": {},
"href": "http://www.脫敏處理.com.cn/new_house/new_house_detail.html",
"origin": "http://www.脫敏處理.com.cn",
"protocol": "http:",
"host": "www.脫敏處理.com.cn",
"hostname": "www.脫敏處理.com.cn",
"port": "",
"pathname": "/new_house/new_house_detail.html",
"search": "",
"hash": ""
}
document = {
"scripts": ["script", "script"]
}
觀察 $_ts
的 key 和 value,和瀏覽器中得到的是一樣的:
注意事項:c.FxJzG50F.dfe1675.js
外鏈 JS 如果你直接下載下來用編輯器打開可能會被自動編碼,和原始數據有出入,導致運行報錯,這里建議直接在瀏覽器在線訪問這個文件,手動復制過來,或者在抓包軟件里將響應內容復制過來,觀察以下兩種情況,第一種情況就可能會導致運行出錯,第二種是正常的:
前面說了這么多,現在終于可以進入主題了,那就是扣代碼,找個好椅子,準備把屁股坐穿,此時你的鍵盤只有 F11 有用,不斷單步調試,只需要億點點細節,就完事兒了!
扣代碼步驟太多,不可能每一步都截圖寫出來,只寫一下比較重要的,如有遺漏的地方,那也沒辦法,首先先在我們替換的 202 頁面里,自執行代碼開始的地方手動加個 debugger,一進入頁面就斷下,方便后續的分析:
通過前面我們的分析,已經知道了入口在 call 的地方,快速搜索并下斷點:
通過前面我們的分析,我們也知道了有兩次生成 Cookie 的地方,快速搜索 (5)
,搜索結果第二個即為入口:
首先單步跟假 Cookie,雖然是假的,但是后續生成真 Cookie 中會用到,在跟的時候你會走到這個邏輯里面:
有一步會調用 _$8e()
方法,而 _$8e = _$Q9
,_$Q9
又嵌套在 _$d0
里的,搜索一下哪里調用了 _$d0
,發現是代碼開頭:
那么傳入的參數 _$Wn
是啥呢?單步跟入,是一個方法,作用就是取 202 頁面的 content 內容,那么我們在本地就直接刪掉這個 _$Wn
方法,直接傳入 content 的值即可,如下圖所示:
另外,我們發現,代碼有非常多的在數組里面按索引取值的情況,比如上圖中的 _$PV[68]
的值,實際上就是字符串 content,很顯然我們要把這個數組的來源找到,直接搜索 _$PV =
,可以找到疑似定義和賦值的地方:
所以我們得看看這個 _$iL
方法,傳入了一個非常長的字符串,打斷點進去看看,果然生成了 _$PV
,是一個 725 位的數組:
接下來在扣代碼的過程中,你會經常遇到一個變量,在本文中是 _$sX
:
有沒有很熟悉?這個值就是我們前面拿到的 $_ts
變量,在開頭就可以看到是將 window.$_ts
賦值給了 _$sX
:
繼續走,會走到以下邏輯中:
這里會遇到六個數組,他們都已經有值了,所以我們得找到他們是咋來的,任意搜索其中一個數組名稱,會找到定義和賦值的地方:
賦值明顯是調用了 _$rv
方法,再搜 _$rv
方法,發現是開頭就調用了:
后續沒有什么特別的,一直單步,最后有個 join('')
操作,就生成了假 Cookie:
接下來是生成 Cookie 的名字 FSSBBIl1UgzbN7N80T
,然后將 Cookie 賦值給 document.cookie
,然后又向 localStorage
里面的 $_ck
賦了個值,localStorage
的內容可以直接復制下來,沒有太大影響。
單步跟真 Cookie,在本文中也就是 _$ZN(768, 1);
,可以看到開始進入了無窮無盡的 if-else
控制流:
這里本地應該怎樣處理呢?我的做法是以 _$Hn
和其值命名函數,function _$Hn768(){}
就表示所有走 768 號控制流的方法,繼續跟,生成真 Cookie 的方法基本上在 747 號控制流,后續我們主要以 747 號控制流的各個步驟來看,747 號控制流扣出來的代碼大致如下:
單步跟 747 號控制流,會有個進入第 709 號控制流的步驟,會取先前生成的假 Cookie,經過一系列操作之后返回一個數組:
至此我們在本地同步扣的代碼,如果正常的話,返回的數組也應該是一樣的(后續的數據就不一樣了,有一些時間戳之類的參數參與運算):
繼續跟 747 號控制流,會進入 268 號控制流,接著進入 154 號控制流,這里面會針對自動化工具做一些檢測,如下圖所示:
這里定義了一個變量 _$iL
,檢測不通過就是1,后續又把這個變量賦值給了 _$aW
,所以我們本地保持一致,也為 false 即可(其實我們不用自動化工具的話,這一段檢測就不用管直接返回 false 就行):
繼續跟 268 號控制流,會進入 668 號控制流,668 號控制流就兩個操作,一是生成一個 16 位數組,二是取 $_ts
里面的 4 個變量,加到前面的 16 位后面,組成一個 20 位數組,這 20 位數組的最后 4 位是瑞數核心,其中的映射關系搞錯了請求是通不過的,在五代中這部分的處理邏輯會更加復雜。
這里不是單純的取 $_ts
里的鍵值對,你在扣代碼的時候,你也許會發現怎么本地到這里取值的時候,取出來的不是數字,而是字符串呢?就像下面這種情況:
實際上我們最開始得到的 $_ts
值,是經過了二次處理的,我們以第一個 _$sX._$Xb
為例,直接搜索 _$sX._$Xb
,可以發現這么一個地方:
很明顯這里給 _$sX._$Xb
重新賦值了一遍,我們可以看到等號右邊,先取了一次 _$sX._$Xb
,其值為 _$Rm
,這和我們初始 $_ts
里面對應的值是一樣的,然后我們就得再看看 _$sX["_$Rm"]
又是何方神圣,直接搜索發現是開頭賦值了一個方法,通過調用這個方法來生成新的值:
另外其他三個值也是同樣的套路,賦值的代碼分別為:
_$sX._$Xb = _$sX[_$sX._$Xb](_$BH, _$DP);
_$sX._$oI = _$sX[_$sX._$oI](_$ZJ, _$DS)
_$sX._$EN = _$sX[_$sX._$EN]();
_$sX._$D9 = _$sX[_$sX._$D9](_$iL);
實際上應該是:
_$sX._$Xb = _$sX["_$Rm"](_$BH, _$DP);
_$sX._$oI = _$sX["_$Nw"](_$ZJ, _$DS)
_$sX._$EN = _$sX["_$Uh"]();
_$sX._$D9 = _$sX["_$ci"](_$iL);
進一步來說,實際上是:
_$sX._$Xb = _$1k(_$BH, _$DP);
_$sX._$oI = _$jH(_$ZJ, _$DS)
_$sX._$EN = _$9M();
_$sX._$D9 = _$oL(_$iL);
靜態分析沒問題,我們可以先固定下來,但是實際應用當中這些值都是動態的,那我們應該怎么處理呢?先來多看幾個對比一下找找規律:
可以發現每次對應的位次都不一樣,但是實際上相同位置的方法點進去都是一樣的,也就是說,變的只有方法名和變量名,實現的邏輯是不變的,所以我們只要知道了這四個值分別對應的位置,就能夠拿到正確的值,在本地,我們就可以這樣做:
1、先利用正則匹配出這四個值,如:[_$sX._$Xb, _$sX._$oI, _$sX._$EN, _$sX._$D9]
;
2、再匹配出 VM 代碼開頭的 20 個賦值的語句,如:_$sX._$RH = _$wI; _$sX._$i5 = _$n5;
等;
3、然后通過 $_ts
取這四個值對應的值,相當于:_$sX._$Xb = _$ts._$Xb = _$Rm
;然后再找這四個值所定義的方法在 20 個賦值語句中的位置,相當于:查找 _$sX._$Rm = _$1k;
在 20 個賦值語句中的位置為 7(索引從 0 開始)
4、我們知道了這四個方法在 20 個賦值語句中的位置,那么我們直接匹配本地對應位置的名稱,進行動態替換即可,當然前提是咱們本地已經扣了一套代碼出來了:
經過這樣處理后,就能夠保證這四個值的準確性了。
除了上面說的 20 位數組里用到了 4 個 $_ts
的值以外,還有其他地方有 7 個值也用到了,直接搜索就能定位,這 7 個值相對較簡單,每次都是固定取 $_ts
里面的第 2、3、4、15、16、17、19 位的值,同樣的,找到對應位置,進行動態替換即可:
特別注意 VM 代碼開頭,會直接調用執行一些方法,某些變量的值就是通過這些方法生成的,當你一步一步跟的時候發現某些參數不對,或者沒有,那么就得注意開頭這些方法了,可能一開始就已經生成了。
后續的其他 XHR 請求中,都帶有一個后綴,這個后綴的值同樣是由 JS 生成的,每次都會變化,當然不同網站,后綴名不一定都是一樣的,本例中是 MmEwMD
,先下一個 XHR 斷點,當 XHR 請求中包含了 MmEwMD=
時就斷下,然后刷新網頁:
可以看到后傳入 l.open()
的 URL 還是正常的,斷下后到 l.send()
就帶有后綴了,再看 l.open()
其實就是 xhr.open()
,明顯和正常的有區別,同樣這個方法也在 VM 代碼里,應該是重寫了方法,可以和正常的做對比:
跟到 VM 代碼里去看看,經過了 _$sd(arguments[1])
方法就變成了帶有后綴的完整鏈接了:
跟進 _$sd
方法,前面都是對 url 做一些處理,后面有個進入第 779 號控制流的流程,實際上就是原來我們生成 Cookie 的步驟,跟一下就行了。
開發者工具的 Watch 功能能夠持續跟蹤某個變量的值,對于這種控制流很多的情況,設置相應的變量跟蹤,能夠讓你知道你現在處于哪個控制流中,以及生成的數組的變化,不至于跟著跟著不知道到哪一步了。
如果整個流程沒問題,代碼也扣得正確,攜帶正確的 Cookie 和正確的后綴,就能成功訪問:
本文標題:人均瑞數系列,瑞數 4 代 JS 逆向分析
鏈接URL:http://m.kartarina.com/article12/dsogggc.html
成都網站建設公司_創新互聯,為您提供服務器托管、做網站、網站建設、云服務器、微信公眾號、標簽優化
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯