某初滑块滑块,难度适中,食用地址(好像更新了代码,更简单了,我会给出复杂一点的版本):
aHR0cHM6Ly9hcHAueWljaHVhcHAuY24vaW5kZXgvaW52aXRlL2luZGV4Lw==
工具站:
"
https://babel.nodejs.cn/docs/babel-core
本文主要是对滑块的思路进行分析讲解,同样分析简单ob混淆的ast还原以及滑块加密逻辑

image.png (307.03 KB, 下载次数: 3)
下载附件
滑块情况
2025-8-11 16:44 上传
一、流程分析

image.png (20.77 KB, 下载次数: 3)
下载附件
debuggger
2025-8-11 16:45 上传
开局一个debugger,掏出多年的hook脚本,直接注入过掉(百度一搜就有,或者自己写一个)
let AAA = Function.prototype.constructor
Function.prototype.constructor = function (x) {
console.log(x)
if (X != "debugger") {
return AAA(x)
}
;
return AAA.call(this, x)
}
解决掉debugger我们就可以按照之前的经验来分析,我们需要进行滑动,来看需要处理的请求接口信息

image.png (95.67 KB, 下载次数: 3)
下载附件
滑动接口
2025-8-11 16:49 上传
很明显,啥都没有,还反手送我一个debugger,还有一串混淆的代码,很明显,这个js代码是我们要处理的,但是我们没有看到校验的逻辑。我们就可以猜测了,因为之前还有假的滑块,先排除一下,会不会是cookie校验

image.png (29.68 KB, 下载次数: 2)
下载附件
cookie前
2025-8-11 16:56 上传

image.png (28.01 KB, 下载次数: 3)
下载附件
cookie信息
2025-8-11 16:52 上传
我们对比一下滑动前和滑动后的参数变化,发现有一个异常的参数
[color=]guard_ret
,简单注入hook一下
(function () {
'use strict';
var cookie_cache = document.cookie;
Object.defineProperty(document, 'cookie', {
get: function () {
return cookie_cache;
},
set: function (val) {
console.log('Setting cookie', val);
if (val.indexOf('guard_ret') != -1) {
debugger;
}
var cookie = val.split(";")[0];
var ncookie = cookie.split("=");
var flag = false;
var cache = cookie_cache.split("; ");
cache = cache.map(function (a) {
if (a.split("=")[0] === ncookie[0]) {
flag = true;
return cookie;
}
return a;
})
cookie_cache = cache.join("; ");
if (!flag) {
cookie_cache += cookie + "; ";
}
return cookie_cache;
}
});
})();

image.png (80.26 KB, 下载次数: 4)
下载附件
cookie堆栈
2025-8-11 17:00 上传
这里就是成功断住了,我们往上看看堆栈,分析产生

image.png (108.16 KB, 下载次数: 3)
下载附件
拼接cookie
2025-8-11 17:01 上传
我们发现这个js文件就是我们返回的那一个js文件,混淆很难看,直接ast一把梭哈
二、ast还原混淆代码逻辑分析
拿到混淆代码先总览一边,看看还原思路。

image.png (662.4 KB, 下载次数: 2)
下载附件
混淆逻辑
2025-8-11 17:49 上传
很明显一个编码混淆。

image.png (319.25 KB, 下载次数: 1)
下载附件
赋值调用
2025-8-11 18:12 上传
然后发现有很多_0xb0e1函数的一个赋值调用,符合ob混淆的特征,是一个解密函数,然后就是找准大数组和数组移位函数

image.png (576.97 KB, 下载次数: 4)
下载附件
大数组逻辑
2025-8-11 18:13 上传
找准了大数组和解密函数,接下来就是开始写ast代码,先搭一个模板,理一下思路
(1)将三要素,大数组,数组移位函数,解密函数,存进内存
(2)编码混淆还原
(3)解密函数调用还原
(4)ob混淆特征大对象还原
一步一步来,找到对应的三要素位置,直接提取出来,存进内存。

