逆向目标

  • 猿人学 – 反混杂刷题渠道 Web 第五题:js 混杂,乱码增强
  • 目标:抓取全部 5 页直播间热度,核算前 5 名直播间热度的加和
  • 主页:match.yuanrenxue.com/…
  • 接口:match.yuanrenxue.com/…
  • 逆向参数:

    • url 恳求参数:m、f
    • Cookie 参数:m、RM4hZBv0dDon443M

逆向进程

抓包剖析

进入网页,点击右键查看页面源代码,查找不到直播间相关数据信息,证明是经过 ajax 加载的数据,ajax 加载有特别的恳求类型 XHR,翻开开发者人员东西,改写网页进行抓包,在 Network 的挑选栏中挑选 XHR,数据接口为 5?m=XXX&f=XXX,在响应预览中能够看到各直播间热度数据:

接口 url 有两个恳求参数 m 和 f,现在还不知道详细怎样来的:

本题提示 cookie 有效期仅为 50 秒钟,即 cookie 值是在动态改变的,经过比照剖析,cookie 中有两个动态改变的参数 m 和 RM4hZBv0dDon443M,接下来需求定位到其生成的方位:

逆向剖析

Cookie 加密参数剖析

能够经过 Hook Cookie 的方法定位参数方位,这里经过 Fiddler 编程猫插件进行 Hook,相关插件在 K哥爬虫大众号发送【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:

清除网页缓存,勾选敞开框,翻开 Fiddler 进行 Hook 注入,能够发现成功断住:

从右侧仓库中向上跟栈,会发现跟到了虚拟机 VMXXX 中,点击右下角 { } 格式化,跳转到了第 978 行,代码部分如下:

_0x3d0f3f[_$Fe] = 'R' + 'M' + '4' + 'h' + 'Z' + 'B' + 'v' + '0' + 'd' + 'D' + 'o' + 'n' + '4' + '4' + '3' + 'M=' + _0x4e96b4['_$ss'] + ';\x20path=/';

在该行打下断点进行调试,控制台打印相关参数:

  • _$Fe:cookie
  • _ 0x4e96b4[‘_$ss’]:RM4hZBv0dDon443M 参数加密后的值

前面各字母组成起来便是 RM4hZBv0dDon443M=,此处便是 RM4hZBv0dDon443M 参数加密后赋值给 cookie 的方位,所以要害的加密部分为 _0x4e96b4['_$ss'],打印相关内容会发现 _0x4e96b4 是 window 目标,window. _$ss 即加密后的值:

直接查找 _$ss 没有成果,相同测验 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]]();

在该行打下断点调试剖析各自意义:

  • '_$s'_$UH[0x348][0x1] _$UH[0x353][0x1] 组合起来:’_$ss’
  • _$UH[0x1f]():toString()
  • _0x29dd83[ _$UH[0x1f]]():将 _0x29dd83 生成的值转化为字符串

因而要害的加密方位肯定在 _0x29dd83 中,往上看, _0x29dd83 界说在第 1225 行,这时分眼前一亮,看到了 mode 和 padding 两个要害字,这里大概率为 AES 或许 DES 加密,将代码解混杂替换后的成果如下:

_$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']();

现在就很明显了,这里为 AES 加密,加密内容为 _$Ww,key 值为 _0x4e96b4['_$qF'],加密模块为 ECB,填充方法为 pkcs7:

  • CBC:Cipher Block Chaining(密码块链接模式),是一种循环模式,前一个分组的密文和当前分组的明文异或操作后再加密,这样做的目的是增强破解难度
  • PKCS7:在填充时首要获取需求填充的字节长度 = 块长度 – (数据长度 % 块长度), 在填充字节序列中所有字节填充为需求填充的字节长度值

_$Ww 的值由 _0x4e96b4['_$pr'] 转化为字符串后经过 utf-8 编码得到,其与 key 值 _0x4e96b4['_$qF'] 都是数组,需求知道这两个数组是怎样生成的,先 ctrl + f 查找 _0x4e96b4['_$qF'],界说在第 1444 行,内容如下:

_0x4e96b4['_$qF'] = CryptoJS['enc']['Utf8'][_$UH[0xff]](_0x4e96b4['btoa'](_0x4e96b4['_$is'])['slice'](0x0, 0x10));

在该行打下断点,控制台打印剖析一下:

由此可见,_0x4e96b4['_$qF'] 是经过 CryptoJS 库将字符串经过 base64 加密后取前 16 位的成果,查找 _0x4e96b4['_$is'],找到字符串生成的方位,在第 674 行,由 _$yw 赋值,在上一行能够看到了解的 _$Fe,即 cookie,发现 cookie 中的 m 参数是在这里界说的:

_0x3d0f3f[_$Fe] = 'm=' + _0x474032(_$yw) + ';\x20path=/';

参数 m 的值也与 _$yw 有关,m 参数是将 _$yw 经过 _0x474032 函数处理后得到,后边再专门进行剖析,_$yw 界说在第 672 行:

_$yw = _0x2d5f5b()[_$UH[0x1f]]();

