Created
February 24, 2018 20:28
-
-
Save tayler-king/d42f64712e1fbc28c806a07aaff109ae to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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