reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,352 @@
|
||||
const vm = require("vm");
|
||||
const { clone } = require("../shared");
|
||||
const { runPatternPass } = require("./shared-pattern-pass");
|
||||
const { createSandbox, evaluateExpression, evaluateNodes, evaluatePreludeStatements, foldWrapperObjects, generateCode, literalNodeFromValue, replaceCallsFromSandbox, replaceMembersFromSandbox, replacePrimitiveIdentifiers, seedSandboxFromAliases, traverse, t } = require("./pattern-utils");
|
||||
|
||||
function inlineAssignmentObjects(ast) {
|
||||
let changed = false;
|
||||
|
||||
traverse(ast, {
|
||||
VariableDeclaration(path) {
|
||||
if (path.node.declarations.length !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const declaration = path.get("declarations.0");
|
||||
if (!declaration.get("id").isIdentifier() || !declaration.get("init").isObjectExpression({ properties: [] })) {
|
||||
return;
|
||||
}
|
||||
|
||||
const name = declaration.node.id.name;
|
||||
const binding = path.scope.getBinding(name);
|
||||
if (!binding) {
|
||||
return;
|
||||
}
|
||||
|
||||
const objectEntries = Object.create(null);
|
||||
binding.referencePaths.forEach((refPath) => {
|
||||
const memberPath = refPath.parentPath;
|
||||
const assignmentPath = memberPath && memberPath.parentPath;
|
||||
if (
|
||||
!memberPath ||
|
||||
!memberPath.isMemberExpression({ object: refPath.node }) ||
|
||||
!assignmentPath ||
|
||||
!assignmentPath.isAssignmentExpression({ left: memberPath.node, operator: "=" })
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = memberPath.node.computed ? String(memberPath.node.property.value) : memberPath.node.property.name;
|
||||
objectEntries[key] = clone(assignmentPath.node.right);
|
||||
assignmentPath.remove();
|
||||
changed = true;
|
||||
});
|
||||
|
||||
const properties = Object.entries(objectEntries).map(([key, valueNode]) => t.objectProperty(t.stringLiteral(key), valueNode));
|
||||
if (properties.length > 0) {
|
||||
declaration.get("init").replaceWith(t.objectExpression(properties));
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
function getLeadingPreludeNodes(ast, maxStatements = 12) {
|
||||
const collected = [];
|
||||
for (const node of ast.program.body) {
|
||||
if (collected.length >= maxStatements) {
|
||||
break;
|
||||
}
|
||||
if (t.isFunctionDeclaration(node) || t.isVariableDeclaration(node)) {
|
||||
collected.push(node);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return collected;
|
||||
}
|
||||
|
||||
function findRotationIifeNode(ast, preludeCount) {
|
||||
const node = ast.program.body[preludeCount];
|
||||
if (!node || !t.isExpressionStatement(node)) {
|
||||
return null;
|
||||
}
|
||||
const expr = node.expression;
|
||||
if (t.isCallExpression(expr) && (t.isFunctionExpression(expr.callee) || t.isArrowFunctionExpression(expr.callee))) {
|
||||
return node;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function findPxSetterFunctionName(ast) {
|
||||
for (const node of ast.program.body) {
|
||||
if (!t.isFunctionDeclaration(node) || !node.id) {
|
||||
continue;
|
||||
}
|
||||
let matched = false;
|
||||
traverse(t.file(t.program([clone(node)])), {
|
||||
AssignmentExpression(path) {
|
||||
if (path.get("left").isIdentifier({ name: "PX" })) {
|
||||
matched = true;
|
||||
path.stop();
|
||||
}
|
||||
}
|
||||
});
|
||||
if (matched) {
|
||||
return node.id.name;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function findDecoderFunctionName(ast) {
|
||||
for (const node of ast.program.body) {
|
||||
if (t.isFunctionDeclaration(node) && node.id && node.id.name) {
|
||||
return node.id.name;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function collectValidationTargets(ast) {
|
||||
const targets = [];
|
||||
const seen = new Set();
|
||||
|
||||
traverse(ast, {
|
||||
CallExpression(path) {
|
||||
const firstArgPath = path.get("arguments.0");
|
||||
if (
|
||||
!path.get("callee").isMemberExpression() ||
|
||||
!firstArgPath ||
|
||||
!firstArgPath.node ||
|
||||
!firstArgPath.isStringLiteral({ value: "|" })
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const propertyPath = path.get("callee.property");
|
||||
if (!propertyPath.isCallExpression() || !propertyPath.get("arguments.0").isNumericLiteral()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const argValue = propertyPath.get("arguments.0").node.value;
|
||||
const objectPath = path.get("callee.object");
|
||||
if (!objectPath.isCallExpression() || !objectPath.get("arguments.0").isNumericLiteral()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const orderArgValue = objectPath.get("arguments.0").node.value;
|
||||
const token = `split:${argValue}:order:${orderArgValue}`;
|
||||
if (seen.has(token)) {
|
||||
return;
|
||||
}
|
||||
|
||||
seen.add(token);
|
||||
targets.push({
|
||||
argValue,
|
||||
expected: "split",
|
||||
orderArgValue
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
function runCodeInSandbox(code, sandbox, timeoutMs) {
|
||||
try {
|
||||
vm.runInNewContext(code, sandbox, { timeout: timeoutMs });
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function buildDynamicDecoderSandbox(preludeCode, rotationCode, pxSetterName, pxValue) {
|
||||
const sandbox = createSandbox();
|
||||
if (!runCodeInSandbox(preludeCode, sandbox, 5000)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
sandbox[pxSetterName] = function patchedPxSetter() {
|
||||
sandbox.PX = pxValue;
|
||||
return "nBfa";
|
||||
};
|
||||
|
||||
// OB variants can spend noticeably longer in the bootstrap rotation loop.
|
||||
if (!runCodeInSandbox(rotationCode, sandbox, 3000)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sandbox;
|
||||
}
|
||||
|
||||
function validateDynamicDecoderSandbox(sandbox, decoderName, targets) {
|
||||
if (!sandbox || typeof sandbox[decoderName] !== "function" || targets.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return targets.every((target) => {
|
||||
if (sandbox[decoderName](target.argValue) !== target.expected) {
|
||||
return false;
|
||||
}
|
||||
if (typeof target.orderArgValue !== "number") {
|
||||
return true;
|
||||
}
|
||||
const orderValue = sandbox[decoderName](target.orderArgValue);
|
||||
return typeof orderValue === "string" && orderValue.includes("|");
|
||||
});
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function bootstrapDynamicDecoderSandbox(ast) {
|
||||
const preludeNodes = getLeadingPreludeNodes(ast);
|
||||
const rotationNode = findRotationIifeNode(ast, preludeNodes.length);
|
||||
const pxSetterName = findPxSetterFunctionName(ast);
|
||||
const decoderName = findDecoderFunctionName(ast);
|
||||
const targets = collectValidationTargets(ast);
|
||||
const preludeCode = preludeNodes.map((node) => generateCode(node)).join(";\n");
|
||||
const rotationCode = rotationNode ? generateCode(rotationNode) : "";
|
||||
|
||||
if (!rotationNode || !pxSetterName || !decoderName || targets.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const pxCandidates = [];
|
||||
for (let pxValue = 256; pxValue <= 512; pxValue += 1) {
|
||||
pxCandidates.push(pxValue);
|
||||
}
|
||||
for (let pxValue = 0; pxValue < 256; pxValue += 1) {
|
||||
pxCandidates.push(pxValue);
|
||||
}
|
||||
|
||||
for (const pxValue of pxCandidates) {
|
||||
const sandbox = buildDynamicDecoderSandbox(preludeCode, rotationCode, pxSetterName, pxValue);
|
||||
if (!validateDynamicDecoderSandbox(sandbox, decoderName, targets)) {
|
||||
continue;
|
||||
}
|
||||
return {
|
||||
sandbox,
|
||||
decoderName,
|
||||
pxValue
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function obVariantPass(ast) {
|
||||
let changed = false;
|
||||
const body = ast.program.body;
|
||||
|
||||
if (body.length >= 3 && body[2].type === "VariableDeclaration" && body[2].declarations.length > 0 && t.isIdentifier(body[2].declarations[0].id)) {
|
||||
const sandbox = createSandbox();
|
||||
const decoderName = body[2].declarations[0].id.name;
|
||||
if (evaluateNodes(body.slice(0, 3), sandbox) && typeof sandbox[decoderName] === "function") {
|
||||
ast.program.body = body.slice(3).map((node) => clone(node));
|
||||
changed = true;
|
||||
|
||||
traverse(ast, {
|
||||
CallExpression(path) {
|
||||
if (!path.get("callee").isIdentifier({ name: decoderName })) {
|
||||
return;
|
||||
}
|
||||
const result = evaluateExpression(path.toString(), sandbox);
|
||||
if (!result.ok) {
|
||||
return;
|
||||
}
|
||||
path.replaceWith(t.valueToNode(result.value));
|
||||
changed = true;
|
||||
},
|
||||
BinaryExpression: {
|
||||
exit(path) {
|
||||
const evaluated = path.evaluate();
|
||||
if (evaluated.confident) {
|
||||
path.replaceWith(t.valueToNode(evaluated.value));
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const dynamicDecoder = bootstrapDynamicDecoderSandbox(ast);
|
||||
const prelude = dynamicDecoder ? null : evaluatePreludeStatements(ast, {
|
||||
sandbox: createSandbox(),
|
||||
maxStatements: 8,
|
||||
timeoutMs: 1500
|
||||
});
|
||||
const sandbox = dynamicDecoder ? dynamicDecoder.sandbox : prelude.sandbox;
|
||||
if (dynamicDecoder || prelude.executedCount > 0) {
|
||||
if (seedSandboxFromAliases(ast, sandbox)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (replaceCallsFromSandbox(ast, sandbox, {
|
||||
timeoutMs: 800,
|
||||
allowPredicate: (path) => path.node.arguments.every((arg) => t.isLiteral(arg))
|
||||
})) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (replaceMembersFromSandbox(ast, sandbox)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
traverse(ast, {
|
||||
VariableDeclarator(path) {
|
||||
const initPath = path.get("init");
|
||||
if (!initPath.node) {
|
||||
return;
|
||||
}
|
||||
const result = evaluateExpression(generateCode(initPath.node), sandbox, 800);
|
||||
const node = result.ok ? literalNodeFromValue(result.value) : null;
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
initPath.replaceWith(node);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (replacePrimitiveIdentifiers(ast, sandbox)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (replaceMembersFromSandbox(ast, sandbox)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (replaceCallsFromSandbox(ast, sandbox, {
|
||||
timeoutMs: 800,
|
||||
allowPredicate: (path) => path.node.arguments.every((arg) => t.isLiteral(arg))
|
||||
})) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (inlineAssignmentObjects(ast)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
let localChanged = false;
|
||||
do {
|
||||
({ changed: localChanged } = foldWrapperObjects(ast, {
|
||||
sandbox,
|
||||
timeoutMs: 800
|
||||
}));
|
||||
if (localChanged) {
|
||||
changed = true;
|
||||
}
|
||||
} while (localChanged);
|
||||
|
||||
return { ast, changed };
|
||||
}
|
||||
|
||||
runPatternPass(obVariantPass);
|
||||
Reference in New Issue
Block a user