js-confuser
js-confuser copied to clipboard
feat: block-level calculator/dispatcher
Is your feature request related to a problem? Please describe.
In addition to the global calculator function, local/block-level calculator functions are very annoying to deal with.
This is an example from a script obfuscated by Cloudflare's proprietary solution:
function hl(d, e) {
e = {
IODHZ: strLookup(1495),
ZaiCw: function (f, g, h) {
return f(g, h);
}
};
e[strLookup(801)](hh, d, function (f) {
f[strLookup(1846)][strLookup(1875)] = strLookup(347);
f[strLookup(1846)].visibility = e.IODHZ;
});
}
As you can see, the e object contains a block-level dispatcher and variable, but it can also include calculators and even the same one multiple times:
f = {
VmRPf: function (j, k) {
return k === j;
},
jzNUF: "complete",
InfVk: function (j, k) {
return k | j;
},
XFOiM: function (j, k) {
return j << k;
},
EpYUF: function (j, k) {
return k ^ j;
},
TGnJl: function (j, k) {
return j - k;
},
ihSaf: function (j, k) {
return k ^ j;
},
netJL: function (j, k) {
return j & k;
},
ftAEZ: function (j, k) {
return j - k;
},
KqEBC: function (j, k) {
return j ^ k;
},
ZKdga: function (j, k) {
return j < k;
},
WxnhP: function (j, k) {
return k ^ j;
},
buCzB: function (j, k) {
return k ^ j;
},
ANDzz: function (j, k) {
return j + k;
},
yGbbD: function (j, k) {
return j + k;
},
xUDZY: function (j, k) {
return k ^ j;
},
sZhYi: function (j, k) {
return j ^ k;
},
OOYGk: function (j, k) {
return j & k;
},
APBrv: function (j, k) {
return j ^ k;
},
UNIAH: function (j, k) {
return k ^ j;
},
nXtSj: function (j, k) {
return j ^ k;
},
xlBks: function (j, k) {
return j - k;
},
PetUx: function (j, k) {
return j ^ k;
},
AnGxH: function (j, k) {
return j ^ k;
},
RbfOG: function (j, k) {
return k === j;
},
fzjxF: function (j, k) {
return k === j;
},
oNtLY: strLookup(1018),
mlhOQ: strLookup(522),
ehmzL: function (j, k) {
return j(k);
},
KexZm: strLookup(1471),
Iajza: strLookup(1762),
oHdAV: strLookup(1217),
IrhEi: strLookup(1560),
ecuWj: function (j, k) {
return j === k;
},
mQGaA: function (j, k) {
return j !== k;
},
BLreP: strLookup(567)
};
Sometimes in deeply nested functions, a block-level dispatcher/calculator also calls another higher-up one:
f = {
Hcgyw: strLookup(1027),
kZhQS: function (l, m) {
return l + m;
},
njgAA: function (l) {
return l();
},
vSaUg: function (l, m) {
return m === l;
},
GddGi: function (l, m) {
return l < m;
},
gTouW: strLookup(347),
bxzGp: strLookup(1349),
cCbfw: strLookup(1072),
TDQGI: strLookup(729),
UtfmT: function (l, m) {
return m === l;
},
BCvmI: strLookup(447),
BeEUo: function (l, m) {
return m === l;
},
QxEvm: strLookup(984),
kcjNA: function (l, m) {
return l === m;
},
YaqZh: strLookup(1329),
EQVuX: strLookup(1876),
wRfVC: strLookup(459),
awDrx: function (l) {
return l();
},
AvtlX: function (l, m) {
return l(m);
},
SPHDi: strLookup(1316),
SCEKk: function (l, m) {
return l(m);
},
gxpYE: function (l) {
return l();
},
IUoch: strLookup(1707),
jMlyM: strLookup(1328),
cVtpB: strLookup(1140),
WcJaN: strLookup(1464),
BRVga: function (l, m) {
return l + m;
}
};
/* later */
l = {
phzoe: function (B, C) {
return f[strLookup(1910)](B, C); // higher-up
},
eFbES: strLookup(1103),
aocNW: function (B, C) {
return f[strLookup(661)](B, C); // higher-up
},
CzTgP: strLookup(493),
PyEZj: "error code: 1020",
YyLwb: f[strLookup(634)], // higher-up
RjTgN: strLookup(1560)
};
Yes that could added to calculator. What I notice is the e object contains strings and other types of functions on it. This could also be implemented in JS-Confuser. What do you think should change?
- Should
calculatordo this? - Should another transformation do this? Dispatcher could be a great candidate for this as it already implements the nested feature.
I am not too familiar with the entire codebase, so you should do the call where best to implement this.
Just a few more notes:
- cf's dispatcher functions always take the real function as first argument, making them trivial to reverse:
e.ZaiCw(hh, d, function (f) {
// does
hh(d, function(f) {
Maybe specialized dispatchers make more sense?
// dispatcher only for hh
function dispatcher_hh(arg1, arg2) {
return hh(arg1, arg2)
}
// dispatcher with an array
// problem: might leak all functions used in the scope, so extra fake values required
function dispatcher(idx, arg1, arg2) {
var fn = [hh][idx];
return fn(arg1, arg2);
}
- cf's minifier removes all
varstatements (really, not a single one in a 5k loc file) by putting variables into unused function arguments
function test() {
var a = 10;
return a;
}
// out
function test(a) {
a = 10;
return a;
}
- cf sometimes has an extra transformation that replaces object literals with a bunch of assignments, eg:
var example = {
abc: "def"
}
// after
var example = {};
example["abc"] = "def"; // most often with string concealment here
Which can get really annoying with >50 assignments.
- cf currently always uses these 5 char random indexes which are inserted in their string concealment array. I think it'd make more sense to just reuse random strings we already have in the string concealment array. This will reduce the size but also make reversing more confusing.
// example, after undoing string concealment
// only one string (ignoring "log") and a lot more confusing imo than an extra random string
var e = {
"hello world": function(a, b) {
return a(b)
}
}
e["hello world"](console.log, "hello world")
- Specialized dispatchers, yes!
- Removing
varand putting it a function parameter is a very cool idea. - transformObjectKeys is neat but it's weak obfuscation.
I think going about this would be to buff up dispatcher. It will probably not be exactly like cf's obfuscation but I'll see what ideas could be implemented when I have the time. The var idea is also nice and would possibly be added to movedDeclarations as it's appropriate transformation.