_$UH[0x1f] 为 “toString”,_$yw 的值是将 _0x2d5f5b() 函数的返回值转化成了字符串得到的,跟进到该函数界说的方位,查找后发现在第 279 行,控制台打印后发现这里便是时刻戳,所以 _$yw 即时刻戳:

因而 _0x4e96b4['_$qF']的值是将时刻戳经过 base64 加密后取了前 16 位的成果,接下来只需求知道 _0x4e96b4['_$pr']是如何生成的,就能复现出 RM4hZBv0dDon443M 参数的加密进程,在第 1224 行打断点调试发现此时的 _0x4e96b4['_$pr'] 数组包含五个值:

现在就需求知道这五个值是在哪传进去的,查找 _0x4e96b4['_$pr'] 看看哪里对其进行了赋值,每个都打下断下,该数组界说在第 270 行:

_0x4e96b4['_$pr'] = new _0x4d2d2c();

_0x4d2d2c 在第 224 行界说为 Array,所以这里是创建了一个数组 _0x4e96b4['_$pr'],接着往后找传值的当地,继续运行断点调试,第 1717 行的断点运行了四次传入了四个值:

_0x4e96b4['_$pr']['push'](_0x474032(_$Wa));

跟进 _$Wa 界说的方位,在第 1715 行,由 _0x12eaf3 函数生成,跟进到这个函数的方位,在第 275 行,返回值解混杂后如下:

Date['parse'](new Date());

再次下一步调试断点会跳转到第 868 行,这时分数组被传入了第五个值,_$yw 为时刻戳,因为 m = _0x474032(_$yw),所以第五个值也便是参数 m 的值,记住这里呈现的 _0x4e96b4['_$is']

_0x3d0f3f[_$Fe] = 'm=' + _0x474032(_$yw) + ';\x20path=/';
_0x4e96b4['_$is'] = _$yw;
_0x4e96b4['_$pr']['push'](_0x474032(_$yw));

数组值的生成方位都找到了,跟 m 参数一样,传入的值都经过了 _0x474032 函数的处理,因而需求跟进 _0x474032 函数,鼠标选中,点击即可跳转到该函数界说的方位:

在第 455 行,返回值为三目表达式:

function _0x474032(_0x233f82, _0xe2ed33, _0x3229f9) {
        return _0xe2ed33 ? _0x3229f9 ? v(_0xe2ed33, _0x233f82) : y(_0xe2ed33, _0x233f82) : _0x3229f9 ? _0x41873d(_0x233f82) : _0x37614a(_0x233f82);
}

在 return 处打下断点调试,_0x233f82 为传入的 _$yw 的值,即时刻戳,后边两个参数均为 undefined,所以无妨将函数简化下:

function _0x474032(_0x233f82, _0xe2ed33, _0x3229f9) {
    return _0x37614a(_0x233f82);
}

接下来需求跟进到 _0x37614a 函数的方位:

function _0x37614a(_0x32e7c1) {
        return _0x499969(_0x41873d(_0x32e7c1));
}

这里就需求跟出 _0x499969 函数和 _0x41873d 函数的内容,接下来便是扣,缺啥补啥,缺函数补函数,缺环境补环境,若报错提示 _$UH is not defined_$UH 是个大数组,直接将其全体解混杂替换掉就行了,例如:

_$UH[0x6c] ---> "length" 

或许写成键值对方式:

_$UH = {
    8: 'prototype',
    15: 'charCodeAt',
    31: 'toString',
    108: 'length'
}

值得注意的是 _0x11a7a2 函数,运行时会报错 op is not defined,op 界说在第 308 行:

op 的值为 26,这里直接将其界说成固定值即可,即 var op = 26;

相同将 _0x42fb36 和 b64pad 也写成固定值,即 _0x42fb36 = 16;b64pad = 1;

调试进程中还发现 window['_$6_']window['_$tT']window['_$Jy'] 这几个参数的值是在动态改变的,不进行改写乃至将相关部分注释掉,在本地 node 环境中都是能够运行出成果的,可是用 python 调用的话会报错,证明在前端会对这几个参数进行校验,这几个参数在 _0x11a7a2 函数中界说,该函数溯源后终究被 _0x474032 函数调用,_0x474032 函数对 _$yw 的值进行处理,生成了 _0x4e96b4['_$pr'] 数组的最终一个值及 m 参数的值,所以如果这几个参数的值匹配过错的话会导致校验失利,咱们只需求打断点看 m 参数的值生成的时分,这三个参数的值是多少,然后写成固定值就行了:

window['_$6_'] = -389564586;
window['_$tT'] = -660478335;
window['_$Jy'] = -405537848;

至此 Cookie 中 RM4hZBv0dDon443M 参数和 m 参数的生成逻辑就疏通了,以下经过 JavaScript 对其复现:

// 以下函数部分内容过长,此处省略
// 完好代码关注 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(_0x474032(_Wa))
}
// _yw 传入一个值
    pr.push(_0x474032(_yw));