image.png (519.9 KB, 下载次数: 4)
下载附件
语法树
2025-8-11 18:14 上传
let parse = require("@babel/parser").parse;
let traverse = require("@babel/traverse").default;
let generator = require("@babel/generator").default;
let fs = require("fs");
let types = require("@babel/types");
let jsc_code = fs.readFileSync("./混淆代码.js", encoding = "utf-8")
let ast = parse(jsc_code)
let init_ast = parse(jsc_code)
traverse(init_ast, {
Program: function (path) {
path.node.body = path.node.body.slice(0, 5)
path.stop()
}
})
let test_code = generator(init_ast, {minified: true}).code
eval(test_code)
接下来做一个编码的还原,掏出俩件套
traverse(ast, {
StringLiteral({node}) {
if (node.extra && /\\[ux]/gi.test(node.extra.raw)) {
node.extra = undefined;
}
}
});
traverse(ast, {
NumericLiteral({node}) {
if (node.extra && /^0[obx]/i.test(node.extra.raw)) {
node.extra = undefined;
}
}
});
完成这一步之后我们可以调用解密函数测试一下,有没有存进内存。

image.png (170.1 KB, 下载次数: 4)
下载附件
解密函数调用
2025-8-11 18:17 上传
因为我们有很多赋值调用的过程,我们把赋值的函数名给收集起来,然后统一替换成解密函数_0xb0e1,执行替换结果,代码如下。
let decode_name = []
traverse(ast, {
VariableDeclarator(path) {
const {id, init} = path.node;
if (init && (init.type === 'Identifier' || init.type === 'FunctionDeclaration') ) {
let name = id.name
decode_name.push(name)
path.remove()
}
}
});
traverse(ast, {
CallExpression: {
exit: function (path) {
let {node} = path
let func_name = node.callee.name
if (!decode_name.includes(func_name)) {
return
}
if (!node.arguments || node.arguments.length !== 1) {
return;
}
let eval_path = path.toString().replace(func_name, "_0xb0e1")
let result = eval(eval_path)
path.replaceWith(types.valueToNode(result))
}
}
});
这样就可以把所有解密函数调用的部分还原,可以看一下结果,还是非常的清晰

image.png (648.07 KB, 下载次数: 2)
下载附件
解密函数还原结果
2025-8-11 18:20 上传
接下来就是处理最难的部分,ob混淆的大数组处理。我的大致思路如下
1、收集所有的符合条件的对象
2、分成两部分处理,一部分是函数和运算符的还原,一部分是字符串的还原
3、通过函数调用和作用域去还原,比较特定,不通用
第一部分是收集所有的符合条件的对象
let obj_name = []
traverse(ast, {
VariableDeclarator: function (path) {
let {parentPath, node, scope} = path
if (!parentPath.isVariableDeclaration()) {
return;
}
let {id, init} = node
if (!types.isObjectExpression(init)) {
return;
}
if (!id.name && id.name === undefined) return;
let name = id.name
obj_name.push(name)
}
})
在最开始写代码建议使用某一个对象去测试,然后去处理函数和运算符的还原
traverse(ast, {
CallExpression: {
exit(path) {
if (path.get("callee.object").node && obj_name.includes(path.get("callee.object").node.name)) {
let property = path.get("callee.property").node.value
let argument_path_array = path.get("arguments")
let name = path.get("callee.object").node.name
path.scope.getBinding(name).scope.path.traverse({
ObjectExpression: function (path_Expression) {
let {node, parentPath} = path_Expression
if (!parentPath.isVariableDeclarator()) {
return
}
if (parentPath.node.id.name !== name) {
return;
}
let properties = node.properties
for (i of properties) {
if (i.key.value && i.key.value === property) {
let value = i.value
switch (value.type) {
case 'FunctionExpression':
let return_path = value.body.body[0].argument
if (value.body.body[0].argument.type === 'BinaryExpression') {
let operator = return_path.operator
let left = argument_path_array[0].node
let right = argument_path_array[1].node
path.replaceInline(types.binaryExpression(operator, left, right))
} else if (value.body.body[0].argument.type === 'CallExpression') {
let func = argument_path_array[0].node
if (!types.isCallExpression(path) && !path.arguments) {
return;
}
let func_arguments = path.node.arguments.slice(1)
path.replaceInline(types.callExpression(func, func_arguments))
}
break
}
}
}
path_Expression.scope.crawl();
}
});
}
}
}
})
第三步是字符串对应的还原
traverse(ast, {
MemberExpression:
function (path) {
if (obj_name.includes(path.get("object.name").node) && path.get('property').isStringLiteral()) {
let _string = path.get('property').node.value
let name = path.get("object.name").node
path.scope.getBinding(name).scope.path.traverse({
ObjectExpression: function (path_Expression) {
let {node, parentPath} = path_Expression
if (!parentPath.isVariableDeclarator()) {
return
}
if (parentPath.node.id.name !== name) {
return;
}
let properties = node.properties
for (i of properties) {
let key_value = i.key.value
let value = i.value
if (key_value === _string) {
switch (value.type) {
case 'StringLiteral':
let real_value = value.value
path.replaceWith(types.valueToNode(real_value))
break
}
}
}
path_Expression.scope.crawl();
}
});
}
}
})
总体还原思路是从函数调用入手,去匹配他的值和对象里的键这样去实现还原。现在更新的加密更加简单(需要处理定时器的逻辑),没有了对象的还原,简单写了一个,仅供参考
traverse(ast, {
CallExpression: {
exit: function (path) {
let {node} = path
let func_name = node.callee.name
if (func_name !== "_0x4de0") {
return;
}
let result = eval(path.toString())
path.replaceWith(types.valueToNode(result))
}
}
});
剩下的就是处理垃圾代码的删除,就可以完成这个的还原。
三、加密逻辑分析

