目标:实现登录,翻页,得到结果。
网站:aHR0cHM6Ly9jbi5yb290ZGF0YS5jb20vcGVvcGxl
[color=]1.注意:这个网站需要挂梯子,国内访问限制。进入登录页面,先随便输入账号密码尝试登录,可以看到登录时需要两个参数,content和sign。

image.png (87.65 KB, 下载次数: 0)
下载附件
2025-5-25 11:38 上传
2.先hook到参数生成的地方,方法有很多的,直接搜索 ,hook JSON ,或者在启动器中跟栈。搜索这两个参数结果是有很多,选择直接hook JSON。
[JavaScript] 纯文本查看 复制代码(function () {
var stringify = JSON.stringify;
JSON.stringify = function (params) {
console.log("Hook JSON.stringify ——> ", params);
debugger;
return stringify(params);
}
})();
(function () {
var parse = JSON.parse;
JSON.parse = function (params) {
console.log("Hook JSON.parse ——> ", params);
debugger;
return parse(params);
}
})();

image.png (31.04 KB, 下载次数: 0)
下载附件
2025-5-25 12:28 上传
3.调试几下可以看到账号密码的明文,往下跟两步,进入js文件,可以看到JSON.stringify的地方,往下走就可以看到content的生成地方,打上断点分析一下js代码。

image.png (108.26 KB, 下载次数: 0)
下载附件
2025-5-25 12:32 上传

image.png (60.45 KB, 下载次数: 0)
下载附件
2025-5-25 12:35 上传
4. (0x0, _0x109419['encryptData'])(_0x3191dd)
_0x3191dd 传入参数--账号密码明文 调用_0x109419下的encryptData 方法。怎么调用这个方法呢
方法一:
直接把整个js保存下载,定义一个全局变量赋值_0x109419 调用里面的方法。
方法二:
还是分析算法直接js或者python还原。
5.复制全部代码,在Notepad++ 中分析,上面是一些定义和自执行函数,主要看下面的webpackJsonp的定义,我们需要的_0x109419就在里面。在控制打印一下_0x109419,跳转进去,key值是0x4b6。

image.png (25.61 KB, 下载次数: 0)
下载附件
2025-5-25 12:48 上传

image.png (45.04 KB, 下载次数: 0)
下载附件
2025-5-25 12:51 上传

image.png (45.27 KB, 下载次数: 0)
下载附件
2025-5-25 12:55 上传
6.这网站之前还没有添加混淆,为了看的方便先进行一波变量名的还原,自上而下分析js代码,可以得到一个规律,想要获得方法名都是走的函数a41_0x4cab返回的结果。函数中显示定义_0x4b41de,结果是一个大数组,a41_0x4cab返回的是function(_0x2d7ed8, _0x220a59) {
_0x2d7ed8 = _0x2d7ed8 - 0x8f;
var _0x3f6884 = _0x4b41de[_0x2d7ed8];
return _0x3f6884;
} 两个参数,第二个没有使用,第一个就是数组的下标,其中进行一下偏移减去0x8f,返回数组中对应索引的值。代码太多了简单写一个小demo进行解混淆测试。

image.png (111.08 KB, 下载次数: 0)
下载附件
2025-5-25 13:08 上传

image.png (26.72 KB, 下载次数: 0)
下载附件
2025-5-25 13:11 上传
7.AST解混淆是通过将混淆的JavaScript代码转成抽象语法树,利用遍历和修改节点的方式,系统性地还原代码的原始结构和逻辑,从而提升代码可读性和逆向效率的技术。
源文件代码太多,写个demo测试自己的ast代码,大致有有以下几种情况
[JavaScript] 纯文本查看 复制代码const builtinMethods = ['push', 'pop', 'shift', 'unshift', 'slice', 'splice', 'map', 'filter', 'reduce', 'forEach'];
var a41_0x24115f = getBuiltinMethod;
function getBuiltinMethod(index, index2) {
if (index = builtinMethods.length) {
return '索引超出范围';
}
return builtinMethods[index];
}
function a() {
var aTest = getBuiltinMethod;
const ss = aTest(1, 1).substr(0, 1);
console.log(ss);
}
a();
function b(param1, param2) {
var bTest = param1;
const ss = bTest(5, 1).substr(0, 5);
console.log(ss);
}
b(getBuiltinMethod, '');
function c() {
var cTest = getBuiltinMethod;
const ss = cTest(5, 1).substr(0, 5);
console.log(ss);
function fun() {
var cTestNest = cTest;
const ss = cTestNest(1, 1).substr(0, 5);
console.log(ss);
}
fun();
}
c();
function d() {
var dTest = a41_0x24115f;
const ss = dTest(5).substr(0, 5);
console.log(ss);
function fun() {
var dTestNest = dTest;
const ss = dTestNest(1).substr(0, 5);
console.log(ss);
}
fun();
}
d();
(function (param1, param2) {
var headLesstest = param1;
const ss = headLesstest(4, 1).substr(0, 5);
console.log(ss);
}(getBuiltinMethod, ''))
不管怎么样最终结果都是调用最终返回数组的函数。
7.1先是定位到数组的地方,提取数组元素。也可以手动赋值。