return pr.toString();
}
var RM4hZBv0dDon443M = rm4Encrypt(_yw, pr());
// m 为数组传入的最终一个值
var m = pr[4];
console.log('RM4hZBv0dDon443M 参数加密后的值为: ' + RM4hZBv0dDon443M)
console.log('m 参数的值为: ' + m)</code></pre><p>运行成果:</p><p></p><h3>恳求头参数剖析</h3><p>Cookie 中的参数剖析完了,还有两个恳求参数 m 和 f 没有解决,直接从接口处跟栈,从 Initiator 中跟到 request 里:</p><p></p><p>点击右下角 { } 格式化后会跳转到 <code>5:formatted</code> 文件的第 856 行,在第 883 行的 list 中能够找到参数 m 和 f 的界说方位:</p><pre><code class="JavaScript">"m": window._is,
"f": window._zw[23]</code></pre><p>m 的值是 <code>window._is,有没感觉似曾相识,便是上文所说的 0x4e96b4['is']</code><code>_0x4e96b4</code> 便是 window,所以这里 m 的值其实便是 <code>_yw ;f 的值是 window._zw[23]</code> ,现在需求知道 <code>_zw[23] 的值怎样生成的,部分查找 _zw</code> 会发现该数组界说在第 611 行,接着往后找,看看数组中的第 23 个是什么,先控制台打印一下内容:</p><p></p><p>第 633 行内容是第六个,顺下去找会发现第 23 个的内容如下:</p><pre><code class="JavaScript">_aiding._zw.push(_t1);

var RM4hZBv0dDon443M = rm4Encrypt(_yw, pr()); // m 为数组传入的最终一个值 var m = pr[4]; console.log('RM4hZBv0dDon443M 参数加密后的值为: ' + RM4hZBv0dDon443M) console.log('m 参数的值为: ' + m)</code></pre><p>运行成果:</p><p></p><h3>恳求头参数剖析</h3><p>Cookie 中的参数剖析完了,还有两个恳求参数 m 和 f 没有解决,直接从接口处跟栈,从 Initiator 中跟到 request 里:</p><p></p><p>点击右下角 { } 格式化后会跳转到 <code>5:formatted</code> 文件的第 856 行,在第 883 行的 list 中能够找到参数 m 和 f 的界说方位:</p><pre><code class="JavaScript">"m": window._is, "f": window._zw[23]</code></pre><p>m 的值是 <code>window._is,有没感觉似曾相识,便是上文所说的 0x4e96b4['is']</code> ,<code>_0x4e96b4</code> 便是 window,所以这里 m 的值其实便是 <code>_yw ;f 的值是 window.zw[23]</code>,现在需求知道<code>_zw[23]</code> ,现在需求知道 <code>_zw[23] 的值怎样生成的,部分查找 zw</code>会发现该数组界说在第611行,接着往后找,看看数组中的第23个是什么,先控制台打印一下内容:</p><p></p><p>第633行内容是第六个,顺下去找会发现第23个的内容如下:</p><pre><codeclass="JavaScript">_zw</code> 会发现该数组界说在第 611 行,接着往后找,看看数组中的第 23 个是什么,先控制台打印一下内容:</p><p></p><p>第 633 行内容是第六个,顺下去找会发现第 23 个的内容如下:</p><pre><code class="JavaScript">_aiding.zw.push(_zw.push(_t1);

在此处打下断点调试验证一下,能够发现成果是一样的:

接下来只需求找到 t1</code>的界说方位即可,ctrl+f部分查找<code>_t1</code> 的界说方位即可,ctrl + f 部分查找 <code>_t1 ,其界说在第 613 行,是个时刻戳:

let _t1 = Date.parse(new Date());</code></pre><ul><li>Date.parse(new Date()):获取的时刻戳是把毫秒改成 000 显现,如 1662691102000</li><li>new Date().valueOf():获取了当前包含毫秒的时刻戳,如 1662691114310</li></ul><p>能够发现与 _Wa 的界说方法一致,比照一下 m 和 f 两个参数的值会发现差值接近于 50 秒,与标题中提示的 Cookie 有效期仅 50 秒钟对应上了:
在虚拟机文件的第 1975 行也有个 50 秒的定时器:
至此所有参数生成的逻辑都调度明晰了,本题并不难,可是扣代码的进程中有许多需求注意的细节,猿人学给大家供给了一个优质的练习渠道,做题也是一个很好的自我提升的方法。
完好代码
bilibili 关注 K 哥爬虫,小助理手把手视频教育:https://space.bilibili.com/16...
GitHub 关注 K 哥爬虫,继续分享爬虫相关代码!欢迎 star !https://github.com/kgepachong/
以下只演示部分要害代码,不能直接运行!
JavaScript 代码
var _0x4e96b4 = window = {};
var _0x1171c8 = 0x67452301;
var _0x4dae05 = -0x10325477;
var _0x183a1d = -0x67452302;
var _0xcfa373 = 0x10325476;
var _0x30bc70 = String;
// 以下函数部分内容过长,此处省略
// 完好代码关注 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(_0x474032(_Wa))
}
var yw = new Date().valueOf().toString(); 
    pr.push(_0x474032(_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 逆向百例】猿人学系列 web 比赛第五题:js 混杂 - 乱码增强,详细剖析