reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
const { clone } = require("../shared");
|
||||
const { runPatternPass } = require("./shared-pattern-pass");
|
||||
const { getTopLevelIifeInfo, traverse, t } = require("./pattern-utils");
|
||||
|
||||
function dingxiangArrayPass(ast) {
|
||||
let changed = false;
|
||||
|
||||
traverse(ast, {
|
||||
Program(programPath) {
|
||||
const info = getTopLevelIifeInfo(programPath);
|
||||
if (!info) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actualArgs = info.argPaths.map((argPath) => argPath.node);
|
||||
const formalParams = info.calleePath.node.params;
|
||||
const paramElementMap = Object.create(null);
|
||||
|
||||
formalParams.forEach((paramNode, index) => {
|
||||
if (!t.isIdentifier(paramNode)) {
|
||||
return;
|
||||
}
|
||||
const actualNode = actualArgs[index];
|
||||
if (t.isArrayExpression(actualNode)) {
|
||||
paramElementMap[paramNode.name] = actualNode.elements.map((element) => (element ? clone(element) : t.identifier("undefined")));
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(paramElementMap).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
info.statementPath.traverse({
|
||||
UnaryExpression(path) {
|
||||
const callPath = path.get("argument");
|
||||
if (
|
||||
!callPath.isCallExpression() ||
|
||||
!callPath.get("callee").isFunctionExpression() ||
|
||||
callPath.node.arguments.length !== 2 ||
|
||||
!callPath.get("arguments").every((argPath) => argPath.isArrayExpression())
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
callPath.get("callee.params").forEach((paramPath) => {
|
||||
if (!paramPath.isIdentifier()) {
|
||||
return;
|
||||
}
|
||||
const elements = paramElementMap[paramPath.node.name];
|
||||
if (!elements) {
|
||||
return;
|
||||
}
|
||||
const binding = paramPath.scope.getBinding(paramPath.node.name);
|
||||
if (!binding || !binding.constant) {
|
||||
return;
|
||||
}
|
||||
binding.referencePaths.forEach((refPath) => {
|
||||
const parent = refPath.parentPath;
|
||||
if (!parent.isMemberExpression({ object: refPath.node }) || !parent.get("property").isNumericLiteral()) {
|
||||
return;
|
||||
}
|
||||
const index = parent.node.property.value;
|
||||
if (!Number.isInteger(index) || index < 0 || index >= elements.length) {
|
||||
return;
|
||||
}
|
||||
parent.replaceWith(clone(elements[index]));
|
||||
changed = true;
|
||||
});
|
||||
});
|
||||
|
||||
path.replaceWithMultiple(callPath.get("callee.body").node.body.map((node) => clone(node)));
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return { ast, changed };
|
||||
}
|
||||
|
||||
runPatternPass(dingxiangArrayPass);
|
||||
@@ -0,0 +1,113 @@
|
||||
const { clone } = require("../shared");
|
||||
const { runPatternPass } = require("./shared-pattern-pass");
|
||||
const { createSandbox, evaluateExpression, evaluateNodes, generateCode, traverse, t } = require("./pattern-utils");
|
||||
|
||||
function geetest4GuardedPass(ast) {
|
||||
let changed = false;
|
||||
const body = ast.program.body;
|
||||
|
||||
if (body.length >= 5) {
|
||||
const sandbox = createSandbox();
|
||||
const decriptPropName = body[2] && body[2].expression && body[2].expression.left ? generateCode(body[2].expression.left) : "";
|
||||
const ctrlVarName = body[3] && body[3].expression && body[3].expression.left ? generateCode(body[3].expression.left) : "";
|
||||
|
||||
if (decriptPropName && evaluateNodes(body.slice(0, 5), sandbox)) {
|
||||
traverse(ast, {
|
||||
VariableDeclaration(path) {
|
||||
if (
|
||||
path.node.declarations.length !== 3 ||
|
||||
!path.get("declarations.0.init").node ||
|
||||
generateCode(path.get("declarations.0.init").node) !== decriptPropName
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const next1 = path.getNextSibling();
|
||||
const next2 = next1 && next1.getNextSibling();
|
||||
if (!next1 || !next2 || !next1.node || !next2.node) {
|
||||
return;
|
||||
}
|
||||
|
||||
const candidateNames = [0, 2]
|
||||
.map((index) => path.get(`declarations.${index}.id`))
|
||||
.filter((idPath) => idPath && idPath.isIdentifier())
|
||||
.map((idPath) => idPath.node.name);
|
||||
|
||||
candidateNames.forEach((name) => {
|
||||
const binding = path.scope.getBinding(name);
|
||||
if (!binding) {
|
||||
return;
|
||||
}
|
||||
binding.referencePaths.forEach((refPath) => {
|
||||
const parent = refPath.parentPath;
|
||||
if (!parent.isCallExpression() || parent.node.arguments.length !== 1 || !parent.get("arguments.0").isNumericLiteral()) {
|
||||
return;
|
||||
}
|
||||
const argValue = parent.node.arguments[0].value;
|
||||
const result = evaluateExpression(`${decriptPropName}(${argValue})`, sandbox);
|
||||
if (!result.ok) {
|
||||
return;
|
||||
}
|
||||
parent.replaceWith(t.valueToNode(result.value));
|
||||
changed = true;
|
||||
});
|
||||
});
|
||||
|
||||
path.remove();
|
||||
next1.remove();
|
||||
next2.remove();
|
||||
changed = true;
|
||||
},
|
||||
ForStatement(path) {
|
||||
if (
|
||||
path.node.init !== null ||
|
||||
path.node.update !== null ||
|
||||
!path.get("test").isBinaryExpression() ||
|
||||
!generateCode(path.get("test.right").node).includes(ctrlVarName) ||
|
||||
!path.get("body").isBlockStatement() ||
|
||||
path.get("body.body").length !== 1 ||
|
||||
!path.get("body.body.0").isSwitchStatement()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const testNamePath = path.get("test.left");
|
||||
if (!testNamePath.isIdentifier()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const testName = testNamePath.node.name;
|
||||
const statements = [];
|
||||
path.get("body.body.0.cases").forEach((casePath) => {
|
||||
casePath.get("consequent").forEach((stmtPath) => {
|
||||
if (stmtPath.isBreakStatement()) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
stmtPath.isExpressionStatement() &&
|
||||
stmtPath.get("expression").isAssignmentExpression() &&
|
||||
stmtPath.get("expression.left").isIdentifier({ name: testName })
|
||||
) {
|
||||
return;
|
||||
}
|
||||
statements.push(clone(stmtPath.node));
|
||||
});
|
||||
});
|
||||
|
||||
if (statements.length > 0) {
|
||||
path.replaceWithMultiple(statements);
|
||||
const prev = path.getPrevSibling();
|
||||
if (prev && prev.node) {
|
||||
prev.remove();
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { ast, changed };
|
||||
}
|
||||
|
||||
runPatternPass(geetest4GuardedPass);
|
||||
@@ -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);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,66 @@
|
||||
const { runPatternPass } = require("./shared-pattern-pass");
|
||||
const { inlineStringMethodCalls, traverse, t } = require("./pattern-utils");
|
||||
|
||||
function pruneUnusedTopLevel(ast) {
|
||||
let changed = false;
|
||||
|
||||
traverse(ast, {
|
||||
Program(path) {
|
||||
path.get("body").forEach((stmtPath) => {
|
||||
if (stmtPath.isVariableDeclaration()) {
|
||||
const removable = [];
|
||||
stmtPath.get("declarations").forEach((declPath) => {
|
||||
if (!declPath.get("id").isIdentifier()) {
|
||||
return;
|
||||
}
|
||||
const binding = declPath.scope.getBinding(declPath.node.id.name);
|
||||
if (binding && !binding.referenced) {
|
||||
removable.push(declPath);
|
||||
}
|
||||
});
|
||||
if (removable.length === stmtPath.node.declarations.length && removable.length > 0) {
|
||||
stmtPath.remove();
|
||||
changed = true;
|
||||
} else if (removable.length > 0) {
|
||||
removable.forEach((declPath) => declPath.remove());
|
||||
changed = true;
|
||||
}
|
||||
} else if (stmtPath.isFunctionDeclaration() && stmtPath.node.id) {
|
||||
const binding = stmtPath.scope.getBinding(stmtPath.node.id.name);
|
||||
if (binding && !binding.referenced) {
|
||||
stmtPath.remove();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
function reese84HeavyPass(ast) {
|
||||
let changed = false;
|
||||
if (inlineStringMethodCalls(ast)) {
|
||||
changed = true;
|
||||
}
|
||||
traverse(ast, {
|
||||
BinaryExpression: {
|
||||
exit(path) {
|
||||
if (path.get("left").isStringLiteral() && path.get("right").isStringLiteral()) {
|
||||
const evaluated = path.evaluate();
|
||||
if (evaluated.confident) {
|
||||
path.replaceWith(t.valueToNode(evaluated.value));
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (pruneUnusedTopLevel(ast)) {
|
||||
changed = true;
|
||||
}
|
||||
return { ast, changed };
|
||||
}
|
||||
|
||||
runPatternPass(reese84HeavyPass);
|
||||
@@ -0,0 +1,17 @@
|
||||
const { parseArgs, parseFile, reparse, saveAst } = require("../shared");
|
||||
|
||||
function runPatternPass(transform) {
|
||||
const { inputPath, outputPath } = parseArgs();
|
||||
let ast = parseFile(inputPath);
|
||||
const result = typeof transform === "function" ? transform(ast) || { ast, changed: false } : { ast, changed: false };
|
||||
if (result.changed) {
|
||||
ast = reparse(result.ast || ast);
|
||||
} else if (result.ast) {
|
||||
ast = result.ast;
|
||||
}
|
||||
saveAst(ast, outputPath);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
runPatternPass
|
||||
};
|
||||
@@ -0,0 +1,189 @@
|
||||
const { runPatternPass } = require("./shared-pattern-pass");
|
||||
const {
|
||||
createSandbox,
|
||||
evaluateNodes,
|
||||
foldWrapperObjects,
|
||||
inlineStringMethodCalls,
|
||||
seedSandboxFromAliases,
|
||||
traverse,
|
||||
t
|
||||
} = require("./pattern-utils");
|
||||
|
||||
function getDecoderPreludeNodes(ast) {
|
||||
const topLevelDeclarator = ast.program.body
|
||||
.filter((node) => t.isVariableDeclaration(node))
|
||||
.flatMap((node) => node.declarations)
|
||||
.find((decl) =>
|
||||
t.isIdentifier(decl.id) &&
|
||||
(
|
||||
t.isFunctionExpression(decl.init) ||
|
||||
(t.isCallExpression(decl.init) && t.isFunctionExpression(decl.init.callee))
|
||||
)
|
||||
);
|
||||
|
||||
const rootFunction = !topLevelDeclarator ? null :
|
||||
t.isFunctionExpression(topLevelDeclarator.init) ? topLevelDeclarator.init :
|
||||
t.isCallExpression(topLevelDeclarator.init) && t.isFunctionExpression(topLevelDeclarator.init.callee) ? topLevelDeclarator.init.callee :
|
||||
null;
|
||||
|
||||
if (!rootFunction || !t.isBlockStatement(rootFunction.body)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const nodes = [];
|
||||
for (const stmt of rootFunction.body.body) {
|
||||
if (t.isFunctionDeclaration(stmt) && stmt.id && (stmt.id.name === "t" || stmt.id.name === "u")) {
|
||||
nodes.push(stmt);
|
||||
continue;
|
||||
}
|
||||
if (t.isVariableDeclaration(stmt) && stmt.declarations.every((decl) => t.isIdentifier(decl.id) && decl.init == null)) {
|
||||
nodes.push(stmt);
|
||||
continue;
|
||||
}
|
||||
if (t.isExpressionStatement(stmt)) {
|
||||
nodes.push(stmt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function tonghuashunOrderPass(ast) {
|
||||
let changed = false;
|
||||
let localChanged = false;
|
||||
const sandbox = createSandbox();
|
||||
const preludeNodes = getDecoderPreludeNodes(ast);
|
||||
if (preludeNodes.length > 0 && evaluateNodes(preludeNodes, sandbox, 2000)) {
|
||||
while (seedSandboxFromAliases(ast, sandbox)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
const decoderAliasNames = collectDecoderAliasNames(ast, ["t"]);
|
||||
do {
|
||||
localChanged = false;
|
||||
|
||||
if (replaceDecoderAliasCalls(ast, sandbox, decoderAliasNames)) {
|
||||
localChanged = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (inlineStringMethodCalls(ast)) {
|
||||
localChanged = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
const foldResult = foldWrapperObjects(ast, {
|
||||
ignoreDynamicUsage: true
|
||||
});
|
||||
if (foldResult.changed) {
|
||||
localChanged = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (inlineStringMethodCalls(ast)) {
|
||||
localChanged = true;
|
||||
changed = true;
|
||||
}
|
||||
} while (localChanged);
|
||||
|
||||
return { ast, changed };
|
||||
}
|
||||
|
||||
function collectDecoderAliasNames(ast, seedNames) {
|
||||
const aliasNames = new Set(seedNames);
|
||||
let expanded = false;
|
||||
|
||||
do {
|
||||
expanded = false;
|
||||
traverse(ast, {
|
||||
VariableDeclarator(path) {
|
||||
if (!path.get("id").isIdentifier() || !path.get("init").isIdentifier()) {
|
||||
return;
|
||||
}
|
||||
const sourceName = path.get("init").node.name;
|
||||
const aliasName = path.get("id").node.name;
|
||||
if (!aliasNames.has(sourceName) || aliasNames.has(aliasName)) {
|
||||
return;
|
||||
}
|
||||
aliasNames.add(aliasName);
|
||||
expanded = true;
|
||||
},
|
||||
AssignmentExpression(path) {
|
||||
if (!path.get("left").isIdentifier() || !path.get("right").isIdentifier()) {
|
||||
return;
|
||||
}
|
||||
const sourceName = path.get("right").node.name;
|
||||
const aliasName = path.get("left").node.name;
|
||||
if (!aliasNames.has(sourceName) || aliasNames.has(aliasName)) {
|
||||
return;
|
||||
}
|
||||
aliasNames.add(aliasName);
|
||||
expanded = true;
|
||||
}
|
||||
});
|
||||
} while (expanded);
|
||||
|
||||
return aliasNames;
|
||||
}
|
||||
|
||||
function nodeFromPrimitiveValue(value) {
|
||||
if (typeof value === "undefined") {
|
||||
return t.identifier("undefined");
|
||||
}
|
||||
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
||||
return t.valueToNode(value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function replaceDecoderAliasCalls(ast, sandbox, aliasNames) {
|
||||
if (typeof sandbox.t !== "function" || aliasNames.size === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let changed = false;
|
||||
traverse(ast, {
|
||||
CallExpression(path) {
|
||||
const calleePath = path.get("callee");
|
||||
let isDecoderAliasCall = false;
|
||||
|
||||
if (calleePath.isIdentifier()) {
|
||||
isDecoderAliasCall = aliasNames.has(calleePath.node.name);
|
||||
} else if (
|
||||
calleePath.isAssignmentExpression({ operator: "=" }) &&
|
||||
calleePath.get("left").isIdentifier() &&
|
||||
calleePath.get("right").isIdentifier()
|
||||
) {
|
||||
isDecoderAliasCall = aliasNames.has(calleePath.get("right").node.name);
|
||||
}
|
||||
|
||||
if (!isDecoderAliasCall) {
|
||||
return;
|
||||
}
|
||||
if (path.node.arguments.length !== 1 || !path.get("arguments.0").isNumericLiteral()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let value;
|
||||
try {
|
||||
value = sandbox.t(path.get("arguments.0").node.value);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
const literalNode = nodeFromPrimitiveValue(value);
|
||||
if (!literalNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
path.replaceWith(literalNode);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
runPatternPass(tonghuashunOrderPass);
|
||||
@@ -0,0 +1,257 @@
|
||||
const { clone } = require("../shared");
|
||||
const { runPatternPass } = require("./shared-pattern-pass");
|
||||
const { createSandbox, evaluateExpression, evaluateNodes, foldWrapperObjects, traverse, t } = require("./pattern-utils");
|
||||
|
||||
function getOpcodeTestInfo(testPath) {
|
||||
if (!testPath.isBinaryExpression()) {
|
||||
return null;
|
||||
}
|
||||
if (!["===", "=="].includes(testPath.node.operator)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (testPath.get("left").isNumericLiteral() && testPath.get("right").isIdentifier()) {
|
||||
return {
|
||||
discriminant: clone(testPath.get("right").node),
|
||||
discriminantName: testPath.get("right").node.name,
|
||||
caseValue: clone(testPath.get("left").node)
|
||||
};
|
||||
}
|
||||
|
||||
if (testPath.get("right").isNumericLiteral() && testPath.get("left").isIdentifier()) {
|
||||
return {
|
||||
discriminant: clone(testPath.get("left").node),
|
||||
discriminantName: testPath.get("left").node.name,
|
||||
caseValue: clone(testPath.get("right").node)
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function blockify(node) {
|
||||
if (!node) {
|
||||
return [];
|
||||
}
|
||||
if (t.isBlockStatement(node)) {
|
||||
return node.body.map((stmt) => clone(stmt));
|
||||
}
|
||||
return [clone(node)];
|
||||
}
|
||||
|
||||
function withTrailingBreak(statements) {
|
||||
if (statements.length === 0) {
|
||||
return [t.breakStatement()];
|
||||
}
|
||||
const last = statements[statements.length - 1];
|
||||
if (t.isBreakStatement(last) || t.isReturnStatement(last) || t.isThrowStatement(last) || t.isContinueStatement(last)) {
|
||||
return statements;
|
||||
}
|
||||
return [...statements, t.breakStatement()];
|
||||
}
|
||||
|
||||
function collectOpcodeCasesFromAlternate(node, discriminantName, cases) {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (t.isIfStatement(node)) {
|
||||
const info = getOpcodeTestInfo({
|
||||
isBinaryExpression: () => t.isBinaryExpression(node.test),
|
||||
node: node.test,
|
||||
get(key) {
|
||||
if (key === "left") {
|
||||
return {
|
||||
isNumericLiteral: () => t.isNumericLiteral(node.test.left),
|
||||
isIdentifier: () => t.isIdentifier(node.test.left),
|
||||
node: node.test.left
|
||||
};
|
||||
}
|
||||
return {
|
||||
isNumericLiteral: () => t.isNumericLiteral(node.test.right),
|
||||
isIdentifier: () => t.isIdentifier(node.test.right),
|
||||
node: node.test.right
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
if (info && info.discriminantName === discriminantName) {
|
||||
cases.push(t.switchCase(info.caseValue, withTrailingBreak(blockify(node.consequent))));
|
||||
collectOpcodeCasesFromAlternate(node.alternate, discriminantName, cases);
|
||||
return;
|
||||
}
|
||||
|
||||
cases.push(t.switchCase(null, withTrailingBreak(blockify(node))));
|
||||
return;
|
||||
}
|
||||
|
||||
if (t.isBlockStatement(node)) {
|
||||
node.body.forEach((stmt) => {
|
||||
if (t.isIfStatement(stmt)) {
|
||||
const info = getOpcodeTestInfo({
|
||||
isBinaryExpression: () => t.isBinaryExpression(stmt.test),
|
||||
node: stmt.test,
|
||||
get(key) {
|
||||
if (key === "left") {
|
||||
return {
|
||||
isNumericLiteral: () => t.isNumericLiteral(stmt.test.left),
|
||||
isIdentifier: () => t.isIdentifier(stmt.test.left),
|
||||
node: stmt.test.left
|
||||
};
|
||||
}
|
||||
return {
|
||||
isNumericLiteral: () => t.isNumericLiteral(stmt.test.right),
|
||||
isIdentifier: () => t.isIdentifier(stmt.test.right),
|
||||
node: stmt.test.right
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
if (info && info.discriminantName === discriminantName) {
|
||||
cases.push(t.switchCase(info.caseValue, withTrailingBreak(blockify(stmt.consequent))));
|
||||
collectOpcodeCasesFromAlternate(stmt.alternate, discriminantName, cases);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cases.push(t.switchCase(null, withTrailingBreak([clone(stmt)])));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
cases.push(t.switchCase(null, withTrailingBreak([clone(node)])));
|
||||
}
|
||||
|
||||
function convertOpcodeIfChains(ast) {
|
||||
let changed = false;
|
||||
|
||||
traverse(ast, {
|
||||
IfStatement(path) {
|
||||
const info = getOpcodeTestInfo(path.get("test"));
|
||||
if (!info) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cases = [
|
||||
t.switchCase(info.caseValue, withTrailingBreak(blockify(path.node.consequent)))
|
||||
];
|
||||
collectOpcodeCasesFromAlternate(path.node.alternate, info.discriminantName, cases);
|
||||
path.replaceWith(t.switchStatement(info.discriminant, cases));
|
||||
path.skip();
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
function stripPseudoForLoops(ast) {
|
||||
let changed = false;
|
||||
|
||||
traverse(ast, {
|
||||
ForStatement(path) {
|
||||
const testPath = path.get("test");
|
||||
if (
|
||||
!testPath.isBinaryExpression() ||
|
||||
path.node.update !== null ||
|
||||
!(
|
||||
testPath.get("left").isNumericLiteral() ||
|
||||
testPath.get("right").isNumericLiteral()
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.node.init) {
|
||||
path.replaceWith(clone(path.node.init));
|
||||
} else {
|
||||
path.remove();
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
function xiaohongshuWrapperPass(ast) {
|
||||
let changed = false;
|
||||
const body = ast.program.body;
|
||||
|
||||
if (
|
||||
body.length >= 3 &&
|
||||
body[1].type === "VariableDeclaration" &&
|
||||
body[1].declarations.length > 0 &&
|
||||
t.isIdentifier(body[1].declarations[0].id)
|
||||
) {
|
||||
const sandbox = createSandbox();
|
||||
const decoderName = body[1].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;
|
||||
|
||||
const aliasNames = new Set([decoderName]);
|
||||
let progress = false;
|
||||
do {
|
||||
progress = false;
|
||||
|
||||
traverse(ast, {
|
||||
VariableDeclarator(path) {
|
||||
if (!path.get("id").isIdentifier() || !path.get("init").isIdentifier()) {
|
||||
return;
|
||||
}
|
||||
if (aliasNames.has(path.node.init.name) && !aliasNames.has(path.node.id.name)) {
|
||||
aliasNames.add(path.node.id.name);
|
||||
progress = true;
|
||||
}
|
||||
},
|
||||
CallExpression(path) {
|
||||
if (!path.get("callee").isIdentifier() || !aliasNames.has(path.node.callee.name) || path.node.arguments.length !== 1) {
|
||||
return;
|
||||
}
|
||||
const result = evaluateExpression(`${decoderName}(${path.get("arguments.0").toString()})`, sandbox);
|
||||
if (!result.ok) {
|
||||
return;
|
||||
}
|
||||
path.replaceWith(t.valueToNode(result.value));
|
||||
progress = true;
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
} while (progress);
|
||||
|
||||
traverse(ast, {
|
||||
VariableDeclarator(path) {
|
||||
if (!path.get("id").isIdentifier() || !path.get("init").isIdentifier()) {
|
||||
return;
|
||||
}
|
||||
if (aliasNames.has(path.node.id.name) && aliasNames.has(path.node.init.name)) {
|
||||
path.remove();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let localChanged = false;
|
||||
do {
|
||||
({ changed: localChanged } = foldWrapperObjects(ast));
|
||||
if (localChanged) {
|
||||
changed = true;
|
||||
}
|
||||
} while (localChanged);
|
||||
|
||||
if (convertOpcodeIfChains(ast)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (stripPseudoForLoops(ast)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return { ast, changed };
|
||||
}
|
||||
|
||||
runPatternPass(xiaohongshuWrapperPass);
|
||||
@@ -0,0 +1,202 @@
|
||||
const { clone } = require("../shared");
|
||||
const { runPatternPass } = require("./shared-pattern-pass");
|
||||
const { createSandbox, evaluateExpression, evaluateNodes, generateCode, traverse, t } = require("./pattern-utils");
|
||||
|
||||
function unwrapTopLevelIife(ast) {
|
||||
const first = ast.program.body[0];
|
||||
if (!first || first.type !== "ExpressionStatement" || first.expression.type !== "CallExpression") {
|
||||
return false;
|
||||
}
|
||||
const callee = first.expression.callee;
|
||||
if (callee.type !== "FunctionExpression") {
|
||||
return false;
|
||||
}
|
||||
ast.program.body = callee.body.body.map((node) => clone(node));
|
||||
return true;
|
||||
}
|
||||
|
||||
function expandSequenceExpressions(ast) {
|
||||
let changed = false;
|
||||
|
||||
traverse(ast, {
|
||||
SequenceExpression(path) {
|
||||
if (path.parentPath.isExpressionStatement()) {
|
||||
path.replaceWithMultiple(path.node.expressions.map((expr) => t.expressionStatement(clone(expr))));
|
||||
changed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.parentPath.isReturnStatement()) {
|
||||
const expressions = path.node.expressions.map((expr) => clone(expr));
|
||||
const last = expressions.pop();
|
||||
if (!last) {
|
||||
return;
|
||||
}
|
||||
path.parentPath.replaceWithMultiple([
|
||||
...expressions.map((expr) => t.expressionStatement(expr)),
|
||||
t.returnStatement(last)
|
||||
]);
|
||||
changed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.parentPath.isVariableDeclarator()) {
|
||||
const declaratorPath = path.parentPath;
|
||||
const declarationPath = declaratorPath.parentPath;
|
||||
if (!declarationPath || !declarationPath.isVariableDeclaration()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const expressions = path.node.expressions.map((expr) => clone(expr));
|
||||
const last = expressions.pop();
|
||||
if (!last) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inserted = [
|
||||
...expressions.map((expr) => t.expressionStatement(expr)),
|
||||
t.variableDeclaration(declarationPath.node.kind, [
|
||||
t.variableDeclarator(clone(declaratorPath.node.id), last)
|
||||
])
|
||||
];
|
||||
|
||||
declarationPath.insertAfter(inserted);
|
||||
declaratorPath.remove();
|
||||
if (declarationPath.get("declarations").length === 0) {
|
||||
declarationPath.remove();
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
function unwrapUnaryIifes(ast) {
|
||||
let changed = false;
|
||||
|
||||
traverse(ast, {
|
||||
UnaryExpression(path) {
|
||||
if (
|
||||
path.node.operator !== "!" ||
|
||||
!path.get("argument").isCallExpression() ||
|
||||
path.get("argument.arguments").length !== 0 ||
|
||||
!path.get("argument.callee").isFunctionExpression() ||
|
||||
path.get("argument.callee.params").length !== 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
path.replaceWithMultiple(path.get("argument.callee.body").node.body.map((node) => clone(node)));
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
function yidunDispatcherPass(ast) {
|
||||
let changed = false;
|
||||
|
||||
if (unwrapTopLevelIife(ast)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (expandSequenceExpressions(ast)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (unwrapUnaryIifes(ast)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
const body = ast.program.body;
|
||||
if (body.length >= 2 && body[1].type === "FunctionDeclaration" && body[1].id && body[1].params.length === 2) {
|
||||
const sandbox = createSandbox();
|
||||
const decoderName = body[1].id.name;
|
||||
if (evaluateNodes(body.slice(0, 2), sandbox) && typeof sandbox[decoderName] === "function") {
|
||||
traverse(ast, {
|
||||
CallExpression(path) {
|
||||
if (!path.get("callee").isIdentifier({ name: decoderName }) || path.node.arguments.length !== 1 || !path.get("arguments.0").isNumericLiteral()) {
|
||||
return;
|
||||
}
|
||||
const result = evaluateExpression(path.toString(), sandbox);
|
||||
if (!result.ok) {
|
||||
return;
|
||||
}
|
||||
path.replaceWith(t.valueToNode(result.value));
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
ast.program.body = ast.program.body.slice(2).map((node) => clone(node));
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
traverse(ast, {
|
||||
VariableDeclaration(path) {
|
||||
if (
|
||||
path.node.declarations.length !== 5 ||
|
||||
!path.get("declarations").every((declarationPath) => {
|
||||
return (
|
||||
declarationPath.get("init").isMemberExpression() &&
|
||||
declarationPath.get("init.object").isNewExpression() &&
|
||||
declarationPath.get("init.property").isStringLiteral()
|
||||
);
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prev = path.getPrevSibling();
|
||||
const next1 = path.getNextSibling();
|
||||
const next2 = next1 && next1.getNextSibling();
|
||||
if (!prev || !next1 || !next2 || !prev.node || !next1.node || !next2.node) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sandbox = createSandbox();
|
||||
if (!evaluateNodes([prev.node, path.node, next1.node, next2.node], sandbox)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const names = []
|
||||
.concat(next1.node.declarations || [])
|
||||
.concat(next2.node.declarations || [])
|
||||
.map((declaration) => declaration.id)
|
||||
.filter((idNode) => t.isIdentifier(idNode))
|
||||
.map((idNode) => idNode.name);
|
||||
|
||||
names.forEach((name) => {
|
||||
const binding = path.scope.getBinding(name);
|
||||
if (!binding || !binding.constant) {
|
||||
return;
|
||||
}
|
||||
binding.referencePaths.forEach((refPath) => {
|
||||
const parent = refPath.parentPath;
|
||||
if (!parent.isMemberExpression({ object: refPath.node }) || !parent.get("property").isNumericLiteral()) {
|
||||
return;
|
||||
}
|
||||
const result = evaluateExpression(generateCode(parent.node), sandbox);
|
||||
if (!result.ok) {
|
||||
return;
|
||||
}
|
||||
parent.replaceWith(t.valueToNode(result.value));
|
||||
changed = true;
|
||||
});
|
||||
});
|
||||
|
||||
prev.remove();
|
||||
path.remove();
|
||||
next1.remove();
|
||||
next2.remove();
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
return { ast, changed };
|
||||
}
|
||||
|
||||
runPatternPass(yidunDispatcherPass);
|
||||
Reference in New Issue
Block a user