Reese84解混淆及流程分析(一)
提示:仅供学习交流,不得用做商业交易,如有侵权请及时联系作者删除
解混淆
- 先把整体代码拉下来,做大致分析,可以根据折叠后的代码可以看出大致分为两个部分,第一个部分的混淆存在大量的substr函数,而第二部分的就是标准的OB混淆了;
第一部分
可以看到,大致的流程就是经过上面几轮对长字符串解码和特殊计算后,在下面需要混淆的地方使用substr的方式进行操作的,那么思路就很明显了,从头找到第一次使用substr的地方,将这段代码复制下来,然后直接运行就可以了,配合AST,匹配所有substr的函数调用,拿到入参后进行模拟调用还原替换,代码如下:
traverse(ast, {
CallExpression(path) {
const { callee, arguments } = path.node;
if (t.isMemberExpression(callee) && t.isIdentifier(callee.object) && callee.property.name == "substr") {
let strName = callee.object.name;
let left = arguments[0].value;
let right = arguments[1].value;
// 优先从已执行的 VM sandbox 中取值,避免直接 eval 丢失上下文
if (Object.prototype.hasOwnProperty.call(sandbox, strName)) {
const recv = sandbox[strName];
if (typeof recv === "string") {
const result = recv.substr(left, right);
console.log("substr =>", strName, left, right, "=", result)
path.replaceWith(t.valueToNode(result));
}
}
}
}
})
运行后输出
第二部分
由于第二部分就是标准的OB混淆,所以直接把解密函数拿下来匹配运行就行了,很简单
var decodeFuncs = new Set();
traverse(ast, {
VariableDeclarator(path) {
const { id, init } = path.node;
if (init) {
if (init.name === "a1_0xcf60" || decodeFuncs.has(init.name)) {
decodeFuncs.add(id.name);
}
}
}
})
traverse(ast, {
CallExpression(path) {
const { callee, arguments } = path.node;
if (decodeFuncs.has(callee.name) && arguments.length == 1) {
var result = a1_0xcf60(arguments[0].value)
console.log(arguments[0].value, result)
path.replaceWith(t.valueToNode(result));
}
}
})
最后在进行一些字符串的拼接,方便分析
function foldStringConcat(path) {
if (!t.isBinaryExpression(path.node, { operator: "+" })) return;
// 递归收集所有叶子节点
const parts = [];
function flatten(node) {
if (t.isBinaryExpression(node, { operator: "+" })) {
flatten(node.left);
flatten(node.right);
} else {
parts.push(node);
}
}
flatten(path.node);
// 如果所有部分都是字符串常量,且不包含潜在转义风险(如反斜杠),则进行合并
const areAllStrings = parts.every(node => t.isStringLiteral(node));
if (areAllStrings) {
// 仅在所有字面量的值中不包含反斜杠时合并,避免将 '\\u'、'\\x' 之类片段拼成无效转义
const hasBackslash = parts.some(node => typeof node.value === "string" && /\\/.test(node.value));
if (!hasBackslash) {
const result = parts.map(p => p.value).join("");
path.replaceWith(t.stringLiteral(result));
}
}
}
traverse(ast, {
BinaryExpression(path) {
foldStringConcat(path);
}
})
效果展示
流程分析
在请求体中我们需要分析的加密值就是P值,解完混淆后可以直接搜到
具体的加密函数就是第一部分最后一个函数,这个就不分析了,抓个包看堆栈跟两步就出来了,这里直接分析一下加密流程
展开U8函数,我们发现前面是一些变量及常量定义,下面是往一个数组中添加了很多函数,在最后一个添加的函数中生成了P参数
那么加密流程其实就在push的函数里,一共是二十个左右(不同的站点会存在不同),下面开始分析这些函数
加密函数分析示例
根据上图可以看出,最终的P值其实就是 by经过一系列的计算后得出,by其实就是一个数据,里面存放了各种加密后的指纹,而by就是在push的函数中一点一点添加的
可以看到WA就是qB,而qB的值就是在中间的代码中添加的,那么只需要分析中间的代码即可
具体加密示例
从下图可以看出,qB的值部分来源于JL,而JL往上跟就是Xe经过计算的值,Xe其实就是收集的指纹对象了,那么从JSON.stringify这里开始,到JL=fi这里就是加密流程了
我们把加密流程单独拿出来后,发现其实就是先是经过一个函数生成了一串类似密钥种子的东西,然后在进行指定长度密钥的生成,然后就是数值计算和fromcharcode,base64编码了,那么尝试抠代码,就变成了下面的样子
window = globalThis
var Xe = {
}
var S0 = window["Math"]["random"]() * 1073741824 | 0;
var qw = new window["RegExp"]("Trident");
var Jg = new window.RegExp("[\\u007F-\\uFFFF]", "g");
function No(kA) {
return "\\u" + ("0000" + kA.charCodeAt(0).toString(16)).substr(-4)
}
function jb(VK, yf) {
var gF = VK;
var KL = yf;
return function () {
var y_ = gF;
y_ ^= y_ << 23;
y_ ^= y_ >> 17;
var l3 = KL;
y_ ^= l3;
y_ ^= l3 >> 26;
KL = y_;
gF = l3;
return (gF + KL) % 4294967296
}
}
var p1 = jb(1650762707, S0);
var Wd = [];
var IA = 0;
while (IA < 3) {
Wd.push(p1() & 255);
IA += 1
}
var H1 = Wd;
var qk = H1;
var ca = window.JSON.stringify(Xe, function (nW, tk) {
return tk === undefined ? null : tk
});
var Yw = ca.replace(Jg, No);
var Tx = [];
var FN = 0;
while (FN < Yw.length) {
Tx.push(Yw.charCodeAt(FN));
FN += 1
}
var G4 = Tx;
var un = G4;
var gH = un.length;
var Iv = qk[0] % 7 + 1;
var uH = [];
var tD = 0;
while (tD < gH) {
uH.push((un[tD] << Iv | un[tD] >> 8 - Iv) & 255);
tD += 1
}
var rH = uH;
var cu = rH.length;
var N3 = [];
var NZ = 0;
while (NZ < cu) {
N3.push(rH[(NZ + qk[1]) % cu]);
NZ += 1
}
var zU = N3;
var jL = [];
for (var nl in zU) {
var QK = zU[nl];
if (zU.hasOwnProperty(nl)) {
var ul = window.String.fromCharCode(QK);
jL.push(ul)
}
}
var fi = window.btoa(jL.join(""));
var JL = fi;
console.log(JL)
后续的流程就是将每个函数抠出来即可,在JSON.stringify的调用地方打上断点看指纹信息是否需要随机或者固定即可。解完混淆后很简单,只是需要很多耐心抠代码。川航的这个还没做完,之前做的另外一个站的已经做完了,也是成功返回token,问了真哥后发现返回的token不一定是能用的,priceline这个站我又没找到校验接口😂😂所以开始搞了真哥推荐的川航。SO:以上流程只是我个人意见,不代表一定能用,也可能就是错的,只是记录下学习心得!!