image.png (42.05 KB, 下载次数: 0)
下载附件
2025-5-25 14:41 上传
7.2 在代码中找到所有调用目标函数的地方,根据传入参数的索引替换成对应的变量名,(或者直接拿着js代码扔给gpt)
注意的是函数通过变量赋值,参数传递等多层绑定,调用时用的函数名不一定是目标函数,需要递归判断变量名是否最终绑定的到目标函数。
[JavaScript] 纯文本查看 复制代码function isBoundToTargetFunc(path, name, visited = new Set()) {
if (cache.has(name)) return cache.get(name);
if (visited.has(name)) {
cache.set(name, false);
return false;
}
visited.add(name);
const binding = path.scope.getBinding(name);
if (binding) {
const node = binding.path.node;
if (t.isVariableDeclarator(node)) {
const init = node.init;
if (!init) {
cache.set(name, false);
return false;
}
if (t.isIdentifier(init, {name: FUNC_NAME})) {
cache.set(name, true);
return true;
}
if (t.isIdentifier(init)) {
const result = isBoundToTargetFunc(binding.path, init.name, visited);
cache.set(name, result);
return result;
}
cache.set(name, false);
return false;
}
}
const funcPath = path.findParent(p => p.isFunction() || p.isArrowFunctionExpression());
if (funcPath) {
const params = funcPath.node.params;
for (let i = 0; i
8.直接保存2a6fadd文件,这个文件是动态的哦,上面跟一下栈就找到那个了或者直接搜索encryptionScheme,文件里面只有一个参数的,也可以定位到。以下是解混淆完整代码。

image.png (36.76 KB, 下载次数: 0)
下载附件
2025-5-25 15:13 上传
[JavaScript] 纯文本查看 复制代码const fs = require('fs');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generator = require('@babel/generator').default;
const t = require('@babel/types');
const code = fs.readFileSync('./2a6fadd.js', 'utf-8');
const FUNC_NAME = 'a41_0x4cab';
const ARRAY_NAME = '_0x4be818';
const ast = parser.parse(code, {
sourceType: 'module',
plugins: ['jsx']
});
// 缓存,避免重复递归
const cache = new Map();
/**
* 递归判断变量名是否绑定到目标函数 FUNC_NAME
* 支持变量声明赋值、函数参数绑定、多层变量赋值链
* @Param {NodePath} path 当前路径
* @param {string} name 变量名
* @param {Set} visited 防止递归死循环
* @returns {boolean}
*/
function isBoundToTargetFunc(path, name, visited = new Set()) {
if (cache.has(name)) {
return cache.get(name);
}
if (visited.has(name)) {
cache.set(name, false);
return false;
}
visited.add(name);
// 1. 查找作用域绑定
const binding = path.scope.getBinding(name);
if (binding) {
const node = binding.path.node;
// 变量声明赋值
if (t.isVariableDeclarator(node)) {
const init = node.init;
if (!init) {
cache.set(name, false);
return false;
}
if (t.isIdentifier(init, { name: FUNC_NAME })) {
cache.set(name, true);
return true;
}
if (t.isIdentifier(init)) {
const result = isBoundToTargetFunc(binding.path, init.name, visited);
cache.set(name, result);
return result;
}
// 如果是函数表达式或箭头函数直接赋值,暂不处理
cache.set(name, false);
return false;
}
// 处理函数参数(函数声明、表达式、箭头函数)不在这里,下面统一处理
}
// 2. 判断是否为函数参数绑定(支持普通函数、箭头函数、匿名函数)
const funcPath = path.findParent(p => p.isFunction() || p.isArrowFunctionExpression());
if (funcPath) {
const params = funcPath.node.params;
for (let i = 0; i {
// if (t.isStringLiteral(el)) {
// return el.value;
// }
// return null;
// });
// }
// }
// });
// 替换调用表达式
traverse(ast, {
CallExpression(path) {
const callee = path.node.callee;
if (t.isIdentifier(callee)) {
if (
path.node.arguments.length >= 1 &&
t.isNumericLiteral(path.node.arguments[0]) &&
isBoundToTargetFunc(path, callee.name)
) {
const index = path.node.arguments[0].value;
if (index >= 0 && index {
if (err) {
console.error('写入文件时发生错误:', err);
} else {
console.log('文件已成功写入:', fileName);
}
});
需要注意的是,索引偏移量,还有数组内容,我这是直接赋值了,在原js代码中数组有一次更改顺序的,我是直接在浏览器脚本代码断运行打上断点直接复制数组。放到ast代码中,看一下结果没啥问题。我这限制了一下,字符串长度和类型,/^[a-zA-Z]+$/.test(builtinArray[index-0x8f]) && builtinArray[index-0x8f].length 100,直接使用浏览器的本地替换。再次定位到content的地方。

image.png (61.81 KB, 下载次数: 0)
下载附件
2025-5-25 15:17 上传

image.png (145.56 KB, 下载次数: 0)
下载附件
2025-5-25 15:19 上传
若是用方法一,可以看一下之前大佬写的文章,WEB前端逆向在nodejs环境中复用webpack代码 哇,写的老全面的。我就老老实实用第二种复原算法吧。进去可以看到这是rsa加密。里面定义了公钥私钥,填充方式,公钥用来生成content,私钥是用来签名的生成sign。

image.png (73.7 KB, 下载次数: 0)
下载附件
2025-5-25 15:42 上传

image.png (48.44 KB, 下载次数: 0)
下载附件
2025-5-25 15:47 上传

image.png (84.72 KB, 下载次数: 0)
下载附件
1
2025-5-25 15:43 上传
先是根据账号明文生成content,然后转成md5,在使用私钥生成sign,直接简化到最简。以下是python代码。测试一下没有什么问题。
[Python] 纯文本查看 复制代码# 公钥和私钥
PUBLIC_KEY_PEM = b"""-----BEGIN PUBLIC KEY-----
-----END PUBLIC KEY-----"""
PRIVATE_KEY_PEM = b"""-----BEGIN PRIVATE KEY-----
-----END PRIVATE KEY-----"""
def encrypt_data(data: str) -> str:
public_key = load_pem_public_key(PUBLIC_KEY_PEM)
encrypted = public_key.encrypt(
data.encode('utf-8'),
padding.PKCS1v15()
)
return base64.b64encode(encrypted).decode('utf-8')
def sign_data(data: str) -> str:
private_key = load_pem_private_key(PRIVATE_KEY_PEM, password=None)
signature = private_key.sign(
data.encode('utf-8'),
padding.PKCS1v15(),
hashes.SHA1()
)
return base64.b64encode(signature).decode('utf-8')
def md5_encrypt(data: str) -> str:
md5 = hashlib.md5()
md5.update(data.encode('utf-8'))
return md5.hexdigest()
def get_data(data_to_encrypt: str) -> str:
encrypted_data = encrypt_data(data_to_encrypt)
sign_code = 'content=' + encrypted_data
sign_hex = md5_encrypt(sign_code)
signed_data = sign_data(sign_hex)
import json
return json.dumps({
"content": encrypted_data,
"sign": signed_data
}, ensure_ascii=False, separators=(',', ':'))

image.png (22.21 KB, 下载次数: 0)
下载附件
2025-5-25 16:00 上传
9.页面上登录账号后,找到x上top人物,先hook,在翻页,相对于登录,我看了一下content和sign是一样的,就响应data有所区别,定住解析后结果直接从结果分析,向上跟栈。

image.png (37.44 KB, 下载次数: 0)
下载附件
2025-5-25 16:07 上传

image.png (18.08 KB, 下载次数: 0)
下载附件
2025-5-25 16:07 上传

image.png (65.23 KB, 下载次数: 0)
下载附件
2025-5-25 16:15 上传
10. 可以很清晰的看到是aes加解密,模式 填充 密钥都有,我直接上python代码。测试一下结果 ok了,没有什么问题(其实翻页不带登录cookies也可以,还没强校验,只是页面先是翻页要登录。)
至此该网站分析完了,大致不难,感谢观看,给点热心值吧,感谢老铁们。

image.png (58.93 KB, 下载次数: 0)
下载附件
2025-5-25 16:21 上传

image.png (65.38 KB, 下载次数: 0)
下载附件
2025-5-25 16:22 上传

image.png (67.62 KB, 下载次数: 0)
下载附件
2025-5-25 16:24 上传