Restringer
Deobfuscate Javascript and reconstruct strings.
Simplify cumbersome logic where possible while adhering to scope limitations.
Try it online @ restringer.tech.
For comments and suggestions feel free to open an issue or find me on Twitter - @ctrl__esc
Table of Contents
Installation
npm
npm install restringer
Clone The Repo
Requires Node 16 or newer.
git clone git@github.com:PerimeterX/restringer.git
cd restringer
npm install
Usage
The restringer.js uses generic deobfuscation methods that reconstruct and restore obfuscated strings and simplifies redundant logic meant only to encumber.
REstringer employs the Obfuscation Detector to identify specific types of obfuscation for which
there's a need to apply specific deobfuscation methods in order to circumvent anti-debugging mechanisms or other code traps
preventing the script from being deobfuscated.
Command-Line Usage
Usage: restringer input_filename [-h] [-c] [-q | -v] [-m M] [-o [output_filename]]
positional arguments:
input_filename The obfuscated JS file
optional arguments:
-h, --help Show this help message and exit.
-c, --clean Remove dead nodes from script after deobfuscation is complete (unsafe).
-q, --quiet Suppress output to stdout. Output result only to stdout if the -o option is not set.
Does not go with the -v option.
-m, --max-iterations M Run at most M iterations
-v, --verbose Show more debug messages while deobfuscating. Does not go with the -q option.
-o, --output [output_filename] Write deobfuscated script to output_filename.
Use <input_filename>-deob.js if no filename is provided.
Use as a Module
const {REstringer} = require('restringer');
const restringer = new REstringer('"RE" + "stringer"');
if (restringer.deobfuscate()) {
console.log(restringer.script);
} else {
console.log('Nothing was deobfuscated :/');
}
Create Custom Deobfuscators
REstringer is highly modularized. It exposes modules that allow creating custom deobfuscators
that can solve specific problems.
The basic structure of such a deobfuscator would be an array of deobfuscation modules
(either safe or unsafe), run via flAST's applyIteratively utility function.
Unsafe modules run code through eval
(using isolated-vm to be on the safe side) while safe modules do not.
const {
safe: {normalizeComputed},
unsafe: {resolveDefiniteBinaryExpressions, resolveLocalCalls},
} = require('restringer').deobModules;
const {applyIteratively} = require('flast').utils;
let script = 'obfuscated JS here';
const deobModules = [
resolveDefiniteBinaryExpressions,
resolveLocalCalls,
normalizeComputed,
];
script = applyIteratively(script, deobModules);
console.log(script);
With the additional candidateFilter
function argument, it's possible to narrow down the targeted nodes:
const {
unsafe: {resolveLocalCalls},
} = require('restringer').deobModules;
const {applyIteratively} = require('flast').utils;
let script = 'obfuscated JS here';
function resolveLocalCallsInGlobalScope(arb) {
return resolveLocalCalls(arb, n => n.parentNode?.type === 'Program');
}
script = applyIteratively(script, [resolveLocalCallsInGlobalScope]);
console.log(script);
You can also customize any deobfuscation method while still using REstringer without running the loop yourself:
const fs = require('node:fs');
const {REstringer} = require('restringer');
const inputFilename = process.argv[2];
const code = fs.readFileSync(inputFilename, 'utf-8');
const res = new REstringer(code);
res.detectObfuscationType = false;
const targetFunc = res.unsafeMethods.find(m => m.name === 'resolveLocalCalls');
let changes = 0;
res.safeMethods[res.unsafeMethods.indexOf(targetFunc)] = function customResolveLocalCalls(n) {return targetFunc(n, () => changes++ < 5)}
res.deobfuscate();
if (res.script !== code) {
console.log('[+] Deob successful');
fs.writeFileSync(`${inputFilename}-deob.js`, res.script, 'utf-8');
} else console.log('[-] Nothing deobfuscated :/');
Boilerplate code for starting from scratch
const {logger, applyIteratively, treeModifier} = require('flast').utils;
const code = `(function() {
function createMessage() {return 'Hello' + ' ' + 'there!';}
function print(msg) {console.log(msg);}
print(createMessage());
})();`;
logger.setLogLevelDebug();
let script = code;
const f = n => n.type === 'Literal' && replacements[n.value];
const m = (n, arb) => arb.markNode(n, {
type: 'Literal',
value: replacements[n.value],
});
const swc = treeModifier(f, m, 'StarWarsChanger');
script = applyIteratively(script, [swc]);
if (code !== script) {
console.log(script);
} else console.log(`No changes`);
Read More