【JS 逆向百例】猿人學系列 web 比賽第二題:js 混淆 - 動態 cookie,詳細剖析

来源:https://www.cnblogs.com/ikdl/archive/2022/11/10/16877291.html
-Advertisement-
Play Games

逆向目標 猿人學 - 反混淆刷題平臺 Web 第二題:js 混淆,動態 cookie 目標:提取全部 5 頁發佈日熱度的值,計算所有值的加和 主頁:https://match.yuanrenxue.com/match/2 介面:https://match.yuanrenxue.com/api/mat ...


1

逆向目標

逆向過程

抓包分析

進入網頁,點擊右鍵查看頁面源代碼,搜索不到直播間相關數據信息,證明是通過 ajax 載入的數據,ajax 載入有特殊的請求類型 XHR,打開開發者人員工具,刷新網頁進行抓包,會跳轉到虛擬機中,進入無限 debugger,過無限 debugger 的方式在往期文章中有詳細介紹,感興趣的可以去閱讀學習一下,這裡直接在 debugger 行右鍵選擇 never pause here,然後下一步斷點即可過掉:

2

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

3

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

4

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

5

逆向分析

通過 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 註入:

6

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

7

向上跟棧到 _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 參數值生成的位置:

8

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

9

m 參數值的格式如下:

0ef478cf61e0749d7444c7997c917679|1663213224000

可以依此將代碼進行簡化:

_0x5500bb[$dbsm_0x42c3(iqOiQ0, QOiq0Q) + '\x47\x6b'](_0x313b78,_0x160e3a) + lOo0QQ + _0x160e3a

控制台列印驗證,結果匹配:

10

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

11

在該文件的第 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 未定義:

12

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

13

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

14

點擊單步調試,一步步查看是哪裡出了問題:

15

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

16

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

17

這部分內容在 $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];

到控制台列印輸出一下,看看該行各部分什麼含義:

18

--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']() 部分內容:

19

這個函數在第 2689 行,再來看看對其進行了怎樣的判斷,跟進到 _0x2940ac 定義位置,在第 2698 行,是個正則表達式對象,控制臺中列印後可知道表達式為:

