【JS 逆向百例】猿人学系列 web 比赛第五题:js 混淆 - 乱码增强,详细剖析
逆向目标
猿人学 - 反混淆刷题平台 Web 第五题:js 混淆,乱码增强
目标:抓取全部 5 页直播间热度,计算前 5 名直播间热度的加和
逆向参数:
- 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)
运行结果:
请求头参数分析
Cookie 中的参数分析完了,还有两个请求参数 m 和 f 没有解决,直接从接口处跟栈,从 Initiator 中跟到 request 里:
点击右下角 { } 格式化后会跳转到 5:formatted
文件的第 856 行,在第 883 行的 list 中可以找到参数 m 和 f 的定义位置:
"m": window._$is,
"f": window.$_zw[23]
m 的值是 window._$is
,有没感觉似曾相识,就是上文所说的 _0x4e96b4['_$is']
,_0x4e96b4
就是 window,所以这里 m 的值其实就是 _$yw
;f 的值是 window.$_zw[23]
,现在需要知道 $_zw[23]
的值怎么生成的,局部搜索 $_zw
会发现该数组定义在第 611 行,接着往后找,看看数组中的第 23 个是什么,先控制台打印一下内容:
第 633 行内容是第六个,顺下去找会发现第 23 个的内容如下:
$_aiding.$_zw.push($_t1);
在此处打下断点调试验证一下,可以发现结果是一样的:
接下来只需要找到 $_t1
的定义位置即可,ctrl + f 局部搜索 $_t1
,其定义在第 613 行,是个时间戳:
let $_t1 = Date.parse(new Date());
- Date.parse(new Date()):获取的时间戳是把毫秒改成 000 显示,如 1662691102000
- new Date().valueOf():获取了当前包括毫秒的时间戳,如 1662691114310
可以发现与 _$Wa 的定义方式一致,对比一下 m 和 f 两个参数的值会发现差值接近于 50 秒,与题目中提示的 Cookie 有效期仅 50 秒钟对应上了:
在虚拟机文件的第 1975 行也有个 50 秒的定时器:
至此所有参数生成的逻辑都调理清晰了,本题并不难,但是扣代码的过程中有许多需要注意的细节,猿人学给大家提供了一个优质的练习平台,做题也是一个很好的自我提升的方式。
完整代码
bilibili 关注 K 哥爬虫,小助理手把手视频教学:https://space.bilibili.com/1622879192
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 混淆 - 乱码增强,详细剖析的更多相关文章
- [js高手之路]深入浅出webpack系列2-配置文件webpack.config.js详解
接着上文,重新在webpack文件夹下面新建一个项目文件夹demo2,然后用npm init --yes初始化项目的package.json配置文件,然后安装webpack( npm install ...
- JS逆向之浏览器补环境详解
JS逆向之浏览器补环境详解 "补浏览器环境"是JS逆向者升职加薪的必备技能,也是工作中不可避免的操作. 为了让大家彻底搞懂 "补浏览器环境"的缘由及原理,本文将 ...
- python爬虫之JS逆向
Python爬虫之JS逆向案例 由于在爬取数据时,遇到请求头限制属性为动态生成,现将解决方式整理如下: JS逆向有两种思路: 一种是整理出js文件在Python中直接使用execjs调用js文件(可见 ...
- python爬虫之JS逆向某易云音乐
Python爬虫之JS逆向采集某易云音乐网站 在获取音乐的详情信息时,遇到请求参数全为加密的情况,现解解决方案整理如下: JS逆向有两种思路: 一种是整理出js文件在Python中直接使用execjs ...
- Blazor组件自做五 : 使用JS隔离封装Google地图
Blazor组件自做五: 使用JS隔离封装Google地图 运行截图 演示地址 正式开始 1. 谷歌地图API 谷歌开发文档 开始学习 Maps JavaScript API 的最简单方法是查看一个简 ...
- 通过JS逆向ProtoBuf 反反爬思路分享
前言 本文意在记录,在爬虫过程中,我首次遇到Protobuf时的一系列问题和解决问题的思路. 文章编写遵循当时工作的思路,优点:非常详细,缺点:文字冗长,描述不准确 protobuf用在前后端传输,在 ...
- 1. web前端开发分享-css,js入门篇
关注前端这么多年,没有大的成就,就入门期间积累了不少技巧与心得,跟大家分享一下,不一定都适合每个人,毕竟人与人的教育背景与成长环境心理活动都有差别,但就别人的心得再结合自己的特点,然后探索适合自己的学 ...
- Web报表工具FineReport的JS API开发(一)
很多报表软件可以利用JS接口来实现更多更复杂的功能.以FineReport为例,开放了大量的JS API给用户,根据执行JS的主体不同可以将分为三大类:FR.FS和contentWindow. 在js ...
- Dynamic CRM 2013学习笔记(二十五)JS调用web service 实现多条记录复制(克隆)功能
前面介绍过如何克隆一条当前的记录: Dynamic CRM 2013学习笔记(十四)复制/克隆记录 , 主要是通过界面上加一个字段,单击form上的clone 按钮时,改变这个字段的值以触发插件来实现 ...
- web前端开发分享-css,js入门篇(转)
转自:http://www.cnblogs.com/jikey/p/3600308.html 关注前端这么多年,没有大的成就,就入门期间积累了不少技巧与心得,跟大家分享一下,不一定都适合每个人,毕竟人 ...
随机推荐
- 再谈BOM和DOM(1):BOM与DOM概述
JavaScript的实现包括以下3个部分: ECMAScript(核心):描述了JS的语法和基本对象. 浏览器对象模型(BOM):与浏览器交互的方法和接口 文档对象模型 (DOM):处理网页内容的方 ...
- allowedOrigins cannot contain the special value "*"
Spring Boot的版本高于 2.4以后 ,原来的配置已经不适合目前的版本 将代码中的allowedOrigins改为allowedOriginPatterns @Configuration pu ...
- JAVA 获取 URL 指定参数的值
JAVA 获取 URL 指定参数的值 @Test void regexTest() { String url = "https://www.cnblogs.com/vipsoft/p/152 ...
- 【AI 安全探索】AI 流行的时代,我们应该担心什么?
视频地址:https://www.bilibili.com/video/BV1eg4y1Q7N5/ 是未来危机,还是眼下的问题? Sasha Luccioni 是 Hugging Face 团队的道德 ...
- Rocketmq学习2——Rocketmq消息过滤&事务消息&延迟消息原理源码浅析
系列文章目录和关于我 零丶引入 在<Rocketmq学习1--Rocketmq架构&消息存储&刷盘机制>中我们学习了rocketmq的架构,以及消息存储设计,在此消息存储设 ...
- 供应链安全情报 | 恶意py包伪装代理SDK进行后门攻击,目标锁定python开发者
概述 2023年11月28号,悬镜供应链安全实验室在Pypi官方仓库(https://pypi.org)监测到两起伪装成http和socks5代理SDK的开源组件投毒事件.python开发者一旦下载安 ...
- kafka集群三、增加密码验证
系列导航 一.kafka搭建-单机版 二.kafka搭建-集群搭建 三.kafka集群增加密码验证 四.kafka集群权限增加ACL 五.kafka集群__consumer_offsets副本数修改 ...
- S3C2440移植uboot之支持NORFLASH
上节S3C2440移植uboot之支持NAND启动修改了代码支持了NAND启动.这节我们分析uboo使其支持NORFLASH的操作. 目录 1.分析启动错误 2.修改代码 3.在匹配数组中添加我们 ...
- 《3D编程模式》写书-第6次记录
大家好,这段时间我完成了对初稿的第二轮修改,已经把稿子提交给编辑了 这里是所有的的写书记录: <3D编程模式>写书记录 本轮修改主要进行了下面的修改: 修改UML描述 增加依赖关系 角色之 ...
- 云网络智慧课堂-Qt程序代码开发规范
序言: 编程规范可以提升代码可读性,提高可维护性. 目录: 一.命名规范 二.内存管理规范 三.函数方法规范 四.控制语句规范 五.注释规范 六.排版规范 七.版本管理规范 八.界面编程 词义解释:强 ...