Skip to content

Instantly share code, notes, and snippets.

@Wh1terat
Last active September 25, 2024 05:24
Show Gist options
  • Save Wh1terat/f78416e4c681becb5bdf0a646aa37566 to your computer and use it in GitHub Desktop.
Save Wh1terat/f78416e4c681becb5bdf0a646aa37566 to your computer and use it in GitHub Desktop.
Incapsula JS Deobfuscator (obfuscator.io) - JS
#!/usr/bin/env node
var fs = require('fs');
var esprima = require('esprima');
var escodegen = require('escodegen');
var estraverse = require('estraverse');
var debug = true;
var rename = true;
var stringrotatefunc = `
(function (array, times) {
var whileFunction = function (times) {
while (--times) {
array['push'](array['shift']());
}
};
var selfDefendingFunc = function () {
var object = {
'data': {
'key': 'cookie',
'value': 'timeout'
},
'setCookie': function (options, name, value, document) {
document = document || {};
var updatedCookie = name + '=' + value;
var i = 0;
for (var i = 0, len = options['length']; i < len; i++) {
var propName = options[i];
updatedCookie += '; ' + propName;
var propValue = options[propName];
options['push'](propValue);
len = options['length'];
if (propValue !== true) {
updatedCookie += '=' + propValue;
}
}
document['cookie'] = updatedCookie;
},
'removeCookie': function () {
return 'dev';
},
'getCookie': function (document, name) {
document = document || function (value) {
return value;
};
var matches = document(new RegExp('(?:^|; )' + name['replace'](/([\.$?*|{}\(\)\[\]\\\/+^])/g, '$1') + '=([^;\]*)'));
var func = function (param1, param2) {
param1(++param2);
};
func(whileFunction, times);
return matches ? decodeURIComponent(matches[1]) : undefined;
}
};
var test1 = function () {
var regExp = new RegExp('\\\\w+ *\\\\(\\\\) *{\\\\w+ *[\\'|"].+[\\'|"];? *}');
return regExp['test'](object['removeCookie']['toString']());
};
object['updateCookie'] = test1;
var cookie = '';
var result = object['updateCookie']();
if (!result) {
object['setCookie'](['*'], 'counter', 1);
} else if (result) {
cookie = object['getCookie'](null, 'counter');
} else {
object['removeCookie']();
}
};
selfDefendingFunc();
}(stringArray, 123));
`
var callwrapper = `
var stringArrayCallsWrapper = function (index, key) {
index = index - 0;
var value = stringArray[index];
if (stringArrayCallsWrapper['initialized'] === undefined) {
(function () {
var getGlobal = Function('return (function() ' + '{}.constructor("return this")( )' + ');');
var that = getGlobal();
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
that['atob'] || (that['atob'] = function (input) {
var str = String(input)['replace'](/=+$/, '');
for (var bc = 0, bs, buffer, idx = 0, output = '';
buffer = str['charAt'](idx++); ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, bc++ % 4) ? output += String['fromCharCode'](255 & bs >> (-2 * bc & 6)) : 0) {
buffer = chars['indexOf'](buffer);
}
return output;
});
}());
var rc4 = function (str, key) {
var s = [], j = 0, x, res = '', newStr = '';
str = atob(str);
for (var k = 0, length = str['length']; k < length; k++) {
newStr += '%' + ('00' + str['charCodeAt'](k)['toString'](16))['slice'](-2);
}
str = decodeURIComponent(newStr);
for (var i = 0; i < 256; i++) {
s[i] = i;
}
for (i = 0; i < 256; i++) {
j = (j + s[i] + key['charCodeAt'](i % key['length'])) % 256;
x = s[i];
s[i] = s[j];
s[j] = x;
}
i = 0;
j = 0;
for (var y = 0; y < str['length']; y++) {
i = (i + 1) % 256;
j = (j + s[i]) % 256;
x = s[i];
s[i] = s[j];
s[j] = x;
res += String['fromCharCode'](str['charCodeAt'](y) ^ s[(s[i] + s[j]) % 256]);
}
return res;
};
stringArrayCallsWrapper['rc4'] = rc4;
stringArrayCallsWrapper['data'] = {};
stringArrayCallsWrapper['initialized'] = !![];
}
var cachedValue = stringArrayCallsWrapper['data'][index];
if (cachedValue === undefined) {
if (stringArrayCallsWrapper['once'] === undefined) {
var StatesClass = function (rc4Bytes) {
this['rc4Bytes'] = rc4Bytes;
this['states'] = [
1,
0,
0
];
this['newState'] = function () {
return 'newState';
};
this['firstState'] = '\\\\w+ *\\\\(\\\\) *{\\\\w+ *';
this['secondState'] = '[\\'|"].+[\\'|"];? *}';
};
StatesClass['prototype']['checkState'] = function () {
var regExp = new RegExp(this['firstState'] + this['secondState']);
return this['runState'](regExp['test'](this['newState']['toString']()) ? --this['states'][1] : --this['states'][0]);
};
StatesClass['prototype']['runState'] = function (stateResult) {
if (!Boolean(~stateResult)) {
return stateResult;
}
return this['getState'](this['rc4Bytes']);
};
StatesClass['prototype']['getState'] = function (rc4Bytes) {
for (var i = 0, len = this['states']['length']; i < len; i++) {
this['states']['push'](Math['round'](Math['random']()));
len = this['states']['length'];
}
return rc4Bytes(this['states'][0]);
};
new StatesClass(stringArrayCallsWrapper)['checkState']();
stringArrayCallsWrapper['once'] = !![];
}
value = stringArrayCallsWrapper['rc4'](value, key);
stringArrayCallsWrapper['data'][index] = value;
} else {
value = cachedValue;
}
return value;
};
`
var singlenode = `
var singleNodeCallControllerFunction = function () {
var firstCall = !![];
return function (context, fn) {
var rfn = firstCall ? function () {
if (fn) {
var res = fn['apply'](context, arguments);
fn = null;
return res;
}
} : function () {
};
firstCall = ![];
return rfn;
};
}();
`
var debugprotection = `
function debugProtectionFunction() {
function debuggerProtection(counter) {
if (('' + counter / counter)['length'] !== 1 || counter % 20 === 0) {
(function () {
}['constructor']('debugger')());
} else {
(function () {
}['constructor']('debugger')());
}
return debuggerProtection(++counter);
}
try {
return debuggerProtection(0);
} catch (y) {
}
}
`
var debugInterval = `
function debugProtectionFunctionInterval() {
if (new windowfoo['Date']()['getTime']() - initTime > 500) {
debugProtectionFunction();
}
}
`
var unicodeprotection = `
var selfDefendingFunction = singleNodeCallControllerFunction(this, function () {
var func1 = function () {
return 'dev';
}, func2 = function () {
return 'window';
};
var test1 = function () {
var regExp = new RegExp('\\\\w+ *\\\\(\\\\) *{\\\\w+ *[\\'|"].+[\\'|"];? *}');
return !regExp['test'](func1['toString']());
};
var test2 = function () {
var regExp = new RegExp('(\\\\[x|u](\\\\w){2,4})+');
return regExp['test'](func2['toString']());
};
var recursiveFunc1 = function (string) {
var i = ~-1 >> 1 + 255 % 0;
if (string['indexOf']('i' === i)) {
recursiveFunc2(string);
}
};
var recursiveFunc2 = function (string) {
var i = ~-4 >> 1 + 255 % 0;
if (string['indexOf']((!![] + '')[3]) !== i) {
recursiveFunc1(string);
}
};
if (!test1()) {
if (!test2()) {
recursiveFunc1('indеxOf');
} else {
recursiveFunc1('indexOf');
}
} else {
recursiveFunc1('indеxOf');
}
});
selfDefendingFunction();
`
function rc4 (str, key) {
var s = [], j = 0, x, res = '', newStr = '';
str = Buffer.from(str, 'base64').toString('utf8');
for (var k = 0, length = str.length; k < length; k++) {
newStr += '%' + ('00' + str.charCodeAt(k).toString(16)).slice(-2);
}
str = unescape(newStr);
for (var i = 0; i < 256; i++) {
s[i] = i;
}
for (i = 0; i < 256; i++) {
j = (j + s[i] + key.charCodeAt(i % key.length)) % 256;
x = s[i];
s[i] = s[j];
s[j] = x;
}
i = 0;
j = 0;
for (var y = 0; y < str.length; y++) {
i = (i + 1) % 256;
j = (j + s[i]) % 256;
x = s[i];
s[i] = s[j];
s[j] = x;
res += String.fromCharCode(str.charCodeAt(y) ^ s[(s[i] + s[j]) % 256]);
}
return res;
}
function debug_log(){
if (debug) console.log.apply(null, arguments);
}
function compare_nodes(node, pattern) {
if (Array.isArray(pattern)) {
if (node.length !== pattern.length) return false;
return node && pattern.every(function(val, index) {
return compare_nodes(node[index], val);
});
} else if (typeof pattern !== "object" || pattern == null) {
return node === pattern;
} else {
return node && Object.keys(pattern).every(function(key) {
// Allow for loose matching
if (['id', 'name','value','raw'].indexOf(key) > -1 ) return true;
return compare_nodes(node[key], pattern[key]);
});
}
};
var args = process.argv.slice(2);
fs.readFile(args[0], 'utf8', function(err, contents) {
var initial = esprima.parseScript(contents);
var ast = esprima.parseScript(contents);
estraverse.traverse(initial, {
enter: function(node,parent){
if (node.type == 'VariableDeclarator' && node.init.value.length > 100)
{
ast = esprima.parseScript(Buffer.from(node.init.value, 'hex').toString());
}
}
})
// Write out intermediate step
var code = escodegen.generate(ast);
fs.writeFileSync(`stage1-${args[0]}`, code, function (err) {
if (err) return console.log(err);
});
fs.writeFileSync(`ast-${args[0]}`, ast, function (err) {
if (err) return console.log(err);
});
// Find string array rotate function
var stringarrays = [];
estraverse.replace(ast, {
enter: function (node, parent) {
if (compare_nodes(node, esprima.parseScript(stringrotatefunc).body[0])) {
debug_log('Found StringRotate func!');
var arrname = node.expression.arguments[0].name;
var arrshift = node.expression.arguments[1].value;
estraverse.replace(ast, {
enter: function (node, parent) {
if (node.type == 'VariableDeclaration' && node.declarations[0].id.name == arrname){
while(arrshift--)node.declarations[0].init.elements.push(node.declarations[0].init.elements.shift());
stringarrays.push({
name: arrname,
elements: node.declarations[0].init.elements
})
this.remove();
}
}
});
// huh? Removing the rotate function seems to remove callwrapper ?!
//this.remove();
}
}
});
debug_log(stringarrays);
// Find string array call wrapper
var callwrappers = [];
estraverse.replace(ast, {
enter: function (node, parent) {
if (compare_nodes(node, esprima.parseScript(callwrapper).body[0])) {
debug_log('Found CallWrapper func!')
callwrappers.push({
name: node.declarations[0].id.name,
stringarray: node.declarations[0].init.body.body[1].declarations[0].init.object.name
});
this.remove();
}
}
});
debug_log(callwrappers);
// Fix string array calls
for (callwrapper of callwrappers){
var stringarray = stringarrays.find(x => x.name === callwrapper.stringarray);
estraverse.replace(ast, {
enter: function (node, parent){
if (node.type == 'CallExpression' && node.callee.name == callwrapper.name) {
if (node.arguments[1].type == 'Literal') {
key = node.arguments[1].value;
} else if (node.arguments[1].type == 'BinaryExpression') {
var left = node.arguments[1].left.name;
var right = node.arguments[1].right.name;
var tmpkname,tmpkey,tmpkoff,tmpkskip = null;
estraverse.traverse(ast, {
enter: function (node, parent){
if (node.type =='ForStatement' && node.body.body[0].type == 'ExpressionStatement') {
if (node.body.body[0].expression.left.name == left){
tmpkname = node.body.body[0].expression.right.object.name;
tmpkoff = node.test.right.value - 1;
estraverse.traverse(parent, {
enter: function(node){
if (node.type == 'VariableDeclaration' && node.declarations[0].id.name == tmpkname){
tmpkey = node.declarations[0].init.value;
this.break;
}
}
});
} else if (node.body.body[0].expression.left.name == right){
tmpkskip = node.init.declarations[0].init.value;
this.break;
}
}
}
});
debug_log('Found Funky Thing!')
key = tmpkey.substr(0, tmpkoff) + tmpkey.substr(tmpkskip)
} else {
debug_log('ERR: Bad String Wrapper:');
debug_log(node);
return false
}
idx = parseInt(node.arguments[0].value, 16);
raw = stringarray.elements[idx].value;
value = rc4(raw, key);
if (debug){
debug_log(`Index: ${idx}, Raw:${raw}, Key:${key}, Value:${value}`);
}
return {
type: 'Literal',
value: value,
raw: value
}
}
}
});
}
// Write out intermediate step
var code = escodegen.generate(ast);
fs.writeFileSync(`stage2-${args[0]}`, code, function (err) {
if (err) return console.log(err);
});
// Fix control flow (switchyness)
var switchreg = new RegExp("(\\d+\\|)+\\d+");
estraverse.replace(ast, {
enter: function(node,parent){
try {
if (node.type =='VariableDeclaration' &&
node.declarations[0].init.callee.property.value == 'split' &&
switchreg.test(node.declarations[0].init.callee.object.value))
{
var name = node.declarations[0].id.name;
var order = node.declarations[0].init.callee.object.value.split('|');
debug_log(order);
estraverse.replace(parent, {
enter: function(node,parent){
if (node.type == 'WhileStatement' && node.body.body[0].discriminant.object.name == name){
for (idx of order){
node.body.body[0].cases[idx].consequent.forEach(function(consequent) {
if (consequent.type != 'ContinueStatement'){
parent.body.push(consequent);
}
})
}
this.remove();
}
}
})
this.remove();
}
} catch (ignore) {}
}
})
// Write out intermediate step
var code = escodegen.generate(ast);
fs.writeFileSync(`stage3-${args[0]}`, code, function (err) {
if (err) return console.log(err);
});
// Fix control flow (indirection)
estraverse.replace(ast, {
enter: function(node,parent){
try {
if(node.declarations[0].init.properties.every(function(prop){
if (prop.value.type == 'FunctionExpression' && prop.value.body.body[0].type == 'ReturnStatement') return true
})) {
for (func of node.declarations[0].init.properties){
foo = node;
estraverse.replace(ast, {
enter: function(node, parent){
try {
if (node.type == 'CallExpression' && node.callee.object.name == foo.declarations[0].id.name && node.callee.property.value == func.key.value){
bar = Object.assign({},func.value.body.body[0].argument);
if (bar.type == 'BinaryExpression'){
bar.left = node.arguments[0];
bar.right = node.arguments[1];
return bar;
} else if (bar.type == 'CallExpression'){
bar.callee.name = node.arguments[0].name;
bar.arguments = node.arguments.slice(1);
return bar;
}
}
} catch (ignore) {}
}
});
}
this.remove();
}
} catch (ignore) {}
}
});
// Write out intermediate step
var code = escodegen.generate(ast);
fs.writeFileSync(`stage4-${args[0]}`, code, function (err) {
if (err) return console.log(err);
});
// Fix up loose ends
estraverse.replace(ast, {
enter: function (node, parent) {
// Remove junk
if (compare_nodes(node,esprima.parse(singlenode).body[0])){
debug_log('Removing singlenode');
this.remove();
}
if (compare_nodes(node,esprima.parse(debugprotection).body[0])){
debug_log('Removing debugprotection');
this.remove();
}
if (compare_nodes(node,esprima.parse(debugInterval).body[0])){
debug_log('Removing debugInterval(and calls to it)');
name = node.id.name;
estraverse.replace(ast, {
enter: function (node, parent) {
if (node.type == 'ExpressionStatement' && node.expression.type == 'CallExpression' && node.expression.callee.name == name) {
this.remove();
} else if (node.type == 'VariableDeclarator' && node.init && node.init.type == 'CallExpression' && node.init.callee.name == name){
newnode = Object.assign({}, node);
newnode.init = {
type: 'Literal',
value: 1
}
return newnode;
}
}
});
this.remove();
}
if (compare_nodes(node,esprima.parse(unicodeprotection).body[0])){
debug_log('Removing unicodeprotection')
this.remove();
}
if (compare_nodes(node,esprima.parse(stringrotatefunc).body[0])){
debug_log('Removing stringrotatefunc')
this.remove();
}
if (rename){
// Rename idents
// Not scope aware but meh
var ecma_reserved = ['break','case','catch','class','const','continue','debugger','default','delete','do','else','export',
'extends','finally','for','function','if','import','in','instanceof','new','return','super','switch',
'this','throw','try','typeof','var','void','while','with','yield','enum','implements','interface','let',
'package','private','protected','await','abstract','boolean','byte','char','double','final','float',
'goto','int','long','native','short']
var idents = []
estraverse.replace(ast, {
enter: function(node,parent){
if (node.type == 'Identifier' && String(node.name).startsWith('_0x')){
if(idents.find(x => x.oldname === node.name)){
node.name = idents.find(x => x.oldname === node.name).newname;
} else {
newname = (function(){
while(true){
//var chars = "ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
//var string_length = 3;
var chars = "abcdefghiklmnopqrstuvwxyz";
var string_length = 4;
var randomstring = '';
for (var i=0; i<string_length; i++) {
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum,rnum+1);
}
if (ecma_reserved.indexOf(randomstring) < 0 && !idents.find(x => x.oldname === randomstring) && !idents.find(x => x.newname === randomstring)){
return randomstring;
}
}
})();
idents.push({
oldname: node.name,
newname: newname
})
debug_log(`Renaming Literal: ${node.name} to: ${newname}`);
node.name = newname;
}
}
}
})
}
// Change calls from foo['bar']() to foo.bar()
if (node.type == 'MemberExpression' && node.computed == true && typeof node.property.value != "undefined" && isNaN(node.property.value)) {
debug_log('Fixing calls')
return {
type: 'MemberExpression',
computed: false,
object: node.object,
property: {
type: 'Identifier',
name: node.property.value
}
}
}
// Fix true/false statements
if (node.type == 'UnaryExpression' && node.operator == '!'){
if (node.argument.type == 'UnaryExpression' && node.argument.operator == '!'){
if (node.argument.argument.type == 'ArrayExpression' && node.argument.argument.elements.length == 0){
debug_log('Fixing true/false statement')
return {
type: 'Literal',
value: true
}
}
} else if (node.argument.type == 'ArrayExpression' && node.argument.elements.length == 0){
debug_log('Fixing true/false statement')
return {
type: 'Literal',
value: false
}
}
}
}
});
//if (debug) fs.writeFileSync('test2.ast', JSON.stringify(ast, null, 4));
var code = escodegen.generate(ast);
fs.writeFileSync(`deob-${args[0]}`, code, function (err) {
if (err) return console.log(err);
});
});
@nullableVoidPtr
Copy link

