JS Confuser
JS-Confuser is a JavaScript obfuscation tool to make your programs impossible to read. Try the web version.
Key features
Presets
JS-Confuser comes with three presets built into the obfuscator.
Preset | Transforms | Performance Reduction | Sample |
---|
High | 21/22 | 98% | Sample |
Medium | 15/22 | 52% | Sample |
Low | 10/22 | 30% | Sample |
You can extend each preset or all go without them entirely.
Installation
$ npm install js-confuser
Usage
var JsConfuser = require("js-confuser");
JsConfuser.obfuscate(`
function fibonacci(num){
var a = 0, b = 1, c = num;
while (num-- > 1) {
c = a + b;
a = b;
b = c;
}
return c;
}
for ( var i = 1; i <= 25; i++ ) {
console.log(i, fibonacci(i))
}
`, {
target: "node",
preset: "high",
stringEncoding: false,
}).then(obfuscated => {
console.log(obfuscated)
})
CLI Usage
<coming soon>
obfuscate(sourceCode, options)
Obfuscates the sourceCode
. Returns a Promise
that resolves to a string of the obfuscated code.
Parameter | Type | Description |
---|
sourceCode | string | The JavaScript code to be obfuscated. |
options | object | The obfuscator settings. |
obfuscateAST(AST, options)
Obfuscates an ESTree compliant AST. Returns a Promise
.
Note: Mutates the object.
Parameter | Type | Description |
---|
AST | object | The ESTree compliant AST. This object will be mutated. |
options | object | The obfuscator settings. |
Options
target
The execution context for your output. Required.
"node"
"browser"
preset
JS-Confuser comes with three presets built into the obfuscator. Optional. ("high"/"medium"/"low"
)
var JsConfuser = require('js-confuser');
JsConfuser.obfuscate(`<source code>`, {
target: "node",
preset: "high"
}).then(obfuscated=>{
console.log(obfuscated)
})
compact
Remove's whitespace from the final output. Enabled by default. (true/false
)
hexadecimalNumbers
Uses the hexadecimal representation (50
-> 0x32
) for numbers. (true/false
)
minify
Minifies redundant code. (true/false
)
es5
Converts output to ES5-compatible code. (true/false
)
Does not cover all cases such as Promises or Generator functions. Use Babel.
renameVariables
Determines if variables should be renamed. (true/false
)
- Potency High
- Resilience High
- Cost Medium
renameGlobals
Renames top-level variables, turn this off for web-related scripts. Enabled by default. (true/false
)
identifierGenerator
Determines how variables are renamed.
Mode | Description | Example |
---|
"hexadecimal" | Random hex strings | _0xa8db5 |
"randomized" | Random characters | w$Tsu4G |
"zeroWidth" | Invisible characters | U+200D |
"mangled" | Alphabet sequence | a, b, c |
"number" | Numbered sequence | var_1, var_2 |
<function> | Write a custom name generator | See Below |
JsConfuser.obfuscate(code, {
target: "node",
renameVariables: true,
identifierGenerator: function () {
return "$" + Math.random().toString(36).substring(7);
},
});
var counter = 0;
JsConfuser.obfuscate(code, {
target: "node",
renameVariables: true,
identifierGenerator: function () {
return "var_" + (counter++);
},
});
JSConfuser tries to reuse names when possible, creating very potent code.
nameRecycling
⚠️ Experimental feature, may break your code!
Attempts to reuse released names.
- Potency Medium
- Resilience High
- Cost Low
function percentage(x) {
var multiplied = x * 100;
var floored = Math.floor(multiplied);
var output = floored + "%"
return output;
}
function percentage(x) {
var multiplied = x * 100;
var floored = Math.floor(multiplied);
multiplied = floored + "%";
return multiplied;
}
controlFlowFlattening
⚠️ Significantly impacts performance, use sparingly!
Control-flow Flattening hinders program comprehension by creating convoluted switch statements. (true/false/0-1
)
Use a number to control the percentage from 0 to 1.
- Potency High
- Resilience High
- Cost High
globalConcealing
Global Concealing hides global variables being accessed. (true/false
)
- Potency Medium
- Resilience High
- Cost Low
stringCompression
String Compression uses LZW's compression algorithm to compress strings. (true/false/0-1
)
"console"
-> inflate('replaĕ!ğğuģģ<~@')
- Potency High
- Resilience Medium
- Cost Medium
stringConcealing
String Concealing involves encoding strings to conceal plain-text values. (true/false/0-1
)
Use a number to control the percentage of strings.
"console"
-> decrypt('<~@rH7+Dert~>')
- Potency High
- Resilience Medium
- Cost Medium
stringEncoding
String Encoding transforms a string into an encoded representation. (true/false/0-1
)
Use a number to control the percentage of strings.
"console"
-> '\x63\x6f\x6e\x73\x6f\x6c\x65'
- Potency Low
- Resilience Low
- Cost Low
stringSplitting
String Splitting splits your strings into multiple expressions. (true/false/0-1
)
Use a number to control the percentage of strings.
"console"
-> String.fromCharCode(99) + 'ons' + 'ole'
- Potency Medium
- Resilience Medium
- Cost Medium
duplicateLiteralsRemoval
Duplicate Literals Removal replaces duplicate literals with a single variable name. (true/false
)
- Potency Medium
- Resilience Low
- Cost High
dispatcher
Creates a middleman function to process function calls. (true/false/0-1
)
- Potency Medium
- Resilience Medium
- Cost High
eval
Security Warning
From MDN: Executing JavaScript from a string is an enormous security risk. It is far too easy for a bad actor to run arbitrary code when you use eval(). Never use eval()!
Wraps defined functions within eval statements.
false
- Avoids using the eval
function. Default.true
- Wraps function's code into an eval
statement.
var Q4r1__ = {
Oo$Oz8t: eval(
"(function(YjVpAp){var gniSBq6=kHmsJrhOO;switch(gniSBq6){case'RW11Hj5x':return console;}});"
),
};
Q4r1__.Oo$Oz8t("RW11Hj5x");
rgf
RGF (Runtime-Generated-Functions) uses the new Function(code...)
syntax to construct executable code from strings. ("all"/true/false
)
- This can break your code. This is also as dangerous as
eval
. - Due to the security concerns of arbitrary code execution, you must enable this yourself.
- The arbitrary code is also obfuscated.
Mode | Description |
---|
"all" | Recursively applies to every scope (slow) |
true | Applies to the top level only |
false | Feature disabled |
function log(x){
console.log(x)
}
log("Hello World")
var C6z0jyO=[new Function('a2Fjjl',"function OqNW8x(OqNW8x){console['log'](OqNW8x)}return OqNW8x(...Array.prototype.slice.call(arguments,1))")];(function(){return C6z0jyO[0](C6z0jyO,...arguments)}('Hello World'))
Extracts object properties into separate variables. (true/false
)
- Potency Medium
- Resilience Medium
- Cost Low
var utils = {
isString: x=>typeof x === "string",
isBoolean: x=>typeof x === "boolean"
}
if ( utils.isString("Hello") ) {
}
var utils_isString = x=>typeof x === "string";
var utils_isBoolean = x=>typeof x === "boolean";
if ( utils_isString("Hello") ) {
}
flatten
Brings independent declarations to the highest scope. (true/false
)
- Potency Medium
- Resilience Medium
- Cost High
deadCode
Randomly injects dead code. (true/false/0-1
)
Use a number to control the percentage from 0 to 1.
- Potency Medium
- Resilience Medium
- Cost Low
calculator
Creates a calculator function to handle arithmetic and logical expressions. (true/false/0-1
)
- Potency Medium
- Resilience Medium
- Cost Low
lock.antiDebug
Adds debugger
statements throughout the code. Additionally adds a background function for DevTools detection. (true/false/0-1
)
lock.context
Properties that must be present on the window
object (or global
for NodeJS). (string[]
)
lock.startDate
When the program is first able to be used. (number
or Date
)
Number should be in milliseconds.
- Potency Low
- Resilience Medium
- Cost Medium
lock.endDate
When the program is no longer able to be used. (number
or Date
)
Number should be in milliseconds.
- Potency Low
- Resilience Medium
- Cost Medium
lock.domainLock
Array of regex strings that the window.location.href
must follow. (Regex[]
or string[]
)
- Potency Low
- Resilience Medium
- Cost Medium
lock.osLock
Array of operating-systems where the script is allowed to run. (string[]
)
- Potency Low
- Resilience Medium
- Cost Medium
Allowed values: "linux"
, "windows"
, "osx"
, "android"
, "ios"
Example: ["linux", "windows"]
lock.browserLock
Array of browsers where the script is allowed to run. (string[]
)
- Potency Low
- Resilience Medium
- Cost Medium
Allowed values: "firefox"
, "chrome"
, "iexplorer"
, "edge"
, "safari"
, "opera"
Example: ["firefox", "chrome"]
lock.nativeFunctions
Set of global functions that are native. Such as require
, fetch
. If these variables are modified the program crashes.
Set to true
to use the default set of native functions. (string[]/true/false
)
- Potency Low
- Resilience Medium
- Cost Medium
lock.integrity
Integrity ensures the source code is unchanged. (true/false/0-1
)
Learn more here.
- Potency Medium
- Resilience High
- Cost High
lock.countermeasures
A custom callback function to invoke when a lock is triggered. (string/false
)
Learn more about the countermeasures function.
Otherwise, the obfuscator falls back to crashing the process.
movedDeclarations
Moves variable declarations to the top of the context. (true/false
)
- Potency Medium
- Resilience Medium
- Cost Low
opaquePredicates
An Opaque Predicate is a predicate(true/false) that is evaluated at runtime, this can confuse reverse engineers from understanding your code. (true/false/0-1
)
- Potency Medium
- Resilience Medium
- Cost Low
shuffle
Shuffles the initial order of arrays. The order is brought back to the original during runtime. ("hash"/true/false/0-1
)
- Potency Medium
- Resilience Low
- Cost Low
Mode | Description |
---|
"hash" | Array is shifted based on hash of the elements |
true | Arrays are shifted n elements, unshifted at runtime |
false | Feature disabled |
stack
Local variables are consolidated into a rotating array. (true/false/0-1
)
Similar to Jscrambler's Variable Masking
- Potency Medium
- Resilience Medium
- Cost Low
function add3(x, y, z){
return x + y + z;
}
function iVQoGQD(...iVQoGQD){
~(iVQoGQD.length = 3, iVQoGQD[215] = iVQoGQD[2], iVQoGQD[75] = 227, iVQoGQD[iVQoGQD[75] - (iVQoGQD[75] - 75)] = iVQoGQD[75] - (iVQoGQD[75] - 239), iVQoGQD[iVQoGQD[iVQoGQD[75] - 164] - 127] = iVQoGQD[iVQoGQD[75] - 238], iVQoGQD[iVQoGQD[75] - 104] = iVQoGQD[75] - 482, iVQoGQD[iVQoGQD[135] + 378] = iVQoGQD[iVQoGQD[135] + 318] - 335, iVQoGQD[21] = iVQoGQD[iVQoGQD[135] + 96], iVQoGQD[iVQoGQD[iVQoGQD[75] - 104] - (iVQoGQD[75] - 502)] = iVQoGQD[iVQoGQD[75] - 164] - 440);
return iVQoGQD[75] > iVQoGQD[75] + 90 ? iVQoGQD[iVQoGQD[135] - (iVQoGQD[135] + 54)] : iVQoGQD[iVQoGQD[135] + 117] + iVQoGQD[iVQoGQD[iVQoGQD[75] - (iVQoGQD[75] - (iVQoGQD[75] - 104))] - (iVQoGQD[135] - 112)] + iVQoGQD[215];
};
High preset
{
target: "node",
preset: "high",
calculator: true,
compact: true,
hexadecimalNumbers: true,
controlFlowFlattening: 0.75,
deadCode: 0.2,
dispatcher: true,
duplicateLiteralsRemoval: 0.75,
flatten: true,
globalConcealing: true,
identifierGenerator: "randomized",
minify: true,
movedDeclarations: true,
objectExtraction: true,
opaquePredicates: 0.75,
renameVariables: true,
renameGlobals: true,
shuffle: { hash: 0.5, true: 0.5 },
stack: true,
stringConcealing: true,
stringCompression: true,
stringEncoding: true,
stringSplitting: 0.75,
eval: false,
rgf: false
}
Medium preset
{
target: "node",
preset: "medium",
calculator: true,
compact: true,
hexadecimalNumbers: true,
controlFlowFlattening: 0.5,
deadCode: 0.025,
dispatcher: 0.75,
duplicateLiteralsRemoval: 0.5,
globalConcealing: true,
identifierGenerator: "randomized",
minify: true,
movedDeclarations: true,
objectExtraction: true,
opaquePredicates: 0.5,
renameVariables: true,
renameGlobals: true,
shuffle: true,
stack: 0.5,
stringConcealing: true,
stringSplitting: 0.25
}
Low Preset
{
target: "node",
preset: "low",
calculator: true,
compact: true,
hexadecimalNumbers: true,
controlFlowFlattening: 0.25,
deadCode: 0.01,
dispatcher: 0.5,
duplicateLiteralsRemoval: true,
identifierGenerator: "randomized",
minify: true,
movedDeclarations: true,
objectExtraction: true,
opaquePredicates: 0.1,
renameVariables: true,
renameGlobals: true,
stringConcealing: true
}
Locks
You must enable locks yourself, and configure them to your needs.
{
target: "node",
lock: {
integrity: true,
domainLock: ["mywebsite.com"],
osLock: ["windows", "linux"],
browserLock: ["firefox"],
startDate: new Date("Feb 1 2021"),
endDate: new Date("Mar 1 2021"),
antiDebug: true,
nativeFunctions: true,
countermeasures: true,
countermeasures: "onLockDetected"
}
}
Optional features
These features are experimental or a security concern.
{
target: "node",
eval: true,
rgf: true,
renameGlobals: false,
identifierGenerator: function(){
return "myvar_" + (counter++);
}
}
Percentages
Most settings allow percentages to control the frequency of the transformation. Fine-tune the percentages to keep file size down, and performance high.
{
target: "node",
controlFlowFlattening: true,
controlFlowFlattening: 0.5,
controlFlowFlattening: 0.01
}
Probabilities
Mix modes using an object with key-value pairs to represent each mode's percentage.
{
target: "node",
identifierGenerator: {
"hexadecimal": 0.25,
"randomized": 0.25,
"mangled": 0.25,
"number": 0.25
},
shuffle: {hash: 0.5, true: 0.5}
}
Custom Implementations
{
target: "node",
renameVariables: name=>name!="jQuery",
identifierGenerator: ()=>{
return "_0x" + Math.random().toString(16).slice(2, 8);
},
stringConcealing: (str)=>{
if (str=="https://mywebsite.com/my-secret-api"){
return true;
}
return Math.random() < 0.6;
},
deadCode: ()=>{
dead++;
return dead < 50;
}
}
Potential Issues
- String Encoding can corrupt files. Disable
stringEncoding
if this happens. - Dead Code can bloat file size. Reduce or disable
deadCode
. - Rename Globals can break web-scripts.
i. Disable
renameGlobals
or
ii. Refactor your code
var myGlobalFunction = ()=>console.log("Called");
window.myGlobalFunction = ()=>console.log("Called");
File size and Performance
Obfuscation can bloat file size and negatively impact performance. Avoid using the following:
Option | Description |
---|
deadCode | Bloats file size. Use low percentages. |
stringSplitting , stringEncoding | Bloats file size. Avoid using these altogether. |
controlFlowFlattening | Significant performance impact. Use very low percentage when source code is large. |
dispatcher | Slow performance. Use low percentage. |
"The obfuscator broke my code!"
Try disabling features in the following order:
flatten
stack
dispatcher
If the error continues then open an issue.
Bug report
Please open an issue with the code and config used.
Feature request
Please open an issue and be descriptive. Don't submit any PRs until approved.
JsConfuser vs. Javascript-obfuscator
Javascript-obfuscator (https://obfuscator.io) is the popular choice for JS obfuscation. This means more attackers are aware of their strategies. JSConfuser provides unique features and is lesser-known.
Automated deobfuscators are aware of https://obfuscator.io's techniques:
https://www.youtube.com/watch?v=_UIqhaYyCMI
However, the dev is quick to fix these. The one above no longer works.
Alternatively, you could go the paid-route with Jscrambler.com (enterprise only) or PreEmptive.com
I've included several alternative obfuscators in the samples/
folder. They are all derived from the input.js
file.
Debugging
Enable logs to view the obfuscator's state.
{
target: "node",
verbose: true
}
About the internals
This obfuscator depends on two libraries to work: acorn
and escodegen
acorn
is responsible for parsing source code into an AST.escodegen
is responsible for generating source from modified AST.
The tree is modified by transformations, which each traverse the entire tree.
Properties starting with $
are for saving information (typically circular data),
these properties are deleted before output.