/\w+ *\(\) *{\w+ *['|"].+['|"];? *}/
  • / ... /:正則表達式限制符,字面量寫法,防轉義,中間正則表達式部分若存在 \d 不會將 \d 的 \ 當作轉義字元
  • *\(\):匹配零個或多個括弧,\ 為轉義字元
  • *{ 、*}:匹配前後大括弧
  • \w+:匹配一個或多個字母數字字元
  • .+:貪婪匹配任意字元
  • ['|"]:匹配單引號或者雙引號
  • ;?:匹配零個或一個分號

所以匹配樣式大致如下:XXX( ){ XXX ' XXX ' ;},並不匹配換行符、製表符、空格等,沒格式化的代碼會被壓縮成一行,所以這裡相當於格式化檢測,由於一開始進行了格式化操作,因此判斷結果為 false,從而進入了無限迴圈,導致程式崩潰,所以只需要將這部分內容壓縮為一行即可,檢驗一下:

20

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

21

報錯在第 3854 行,內容如下:

_0x5500bb[$dbsm_0x42c3(QoLq0i, q0Oqqo) + '\x5a\x49']

接著在瀏覽器中進行調試,在這一行上面打上 debugger;然後運行腳本,斷住後列印分析一下:

22

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

23

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

24

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

25

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

26

這裡是對 removeCookie 處的代碼進行了格式化檢測,同樣將函數體部分寫成一行即可:

'removeCookie': function() {return 'dev';},

接著運行,又提示 _0x3c9ca8 未定義,ctrl + f 局部搜索找到函數定義位置扣下來即可,運行後又提示 _0x1316f4 未定義,這個扣下來之後記得將後面的自執行的括弧刪掉,接著會提示 _0x12a78e 未定義,扣下來的時候同樣記得刪掉末尾的括弧,再接著就沒什麼特別需要註意的了,差哪個函數補哪個就行了,到後面提示 navigator 未定義,簡單地補瀏覽器環境即可,node 環境下 window 設置為 global:

var window = global; 
window.navigator = {};

自然不會這麼輕易的結束了,運行後又會提示 _0x184fb0 未定義,跟之前一樣,搜到扣下來即可,後面就是漫長的補函數的過程,沒別的技巧,就是需要耐心,手都 cv 酸了,直到出現如下報錯:

27

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

28

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

var result = console.log;

至此,終於結束了!成功列印出 m 參數的值:

28

這個題倒是不難,逆向下來思路也很清晰,但是扣代碼的過程繁雜且坑不少,還是很值得大家上手去練習的。

完整代碼

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 = 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()

29


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 註釋 給別人看的,機器並不會執行這行語句 1.單行註釋 // 我是單行註釋 2.多行註釋 /* 我是多行註釋 我是多行註釋 我是多行註釋 我是多行註釋 */ // 這是一個main函數,這個是go語言啟動的入口 func main() { //fmt.Println :列印一句話,然後執行完畢後,進 ...
  • 大家好,這篇文章跟大家聊下 SpringCloudAlibaba 中的微服務組件 Nacos。Nacos 既能做註冊中心,又能做配置中心,這篇文章主要來聊下做配置中心時 client 端的一些設計,主要從源碼層面進行分析,相信看完這篇文章你對 Nacos client 端的工作原理應該有比較深刻的了 ...
  • 這一篇文章主要介紹一些python的基礎知識,包括演算法、數字和表達式、變數、語句、獲取用戶輸入等。 什麼是演算法 什麼是電腦編程呢?簡單的來說,電腦編程就是告訴電腦如何做。 而演算法只不過是流程或菜譜的時髦說法,詳盡的描述瞭如何完成某項任務,以便於電腦更好的執行。 例如下麵的菜譜,雞蛋火腿腸: ...
  • 一個龐大的分散式系統,各個組件間是如何協調工作的?組件是如何解耦的?線程運行如何更高效,減少阻塞帶來的低效問題?本節將對 Yarn 的服務庫和事件庫進行介紹,看看 Yarn 是如何解決這些問題的。 ...
  • 簡介: 原型模式,屬於創建型模式的一種。 主要針對對象進行克隆,把被克隆的對象稱之為原型,原型模式稱之為克隆模式也許更為貼切。 用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。 適用場景: 實例化對象的資源開銷過大時可直接克隆。 需要迴圈創建大量對象,此時用克隆也是一個挺不錯的選擇。 ...
  • 1 文件結構 每個C/C++程式通常分為兩個文件,頭文件(保存程式的聲明)和定義文件(保持程式的實現)。 頭文件以“.h”為尾碼;C程式的定義文件以“.c”為尾碼,C++程式的定義文件通常以“.cpp”為尾碼(也有一些“.cc”、“.cxx”、“.hpp”為尾碼)。 1.1 版權和版本的聲明 每個頭 ...
  • 前言 大家早好、午好、晚好吖 ❤ ~ 最近,一部名叫《點燃我,溫暖你》得電視劇衝進了大家得視野~ 講述得是肆意張揚的編程天才李峋與勇敢堅韌的少女學霸朱韻從青澀校園到職場拼搏幾經波折,依然攜手前行的成長愛情故事! 其中李峋用代碼做出的紅色跳動的愛心,一下子跳到朱韻的心坎里,同樣也跳到我們的心坎 今天, ...
  • 前後端分離開發,必須解決跨域問題! 跨域:對於 url 如 http://localhost:8080,請求協議、ip 地址、埠號,只要發送請求方和接收請求方的這三個數據中,只要有一個不同,就表示是跨域訪問! AJAX 跨域訪問:用戶訪問 A 網站時所產生的對 B 網站的跨域訪問請求均提交到 A ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...