Skip to content

Commit

Permalink
feat: deobfuscate partial control flow object
Browse files Browse the repository at this point in the history
  • Loading branch information
j4k0xb committed Nov 5, 2023
1 parent 98da0ae commit 41fdd9b
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 32 deletions.
83 changes: 51 additions & 32 deletions src/deobfuscator/controlFlowObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
findParent,
isReadonlyObject,
} from '../utils/matcher';
import { renameFast } from '../utils/rename';

/**
* Explanation: https://excalidraw.com/#json=0vehUdrfSS635CNPEQBXl,hDOd-UO9ETfSDWT9MxVX-A
Expand All @@ -26,7 +25,7 @@ export default {
const varId = m.capture(m.identifier());
const propertyName = m.matcher<string>(name => /^[a-z]{5}$/i.test(name));
const propertyKey = constKey(propertyName);
const property = m.or(
const propertyValue = m.or(
// E.g. "6|0|4|3|1|5|2"
m.stringLiteral(),
// E.g. function (a, b) { return a + b }
Expand Down Expand Up @@ -67,14 +66,16 @@ export default {
);
// E.g. "rLxJs": "6|0|4|3|1|5|2"
const objectProperties = m.capture(
m.arrayOf(m.objectProperty(propertyKey, property))
m.arrayOf(m.objectProperty(propertyKey, propertyValue))
);
const aliasId = m.capture(m.identifier());
const aliasVar = m.variableDeclarator(aliasId, m.fromCapture(varId));
const aliasVar = m.variableDeclaration(m.anything(), [
m.variableDeclarator(aliasId, m.fromCapture(varId)),
]);
// E.g. "rLxJs"
const assignedKey = m.capture(propertyName);
// E.g. "6|0|4|3|1|5|2"
const assignedValue = m.capture(property);
const assignedValue = m.capture(propertyValue);
// E.g. obj.rLxJs = "6|0|4|3|1|5|2"
const assignment = m.expressionStatement(
m.assignmentExpression(
Expand All @@ -83,6 +84,12 @@ export default {
assignedValue
)
);
const looseAssignment = m.expressionStatement(
m.assignmentExpression(
'=',
constMemberExpression(m.fromCapture(varId), assignedKey)
)
);
// E.g. obj.rLxJs
const memberAccess = constMemberExpression(
m.or(m.fromCapture(varId), m.fromCapture(aliasId)),
Expand All @@ -106,8 +113,7 @@ export default {
const binding = path.scope.getBinding(varId.current!.name);
if (!binding) return changes;
if (!isConstantBinding(binding)) return changes;
if (objectProperties.current!.length === 0)
transformObjectKeys(binding);
if (!transformObjectKeys(binding)) return changes;
if (!isReadonlyObject(binding, memberAccess)) return changes;

const props = new Map(
Expand Down Expand Up @@ -153,42 +159,55 @@ export default {
* When the `Transform Object Keys` option is enabled, the obfuscator generates an empty
* object, assigns the properties later and adds an alias variable to the object.
* This function undoes that by converting the assignments to inline object properties.
*
* In some forked versions of the obfuscator, some properties may be in the object
* and others are assigned later.
*/
function transformObjectKeys(objBinding: Binding) {
const refs = objBinding.referencePaths;
function transformObjectKeys(objBinding: Binding): boolean {
const container = objBinding.path.parentPath!.container as t.Statement[];
const startIndex = (objBinding.path.parentPath!.key as number) + 1;
const properties: t.ObjectProperty[] = [];

if (refs.length < 2) return;
if (!aliasVar.match(refs.at(-1)?.parent)) return;
for (let i = startIndex; i < container.length; i++) {
const statement = container[i];

const assignments: NodePath[] = [];

for (let i = 0; i < refs.length - 1; i++) {
const expressionStatement = refs[i].parentPath?.parentPath?.parentPath;
// Example: _0x29d709["kHAOU"] = "5|1|2" + "|4|3|" + "0|6";
traverse(expressionStatement!.node, mergeStrings.visitor(), undefined, {
changes: 0,
});
if (!assignment.match(expressionStatement?.node)) return;

assignments.push(expressionStatement!);
objectProperties.current!.push(
t.objectProperty(
t.identifier(assignedKey.current!),
assignedValue.current!
)
);
// For performance reasons, only traverse if it is a potential match (value doesn't matter)
if (looseAssignment.match(statement)) {
traverse(statement, mergeStrings.visitor(), undefined, {
changes: 0,
});
}

if (assignment.match(statement)) {
properties.push(
t.objectProperty(
t.identifier(assignedKey.current!),
assignedValue.current!
)
);
} else {
break;
}
}

// If all properties are in the object then there typically won't be an alias variable
const aliasAssignment = container[startIndex + properties.length];
if (!aliasVar.match(aliasAssignment)) return true;

// Avoid false positives
if (objBinding.references !== properties.length + 1) return false;

const aliasBinding = objBinding.scope.getBinding(aliasId.current!.name)!;
if (!isReadonlyObject(aliasBinding, memberAccess)) return;
if (!isReadonlyObject(aliasBinding, memberAccess)) return false;

objectProperties.current!.push(...properties);
container.splice(startIndex, properties.length);
objBinding.referencePaths = aliasBinding.referencePaths;
objBinding.references = aliasBinding.references;

renameFast(aliasBinding, objBinding.identifier.name);

assignments.forEach(p => p.remove());
objBinding.identifier.name = aliasBinding.identifier.name;
aliasBinding.path.remove();
return true;
}

return {
Expand Down
11 changes: 11 additions & 0 deletions test/__snapshots__/deobfuscator.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ exports[`deobfuscate obfuscator.io-control-flow-keys.js 1`] = `
hi();"
`;

exports[`deobfuscate obfuscator.io-control-flow-partial-keys.js 1`] = `
"function hi() {
const _0x46643e = 1;
const _0x4d295f = 2;
if (_0x46643e < _0x4d295f) {
console.log(\\"Hello World!\\");
}
}
hi();"
`;

exports[`deobfuscate obfuscator.io-control-flow-split-strings.js 1`] = `
"function _0x225c6a() {
console.log(\\"Hello World!\\");
Expand Down
1 change: 1 addition & 0 deletions test/deobfuscator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ test.each([
'obfuscator.io-control-flow.js',
'obfuscator.io-control-flow-split-strings.js',
'obfuscator.io-control-flow-keys.js',
'obfuscator.io-control-flow-partial-keys.js',
'obfuscator.io-control-flow-switch-return.js',
'obfuscator.io-control-flow-spread.js',
'obfuscator.io-high.js',
Expand Down
75 changes: 75 additions & 0 deletions test/samples/obfuscator.io-control-flow-partial-keys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
(function (_0x58f0bf, _0x449fa3) {
const _0x5b9b8d = _0x1f65;
const _0xb0cb83 = _0x58f0bf();
while (!![]) {
try {
const _0x38a2f0 =
(-parseInt(_0x5b9b8d(0x1a8)) / 0x1) *
(-parseInt(_0x5b9b8d(0x1a5)) / 0x2) +
(parseInt(_0x5b9b8d(0x1aa)) / 0x3) *
(parseInt(_0x5b9b8d(0x19e)) / 0x4) +
parseInt(_0x5b9b8d(0x1a1)) / 0x5 +
(parseInt(_0x5b9b8d(0x1a0)) / 0x6) *
(-parseInt(_0x5b9b8d(0x1ac)) / 0x7) +
-parseInt(_0x5b9b8d(0x19f)) / 0x8 +
(parseInt(_0x5b9b8d(0x1a4)) / 0x9) *
(-parseInt(_0x5b9b8d(0x1a9)) / 0xa) +
(parseInt(_0x5b9b8d(0x1a3)) / 0xb) * (parseInt(_0x5b9b8d(0x1ad)) / 0xc);
if (_0x38a2f0 === _0x449fa3) {
break;
} else {
_0xb0cb83['push'](_0xb0cb83['shift']());
}
} catch (_0x4d8b7f) {
_0xb0cb83['push'](_0xb0cb83['shift']());
}
}
})(_0x1b75, 0x90cfc);
function hi() {
const _0xbdaae1 = _0x1f65;
const _0x4421b9 = {
[_0xbdaae1(0x1a6)]: function (_0x48317b, _0x3eb9bf) {
return _0x48317b < _0x3eb9bf;
}
};
_0x4421b9[_0xbdaae1(0x1a2)] = _0xbdaae1(0x1ab);
const _0x2d22bf = _0x4421b9;
const _0x46643e = 0x1,
_0x4d295f = 0x2;
if (_0x2d22bf[_0xbdaae1(0x1a6)](_0x46643e, _0x4d295f))
console[_0xbdaae1(0x1a7)](_0x2d22bf[_0xbdaae1(0x1a2)]);
}
function _0x1f65(_0x5cd6e4, _0x590f6f) {
const _0x1b75ea = _0x1b75();
_0x1f65 = function (_0x1f6544, _0x43222d) {
_0x1f6544 = _0x1f6544 - 0x19e;
let _0x15544e = _0x1b75ea[_0x1f6544];
return _0x15544e;
};
return _0x1f65(_0x5cd6e4, _0x590f6f);
}
hi();
function _0x1b75() {
const _0x108455 = [
'1701GbYXRJ',
'552luNNhF',
'997208TlAUWj',
'8869840SjjMps',
'6006rJfVYl',
'2285250dZqjSM',
'RLGat',
'235169ZbwBFT',
'279Azbvfn',
'1553386QHtAGa',
'pOUyw',
'log',
'1ZglIoW',
'248600LAYnNv',
'6vhnWfl',
'Hello\x20World!',
];
_0x1b75 = function () {
return _0x108455;
};
return _0x1b75();
}

0 comments on commit 41fdd9b

Please sign in to comment.