FBI WARNING
这里的超星考试指本人的原创单机游戏【超级星星V.666】单词大考验关卡
并非通俗意义超星
请勿联想
正文
点击考试预览可以追到AST部分
这里可以看到是ob混淆+字符串转义
我们先解决字符串转义部分
直接利用babel的最小化就可以将字符串转义压缩成正常字符串
const parser = require("@babel/parser");
const generator = require("@babel/generator").default;
const code_js = fs.readFileSync("./chaoxing_exam.js", {
encoding: "utf-8",
});
const ast = parser.parse(code_js)
let code = generator(ast, {
minified: true,
jsescOption: { minimal: true },
}).code;
fs.writeFile("./convert1.js", code, (err) => {});
然后查看一下结果
然后解字符串,这里是_0x56bc
直接抬手一个转义,因为没有多层,所以只搜集_0x56bc即可
老规矩,复制_0x56bc到nodejs作为解密函数
首先测试一下,发现卡死了,多半是正则爆破,搜索Reg找到了
_0x4337b4["prototype"]["RvZSqA"] = function () {
var _0x403f16 = new RegExp(this["MTxJBI"] + this["FMdsHI"]);
var _0x55e9af = _0x403f16["test"](this["ZxBleI"]["toString"]())
? --this["pNCgln"][1]
: --this["pNCgln"][0];
return this["AfmIcA"](_0x55e9af);
};
_0x4337b4["prototype"]["AfmIcA"] = function (_0x172353) {
if (!Boolean(~_0x172353)) {
return _0x172353;
}
return this["ebdBfN"](this["GRlANK"]);
};
_0x4337b4["prototype"]["ebdBfN"] = function (_0xa7fe72) {
for (
var _0x5f3bbf = 0, _0x1ed88a = this["pNCgln"]["length"];
_0x5f3bbf < _0x1ed88a;
_0x5f3bbf++
) {
this["pNCgln"]["push"](Math["round"](Math["random"]()));
_0x1ed88a = this["pNCgln"]["length"];
}
return _0xa7fe72(this["pNCgln"][0]);
};
应该是正则下面的三元运算符在捣乱,我们跑一下发现是false,所以直接强制改成true内的--this["pNCgln"][1]
_0x4337b4["prototype"]["RvZSqA"] = function () {
var _0x403f16 = new RegExp(this["MTxJBI"] + this["FMdsHI"]);
var _0x55e9af = --this["pNCgln"][1]
return this["AfmIcA"](_0x55e9af);
};
在运行发现还是存在内存爆破
向上找到了字符串混淆内存在分支污染和内存爆破
直接将第一个设为false,第二个设为true
再运行crp函数
解密成功
然后直接开始解字符串混淆
const fuck_js = fs.readFileSync("./convert2.js", {
encoding: "utf-8",
});
const ast = parser.parse(fuck_js);
traverse(ast, {
CallExpression(path) {
if (path.node.callee.name === "_0x56bc") {
let loc1 = path.node.arguments[0].value;
let loc2 = path.node.arguments[1]?.value;
let str_node = types.stringLiteral(_0x56bc(loc1, loc2));
path.replaceInline(str_node);
}
},
});
let code = generator(ast, {
minified: true,
jsescOption: { minimal: true },
}).code;
fs.writeFile("./convert3.js", code, (err) => {});
已经具备基本的可读了
接下来就是硬干了
我们目标比较简单
分析这些数据吧
value像是鼠标的数据,我们先看value,确实是鼠标的相对页面键值
var scrollLeft =
document["documentElement"]["scrollLeft"] ||
document["body"]["scrollLeft"],
scrollTop =
document["documentElement"]["scrollTop"] ||
document["body"]["scrollTop"];
(mouseMap["x"] =
winEvent["pageX"] || winEvent["clientX"] + scrollLeft),
(mouseMap["y"] =
winEvent["pageY"] || winEvent["clientY"] + scrollTop),
var value =
"(" +
Math["ceil"](mouseMap["x"]) +
"|" +
Math["ceil"](mouseMap["y"]) +
")";
接下来看qid,取自页面的questionId数据
_0x210627 = document["getElementById"]("questionId"),
(qid = _0x210627["value"])
看rd,随机数
var rd = Math["random"](),
接下来只剩enc和_edt了,先看enc
里面是一个闭包
看大概应该是一个转义字符代码,懒得搞了,直接抽离
function getEncData(posData, userId, questionId, randomNum, rd) {
let userIdAndQuestionIdAndRandom =
userId + "_" + questionId + "|" + randomNum;
if (
null == userIdAndQuestionIdAndRandom ||
userIdAndQuestionIdAndRandom["length"] <= 0
)
return null;
for (
var _0x469e62 = "", _0x388a1f = 0;
_0x388a1f < userIdAndQuestionIdAndRandom["length"];
_0x388a1f++
)
_0x469e62 +=
userIdAndQuestionIdAndRandom["charCodeAt"](_0x388a1f)["toString"]();
var _0x210627 = Math["floor"](_0x469e62["length"] / 5),
_0x215858 = parseInt(
_0x469e62["charAt"](_0x210627) +
_0x469e62["charAt"](2 * _0x210627) +
_0x469e62["charAt"](3 * _0x210627) +
_0x469e62["charAt"](4 * _0x210627)
),
_0x408603 = Math["ceil"](userIdAndQuestionIdAndRandom["length"] / 2),
_0x3ed445 = Math["pow"](2, 31) - 1;
if (_0x215858 < 2) return null;
var rd = rd,
_0x58d995 = Math["round"](1000000000 * rd) % 100000000;
10 < (_0x469e62 += _0x58d995)["length"] &&
(_0x469e62 = parseInt(_0x469e62["substring"](0, 10))["toString"]()),
(_0x469e62 = (_0x215858 * _0x469e62 + _0x408603) % _0x3ed445);
var _0x3c1f52 = "",
_0x435dce = "";
for (_0x388a1f = 0; _0x388a1f < posData["length"]; _0x388a1f++)
(_0x435dce +=
(_0x3c1f52 = parseInt(
posData["charCodeAt"](_0x388a1f) ^
Math["floor"]((_0x469e62 / _0x3ed445) * 255)
)) < 16
? "0" + _0x3c1f52["toString"](16)
: _0x3c1f52["toString"](16)),
(_0x469e62 = (_0x215858 * _0x469e62 + _0x408603) % _0x3ed445);
for (_0x58d995 = _0x58d995["toString"](16); _0x58d995["length"] < 8; )
_0x58d995 = "0" + _0x58d995;
return (_0x435dce += _0x58d995);
}
console.log(
'getEncData("(1890|22)", userId, questionId, 265, 0.7836752714778081)',
getEncData("(1890|22)", 用户id, 试卷id, 265, 0.7836752714778081)
);
官方数据
抽离代码
代码生成成功,然后看最后的_edt,来源是 "&_edt=" +
(_0x408603 + randomNumA),其中randomNumA是一个生成的随机数
function _0xe7b063() {
if ("ySLJF" !== "ySLJF") {
(function () {
return ![];
})
["constructor"]("debu" + "gger")
["apply"]("stateObject");
} else {
return Math["floor"](10 * Math["random"]());
}
}
而另外一个603的变量则是时间
_0x408603 = "" + new Date()["getTime"](),
至此全部破译完毕
结语
撒花