真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

【JS 逆向百例】猿人學(xué)系列 web 比賽第五題:js 混淆

創(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ù):

    • url 請求參數(shù):m、f
    • Cookie 參數(shù):m、RM4hZBv0dDon443M

逆向過程

抓包分析

進入網(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ù):

  • _$Fe:cookie
  • _ 0x4e96b4['_$ss']:RM4hZBv0dDon443M 參數(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:

  • CBC:Cipher Block Chaining(密碼塊鏈接模式),是一種循環(huán)模式,前一個分組的密文和當(dāng)前分組的明文異或操作后再加密,這樣做的目的是增強破解難度
  • PKCS7:在填充時首先獲取需要填充的字節(jié)長度 = 塊長度 - (數(shù)據(jù)長度 % 塊長度), 在填充字節(jié)序列中所有字節(jié)填充為需要填充的字節(jié)長度值

_$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é)果:

請求頭參數(shù)分析

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());
  • Date.parse(new Date()):獲取的時間戳是把毫秒改成 000 顯示,如
  • new Date().valueOf():獲取了當(dāng)前包括毫秒的時間戳,如

可以發(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)鍵代碼,不能直接運行!

JavaScript 代碼

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

Python 代碼

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


本文名稱:【JS 逆向百例】猿人學(xué)系列 web 比賽第五題:js 混淆
鏈接URL:http://weahome.cn/article/dsogscd.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部