Skip to content

Instantly share code, notes, and snippets.

@tayler-king
Created February 24, 2018 20:28
Show Gist options
  • Save tayler-king/d42f64712e1fbc28c806a07aaff109ae to your computer and use it in GitHub Desktop.
Save tayler-king/d42f64712e1fbc28c806a07aaff109ae to your computer and use it in GitHub Desktop.
const minimist = require('minimist');
const beautify = require('js-beautify').js_beautify;
const fs = require('fs');
const randomWord = require('random-word');
const path = require('path');
var marked = require('marked');
var TerminalRenderer = require('marked-terminal');
marked.setOptions({
// Define custom renderer
renderer: new TerminalRenderer()
});
const args = minimist(process.argv.slice(2));
if(!args.i)
return console.log('Please enter an input file');
const baseLocation = args.i.split('.js')[0];
const inputLocation = path.join(__dirname, baseLocation + '.js');
const outputLocation = path.join(__dirname, baseLocation + '.unpacked.js');
if(!fs.existsSync(inputLocation))
return console.log('Please enter a valid file to decompile');
const inputFile = fs.readFileSync(inputLocation).toString();
const beautified = beautify(inputFile, { indent_size: 4 });
let lines = beautified.split('\n');
const constants = {
variables: lines[0].match(/(_[0-9]x[0-9a-z]*)/)[0],
shift: lines[8].match(/(0x[0-9a-f]*)\)/)[1],
keys: {}
}
const partitions = {
variables: JSON.parse(lines[0].slice(lines[0].indexOf('['), lines[0].indexOf(']') + 1).replace(new RegExp('\'', 'g'), '"')),
variableFixer: lines.slice(1, 8).concat(`}(partitions.variables, ${constants.shift}));`).join('\n'),
decoder: {},
payload: {}
};
for(let i = 9; i < lines.length; i++) {
if(lines[i] == '};') {
partitions.decoder = lines.slice(9, i + 1).join('\n');
partitions.payload = lines.slice(i + 1).join('\n');
break;
}
}
constants.variableFn = partitions.decoder.split('\n')[0].match(/_0x([0-9a-f]*)/)[0];
partitions.decoder = partitions.decoder.replace(new RegExp(constants.variableFn, 'g'), 'decoder').replace(new RegExp(constants.variables, 'g'), 'partitions.variables');
eval(`${partitions.variableFixer}`);
eval(`${partitions.decoder}`);
function toHex(decimal) {
return "0x" + (Number(decimal).toString(16)).slice(-2);
}
// Replace garbled variable functions with their value
partitions.payload = partitions.payload.replace(new RegExp(`${constants.variableFn}\\('(0x[0-9a-f]*)'\\)`, 'g'), (capture, match) => {
return `'${decoder(match)}'`;
});
// Replace square brackets with contents: var['test'] => var.test
partitions.payload = partitions.payload.replace(/\['([A-Za-z]+)'\]/g, (capture, match) => {
//console.log(capture, match);
return `.${match}`;
});
let payloadLines = partitions.payload.split('\n');
let objects = {};
for(let i = 0; i < payloadLines.length; i++) {
const line = payloadLines[i];
const matches = /(var|const|let) (_\w+) = \{/g.exec(line);
if(!matches)
continue;
if(line[line.length - 2] == '}')
continue;
const [ capture, token, variable ] = matches;
let from = i + 0;
let to = 0;
for(let x = from; x < payloadLines.length; x++) {
const line = payloadLines[x];
if(line.replace(/\s/g, '') !== '};')
continue;
to = x + 1;
break;
}
let payload = payloadLines.slice(from, to);
// Replace apostophes with quotation marks (only start and end of strings)
// Essentially means we could run JSON.parse
payload = payload.map(line => {
line = line.replace(/'(\w+)'/g, (capture, match) => {
return `"${match}"`;
});
line = line.replace(/'(.*)'/, (capture, match) => {
return `"${match}"`;
});
return line;
}).join('\n');
payload = payload.replace(/(_0x\d+\w+)\.(\w+)/g, (capture, variable, property) => {
if(!objects.hasOwnProperty(variable))
return capture;
const value = objects[variable][property];
if(!value)
return capture;
if(typeof value == 'function')
return capture;
return `"${value}"`;
});
let template = `
(function context() {
${payload}
return ${variable};
})();
`;
try {
const result = function(payload) {
return eval(payload);
}.call({}, template);
objects[variable] = result;
} catch (ex) {
console.log('Failed to decode object');
}
}
const replacers = [];
partitions.payload = partitions.payload.replace(/(_0x\d+\w+)\.(\w+)/g, (capture, variable, property) => {
if(!objects.hasOwnProperty(variable))
return capture;
const value = objects[variable][property];
if(!value)
return capture;
const lines = partitions.payload.split('\n');
if(typeof value == 'function') {
let fnType = false;
for(let i = 0; i < lines.length; i++) {
const line = lines[i];
if(!line.includes(property))
continue;
let start = i;
let end = 0;
for(let x = i; x < lines.length; x++) {
if(lines[x].includes('}')) {
end = x + 1;
i = x;
break;
}
}
if((end - start) !== 3)
continue;
const [ _, func ] = lines.slice(start, end);
const match = (/return (_0x\d\w*) (.*) (_0x\d\w*);/.exec(func));
if(!match || match.length !== 4)
continue;
fnType = match[2]
if(end)
break;
}
let fnLine = partitions.payload.split('\n').filter(line => line.includes(property))[1] || false;
let exec = new RegExp(`${variable}\\.${property}\\(([^,]*), ([^,]*)\\)`).exec(fnLine);
if(!fnLine || !fnType)
return capture;
if(!exec || exec.length !== 3)
return capture;
let [ leftVar, rightVar ] = exec.slice(1);
rightVar = rightVar.substr(0, rightVar.length - 1);
replacers.push({ replace: `${variable}.${property}(${leftVar}, ${rightVar})`, with: `${leftVar} ${fnType} ${rightVar}` });
return capture;
}
return `"${value}"`;
});
// This can't find most replacers so we gotta fix that..
replacers.forEach(replacer => {
const from = replacer.replace.replace(/\s+/g, " ").trim();
const to = replacer.with.replace(/\s+/g, " ").trim();
partitions.payload = partitions.payload.replace(from, to);
});
console.log(marked([ '```js', partitions.payload, '```'].join('\n')));
fs.writeFile(outputLocation, partitions.payload, err => {
if(err)
return console.error(err);
console.log(`Output written to ${outputLocation}`);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment