
逆向目標
-
猿人學 - 反混淆刷題平臺 Web 第二題:js 混淆,動態 cookie
-
目標:提取全部 5 頁發布日熱度的值,計算所有值的加和
-
主頁:https://match.yuanrenxue.com/match/2
-
介面:https://match.yuanrenxue.com/api/match/2
-
逆向引數:
- Cookie 引數:m
逆向程序
抓包分析
進入網頁,點擊右鍵查看頁面源代碼,搜索不到直播間相關資料資訊,證明是通過 ajax 加載的資料,ajax 加載有特殊的請求型別 XHR,打開開發者人員工具,重繪網頁進行抓包,會跳轉到虛擬機中,進入無限 debugger,過無限 debugger 的方式在往期文章中有詳細介紹,感興趣的可以去閱讀學習一下,這里直接在 debugger 行右鍵選擇 never pause here,然后下一步斷點即可過掉:

在 Network 的篩選欄中選擇 XHR,資料介面為 2,在回應預覽中可以看到當前頁各手機發布日的熱度:

這時候點擊第二頁,會彈出提示框:cookie 失效,正在重置頁面,證明 cookie 是有時效性的,并且會進行校驗:

cookie 中有個關鍵加密引數 m,其內容如下:

逆向分析
通過 hook cookie 中 m 引數的方式對其進行定位,hook 的方式有很多種,可以閱讀 K 哥往期文章,對其有詳細介紹,這里使用編程貓 Fiddler 插件進行 hook,相關插件在 K哥爬蟲公眾號發送【Fiddler插件】即可獲取,Hook 代碼如下:
(function () {
'use strict';
var cookieTemp = '';
Object.defineProperty(document, 'cookie', {
set: function (val) {
if (val.indexOf('m') != -1) {
debugger;
}
console.log('Hook捕獲到cookie設定->', val);
cookieTemp = val;
return val;
},
get: function () {
return cookieTemp;
},
});
})();
勾選開啟框,啟動 Fiddler 進行 hook 注入:

重繪網頁,如果進入無限 debugger,則按上述方式解決,不過直接通過 m 引數定位并不是最好的方案,因為該 cookie 中還有其他引數包含 m 字母,位置不對則重繪網頁,這里成功斷在 m 引數的值生成的位置:

向上跟堆疊到 _0xdad69f (2:18) 處,然后點擊左下角 { } 格式化代碼,會跳轉到 2:formatted 檔案的第 4943 行,該行內容如下:
document[$dbsm_0x42c3(qqLQOq, iOiqII) + $dbsm_0x42c3(q1IoqQ, QQlLlq)] = _0x5500bb['\x4e\x74\x44' + '\x72\x43'](_0x5500bb[$dbsm_0x42c3(qqqQoq, oqQiiO) + '\x6d\x65'](_0x5500bb[$dbsm_0x42c3(Ioo0ql, olq0Oq) + '\x6d\x65'](_0x5500bb[$dbsm_0x42c3(qOIqQi, OOqIQi) + '\x72\x44'](_0x5500bb[$dbsm_0x42c3(Q1qoqQ, lILOOq) + '\x72\x44'](_0x5500bb[$dbsm_0x42c3(qOO1Q0, oiqlQQ) + '\x72\x44'](Ql1OO0, _0x5500bb['\x7a\x76\x67' + '\x6c\x77'](_0x3c9ca8)), Qoqq0I), _0x5500bb[$dbsm_0x42c3(iqOiQ0, QOiq0Q) + '\x47\x6b'](_0x313b78, _0x160e3a)), lOo0QQ), _0x160e3a), _0x5500bb[$dbsm_0x42c3(qiOOiO, liQIoQ) + '\x4e\x5a']),
控制臺列印后可知這里就是 cookie 中 m 引數值生成的位置:

在控制臺中進一步列印分析下其他部分含義:

m 引數值的格式如下:
0ef478cf61e0749d7444c7997c917679|1663213224000
可以依此將代碼進行簡化:
_0x5500bb[$dbsm_0x42c3(iqOiQ0, QOiq0Q) + '\x47\x6b'](_0x313b78,_0x160e3a) + lOo0QQ + _0x160e3a
控制臺列印驗證,結果匹配:

接下來先跟進到 _0x5500bb[$dbsm_0x42c3(iqOiQ0, QOiq0Q) + '\x47\x6b'] 中,滑鼠選中后點擊進入:

