上述文章中,詳細(xì)介紹了瑞數(shù)的特征、如何區(qū)分不同版本、瑞數(shù)的代碼結(jié)構(gòu)以及各自的作用,本文就不再贅述了,不了解的同志可以先去看看之前的文章。
逆向目標(biāo)
- 目標(biāo):瑞數(shù) 6 代
- 網(wǎng)站:aHR0cHM6Ly93d3cudXJidGl4LmhrLw==
Cookie 入口定位
與以往的四代、五代一樣,定位 Cookie,首選 Hook,通過(guò) Fiddler 插件、油猴腳本、瀏覽器插件等方式注入以下 Hook 代碼:
(function() {
var cookieTemp = "";
Object.defineProperty(document, 'cookie', {
set: function(val) {
console.log('Hook捕獲到cookie設(shè)置- >', val);
debugger;
cookieTemp = val;
return val;
},
get: function() {
return cookieTemp;
}
});
})();
VM 代碼以及 $_ts 變量獲取
參考 5 代文章:人均瑞數(shù)系列,瑞數(shù) 5 代 JS 逆向分析
流程分析
與五代一致,用本地替換固定一套代碼。通過(guò) (947, 1)
定位到加密流程入口,開(kāi)始進(jìn)行流程分析,從現(xiàn)在開(kāi)始只需要 F9
操作,并且做好記錄,其中大部分流程與 5 代一致,可以參考之前的文章。下文中不會(huì)對(duì)流程中的每一步進(jìn)行講解,只會(huì)記錄對(duì)結(jié)果有影響的關(guān)鍵步驟。
步驟1
這一步調(diào)用了一個(gè)方法,得到了一個(gè)類似時(shí)間戳的值,進(jìn)入方法內(nèi)部:
對(duì)兩個(gè)時(shí)間戳以及當(dāng)前時(shí)間戳做了計(jì)算,記錄 _$MM
,_$En
的值,后續(xù)還會(huì)用到。
步驟2
這里對(duì)兩個(gè)數(shù)組進(jìn)行了拼接操作,_$4y
為 16 位數(shù)組,_$bx
為 4 位數(shù)組:
先來(lái)看 16 位數(shù)組 _$4y
的生成,搜索 _$4y =
,可以定位到一處:
先看參數(shù) _$yx._$N$
的值,AMEExbhbQVYKGNjj8cTp.A
,通過(guò)全局搜索可以發(fā)現(xiàn)這個(gè)值是在 JS 文件中:
再看 _$yx
,觀察它的值,可以發(fā)現(xiàn)與 $_ts
一致 ,后續(xù)還會(huì)有多處會(huì)用到 _$yx
:
參數(shù)值找到了,還剩方法 _$Vg
。進(jìn)入方法內(nèi)部,可以看到它進(jìn)行了很多運(yùn)算,這里直接扣下來(lái)就行:
16 位數(shù)組搞定了,還有 4 位數(shù)組 _$bx
,同樣進(jìn)行搜索,一共有 6 處,其中 5 處能夠比較明顯的看出是 _$bx
的生成流程,全部打下斷點(diǎn)。首選創(chuàng)建了一個(gè) 4 位數(shù)組:
下面就是對(duì)數(shù)組的每一位進(jìn)行了賦值,這段邏輯很簡(jiǎn)單,但是實(shí)現(xiàn)卻比較復(fù)雜:
var _$3B = _$5W[_$yx._$Go](_$Ke, _$tm);
_$bx[1] = _$3B; // 102
============================================
var _$I$ = _$5W[_$yx._$OA](_$dk);
_$bx[3] = _$I$; // 102
============================================
var _$dk = _$5W[_$yx._$Lr]();
_$bx[2] = _$dk; // 127
============================================
var _$j9 = _$5W[_$yx._$0f](_$jU, _$I$);
_$bx[0] = _$j9; // 108
首先 _$yx._$xx
返回一個(gè)變量名,上文中講到了 _$yx
就是 $_ts
。然后 _$5W
是一個(gè)對(duì)象,里面存放著多個(gè)方法,根據(jù)_$yx._$xx
返回的變量名來(lái)調(diào)用對(duì)應(yīng)的方法。_$5W
中的方法都有一個(gè)特點(diǎn),就是結(jié)構(gòu)相同,如下:
function _$Vk() {
var _$pn = [249];
Array.prototype.push.apply(_$pn, arguments);
return _$2W.apply(this, _$pn);
}
這里其實(shí)不用在意方法內(nèi)部做了什么,經(jīng)測(cè)試可以發(fā)現(xiàn),方法中的數(shù)組如 [249]
與方法最終結(jié)果值存在對(duì)應(yīng)關(guān)系,我們只需要找到調(diào)用的方法中數(shù)組值就可以知道方法的返回值,這里直接將關(guān)系給出:
valueMap = {
194: 103,
274: 103,
306: 100,
251: 203,
247: 0,
272: 126,
240: 103,
290: 225,
285: 203,
249: 102,
283: 102,
298: 181,
281: 11,
256: 224,
264: 181,
266: 108,
268: 240,
302: 208,
304: 180,
308: 127,
270: 101,
}
如 _$5W[_$yx._$Go](_$Ke, _$tm)
,這個(gè)方法中數(shù)組為 [249]
,而 249
對(duì)應(yīng)的值為 102
,那么 _$5W[_$yx._$Go](_$Ke, _$tm)
的返回值就是 102
。
到這里就是 16 位數(shù)組和 4 位數(shù)組的生成,將它們拼接后得到一個(gè) 20 位數(shù)組。
步驟3
這里對(duì)時(shí)間戳進(jìn)行了運(yùn)算,_$tm
的值為步驟1中時(shí)間戳計(jì)算的結(jié)果 _$I$
。
步驟4
步驟5
這里將步驟3、4中的結(jié)果存入數(shù)組賦值給了 _$xg
。
步驟6
這里將兩位數(shù)組轉(zhuǎn)為了八位數(shù)組,進(jìn)入 _$CY
方法內(nèi)部看看,也是一些樸實(shí)無(wú)華的操作,扣下來(lái)即可:
步驟7
下面有一段較長(zhǎng)的流程,都是在對(duì)一些自動(dòng)化特征進(jìn)行檢測(cè),可以直接跳過(guò):
步驟8
生成了一個(gè) 128 位數(shù)組,最終 cookie
也是由這個(gè)數(shù)組轉(zhuǎn)化得來(lái):
步驟9
首先將步驟2中生成的 20 位數(shù)組存入 128 位數(shù)組:
步驟10
這四處值可以固定:
步驟11
這里 _$Ke
值為 4 位數(shù)組:
搜索 _$Ke
可以定位到生成點(diǎn),由方法 _$Js
生成:
進(jìn)入 _$Js
內(nèi)部,發(fā)現(xiàn)值的生成由 _$Zb
實(shí)現(xiàn):
進(jìn)入 _$Zb
,可以發(fā)現(xiàn)這行是用于生成 0 - 255
的隨機(jī)數(shù):
那么 4 位數(shù)組的生成就解決了,由四個(gè) 0 - 255
間的隨機(jī)數(shù)組成。
步驟12
_$g5
為 8 位數(shù)組,這個(gè)數(shù)組的由來(lái)比較棘手,先搜索_$g5
,一共有四處結(jié)果,全部斷下:
這里可以看到要找的值是 _$zi
,但是 _$zi
出現(xiàn)的地方很多,通過(guò)搜索定位不到 8 位數(shù)組的生成位置,這里只能追棧,回到上一個(gè)棧:
可以看到 _$pn
中包含了一個(gè)字符串 zbOdssUZRkdTixew3tpf4WGN.rNLK_jWMTTqMIafmZV
,這個(gè)字符串就是八位數(shù)組生成的關(guān)鍵值,經(jīng)測(cè)試,這個(gè)字符串可以固定。那么 F9 繼續(xù)往下走:
這里會(huì)進(jìn)入一個(gè)新分支,而生成的值就是我們要找的八位數(shù)組,跟進(jìn)去:
到這里就找到了八位數(shù)組的生成點(diǎn),_$mq
為上文中的字符串,_$gr
會(huì)生成隨機(jī)的 21
位數(shù)組,_$zW
生成最終八位數(shù)組:
先看 _$gr
,進(jìn)入該方法,代碼如下。
var _$dk = _$Vg(_$2s(_$SK[46]) + _$yx._$1E);
return _$Gi(_$dk);
_$Vg
方法前文中已經(jīng)講到了,扣下來(lái)即可。前文講到了,_$yx
就是 $_ts
,因此 _$yx._$1E
的值在網(wǎng)頁(yè)返回的代碼中,需要?jiǎng)討B(tài)匹配。再看 _$2s
,進(jìn)入該方法:
var _$zi = _$mq % _$SK[83];
var _$uC = _$mq - _$zi;
_$zi = _$cl(_$zi);
_$zi ^= _$yx._$y3;
_$uC += _$zi;
return _$Yv[_$uC];
首先看方法 _$cl
,需要關(guān)注的值是 _$yx._$O2
,動(dòng)態(tài)匹配即可:
var _$dk = [0, 1, _$SK[113], _$SK[11], _$SK[124], _$SK[41]];
return (_$k4 > > _$yx._$O2) | ((_$k4 & _$dk[_$yx._$O2]) < < (_$SK[91] - _$yx._$O2));
然后是 _$yx._$y3
,同樣需要?jiǎng)討B(tài)匹配。最后是 _$Yv
,這是一個(gè) 64
位數(shù)組,通過(guò)搜索 _$Yv[
可以定位到它的生成點(diǎn):
_$k4
的值也是網(wǎng)頁(yè)返回的 JS 代碼中的,需動(dòng)態(tài)匹配,_$j9
方法直接扣下來(lái)即可。
到這里 _$dk
的值就能拿到了,得到的是一個(gè) 16
位數(shù)組。還剩 _$Gi
,這個(gè)方法主要是對(duì)數(shù)組值進(jìn)行了一些邏輯操作,缺啥補(bǔ)啥即可。
到這里 21
位數(shù)組也得到了,離最終的八位數(shù)組還剩 _$zW
方法,代碼如下:
var _$dk = _$Vg(_$k4);
var _$jU = new _$35(_$fO);
return _$jU._$ZL(_$dk, true);
_$Vg
講過(guò)了,_$35
中內(nèi)容比較多,這里不做講解,缺啥補(bǔ)啥即可。
那么八位數(shù)組的生成就結(jié)束了。
步驟13
以下四處值可以固定:
步驟14
這里將一個(gè)八位數(shù)組 _$tj
的值添加到了數(shù)組中,而這個(gè)八位數(shù)組就是 步驟6 中生成的八位數(shù)組:
步驟15
這里將下標(biāo) 12
的位置空了出來(lái),其余各處值均可固定:
在該步驟中,也是對(duì)一些環(huán)境進(jìn)行了檢測(cè),流程較長(zhǎng),慢慢跟即可。
步驟16
其中 _$rt
固定為 https:443
,_$wk
方法將字符串轉(zhuǎn)數(shù)組,該方法可以直接扣下來(lái):
步驟17
這里會(huì)進(jìn)入一個(gè)新分支,得到一個(gè)固定值,感興趣的可以跟進(jìn)去看一下,流程比較長(zhǎng),主要是對(duì) UA
等環(huán)境值進(jìn)行了處理:
步驟18
這里對(duì) 128
位數(shù)組下標(biāo) 12
的位置做了重新賦值,_$jU
的值為固定值,細(xì)心的朋友在前面幾個(gè)步驟的調(diào)試過(guò)程中會(huì)發(fā)現(xiàn)一些 |
運(yùn)算,如 _$jU |= _$SK[189];
這些就是在計(jì)算 _$jU
的值:
進(jìn)入 _$8c
方法中,代碼如下:
[(_$k4 > >> _$SK[162]) & _$SK[4], (_$k4 > >> _$SK[189]) & _$SK[4], (_$k4 > >> _$SK[43]) & _$SK[4], _$k4 & _$SK[4]];
也是在進(jìn)行一些邏輯運(yùn)行,這里直接扣下來(lái)即可。
步驟19
這里對(duì) 128
位數(shù)組進(jìn)行了切割,保留了有值的部分,得到一個(gè) 18
位數(shù)組:
步驟20
這行代碼利用了 concat
與 apply
方法將 18
位數(shù)組轉(zhuǎn)為了一個(gè)一維的大數(shù)組:
步驟21
這一步會(huì)進(jìn)入一個(gè)新分支,得到一個(gè) 32 位的數(shù)組,跟進(jìn)去:
兩個(gè)方法 _$BW
與 _$o9
,_$o9
生成一個(gè)隨機(jī)的 37
位數(shù)組,_$BW
生成 32
位數(shù)組,先看 _$o9
。:
_$o9
與步驟12中的 _$gr
方法相似,區(qū)別在于 _$2s
的參數(shù)值以及 _$yx._$BL
:
var _$dk = _$Vg(_$2s(_$SK[66]) + _$yx._$BL);
_$sP(_$SK[152], _$dk.length !== _$SK[173]);
return _$Gi(_$dk);
然后看 _$BW
,在步驟12中提到了一個(gè)方法_$35
,在扣 _$35
時(shí)也會(huì)遇到 _$BW
,這里就單獨(dú)的講一下_$BW
:
將代碼整理一下,如下:
function _$BW(_$k4) {
var _$dk = _$k4.slice(0);
if (_$dk.length < 5) {
return;
}
var _$jU = _$dk.pop();
var _$I$ = 0
, _$IM = _$dk.length;
while (_$I$ < _$IM) {
_$dk[_$I$++] ^= _$jU;
}
var _$j9 = _$dk.length - 4;
var _$Ff = _$PO() - _$0f(_$dk.slice(_$j9))[0];
if (_$Ff > _$rT) {
if (_$Ff > 255) {
_$rT = 255;
} else {
_$rT = _$Ff;
}
}
_$dk = _$dk.slice(0, _$j9);
var _$df = parseFloat("11.678");
var _$52 = Math.floor(Math.log(_$Ff / _$df + Math.floor("1.234")));
var _$zi = _$dk.length;
var _$Pa = _$yx._$AX[_$ic];
_$I$ = 0;
while (_$I$ < _$zi) {
_$dk[_$I$] = _$52 | (_$dk[_$I$++] ^ _$Pa);
}
_$Db(_$SK[43], _$52);
return _$dk;
}
可以發(fā)現(xiàn)關(guān)鍵點(diǎn)有三處,_$PO
、_$0f
與 _$yx._$AX
。
_$PO
返回當(dāng)前時(shí)間戳(秒)的四舍五入整數(shù)值。_$0f
方法則是數(shù)組進(jìn)行轉(zhuǎn)換,其中涉及到一些邏輯運(yùn)算,可以直接扣下來(lái)。_$yx._$AX
不用多說(shuō),需動(dòng)態(tài)匹配。
這里就得到了一個(gè) 32
位數(shù)組,但是該分支還沒(méi)有結(jié)束,繼續(xù)往下走。
下面又對(duì)生成的 32
位數(shù)組進(jìn)行了處理,得到一個(gè) 16
位數(shù)組,兩個(gè)方法 _$aT
與 _$9J
:
_$aT
代碼整理后如下,直接用即可:
function _$aT(_$k4) {
var _$dk = _$k4.slice(0, 16);
var _$jU, _$I$ = 0, _$IM;
_$IM = _$dk.length;
while (_$I$ < _$IM) {
_$jU = Math.abs(_$dk[_$I$]);
_$dk[_$I$++] = _$jU > 256 ? 256 : _$jU;
}
return _$dk;
}
_$9J
代碼整理后如下,有一個(gè)_$4c
方法需注意,也是缺少補(bǔ)啥:
function _$9J() {
var _$dk = new _$4c();
for (var _$jU = 0; _$jU < arguments.length; _$jU++) {
_$dk._$1l(arguments[_$jU]);
}
return _$dk._$Dt().slice(0, 16);
}
16
位數(shù)組跟完后繼續(xù)往下走,會(huì)生成另一個(gè) 16
位數(shù)組,不過(guò)這個(gè)就比較簡(jiǎn)單了,_$9J
、_$BW
、_$gr
在前文都已經(jīng)提到了:
繼續(xù)往下走,會(huì)到一個(gè)for
循環(huán)里面,這里對(duì)上面生成的 32
位數(shù)組以及 16
位數(shù)組進(jìn)行處理,生成一個(gè)32
位數(shù)組:
到這里該分支就結(jié)束了,最終得到了32
位數(shù)組。
步驟22
下面主要是對(duì)時(shí)間戳進(jìn)行了一些處理,涉及到的時(shí)間戳都來(lái)自于步驟1中:
這里通過(guò)時(shí)間戳計(jì)算得到了四個(gè)值,[1695610803, 1695611070, 394, -901278768]
,下面又將這四個(gè)值轉(zhuǎn)成了一個(gè) 16
位的數(shù)組,_$CY
方法在上文中也提到了:
步驟23
這里對(duì)上一步中生成的數(shù)組進(jìn)行了位異或操作:
在這里就生成了最終cookie
的一部分,_$52
是上面處理后的 16
位數(shù)組,方法 _$Cj
前面沒(méi)有遇到,這里直接扣下來(lái)即可:
這里也是將瑞數(shù)的標(biāo)識(shí)加上了,那么到這里 173
位 cookie
的第一部分就出來(lái)了:
步驟24
這里又進(jìn)到了一個(gè)新分支:
首先取了一個(gè)值,也是需要?jiǎng)討B(tài)匹配的:
然后將該值拼接到了一個(gè)數(shù)組 _$r3
后面,_$r3
的值就是 步驟20 中 18
位數(shù)組合并成的新數(shù)組:
這里將數(shù)組轉(zhuǎn)成了一串?dāng)?shù)字:
進(jìn)入方法_$hM
內(nèi)部,主要涉及到了一個(gè)256
位數(shù)組 _$yx._$4y
,這個(gè)值可以直接固定,整理代碼如下:
function _$hM(_$k4) {
if (typeof _$k4 === _$A9(_$PM[7]))
_$k4 = _$wk(_$k4);
var _$dk = _$yx._$4y || (_$yx._$4y = _$iV());
var _$jU = -1
, _$I$ = _$k4.length;
for (var _$IM = 0; _$IM < _$I$; ) {
_$jU = (_$jU > >> -1) ^ _$dk[(_$jU ^ _$k4[_$IM++]) & 255];
}
return (_$jU ^ -1) > >> 0;
}
這里對(duì)那串?dāng)?shù)字進(jìn)行了轉(zhuǎn)換,得到了一個(gè)四位數(shù)組,_$8c
上文已經(jīng)提到了:
到這里該分支就結(jié)束了,得到了四位數(shù)組。
步驟25
這里將四位數(shù)組與 _$r3
進(jìn)行了拼接:
_$dk
為 步驟21 中的 32
位數(shù)組,_$Cj
上文提到了,那么還剩 _$o$
,也是缺啥補(bǔ)啥即可:
到這里 173
位 cookie
的第二部分就出來(lái)了,最后將兩部分拼接就得到了最終的 173
位 cookie
:
至此,逆向流程結(jié)束。
動(dòng)態(tài)匹配
六代與五代最大的區(qū)別應(yīng)該就是動(dòng)態(tài)值的匹配方式發(fā)生了變化。數(shù)據(jù)匹配一般有兩種方案,正則和AST,這里推薦正則。
以 步驟2 中的四位數(shù)組為例:
var _$3B = _$5W[_$yx._$Go](_$Ke, _$tm);
var _$I$ = _$5W[_$yx._$OA](_$dk);
var _$dk = _$5W[_$yx._$Lr]();
var _$j9 = _$5W[_$yx._$0f](_$jU, _$I$);
前面已經(jīng)講到了 _$3B
值與所引用的方法內(nèi)部的一位數(shù)組存在映射關(guān)系,想要拿到值就需要找到對(duì)應(yīng)的方法。已知 _$5W
是一個(gè)對(duì)象,里面包含所有方法,_$yx._$Go
返回一個(gè)字符串,根據(jù)返回的字符串來(lái)引用方法,得到結(jié)果。那么首先要定位 _$5W
,因?yàn)榇a是動(dòng)態(tài)的,每一次這個(gè)包含方法的對(duì)象名都不一樣,所以這里就需要找到一個(gè)固定的關(guān)鍵字來(lái)進(jìn)行定位。這里可以用 842,
來(lái)找到 _$5W
。定位到 _$5W
后就可以通過(guò) _$5W[
來(lái)匹配四個(gè)索引:
這里_$yx._$Go
的值為 _$ym
,對(duì)應(yīng)的方法為_$3$
。那么就需要找到 _$ym
和 _$3$
是怎么映射起來(lái)的:
通過(guò)搜索 ._$ym
可以定位到,同理 _$yx._$OA
的值為 _$xy
,也可以通過(guò)這個(gè)方法來(lái)定位到方法名:
方法名找到后可以通過(guò) function 方法名
來(lái)進(jìn)行定位:
梳理一下流程:
- 通過(guò)842來(lái)匹配對(duì)象名;
- 通過(guò)對(duì)象名來(lái)匹配四個(gè)索引名
(_$yx._$Go)
;- 根據(jù)
$_ts 拿到索引值(_$ym
);- 通過(guò)
.索引值 (._$ym)
來(lái)匹配到真實(shí)方法名;- 通過(guò)
function 方法名
匹配一位數(shù)組;- 根據(jù)數(shù)組值拿到方法返回值。
通過(guò)以上流程就能得到四位數(shù)組。
-
瀏覽器
+關(guān)注
關(guān)注
1文章
1030瀏覽量
35400 -
代碼
+關(guān)注
關(guān)注
30文章
4798瀏覽量
68725 -
插件
+關(guān)注
關(guān)注
0文章
331瀏覽量
22450 -
時(shí)間戳
+關(guān)注
關(guān)注
0文章
15瀏覽量
2613
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論