創(chuàng)新互聯(lián)公司主要從事成都做網(wǎng)站、成都網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)松溪,10余年網(wǎng)站建設(shè)經(jīng)驗,價格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):18982081108
猿人學(xué) - 反混淆刷題平臺 Web 第五題:js 混淆,亂碼增強
目標:抓取全部 5 頁直播間熱度,計算前 5 名直播間熱度的加和
主頁:https://match.yuanrenxue.com/match/5
接口:https://match.yuanrenxue.com/api/match/5?m=XXX&f=XXX
逆向參數(shù):
進入網(wǎng)頁,點擊右鍵查看頁面源代碼,搜索不到直播間相關(guān)數(shù)據(jù)信息,證明是通過 ajax 加載的數(shù)據(jù),ajax 加載有特殊的請求類型 XHR,打開開發(fā)者人員工具,刷新網(wǎng)頁進行抓包,在 Network 的篩選欄中選擇 XHR,數(shù)據(jù)接口為 5?m=XXX&f=XXX,在響應(yīng)預(yù)覽中可以看到各直播間熱度數(shù)據(jù):
接口 url 有兩個請求參數(shù) m 和 f,現(xiàn)在還不知道具體怎么來的:
本題提示 cookie 有效期僅為 50 秒鐘,即 cookie 值是在動態(tài)變化的,經(jīng)過對比分析,cookie 中有兩個動態(tài)變化的參數(shù) m 和 RM4hZBv0dDon443M,接下來需要定位到其生成的位置:
可以通過 Hook Cookie 的方式定位參數(shù)位置,這里通過 Fiddler 編程貓插件進行 Hook,相關(guān)插件在 K哥爬蟲公眾號發(fā)送【Fiddler插件】即可獲取,Hook 代碼如下:
(function () {
'use strict';
var cookieTemp = '';
Object.defineProperty(document, 'cookie', {
set: function (val) {
if (val.indexOf('RM4hZBv0dDon443M') != -1) {
debugger;
}
console.log('Hook捕獲到cookie的值->', val);
cookieTemp = val;
return val;
},
get: function () {
return cookieTemp;
},
});
})();
將以上代碼寫入插件中,注入 Hook:
清除網(wǎng)頁緩存,勾選開啟框,打開 Fiddler 進行 Hook 注入,可以發(fā)現(xiàn)成功斷?。?/p>
從右側(cè)堆棧中向上跟棧,會發(fā)現(xiàn)跟到了虛擬機 VMXXX 中,點擊右下角 { } 格式化,跳轉(zhuǎn)到了第 978 行,代碼部分如下:
_0x3d0f3f[_$Fe] = 'R' + 'M' + '4' + 'h' + 'Z' + 'B' + 'v' + '0' + 'd' + 'D' + 'o' + 'n' + '4' + '4' + '3' + 'M=' + _0x4e96b4['_$ss'] + ';\x20path=/';
在該行打下斷點進行調(diào)試,控制臺打印相關(guān)參數(shù):
前面各字母組成起來就是 RM4hZBv0dDon443M=,此處就是 RM4hZBv0dDon443M 參數(shù)加密后賦值給 cookie 的位置,所以關(guān)鍵的加密部分為 _0x4e96b4['_$ss']
,打印相關(guān)內(nèi)容會發(fā)現(xiàn) _0x4e96b4 是 window 對象,window. _$ss 即加密后的值:
直接搜索 _$ss 沒有結(jié)果,同樣嘗試 Hook,Hook 代碼:
(function () {
'use strict'
Object.defineProperty(window, '_$ss', {
set: function (val) {
console.log('Hook捕獲到_$ss的值->', val);
debugger;
},
});
})();
成功斷住:
同樣向上跟棧,找到其定義位置,跟到了虛擬機中,格式化后跳到第 1229 行:
_0x4e96b4['_$' + _$UH[0x348][0x1] + _$UH[0x353][0x1]] = _0x29dd83[_$UH[0x1f]]();
在該行打下斷點調(diào)試分析各自含義:
'_$s'
、_$UH[0x348][0x1]
、 _$UH[0x353][0x1]
組合起來:'_$ss'_$UH[0x1f]()
:toString()_0x29dd83[ _$UH[0x1f]]()
:將 _0x29dd83 生成的值轉(zhuǎn)換為字符串因此關(guān)鍵的加密位置肯定在 _0x29dd83 中,往上看, _0x29dd83 定義在第 1225 行,這時候眼前一亮,看到了 mode 和 padding 兩個關(guān)鍵字,這里大概率為 AES 或者 DES 加密,將代碼解混淆替換后的結(jié)果如下:
_$Ww = _$Tk['enc']['utf-8']['parse'](_0x4e96b4['_$pr']['toString']()),
_0x29dd83 = _$Tk['AES'](_$Ww, _0x4e96b4['_$qF'], {
'mode': _$Tk['mode']['ECB'],
'padding': _$Tk['pad']['pkcs7']
}),
_0x4e96b4['_$ss'] = _0x29dd83['toString']();
現(xiàn)在就很明顯了,這里為 AES 加密,加密內(nèi)容為 _$Ww
,key 值為 _0x4e96b4['_$qF']
,加密模塊為 ECB,填充方式為 pkcs7:
_$Ww
的值由 _0x4e96b4['_$pr']
轉(zhuǎn)換為字符串后經(jīng)過 utf-8 編碼得到,其與 key 值 _0x4e96b4['_$qF']
都是數(shù)組,需要知道這兩個數(shù)組是怎么生成的,先 ctrl + f 搜索 _0x4e96b4['_$qF']
,定義在第 1444 行,內(nèi)容如下:
_0x4e96b4['_$qF'] = CryptoJS['enc']['Utf8'][_$UH[0xff]](_0x4e96b4['btoa'](_0x4e96b4['_$is'])['slice'](0x0, 0x10));
在該行打下斷點,控制臺打印分析一下:
由此可見,_0x4e96b4['_$qF']
是通過 CryptoJS 庫將字符串經(jīng)過 base64 加密后取前 16 位的結(jié)果,搜索 _0x4e96b4['_$is']
,找到字符串生成的位置,在第 674 行,由 _$yw 賦值,在上一行可以看到熟悉的 _$Fe,即 cookie,發(fā)現(xiàn) cookie 中的 m 參數(shù)是在這里定義的:
_0x3d0f3f[_$Fe] = 'm=' + _0x(_$yw) + ';\x20path=/';
參數(shù) m 的值也與 _$yw
有關(guān),m 參數(shù)是將 _$yw
經(jīng)過 _0x
函數(shù)處理后得到,后面再專門進行分析,_$yw
定義在第 672 行:
_$yw = _0x2d5f5b()[_$UH[0x1f]]();
_$UH[0x1f]
為 “toString”,_$yw
的值是將 _0x2d5f5b()
函數(shù)的返回值轉(zhuǎn)換成了字符串得到的,跟進到該函數(shù)定義的位置,搜索后發(fā)現(xiàn)在第 279 行,控制臺打印后發(fā)現(xiàn)這里就是時間戳,所以 _$yw
即時間戳:
因此 _0x4e96b4['_$qF']
的值是將時間戳經(jīng)過 base64 加密后取了前 16 位的結(jié)果,接下來只需要知道 _0x4e96b4['_$pr']
是如何生成的,就能復(fù)現(xiàn)出 RM4hZBv0dDon443M 參數(shù)的加密過程,在第 1224 行打斷點調(diào)試發(fā)現(xiàn)此時的 _0x4e96b4['_$pr']
數(shù)組包含五個值:
現(xiàn)在就需要知道這五個值是在哪傳進去的,搜索 _0x4e96b4['_$pr']
看看哪里對其進行了賦值,每個都打下斷下,該數(shù)組定義在第 270 行:
_0x4e96b4['_$pr'] = new _0x4d2d2c();
_0x4d2d2c
在第 224 行定義為 Array,所以這里是創(chuàng)建了一個數(shù)組 _0x4e96b4['_$pr']
,接著往后找傳值的地方,繼續(xù)運行斷點調(diào)試,第 1717 行的斷點運行了四次傳入了四個值:
_0x4e96b4['_$pr']['push'](_0x(_$Wa));
跟進 _$Wa 定義的位置,在第 1715 行,由 _0x12eaf3 函數(shù)生成,跟進到這個函數(shù)的位置,在第 275 行,返回值解混淆后如下:
Date['parse'](new Date());
再次下一步調(diào)試斷點會跳轉(zhuǎn)到第 868 行,這時候數(shù)組被傳入了第五個值,_$yw
為時間戳,由于 m = _0x(_$yw)
,所以第五個值也就是參數(shù) m 的值,記住這里出現(xiàn)的 _0x4e96b4['_$is']
:
_0x3d0f3f[_$Fe] = 'm=' + _0x(_$yw) + ';\x20path=/';
_0x4e96b4['_$is'] = _$yw;
_0x4e96b4['_$pr']['push'](_0x(_$yw));
數(shù)組值的生成位置都找到了,跟 m 參數(shù)一樣,傳入的值都經(jīng)過了 _0x 函數(shù)的處理,因此需要跟進 _0x 函數(shù),鼠標選中,點擊即可跳轉(zhuǎn)到該函數(shù)定義的位置:
在第 455 行,返回值為三目表達式:
function _0x(_0x233f82, _0xe2ed33, _0x3229f9) {
return _0xe2ed33 ? _0x3229f9 ? v(_0xe2ed33, _0x233f82) : y(_0xe2ed33, _0x233f82) : _0x3229f9 ? _0xd(_0x233f82) : _0xa(_0x233f82);
}
在 return 處打下斷點調(diào)試,_0x233f82 為傳入的 _$yw 的值,即時間戳,后面兩個參數(shù)均為 undefined,所以不妨將函數(shù)簡化下:
function _0x(_0x233f82, _0xe2ed33, _0x3229f9) {
return _0xa(_0x233f82);
}
接下來需要跟進到 _0xa 函數(shù)的位置:
function _0xa(_0x32e7c1) {
return _0x(_0xd(_0x32e7c1));
}
這里就需要跟出 _0x
函數(shù)和 _0xd
函數(shù)的內(nèi)容,接下來就是扣,缺啥補啥,缺函數(shù)補函數(shù),缺環(huán)境補環(huán)境,若報錯提示 _$UH is not defined
,_$UH
是個大數(shù)組,直接將其整體解混淆替換掉就行了,例如:
_$UH[0x6c] ---> "length"
或者寫成鍵值對形式:
_$UH = {
8: 'prototype',
15: 'charCodeAt',
31: 'toString',
108: 'length'
}
值得注意的是 _0x11a7a2 函數(shù),運行時會報錯 op is not defined
,op 定義在第 308 行:
op 的值為 26,這里直接將其定義成固定值即可,即 var op = 26;
同樣將 _0x42fb36
和 b64pad 也寫成固定值,即 _0x42fb36 = 16;
、b64pad = 1
;
調(diào)試過程中還發(fā)現(xiàn) window['_$6_']
、window['_$tT']
、window['_$Jy']
這幾個參數(shù)的值是在動態(tài)變化的,不進行改寫甚至將相關(guān)部分注釋掉,在本地 node 環(huán)境中都是可以運行出結(jié)果的,但是用 python 調(diào)用的話會報錯,證明在前端會對這幾個參數(shù)進行校驗,這幾個參數(shù)在 _0x11a7a2
函數(shù)中定義,該函數(shù)溯源后最終被 _0x
函數(shù)調(diào)用,_0x
函數(shù)對 _$yw
的值進行處理,生成了 _0x4e96b4['_$pr']
數(shù)組的最后一個值及 m 參數(shù)的值,所以如果這幾個參數(shù)的值匹配錯誤的話會導(dǎo)致校驗失敗,我們只需要打斷點看 m 參數(shù)的值生成的時候,這三個參數(shù)的值是多少,然后寫成固定值就行了:
window['_$6_'] = -;
window['_$tT'] = -;
window['_$Jy'] = -;
至此 Cookie 中 RM4hZBv0dDon443M 參數(shù)和 m 參數(shù)的生成邏輯就疏通了,以下通過 JavaScript 對其復(fù)現(xiàn):
// 以下函數(shù)部分內(nèi)容過長,此處省略
// 完整代碼關(guān)注 GitHub:https://github.com/kgepachong/crawler
var CryptoJS = require('crypto-js');
function rm4Encrypt(_$yw, pr){
var value = Buffer.from(_$yw).toString('base64').slice(0, 16);
var srcs = CryptoJS.enc.Utf8.parse(pr);
var key = CryptoJS.enc.Utf8.parse(value);
var encrypted = CryptoJS.AES.encrypt(srcs, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.toString();
}
var _$yw = new Date().valueOf().toString();
var _$Wa = Date.parse(new Date())
function pr(){
pr = [];
for (i = 1; i < 5; i++) {
// _$Wa 傳入四個值
pr.push(_0x(_$Wa))
}
// _$yw 傳入一個值
pr.push(_0x(_$yw));
return pr.toString();
}
var RM4hZBv0dDon443M = rm4Encrypt(_$yw, pr());
// m 為數(shù)組傳入的最后一個值
var m = pr[4];
console.log('RM4hZBv0dDon443M 參數(shù)加密后的值為: ' + RM4hZBv0dDon443M)
console.log('m 參數(shù)的值為: ' + m)
運行結(jié)果:
Cookie 中的參數(shù)分析完了,還有兩個請求參數(shù) m 和 f 沒有解決,直接從接口處跟棧,從 Initiator 中跟到 request 里:
點擊右下角 { } 格式化后會跳轉(zhuǎn)到 5:formatted
文件的第 856 行,在第 883 行的 list 中可以找到參數(shù) m 和 f 的定義位置:
"m": window._$is,
"f": window.$_zw[23]
m 的值是 window._$is
,有沒感覺似曾相識,就是上文所說的 _0x4e96b4['_$is']
,_0x4e96b4
就是 window,所以這里 m 的值其實就是 _$yw
;f 的值是 window.$_zw[23]
,現(xiàn)在需要知道 $_zw[23]
的值怎么生成的,局部搜索 $_zw
會發(fā)現(xiàn)該數(shù)組定義在第 611 行,接著往后找,看看數(shù)組中的第 23 個是什么,先控制臺打印一下內(nèi)容:
第 633 行內(nèi)容是第六個,順下去找會發(fā)現(xiàn)第 23 個的內(nèi)容如下:
$_aiding.$_zw.push($_t1);
在此處打下斷點調(diào)試驗證一下,可以發(fā)現(xiàn)結(jié)果是一樣的:
接下來只需要找到 $_t1
的定義位置即可,ctrl + f 局部搜索 $_t1
,其定義在第 613 行,是個時間戳:
let $_t1 = Date.parse(new Date());
可以發(fā)現(xiàn)與 _$Wa 的定義方式一致,對比一下 m 和 f 兩個參數(shù)的值會發(fā)現(xiàn)差值接近于 50 秒,與題目中提示的 Cookie 有效期僅 50 秒鐘對應(yīng)上了:
在虛擬機文件的第 1975 行也有個 50 秒的定時器:
至此所有參數(shù)生成的邏輯都調(diào)理清晰了,本題并不難,但是扣代碼的過程中有許多需要注意的細節(jié),猿人學(xué)給大家提供了一個優(yōu)質(zhì)的練習(xí)平臺,做題也是一個很好的自我提升的方式。
bilibili 關(guān)注 K 哥爬蟲,小助理手把手視頻教學(xué):https://space.bilibili.com/
GitHub 關(guān)注 K 哥爬蟲,持續(xù)分享爬蟲相關(guān)代碼!歡迎 star !https://github.com/kgepachong/
以下只演示部分關(guān)鍵代碼,不能直接運行!
var _0x4e96b4 = window = {};
var _0x1171c8 = 0x;
var _0x4dae05 = -0x;
var _0x183a1d = -0x;
var _0xcfa373 = 0x;
var _0x30bc70 = String;
// 以下函數(shù)部分內(nèi)容過長,此處省略
// 完整代碼關(guān)注 GitHub:https://github.com/kgepachong/crawler
var CryptoJS = require('crypto-js');
function rm4Encrypt(_$yw, pr){
var value = Buffer.from(_$yw).toString('base64').slice(0, 16);
var _$Ww = CryptoJS.enc.Utf8.parse(pr);
var key = CryptoJS.enc.Utf8.parse(value);
var encrypted = CryptoJS.AES.encrypt(_$Ww, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.toString();
}
function getParamers() {
pr = [];
for (i = 1; i < 5; i++) {
var _$Wa = Date.parse(new Date());
pr.push(_0x(_$Wa))
}
var _$yw = new Date().valueOf().toString();
pr.push(_0x(_$yw));
cookie_m = pr[4];
cookie_rm4 = rm4Encrypt(_$yw, pr.toString());
return{
"cookie_m": cookie_m,
"cookie_rm4": cookie_rm4,
"m": _$yw,
"f": Date.parse(new Date()).toString()
}
}
console.log(getParamers());
# =======================
# --*-- coding: utf-8 --*--
# @Time : 2022/9/8
# @Author : 微信公眾號:K哥爬蟲
# @FileName: yrx5.py
# @Software: PyCharm
# =======================
import execjs
import requests
import re
def encrypt_yrx5():
room_heat_all = []
for page_num in range(1, 6):
with open('yrx5.js', 'r', encoding='utf-8') as f:
encrypt = f.read()
encrypt_params = execjs.compile(encrypt).call('getParamers')
headers = {
"user-agent": "yuanrenxue,project",
}
cookies = {
# 填入自己的 sessionid
"sessionid": " your sessionid ",
"m": encrypt_params['cookie_m'],
"RM4hZBv0dDon443M": encrypt_params['cookie_rm4']
}
params = {
"m": encrypt_params['m'],
"f": encrypt_params['f']
}
url = "https://match.yuanrenxue.com/api/match/5?page=%s" % page_num
response = requests.get(url, headers=headers, cookies=cookies, params=params)
for i in range(10):
value = response.json()['data'][i]
room_heat = re.findall(r"'value': (.*?)}", str(value))[0]
room_heat_all.append(room_heat)
room_heat_all.sort(reverse=True)
top_five_total = 0
for i in range(5):
top_five_total += int(room_heat_all[i])
print(top_five_total)
if __name__ == '__main__':
encrypt_yrx5()