Skip to content

Instantly share code, notes, and snippets.

@theseanl
Last active March 15, 2021 05:57
Show Gist options
  • Save theseanl/bd3184a2d4f9e920f18439befd3a910b to your computer and use it in GitHub Desktop.
Save theseanl/bd3184a2d4f9e920f18439befd3a910b to your computer and use it in GitHub Desktop.
Sandboxed eval
var esprima = require('esprima');
var escodegen = require('escodegen');
var estraverse = require('estraverse');
var comm = require('./comm.js');
var SandboxEval = require('./sandboxeval.js');
/* Temporary, syntax should be improved */
deobfuscator = (function(){
var fnLiterals = [], seval;
var validateInput = function(expr) {
var len = expr.body.length;
if(expr.body[len - 1].type == "ExpressionStatement" &&
expr.body[len - 1].expression.type == "ArrayExpression") {
expr.body[len - 1].expression["elements"].forEach(function(el) {
if(!el.value) {
return false;
}
fnLiterals.push(el.value);
});
return true;
}
else {
return false;
}
};
var condition = function(node) {
if(node.type == "CallExpression" &&
node.callee &&
node.callee.type == "Identifier") {
var index = fnLiterals.indexOf(node.callee.name);
return index == -1 ? false : index;
}
};
var replace = function(node) {
// This part is a pseudocode, have not been tested
var codeToEval = escodegen.generate(node);
var result = seval.getResult(codeToEval);
return {
type: "Literal",
value: result
};
};
var deobfuscate = function(inputvar, code) {
var inputExpr = esprima.parse(inputvar);
if(!validateInput(inputExpr)) {
comm.throwError("Unexpected input.");
return false;
}
seval = new SandboxEval();
seval.getResult(inputvar); // declare functions in the iframe's scope, we don't expect any return value.
var codeExpr = esprima.parse(code);
codeExpr = estraverse.replace(codeExpr, {
enter: function(node) {
var i = condition(node);
if( i !== false ) {
return replace(node);
}
}
});
return escodegen.generate(comm.concatStrings(codeExpr));
};
return {
deobfuscate: deobfuscate
};
})();
module.exports = deobfuscator;
/**
* Calls eval in a sandboxed iframe.
* Usage: var seval = new SandboxEval(window.location.href);
* seval.getResult(code);
*
* @constructor
*/
var SandboxEval = function () {
// The below function is a setup to run in an iframe. Receives messages, evaluates it, and pass back the result.
function receiveAndPassBack(){
function evalScript(evt){
//Check that the message came from where we expect.
if(evt.origin != "%ORIGIN%") {
return;
}
try{
parent.postMessage({"success": 1, "result":_eval(evt.data)}, "%ORIGIN%");
} catch(error) {
parent.postMessage({"success":0, "error": error}, "%ORIGIN%");
}
}
// Store eval in case of bad situations.
_eval = window.eval;
window.addEventListener("message", evalScript);
}
// Create an iframe.
// Can't use contentWindow.document.write in a sandboxed iframe,
// so using data:text/html.
var sandbox = document.createElement('iframe');
sandbox.id ='sandbox';
sandbox.sandbox ='allow-scripts'; //sandboxing
var html = "\x3Cscript>(" + receiveAndPassBack.toString().replace(/"%ORIGIN%"/g, '"' + window.location.origin + '"') + ")();\x3C/script>";
sandbox.src = 'data:text/html;charset=utf-8,' + encodeURI(html);
document.body.appendChild(sandbox);
// register a local variable that we store the returned result.
var result;
function returnResult(evt) {
if(evt.success == 1) {
resolve(evt.data.result);
}
else if(evt.sucess == 0) {
reject(evt.data.error);
}
else {
reject(Error("Internal error."));
}
}
this.getResult = function(code) {
result = undefined;
(new Promise(function(resolve, reject) {
window.addEventListener("message", returnResult, false);
sandbox.contentWindow.postMessage(code, "*");
})).then(/* some code goes here.. */)
window.addEventListener("message", returnResult, false);
sandbox.contentWindow.postMessage(code, "*");
return result;
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment