If you don't take precautions with your JavaScript code, you're making life easy for anyone who wants to clone it. But if your programming processes are more than one click away, attackers will prefer to clone a competitor's software. Bots and other miscreants combing the internet for AWS or Azure credentials usually do not bother with obfuscated code – the next website's login information can also be used to mine bitcoin and can be harvested with less effort.
Obfuscating JavaScript used to be complex. However, the process has become significantly easier during the last few years. Today, even small companies can and should protect their code from prying eyes. In some cases, getting secure is just an invocation of a Node.js package away.
The following steps introduce a commonly used JavaScript obfuscator, and also look at a few other topics related to the problem at hand.
01. Version check
Our JavaScript obfuscator lives in the Node runtime environment. Let us start out by checking the versions used. The output below provides the version state found on yours truly's workstation used for the following steps:
tamhan@tamhan-thinkpad:~$ node --version
v8.12.0
tamhan@tamhan-thinkpad:~$ npm --version
6.4.1
02. Install the program
Javascript-obfuscator should be installed into the global assembly cache of your workstation. Invoke npm with the -g parameter and don't forget to provide superuser rights – the actual deployment process should be done in a few seconds.
tamhan@tamhan-thinkpad:~$ sudo npm install -g
javascript-obfuscator
[sudo] password for tamhan:
. . .
+ javascript-obfuscator@0.18.1
added 103 packages from 162 contributors
in 4.4s
03. Create a sample
Testing obfuscation works best if we have some 'real' code. So let us start out with a small HTML webpage. It loads a JavaScript file called worker.js, declares a button and contains a small bit of inline scripting. When it's loaded in a browser, click the button to show a textbox.
<html>
<body>
<script src="worker.js"></script>
<script>
function worker(){
alert(doTheTrick());
}
</script>
<button type="button" onclick="worker(
)">Click Me!</button>
</body>
</html>
04. Add some code
Worker.js starts out with a string variable. They are a classic attack target – if a ROM is to be decoded, the assembler usually starts out by looking for tables containing string sequences. Furthermore, encryption is performed using a set of variables with very 'speaking' names.
Get top Black Friday deals sent straight to your inbox: Sign up now!
We curate the best offers on creative kit and give our expert recommendations to save you time this Black Friday. Upgrade your setup for less with Creative Bloq.
var myString = "Hello from Future plc"
function doTheTrick(){
var chiffrat;
chiffrat = rot13(myString);
return chiffrat;
}
05. Implement the encryption
As this is not intended as an encryption 101, we should settle on a comparatively simple substitution cipher. ROT13 is not difficult, but it can be programmed quite verbosely. Dsoares' implementation comes with a set of 'speaking' variables and provides lots of food for our obfuscator.
function rot13(str) {
var re = new RegExp("[a-z]", "i");
var min = 'A'.charCodeAt(0);
var max = 'Z'.charCodeAt(0);
var factor = 13;
var result = "";
str = str.toUpperCase();
for (var i=0; i<str.length; i++) {
result += (re.test(str[i]) ?
String.fromCharCode((str.charCodeAt
(i) - min + factor) % (max-min+1)
+ min) : str[i]);
}
return result;
}
06. First obfuscation
Performing an obfuscation run of the code is simple. Invoke javascript-obfuscator, and pass in a dot to tell the program to work on all files found in the current working directory. This shows the result on the author's IBM workstation:
tamhan@tamhan-thinkpad:~/FutureObfuscate/
code$ javascript-obfuscator .
07. Redirect output
Firing off the files directly into the container folder is inefficient, as names must be changed before they can be used. A better way involves the use of the output parameter. If javascript-obfuscator finds it, the program generates a subfolder in the current working directory and dumps the results of its labours there.
tamhan@tamhan-thinkpad:~/FutureObfuscate/code$
javascript-obfuscator . --output ./obfusca
08. Analyse the results
Dive into the 'obfusca' folder and open the new version of worker.js to feast your eyes on the abomination that is shown accompanying this step. The code's formatting was mangled badly. Method names, however, remained the same, as they are needed for external invocations. Furthermore, strings now sit in an array where an attacker can harvest them conveniently.
var a0_0xb9e2=['Hello\x20from\x20Future
\x20plc','[a-z]','test','charCodeAt'];
09. Prevent string harvesting
Javascript-obfuscator comes with a selection of string-mangling algorithms, which can be configured using --string-array-encoding. Keep in mind that the output directory must be emptied out before each invocation, because forgetting to do so leads to 'recursive' obfuscation of the output files from the previous run.
tamhan@tamhan-thinkpad:~/FutureObfuscate/code$
javascript-obfuscator . --output ./obfusca
--string-array-encoding base64
10. Look at the results again
At this point, our obfuscator's output looks different – the array at the top of the file now is much less readable. This, however, does not solve all problems. If you perform a few obfuscation cycles, you will eventually end up with mark-up similar to the one accompanying this step:
var a0_0x31e5=['bGVuZ3Ro','dGVzdA==','
ZnJvbUNoYXJDb2Rl','Y2hhckNvZGVBdA==','
dG9VcHBlckNhc2U='];
. . .
. . .
var myString='Hello\x20from\x20Future\x20plc'
11. Understanding randomisation
Obfuscators work in a pressure field between high performance and code protection. One way to address the problem involves 'randomising' elements. The runtime-expensive obfuscations are not added to all nodes, but only to a subset. Detecting if nodes are affected or not is usually done via a random number generator, whose sensitivity can be tuned.
12. Go after strings
The above-mentioned random number generator emits numbers ranging from zero to one. If the number is larger than the threshold, the modification will not take place. Setting stringArrayThreshold to a value of one means that all numbers are smaller than the threshold, ensuring that each and every string gets mangled.
tamhan@tamhan-thinkpad:~/FutureObfuscate/code$
javascript-obfuscator . --output ./obfusca
--string-array-encoding base64
--stringArrayThreshold 1
13. Inject random code
Analysis tools such as JSNice profit from having a small code base. Given that javascript-obfuscator breaks the code into an AST anyway, nothing speaks against inserting 'garbage code' on the fly. As this feature adds significant bloat, developers must activate it manually via the two parameters shown next to this:
--dead-code-injection <boolean>
--dead-code-injection-threshold <number>
14. Enjoy the chaos
Running the obfuscator with both --dead-code-injection true and --dead-code-injection-threshold 1 set leads to a filesize of about 2.5KB. Attempting to nicify the code leads to an almost indecypherable wall of mostly tautological JavaScript.
15. Aggressively modify program flow
Breaking code into an AST allows for deep transformations. Setting the controlFlowFlattening attribute to true tells the program that it can de-inline function calls. This leads to a significant expansion of the generated code – keep in mind that the results can take a 150 per cent performance hit.
16. Annoy the debugger
Artefacts from debugging are the world's greatest gift to hackers. A few calls to console.log() and its friends can give an attacker valuable information on what happens inside the program – a good example would be the snippet shown:
function doTheTrick()
{
var chiffrat;
console.log("Preparing to encrypt
chiffrat");
chiffrat = rot13(myString);
console.log("Returning chiffrat");
return chiffrat;
}
17. Evaluate technical problems
We can attempt to re-obfuscate the program with the command below. It disables the string caching feature and should disable console logging via redirection.
tamhan@tamhan-thinkpad:~/Desktop/
DeadStuff/2018Nov/FutureObfuscate/code$
javascript-obfuscator . --output ./obfusca
--string-array-encoding false
--disableConsoleOutput true
18. Play cat and mouse games
Obfuscator and browser vendors fight a long and bitter battle about the debugger function. Due to this, 'aggressive' measures, such as console redirection of the program flow interruptors shown accompanying this step, usually don't work for long.
debugProtection: false,
debugProtectionInterval: false,
19. Quick online obfuscation
Installing the entire Node.js package for obfuscating one or two files is pointless. Visit obfuscator.io to access an online version of the program that lives in your browser. Check and comboboxes below the main input let you modify program behaviour as outlined in the steps above.
20. Learn more
The developer team maintains relatively detailed documentation explaining the way the various command line parameters interact with one another. Simply visit head here if the 'short help' output shown above does not help you reach your goal.
21. An obfuscator's mortal enemy
The ETH Zurich provides a de-obfuscation service hosted at jsnice.org. It uses neural networks and a knowledge database made up of existing code to determine variable names. While sprucing up the formatting usually works pretty well, some of the names – such as pixelSizeTargetMax in the encryption routine accompanying this step – can be rather amusing.
function rot13(PL$120) {
/** @type {!RegExp} */
var insufficientRegExp = new RegExp
(a0_0x395d("0x1"), "i"); var
pixelSizeTargetMax = "A" ["charCodeAt"](0);
var zeroSizeMax = "Z"["charCodeAt"](0);
/** @type {number} */
var minh = 13;
/** @type {string} */
var out = "";
This article was originally published in issue 283 of creative web design magazine Web Designer. Buy issue 283 here or subscribe to Web Designer here.
Related articles:
Thank you for reading 5 articles this month* Join now for unlimited access
Enjoy your first month for just £1 / $1 / €1
*Read 5 free articles per month without a subscription
Join now for unlimited access
Try first month for just £1 / $1 / €1
Tam Hanna is a software consultant who specialises in the management of carrier and device manufacturer relationships, mobile application distribution and development, design and prototyping of process computers and sensors. He was a regular contributor to Web Designer magazine in previous years, and now occupies his time as the owner of Tamoggemon Software and Computer Software.