https://github.com/perimeterx/restringer
A Javascript Deobfuscator
https://github.com/perimeterx/restringer
Last synced: 8 months ago
JSON representation
A Javascript Deobfuscator
- Host: GitHub
- URL: https://github.com/perimeterx/restringer
- Owner: PerimeterX
- License: mit
- Created: 2022-07-20T18:04:34.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2024-12-25T14:11:09.000Z (about 1 year ago)
- Last Synced: 2025-05-15T04:04:14.710Z (8 months ago)
- Language: JavaScript
- Homepage:
- Size: 657 KB
- Stars: 428
- Watchers: 11
- Forks: 42
- Open Issues: 15
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# Restringer
[](https://github.com/PerimeterX/restringer/actions/workflows/node.js.yml)
[](https://www.npmjs.com/package/restringer)
Deobfuscate Javascript and reconstruct strings.
Simplify cumbersome logic where possible while adhering to scope limitations.
Try it online @ [restringer.tech](https://restringer.tech).
For comments and suggestions feel free to open an issue or find me on Twitter - [@ctrl__esc](https://twitter.com/ctrl__esc)
## Table of Contents
* [Installation](#installation)
* [npm](#npm)
* [Clone The Repo](#clone-the-repo)
* [Usage](#usage)
* [Command-Line Usage](#command-line-usage)
* [Use as a Module](#use-as-a-module)
* [Create Custom Deobfuscators](#create-custom-deobfuscators)
* [Boilerplate Code for Starting from Scratch](#boilerplate-code-for-starting-from-scratch)
* [Read More](#read-more)
***
## Installation
### npm
```shell
npm install -g restringer
```
### Clone The Repo
Requires Node 16 or newer.
```shell
git clone git@github.com:PerimeterX/restringer.git
cd restringer
npm install
```
***
## Usage
The [restringer.js](src/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](https://github.com/PerimeterX/obfuscation-detector/blob/main/README.md) 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.
-deob.js is used if no filename is provided.
```
Examples:
- Print the deobfuscated script to stdout.
```shell
restringer [target-file.js]
```
- Save the deobfuscated script to output.js.
```shell
restringer [target-file.js] -o output.js
```
- Deobfuscate and print debug info.
```shell
restringer [target-file.js] -v
```
- Deobfuscate without printing anything but the deobfuscated output.
```shell
restringer [target-file.js] -q
```
### Use as a Module
```javascript
import {REstringer} from 'restringer';
const restringer = new REstringer('"RE" + "stringer"');
if (restringer.deobfuscate()) {
console.log(restringer.script);
} else {
console.log('Nothing was deobfuscated :/');
}
// Output: 'REstringer';
```
***
## 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](src/modules/safe) or [unsafe](src/modules/unsafe)), run via flAST's applyIteratively utility function.
Unsafe modules run code through `eval` (using [isolated-vm](https://www.npmjs.com/package/isolated-vm) to be on the safe side) while safe modules do not.
```javascript
import {applyIteratively} from 'flast';
import {safe, unsafe} from 'restringer';
const {normalizeComputed} = safe;
const {resolveDefiniteBinaryExpressions, resolveLocalCalls} = unsafe;
let script = 'obfuscated JS here';
const deobModules = [
resolveDefiniteBinaryExpressions,
resolveLocalCalls,
normalizeComputed,
];
script = applyIteratively(script, deobModules);
console.log(script); // Deobfuscated script
```
With the additional `candidateFilter` function argument, it's possible to narrow down the targeted nodes:
```javascript
import {unsafe} from 'restringer';
const {resolveLocalCalls} = unsafe;
import {applyIteratively} from 'flast';
let script = 'obfuscated JS here';
// It's better to define a function with a meaningful name that can show up in the log
function resolveLocalCallsInGlobalScope(arb) {
return resolveLocalCalls(arb, n => n.parentNode?.type === 'Program');
}
script = applyIteratively(script, [resolveLocalCallsInGlobalScope]);
console.log(script); // Deobfuscated script
```
You can also customize any deobfuscation method while still using REstringer without running the loop yourself:
```javascript
import fs from 'node:fs';
import {REstringer} from 'restringer';
const inputFilename = process.argv[2];
const code = fs.readFileSync(inputFilename, 'utf-8');
const res = new REstringer(code);
// res.logger.setLogLevelDebug();
res.detectObfuscationType = false; // Skip obfuscation type detection, including any pre and post processors
const targetFunc = res.unsafeMethods.find(m => m.name === 'resolveLocalCalls');
let changes = 0; // Resolve only the first 5 calls
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
```javascript
import {applyIteratively, logger} from 'flast';
// Optional loading from file
// import fs from 'node:fs';
// const inputFilename = process.argv[2] || 'target.js';
// const code = fs.readFileSync(inputFilename, 'utf-8');
const code = `(function() {
function createMessage() {return 'Hello' + ' ' + 'there!';}
function print(msg) {console.log(msg);}
print(createMessage());
})();`;
logger.setLogLevelDebug();
/**
* Replace specific strings with other strings
* @param {Arborist} arb
* @return {Arborist}
*/
function replaceSpecificLiterals(arb) {
const replacements = {
'Hello': 'General',
'there!': 'Kenobi!',
};
// Iterate over only the relevant nodes by targeting specific types using the typeMap property on the root node
const relevantNodes = [
...(arb.ast[0].typeMap.Literal || []),
// ...(arb.ast.typeMap.TemplateLiteral || []), // unnecessary for this example, but this is how to add more types
];
for (const n of relevantNodes) {
if (replacements[n.value]) {
// dynamically define a replacement node by creating an object with a type and value properties
// markNode(n) would delete the node, while markNode(n, {...}) would replace the node with the supplied node.
arb.markNode(n, {type: 'Literal', value: replacements[n.value]});
}
}
return arb;
}
let script = code;
script = applyIteratively(script, [
replaceSpecificLiterals,
]);
if (code !== script) {
console.log(script);
// fs.writeFileSync(inputFilename + '-deob.js', script, 'utf-8');
} else console.log(`No changes`);
```
***
## Read More
* [Processors](src/processors/README.md)
* [Contribution guide](CONTRIBUTING.md)
* [Obfuscation Detector](https://github.com/PerimeterX/obfuscation-detector/blob/main/README.md)
* [flAST](https://github.com/PerimeterX/flast/blob/main/README.md)