Closed Memexurer closed 9 months ago
Seems to be specific to another obfuscator and there isn't yet a way to customize transforms when importing webcrack, but you're free to clone the repo and add it somewhere
Theoretically you could do some static analysis to keep track of the array contents at every line and inline accesses (ZQUk8w[223] + 198
-> 25 + 198
-> 223
), but that's cumbersome and gets even more complicated because of the global variables
And its basically the same as executing the js, so why not just cheat and execute it in a sandbox :)
very similar to how https://github.com/j4k0xb/webcrack/blob/7bae73b10f6046032c8e1d9081375fe6ad8e1d1c/src/deobfuscator/inlineDecodedStrings.ts, https://github.com/j4k0xb/webcrack/blob/7bae73b10f6046032c8e1d9081375fe6ad8e1d1c/src/deobfuscator/vm.ts works
detecting this type of function:
const param = m.capture(m.identifier());
const clearArray = m.expressionStatement(
m.assignmentExpression(
'=',
m.memberExpression(m.fromCapture(param), m.identifier('length')),
m.numericLiteral(0)
)
);
const assignment = m.expressionStatement(
m.assignmentExpression(
'=',
m.memberExpression(m.fromCapture(param), m.anything(), true)
)
);
const lastExpression = m.capture(m.anyExpression());
const lastCallName = m.capture(m.identifier());
const matcher = m.functionDeclaration(
m.anything(),
[m.restElement(param)],
m.blockStatement(
m.anyList(
clearArray, // param.length = 0;
m.oneOrMore(assignment), // param[223] = 25; or param[param[223] + 222] = fn(anything);
m.expressionStatement(m.callExpression(lastCallName, [lastExpression])) // otherFn(param[param[223] + 124]);
)
)
);
traverse(ast, {
FunctionDeclaration(path) {
if (!matcher.match(path.node)) return;
// ...
}
});
First you have to get the code of global variables/functions like xgCiq2
or pcaDZd
so they can be executed later:
let code = '';
for (const statement of path.get('body').get('body')) {
if (assignment.match(statement.node)) {
statement.traverse({
ReferencedIdentifier(path) {
if (path.node.name === param.current!.name) return;
const binding = path.scope.getBinding(path.node.name)!;
code += binding.path.getStatementParent()!.toString();
},
});
}
}
I assume this call is the last statement which has some side effect, to get the argument it is replaced with:
- j7ibOsb(ZQUk8w[ZQUk8w[223] + 124]);
+ return ZQUk8w[ZQUk8w[223] + 124];
}
const fnClone = t.cloneNode(path.node, true);
fnClone.body.body.pop();
fnClone.body.body.push(t.returnStatement(lastExpression.current));
code += `(${generate(fnClone).code})()`;
So now the code looks like this and evaluates to the last returned expression:
function xgCiq2(x) { ... }
const pcaDZd = 3;
(function W8xps6(...ZQUk8w) {
ZQUk8w.length = 0;
...
return ZQUk8w[ZQUk8w[223] + 124];
})()
Finally execute the code and replace the whole original function body with:
j7ibOsb(1337 /* result */);
const result = t.valueToNode(sandbox(code));
path
.get('body')
.replaceWith(
t.blockStatement([
t.expressionStatement(
t.callExpression(lastCallName.current, [result])
),
])
);
nvm, my brain is too flat for coding in typescript
I need a transformer which would be able to simplify functions like this: (varargs are empty btw)