【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 MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...