在該檔案的第 3911 行,內容如下:
_0x434ddb[$dbsm_0x42c3(Iooo0l, Qq1oqI) + '\x47\x6b'] = function(_0x105ffe, _0x733be0) {
return _0x105ffe(_0x733be0);
}
回傳值為 _0x105ffe(_0x733be0),該函式傳入的引數為 _0x313b78 和 _0x160e3a,所以可以進一步改寫:
_0x313b78(_0x160e3a) + lOo0QQ + _0x160e3a
_0x160e3a 為時間戳,因此 m 引數的值是將時間戳作為引數傳入 _0x313b78 函式后加密得到的,所以需要進一步跟進到 _0x313b78 函式定義的位置,同樣滑鼠選中,點擊即可跳轉到第 4933 行,到 node 環境中除錯,初步代碼為:
function _0x313b78(_0x575158, _0x1fa91a, _0x1cf5de) {
// 以下部分內容過長,此處省略
// 完整代碼關注 GitHub:https://github.com/kgepachong/crawler
}
var _0x160e3a = Date.parse(new Date());
var m = _0x313b78(_0x160e3a) + lOo0QQ + _0x160e3a;
console.log(m);
運行后會提示 _0x5500bb 未定義,到原檔案中 ctrl + f 區域搜索這個函式,在第 3940 行:
_0x5500bb = _0x434ddb
補上運行后會提示 _0x434ddb 未定義,搜索后發現 _0x434ddb 在第 2817 行定義為一個空物件,后面向其中傳入了很多值,類似于一個大陣列,不能只補 _0x434ddb = {};,需要把傳值部分補進去,不然后面運行時會出現些報錯,經測驗有的部分不要也可以,但是細扣就很麻煩了,直接全補即可,這就很多了,從第 2817 行一直扣到第 3939 行,補完后接著運行程式,這次又提示 $dbsm_0x42c3 未定義,接著搜找其定義位置,在第 94 行,補了后提示 OooIi1 未定義,在第 209 行,需要從第 209 行到第 2816 行全部補上,不然會提示其中某一個未定義,同樣的,雖然經除錯有的不需要也行,但是一個個調麻煩且沒有必要,補完后接著運行又會提示 $dbsm_0x123c 未定義:

其在第 22 行,是個大陣列,補了之后運行程式后發現卡住了,一段時間后程度報錯:

這個報錯可能是記憶體資源耗盡導致程式崩潰了,將這部分代碼復制到瀏覽器中進行除錯,開啟一個新頁面,打開開發者人員工具,在 Sources 中選擇 Snippets,新建一個腳本,將已經扣下來的代碼粘貼進去,在第一行寫入 debugger;手動打斷點除錯,ctrl + s 保存檔案后點擊右下角按鈕運行腳本即會在第一行斷住:

點擊單步除錯,一步步查看是哪里出了問題:

點了幾步后,卡了一下,跳到第 2711 行,是個 for 回圈,右側出現紅框報錯,意思是潛在的記憶體崩潰,即單步除錯斷到到此處時程式臨近記憶體崩潰:

接著往后單步除錯,會發現一直在第 2712 行和第 2713 行間來回執行,到后來甚至瀏覽器崩潰了,所以問題出在 WxzuQr 物件中出現了無限回圈,直至耗盡了記憶體資源:

這部分內容在 $dbsm_0x42c3 函式中,接下來需要研究一下崩潰原因,右側堆疊中向上跟堆疊,上兩步分別通過建構式創建了兩個實體物件 WjJIeN 和 vnuqco,WjJIeN 部分如下:
_0x11a714['prototype']['WjJIeN'] = function(_0x4859ef) {
if (!Boolean(~_0x4859ef)) {
return _0x4859ef;
}
return this['WxzuQr'](this['yewpLt']);
}
這里進行了一個 if 判斷,~ 為按位取反,意思是如果 !Boolean(~_0x4859ef) 的值為 false,則執行 WxzuQr 的無限回圈行為,直至程式崩潰,接著跟進到 vnuqco 部分,查看 _0x4859ef 是啥,對什么進行了判斷:
_0x11a714['prototype']['vnuqco'] = function() {
_0x2940ac = new RegExp(this['PuKGlh'] + this['CTXIfT']),
_0x3fba94 = _0x2940ac['test'](this['XxpyjG']['toString']()) ? --this['yHmSUE'][0x1] : --this['yHmSUE'][0x0];
return this['WjJIeN'](_0x3fba94);
}
回傳值中給 WjJIeN 傳入的引數為 _0x3fba94,其定義在第 2699 行,是個三目運算式:
_0x2940ac['test'](this['XxpyjG']['toString']()) ? --this['yHmSUE'][0x1] : --this['yHmSUE'][0x0];
到控制臺列印輸出一下,看看該行各部分什么含義:

