mirror of
https://github.com/rastvl/akamai-deobfuscator-2.0.git
synced 2025-04-18 00:57:44 +08:00
635 lines
18 KiB
JavaScript
635 lines
18 KiB
JavaScript
const t = require('@babel/types');
|
|
const generate = require("@babel/generator").default;
|
|
const Environment = require('./Environment');
|
|
const ExecutionContext = require('./ExecutionContext');
|
|
const GlobalExecutionContext = require('./GlobalExecutionContext');
|
|
const { userFunctionToString } = require('./../utils/constants');
|
|
const fs = require('fs');
|
|
|
|
class Interpreter {
|
|
constructor(code, execCtx = GlobalExecutionContext) {
|
|
this.scriptCode = code;
|
|
this.callStack = [execCtx];
|
|
this.flags = {
|
|
continue: false,
|
|
break: false
|
|
}
|
|
}
|
|
|
|
eval(node, ctx = this.callStack[this.callStack.length - 1]) {
|
|
if (global.allTimeoutsCleaned) {
|
|
console.log('SetContext!');
|
|
global.interpreterState = ctx;
|
|
global.allTimeoutsCleaned = false;
|
|
return;
|
|
}
|
|
|
|
if (t.isProgram(node)) {
|
|
this._hoistVariables(node, ctx);
|
|
let result;
|
|
node.body.forEach((node) => {
|
|
result = this.eval(node, ctx);
|
|
});
|
|
return result;
|
|
}
|
|
|
|
if (t.isExpressionStatement(node)) {
|
|
return this.eval(node.expression, ctx);
|
|
}
|
|
|
|
if (t.isLiteral(node)) {
|
|
// return global.eval(generate(node).code)
|
|
if (t.isNullLiteral(node)) {
|
|
return null;
|
|
}
|
|
if (t.isRegExpLiteral(node)) {
|
|
return new RegExp(node.pattern, node.flags);
|
|
}
|
|
return node.value;
|
|
}
|
|
|
|
if (t.isBinaryExpression(node)) {
|
|
const left = this.eval(node.left, ctx);
|
|
const right = this.eval(node.right, ctx);
|
|
switch (node.operator) {
|
|
case '+':
|
|
return left + right;
|
|
case '-':
|
|
return left - right;
|
|
case '*':
|
|
return left * right;
|
|
case '/':
|
|
return left / right;
|
|
case '%':
|
|
return left % right;
|
|
case '**':
|
|
return left ** right;
|
|
case '==':
|
|
return left == right;
|
|
case '===':
|
|
return left === right;
|
|
case '!=':
|
|
return left != right;
|
|
case '!==':
|
|
return left !== right;
|
|
case '<':
|
|
return left < right;
|
|
case '<=':
|
|
return left <= right;
|
|
case '>':
|
|
return left > right;
|
|
case '>=':
|
|
return left >= right;
|
|
case '|':
|
|
return left | right;
|
|
case '&':
|
|
return left & right;
|
|
case '^':
|
|
return left ^ right;
|
|
case '<<':
|
|
return left << right;
|
|
case '>>':
|
|
return left >> right;
|
|
case '>>>':
|
|
return left >>> right;
|
|
case 'in':
|
|
return left in right;
|
|
case 'instanceof':
|
|
return left instanceof right;
|
|
default:
|
|
throw `Unknown operator ${node.operator}`;
|
|
}
|
|
}
|
|
|
|
if (t.isUnaryExpression(node)) {
|
|
const arg = this.eval(node.argument, ctx);
|
|
switch (node.operator) {
|
|
case '+':
|
|
return +arg;
|
|
case '-':
|
|
return -arg;
|
|
case '!':
|
|
return !arg;
|
|
case '~':
|
|
return ~arg;
|
|
case 'typeof':
|
|
return typeof arg;
|
|
case 'void':
|
|
return void arg;
|
|
default:
|
|
throw new Error(`Unknown unary operator ${node.operator}`);
|
|
}
|
|
}
|
|
|
|
if (t.isLogicalExpression(node)) {
|
|
switch (node.operator) {
|
|
case '||':
|
|
return this.eval(node.left, ctx) || this.eval(node.right, ctx);
|
|
case '&&':
|
|
return this.eval(node.left, ctx) && this.eval(node.right, ctx);
|
|
case '??':
|
|
return this.eval(node.left, ctx) ?? this.eval(node.right, ctx);
|
|
default:
|
|
throw new Error(`Unknown operator ${node.operator}`);
|
|
}
|
|
}
|
|
|
|
if (t.isVariableDeclaration(node)) {
|
|
let result;
|
|
node.declarations.forEach(variableDeclarator => {
|
|
result = this.eval(variableDeclarator, ctx);
|
|
});
|
|
return result;
|
|
}
|
|
|
|
if (t.isVariableDeclarator(node)) {
|
|
const name = node.id.name;
|
|
if (!node.init) { // Переменная уже определена из-за hoisting
|
|
return;
|
|
}
|
|
const value = this.eval(node.init, ctx);
|
|
return ctx.env.define(name, value);
|
|
}
|
|
|
|
if (t.isIdentifier(node)) {
|
|
return ctx.env.lookup(node.name)
|
|
}
|
|
|
|
if (t.isAssignmentExpression(node)) {
|
|
if (t.isIdentifier(node.left)) {
|
|
const left = node.left.name;
|
|
const right = this.eval(node.right, ctx);
|
|
// if (left === 'wPE') {
|
|
// console.log(`CODE: ${generate(node).code} | RESULT: ${right}`);
|
|
// }
|
|
let prevValue = this.eval(node.left, ctx);
|
|
switch(node.operator) {
|
|
case '=':
|
|
return ctx.env.assign(left, right);
|
|
case '+=':
|
|
return ctx.env.assign(left, prevValue + right);
|
|
case '-=':
|
|
return ctx.env.assign(left, prevValue - right);
|
|
case '*=':
|
|
return ctx.env.assign(left, prevValue * right);
|
|
case '/=':
|
|
return ctx.env.assign(left, prevValue / right);
|
|
case '^=':
|
|
return ctx.env.assign(left, prevValue ^ right);
|
|
case '&=':
|
|
return ctx.env.assign(left, prevValue & right);
|
|
case '|=':
|
|
return ctx.env.assign(left, prevValue | right);
|
|
case '%=':
|
|
return ctx.env.assign(left, prevValue % right);
|
|
default:
|
|
throw `Unimplement operator assignment ${node.operator}`
|
|
}
|
|
}
|
|
|
|
if (t.isMemberExpression(node.left)) {
|
|
let objectName = node.left.object.name;
|
|
let object;
|
|
if (objectName === undefined) {
|
|
object = this.eval(node.left.object, ctx);
|
|
} else {
|
|
object = ctx.env.lookup(objectName);
|
|
}
|
|
if (!object) {
|
|
debugger;
|
|
throw `Undefined object in assignment... ${generate(node).code}`;
|
|
}
|
|
let prop;
|
|
if (node.left.computed) {
|
|
prop = this.eval(node.left.property, ctx);
|
|
} else {
|
|
prop = node.left.property.name;
|
|
}
|
|
if (prop == undefined) {
|
|
throw `Undefined property in assignment... ${generate(node).code}`
|
|
}
|
|
const propValue = this.eval(node.right, ctx);
|
|
const prevValue = object[prop];
|
|
switch(node.operator) {
|
|
case '=':
|
|
return object[prop] = propValue;
|
|
case '+=':
|
|
return object[prop] = prevValue + propValue;
|
|
case '-=':
|
|
return object[prop] = prevValue + propValue;
|
|
case '*=':
|
|
return object[prop] = prevValue * propValue;
|
|
case '/=':
|
|
return object[prop] = prevValue / propValue;
|
|
case '^=':
|
|
return object[prop] = prevValue ^ propValue;
|
|
case '&=':
|
|
return object[prop] = prevValue & propValue;
|
|
case '|=':
|
|
return object[prop] = prevValue | propValue;
|
|
case '%=':
|
|
return object[prop] = prevValue % propValue;
|
|
default:
|
|
throw `Unimplement operator assignment ${node.operator}`
|
|
}
|
|
}
|
|
|
|
throw `Unimplement assignment for node type ${node.left.type}`;
|
|
}
|
|
|
|
if (t.isUpdateExpression(node)) {
|
|
if (t.isIdentifier(node.argument)) {
|
|
const varName = node.argument.name;
|
|
const varValue = this.eval(node.argument, ctx);
|
|
const newValue = node.operator === '++' ? varValue + 1 : varValue - 1;
|
|
if (node.prefix) {
|
|
return ctx.env.assign(varName, newValue);
|
|
}
|
|
ctx.env.assign(varName, newValue);
|
|
return varValue;
|
|
}
|
|
|
|
if (t.isMemberExpression(node.argument)) {
|
|
const objectEnv = this.eval(node.argument.object, ctx);
|
|
const prop = node.argument.computed ? this.eval(node.argument.property, ctx) : node.argument.property.name;
|
|
const propValue = objectEnv[prop];
|
|
const newValue = node.operator === '++' ? propValue + 1 : propValue - 1;
|
|
if (node.prefix) {
|
|
return objectEnv[prop] = newValue;
|
|
}
|
|
objectEnv[prop] = newValue;
|
|
return propValue;
|
|
}
|
|
}
|
|
|
|
if (t.isEmptyStatement(node)) {
|
|
return;
|
|
}
|
|
|
|
if (t.isSequenceExpression(node)) {
|
|
let result;
|
|
const { expressions } = node;
|
|
expressions.forEach(expr => {
|
|
result = this.eval(expr, ctx);
|
|
});
|
|
return result;
|
|
}
|
|
|
|
if (t.isThisExpression(node)) {
|
|
return ctx.thisValue;
|
|
}
|
|
|
|
if (t.isObjectExpression(node)) {
|
|
const object = {};
|
|
node.properties.forEach(prop => {
|
|
const key = prop.key.name || prop.key.value;
|
|
const value = this.eval(prop.value, ctx);
|
|
object[key] = value;
|
|
})
|
|
return object;
|
|
}
|
|
|
|
if (t.isArrayExpression(node)) {
|
|
const elements = node.elements.map(el => this.eval(el, ctx));
|
|
const array = [...elements];
|
|
return array;
|
|
}
|
|
|
|
if (t.isConditionalExpression(node)) {
|
|
if (this.eval(node.test, ctx)) {
|
|
return this.eval(node.consequent, ctx)
|
|
} else {
|
|
return this.eval(node.alternate, ctx);
|
|
}
|
|
}
|
|
|
|
if (t.isIfStatement(node)) {
|
|
const test = this.eval(node.test, ctx);
|
|
if (test) {
|
|
return this.eval(node.consequent, ctx)
|
|
} else if (node.alternate !== null) {
|
|
return this.eval(node.alternate, ctx)
|
|
} else {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
if (t.isBlockStatement(node)) {
|
|
this._hoistVariables(node, ctx);
|
|
let result;
|
|
for (let i = 0; i < node.body.length; ++i) {
|
|
const stmt = node.body[i];
|
|
result = this.eval(stmt, ctx);
|
|
if (this.callStack[this.callStack.length - 1] !== ctx) {
|
|
return result;
|
|
}
|
|
|
|
if (this.flags.continue || this.flags.break) {
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
if (t.isFunctionDeclaration(node)) {
|
|
const self = this;
|
|
const parentEnv = ctx.env;
|
|
const func = function(...args) {
|
|
const activationRecord = {};
|
|
for (let i = 0; i < node.params.length; ++i) {
|
|
activationRecord[node.params[i].name] = args[i];
|
|
}
|
|
activationRecord['arguments'] = [...args];
|
|
const execCtx = new ExecutionContext(
|
|
this,
|
|
new Environment(activationRecord, parentEnv)
|
|
);
|
|
|
|
self.callStack.push(execCtx);
|
|
|
|
if (new.target) {
|
|
self._evalFunctionBlock(node.body, execCtx);
|
|
return this;
|
|
}
|
|
|
|
let result = self._evalFunctionBlock(node.body, execCtx);
|
|
return result;
|
|
}
|
|
|
|
const funcString = this.scriptCode.substring(node.loc.start.column, node.loc.end.column);
|
|
userFunctionToString.set(func, funcString);
|
|
|
|
ctx.env.define(node.id.name, func);
|
|
return;
|
|
}
|
|
|
|
if (t.isFunctionExpression(node)) {
|
|
const name = node.id ? node.id.name : undefined;
|
|
const self = this;
|
|
const parentEnv = ctx.env;
|
|
const func = function(...args) {
|
|
const activationRecord = {};
|
|
if (name) {
|
|
activationRecord[name] = func;
|
|
}
|
|
for (let i = 0; i < node.params.length; ++i) {
|
|
activationRecord[node.params[i].name] = args[i];
|
|
}
|
|
activationRecord['arguments'] = [...args];
|
|
const execCtx = new ExecutionContext(
|
|
this,
|
|
new Environment(activationRecord, parentEnv)
|
|
);
|
|
|
|
self.callStack.push(execCtx);
|
|
|
|
if (new.target) {
|
|
self._evalFunctionBlock(node.body, execCtx);
|
|
return this;
|
|
}
|
|
|
|
let result = self._evalFunctionBlock(node.body, execCtx);
|
|
return result;
|
|
}
|
|
|
|
const funcString = this.scriptCode.substring(node.loc.start.column, node.loc.end.column);
|
|
userFunctionToString.set(func, funcString);
|
|
|
|
return func;
|
|
}
|
|
|
|
if (t.isReturnStatement(node)) {
|
|
let functionResult;
|
|
if (node.argument !== null) {
|
|
functionResult = this.eval(node.argument, ctx);
|
|
}
|
|
this.callStack.pop();
|
|
return functionResult;
|
|
}
|
|
|
|
if (t.isCallExpression(node)) {
|
|
let thisCtx;
|
|
let fn;
|
|
|
|
if (t.isMemberExpression(node.callee)) {
|
|
thisCtx = this.eval(node.callee.object, ctx);
|
|
|
|
const prop = node.callee.computed
|
|
? this.eval(node.callee.property, ctx)
|
|
: node.callee.property.name;
|
|
|
|
fn = thisCtx[prop];
|
|
} else {
|
|
fn = this.eval(node.callee, ctx);
|
|
thisCtx = ctx.thisValue;
|
|
}
|
|
|
|
if (fn === undefined) {
|
|
throw `function is not defined ${generate(node).code}`;
|
|
}
|
|
|
|
const args = node.arguments.map(arg => this.eval(arg, ctx));
|
|
// if (args[1] && args[1] === 493711) {
|
|
// // console.log('here')
|
|
// return 3031957943;
|
|
// }
|
|
const result = fn.call(thisCtx, ...args);
|
|
// const resultBlackList = ['length', 'push', 'pop', 'charCodeAt', 'charAt', toString];
|
|
// if (
|
|
// typeof result === 'string' &&
|
|
// !resultBlackList.includes(result) &&
|
|
// result.length > 1 && result.length < 100
|
|
// ) {
|
|
// fs.appendFileSync('./interpreter-logs/callsLog.txt', result + '\n');
|
|
// }
|
|
return result;
|
|
|
|
return fn.call(thisCtx, ...args)
|
|
}
|
|
|
|
if (t.isMemberExpression(node)) {
|
|
const object = this.eval(node.object, ctx);
|
|
let prop;
|
|
if (node.computed) {
|
|
prop = this.eval(node.property, ctx);
|
|
} else {
|
|
prop = node.property.name;
|
|
}
|
|
return object[prop];
|
|
}
|
|
|
|
if (t.isNewExpression(node)) {
|
|
const callee = this.eval(node.callee, ctx);
|
|
const args = node.arguments.map(arg => this.eval(arg, ctx));
|
|
const result = new callee(...args);
|
|
return result
|
|
}
|
|
|
|
if (t.isWhileStatement(node)) {
|
|
const { test, body } = node;
|
|
let result;
|
|
while(this.callStack[this.callStack.length - 1] === ctx && this.eval(test, ctx)) {
|
|
result = this.eval(body, ctx);
|
|
if (this.flags.continue) {
|
|
this.flags.continue = false;
|
|
}
|
|
if (this.flags.break) {
|
|
this.flags.break = false;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
if (t.isForStatement(node)) {
|
|
const { init, test, body } = node;
|
|
let result;
|
|
if (node.init) this.eval(init, ctx);
|
|
while(this.callStack[this.callStack.length - 1] === ctx && (test ? this.eval(test, ctx) : 1)) {
|
|
result = this.eval(body, ctx);
|
|
if (this.flags.continue) {
|
|
this.flags.continue = false;
|
|
}
|
|
if (this.flags.break) {
|
|
this.flags.break = false;
|
|
break;
|
|
}
|
|
if (node.update) {
|
|
this.eval(node.update, ctx);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
if (t.isDoWhileStatement(node)) {
|
|
const { test, body } = node;
|
|
let result;
|
|
do {
|
|
result = this.eval(body, ctx);
|
|
if (this.flags.continue) {
|
|
this.flags.continue = false;
|
|
}
|
|
if (this.flags.break) {
|
|
this.flags.break = false;
|
|
break;
|
|
}
|
|
} while(this.callStack[this.callStack.length - 1] === ctx && this.eval(test, ctx));
|
|
return result;
|
|
}
|
|
|
|
if (t.isContinueStatement(node)) {
|
|
this.flags.continue = true;
|
|
return;
|
|
}
|
|
|
|
if (t.isBreakStatement(node)) {
|
|
this.flags.break = true;
|
|
return;
|
|
}
|
|
|
|
if (t.isThrowStatement(node)) {
|
|
throw this.eval(node.argument, ctx);
|
|
}
|
|
|
|
if (t.isTryStatement(node)) {
|
|
let result;
|
|
try {
|
|
result = this.eval(node.block, ctx);
|
|
} catch(e) {
|
|
console.debug('debug:', e);
|
|
const paramName = node.handler.param.name;
|
|
ctx.env.define(paramName, e);
|
|
result = this.eval(node.handler.body, ctx);
|
|
}
|
|
if (node.finalizer) {
|
|
return this.eval(node.finalizer, ctx)
|
|
}
|
|
return result;
|
|
}
|
|
|
|
if (t.isForInStatement(node)) {
|
|
const object = this.eval(node.right, ctx);
|
|
const varName = node.left.declarations[0].id.name;
|
|
for (var key in object) {
|
|
ctx.env.define(varName, key);
|
|
this.eval(node.body, ctx);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (t.isSwitchStatement(node)) {
|
|
const test = this.eval(node.discriminant, ctx);
|
|
let result;
|
|
for (let i = 0; i < node.cases.length; ++i) {
|
|
const caseClause = node.cases[i];
|
|
if (
|
|
caseClause.test !== null &&
|
|
this.eval(caseClause.test, ctx) === test
|
|
) {
|
|
result = this._evalCaseClause(caseClause, ctx);
|
|
if (this.flags.break === true) {
|
|
this.flags.break = false;
|
|
}
|
|
break; // break из текущего цикла for
|
|
} else if (caseClause.test === null) { // ветка default
|
|
result = this._evalCaseClause(caseClause, ctx);
|
|
if (this.flags.break === true) {
|
|
this.flags.break = false;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
throw `Unimplemented ${node.type} node`;
|
|
}
|
|
|
|
_hoistVariables(block, ctx) {
|
|
block.body.forEach(stmt => {
|
|
if (t.isFunctionDeclaration(stmt)) {
|
|
this.eval(stmt, ctx)
|
|
}
|
|
|
|
if (t.isVariableDeclaration(stmt)) {
|
|
for (const variableDeclarator of stmt.declarations) {
|
|
const name = variableDeclarator.id.name;
|
|
ctx.env.define(name, undefined);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
_evalFunctionBlock(block, ctx) {
|
|
this._hoistVariables(block, ctx);
|
|
let result;
|
|
for (let s = 0; s < block.body.length; ++s) {
|
|
const stmt = block.body[s];
|
|
result = this.eval(stmt, ctx);
|
|
if (this.callStack[this.callStack.length - 1] !== ctx) {
|
|
return result;
|
|
}
|
|
}
|
|
this.callStack.pop();
|
|
return result;
|
|
}
|
|
|
|
_evalCaseClause(caseClause, ctx) {
|
|
let result;
|
|
for (let i = 0; i < caseClause.consequent.length; ++i) {
|
|
const stmt = caseClause.consequent[i];
|
|
result = this.eval(stmt, ctx);
|
|
// switch-case мог быть внутри функции,
|
|
// поэтому мог наткнуться на return
|
|
if (this.callStack[this.callStack.length - 1] !== ctx) {
|
|
if (this.flags.break === true) {
|
|
this.flags.break = false;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
module.exports = Interpreter;
|