image.png (713.85 KB, 下载次数: 4)
下载附件
加密逻辑
2025-8-11 18:29 上传
还原后的代码一目了然,可以看到清晰的逻辑,找一下 _0x2b7a59的一个生成逻辑
const _0x167271 = _0x39eb8c["getBoundingClientRect"](),
_0x4b9064 = _0x255abf['getBoundingClientRect'](),
_0x3f4be5 = _0x167271["left"] - _0x4b9064['left'],
_0x32d3e9 = _0x167271["top"] - _0x4b9064["top"];
x = Math["round"](_0x3f4be5);
y = Math["round"](_0x32d3e9);
var _0x2c1619 = _0x5ce987("guard"),
_0x564781 = _0x2c1619["substr"](0, 8),
_0x1499fd = _0x5cc496(x["toString"]() + 'x' + y["toString"](), _0x564781),
_0x2b7a59 = _0x40fb99(_0x1499fd);
document["cookie"] = "guardret=" + _0x2b7a59;
function _0x5ce987(_0x17f493) {
const _0x5b6ec3 = document['cookie']["split"](';');
let _0x119d4b = null;
for (let _0x4de2de of _0x5b6ec3) {
const [_0x1cc261, _0xeec935] = _0x4de2de["trim"]()['split']('=');
if (_0x1cc261 === _0x17f493) {
_0x119d4b = _0xeec935
}
}
return _0x119d4b;
}
function _0x5cc496(_0x49fb4d, _0x45898e) {
let _0x2f7fee = '';
var _0x45898e = _0x45898e + "zVbhsiCROg";
for (let _0x351fbd = 0; _0x351fbd
function _0x40fb99(_0x20cad9) {
return btoa(_0x20cad9)
}
比较简单的一个逻辑,用python代码实现一下
def encrypt(data, key):
result = []
key += "zVbhsiCROg"
key_length = len(key)
for i in range(len(data)):
data_char_code = ord(data)
key_char_code = ord(key[i % key_length])
xor_result = data_char_code ^ key_char_code
result.append(chr(xor_result))
return ''.join(result)
四、总结
这个站整体偏简单,没想到他竟然更新的更简单了,利用ast可以更快处理,还是用老版本做了一个示例,大佬们可以试试搞一下新版本,一把梭哈,cookie加密原理没有变{:1_932:} 给一个老版本混淆可以拿来练手
混淆代码.zip
(12.46 KB, 下载次数: 1)
2025-8-11 18:39 上传
点击文件名下载附件
下载积分: 吾爱币 -1 CB