--this['yHmSUE'][0x1] 的值固定為 -1,而每運行一次 this['yHmSUE'][0x0] 的值即減一:
console.log(!Boolean(~-1)) // true
console.log(!Boolean(~-2)) // false
所以只有當 _0x2940ac['test'](this['XxpyjG']['toString']()) 的值為 true 時才不會進入無限回圈,在控制臺列印下 this['XxpyjG']['toString']() 部分內容:

這個函式在第 2689 行,再來看看對其進行了怎樣的判斷,跟進到 _0x2940ac 定義位置,在第 2698 行,是個正則運算式物件,控制臺中列印后可知道運算式為:
/\w+ *\(\) *{\w+ *['|"].+['|"];? *}/
- / ... /:正則運算式限制符,字面量寫法,防轉義,中間正則運算式部分若存在 \d 不會將 \d 的 \ 當作轉義字符
*\(\):匹配零個或多個括號,\ 為轉義字符*{ 、*}:匹配前后大括號- \w+:匹配一個或多個字母數字字符
- .+:貪婪匹配任意字符
- ['|"]:匹配單引號或者雙引號
- ;?:匹配零個或一個分號
所以匹配樣式大致如下:XXX( ){ XXX ' XXX ' ;},并不匹配換行符、制表符、空格等,沒格式化的代碼會被壓縮成一行,所以這里相當于格式化檢測,由于一開始進行了格式化操作,因此判斷結果為 false,從而進入了無限回圈,導致程式崩潰,所以只需要將這部分內容壓縮為一行即可,檢驗一下:

沒有格式化后列印出的結果為 true,即不會呼叫到 WxzuQr 物件,從而進入無限回圈,修改后再次運行程式,結束了嗎,當然沒有,上個問題倒是解決了,又出現了以下報錯:

報錯在第 3854 行,內容如下:
_0x5500bb[$dbsm_0x42c3(QoLq0i, q0Oqqo) + '\x5a\x49']
接著在瀏覽器中進行除錯,在這一行上面打上 debugger;然后運行腳本,斷住后列印分析一下:

'\x5a\x49' 即 ‘ZI’,QoLq0i、q0Oqqo 為定值,因此問題出在 $dbsm_0x42c3 函式中,其實如果對 OB 混淆了解的話會知道這種混淆方式有一些特征,其一般由三部分組成:大陣列、移位自執行函式、解密字串函式,大陣列我們之前已經找到了,就是 $dbsm_0x123c,而 $dbsm_0x42c3 是解密字串函式,這里差了個移位自執行函式,缺東西自然結果會不對,需要找到將其補上,在第 23 行到第 93 行,夾在 $dbsm_0x123c 和 $dbsm_0x42c3 之間,補完后運行程式,又到了熟悉的卡住,過了一會后報錯:

報錯在第 27 行,放到瀏覽器中進行除錯,還是在開頭打上 debugger;運行后單步向下執行,點了幾下熟悉的卡住,然后跳到第 24 行 for 回圈處:

右側出現熟悉的警告提示,證明又進入到無限回圈了,果不其然,過了一會瀏覽器頁面就崩潰了:

根據之前的經驗,看看是不是哪又有個格式化檢測導致進入到這個回圈里,果不其然,在第 55 行:

這里是對 removeCookie 處的代碼進行了格式化檢測,同樣將函式體部分寫成一行即可:
'removeCookie': function() {return 'dev';},
接著運行,又提示 _0x3c9ca8 未定義,ctrl + f 區域搜索找到函式定義位置扣下來即可,運行后又提示 _0x1316f4 未定義,這個扣下來之后記得將后面的自執行的括號刪掉,接著會提示 _0x12a78e 未定義,扣下來的時候同樣記得刪掉末尾的括號,再接著就沒什么特別需要注意的了,差哪個函式補哪個就行了,到后面提示 navigator 未定義,簡單地補瀏覽器環境即可,node 環境下 window 設定為 global:
var window = global;
window.navigator = {};
自然不會這么輕易的結束了,運行后又會提示 _0x184fb0 未定義,跟之前一樣,搜到扣下來即可,后面就是漫長的補函式的程序,沒別的技巧,就是需要耐心,手都 cv 酸了,直到出現如下報錯:

報錯提示 history 未定義,這是個瀏覽器物件,顯示在 console.log 處報錯,在 console.log 行打斷點除錯,運行到這里時會跳轉到虛擬機中,其中代碼如下:

history.pushState 是向瀏覽器的會話歷史中添加記錄,當使用 console.log 輸出結果的時候,就會執行 history.pushState,但是我們并沒有 history 環境,所以會報錯,補了 history 環境后運行程式發現一直卡著,仔細看代碼才發現有個 while 回圈,最離譜的是里面的 for 回圈設定了 1100000 次,幾乎可以說是在不間斷檢測,等不得等到猴年馬月去了,這里直接將 console.log 賦值給一個變數替換掉即可,記得放到前面:
var result = console.log;
至此,終于結束了!成功列印出 m 引數的值:

這個題倒是不難,逆向下來思路也很清晰,但是扣代碼的程序繁雜且坑不少,還是很值得大家上手去練習的,
完整代碼
bilibili 關注 K 哥爬蟲,小助理手把手視頻教學:https://space.bilibili.com/1622879192
GitHub 關注 K 哥爬蟲,持續分享爬蟲相關代碼!歡迎 star !https://github.com/kgepachong/
以下只演示部分關鍵代碼,不能直接運行!
JavaScript 代碼
var window = global;
window.navigator = {};
var result = console.log;
// 以下部分內容過長,此處省略
// 完整代碼關注 GitHub:https://github.com/kgepachong/crawler
function _0x313b78(_0x575158, _0x1fa91a, _0x1cf5de) {
if (_0x5500bb[$dbsm_0x42c3(QoLq0i, q0Oqqo) + '\x5a\x49'](_0x5500bb[$dbsm_0x42c3(LQOI0Q, QqOI00) + '\x73\x42'], _0x5500bb[$dbsm_0x42c3(Q00oiq, QIioOo) + '\x5a\x76'])) {
VWQQuv['\x6f\x4f\x61' + '\x68\x47'](debuggerProtection, Q0LiqQ);
} else {
_0x5500bb[$dbsm_0x42c3(i1lQqq, q110Lq) + '\x62\x45'](_0x3c9ca8);
return _0x1fa91a ? _0x1cf5de ? _0x5500bb[$dbsm_0x42c3(iqqLQO, LoOOOq) + '\x4b\x6b'](_0x21cf21, _0x1fa91a, _0x575158) : _0x5500bb['\x72\x71\x75' + '\x4b\x51'](y, _0x1fa91a, _0x575158) : _0x1cf5de ? _0x5500bb[$dbsm_0x42c3(qLQQ1q, I1oOQ1) + '\x4d\x6e'](_0x443ca7, _0x575158) : _0x5500bb[$dbsm_0x42c3(qLLoQi, iO0OQo) + '\x4d\x6e'](_0x184fb0, _0x575158);
}
}
function getCookieM(){
var _0x160e3a = Date.parse(new Date());
var m = _0x313b78(_0x160e3a) + lOo0QQ + _0x160e3a;
return m;
}
// var _0x160e3a = Date.parse(new Date());
// var m = _0x313b78(_0x160e3a) + lOo0QQ + _0x160e3a;
// result(m);
Python 代碼
# =======================
# --*-- coding: utf-8 --*--
# @Time : 2022/9/8
# @Author : 微信公眾號:K哥爬蟲
# @FileName: yrx5.py
# @Software: PyCharm
# =======================
import execjs
import requests
import re
def get_cookie_m():
heat_total = 0
for page_num in range(1, 6):
with open('yrx2.js', 'r', encoding='utf-8') as f:
encrypt = f.read()
cookie_m = execjs.compile(encrypt).call('getCookie')
headers = {
"user-agent": "yuanrenxue,project",
}
cookies = {
"sessionid": " 填入自己的 sessionid ",
"m": cookie_m
}
url = "https://match.yuanrenxue.com/api/match/2?page=%s" % page_num
response = requests.get(url, headers=headers, cookies=cookies)
for i in range(10):
value = https://www.cnblogs.com/ikdl/p/response.json()['data'][i]
heat = re.findall(r"'value': (.*?)}", str(value))[0]
heat_total += int(heat)
print(heat_total)
if __name__ == '__main__':
get_cookie_m()


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