Thank you for the prompt reply.

Just seen your project - wish it had existed when I started looking at this!

No problem! Though I do feel it can still be more robust in some areas.

@m-abu
Copy link

m-abu commented Jul 29, 2021

I am totally new to this but I gave this a try and it seems to be partially working for me .. I managed to workaround the options as you mentioned and got the 4 stages. However, I am not able to see or construct the actual URL that I should call eventually to bypass Incapsula but maybe I am missing something.

@Wh1terat
Copy link
Author

@m-abu:
Incapsula's role is to generate a cookie, not a final URL.

There's some stuff that's changed with regard to extracting the elements needed to generate that cookie - a few releases ago incapsula changed some stuff to do with that - more obfuscation.

@alexnrj
Copy link

alexnrj commented Nov 30, 2021

Hello @Wh1terat I'm using your code to analyze incapsula resources SWJIYLWA. Using three samples for two of them the code throw error generating stage2. Is that normal? This one of files that doesn't work https://pastebin.com/Z25E03z7

@egerdnc
Copy link

egerdnc commented Dec 16, 2021

Hi @Wh1terat i saw your reply to "sugappa" and commented those lines you just mentioned but right after this
the file has been created but also throw an error

Some help would be really useful ❤️

C:\deobf>node deobfuscate.js server.js
node:internal/fs/utils:879
  throw new ERR_INVALID_ARG_TYPE(
  ^

TypeError [ERR_INVALID_ARG_TYPE]: The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received an instance of Script
←[90m    at Object.writeFileSync (node:fs:2146:5)←[39m
    at C:\deobf\deobfuscate.js:324:8
←[90m    at FSReqCallback.readFileAfterClose [as oncomplete] (node:internal/fs/read_file_context:68:3)←[39m {
  code: ←[32m'ERR_INVALID_ARG_TYPE'←[39m
}

Media:
https://prnt.sc/23749n1

Code:
https://www.toptal.com/developers/hastebin/deloqobuyi.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment