Skip to main content

How to hide your JavaScript code from View Source

How to hide your JavaScript code

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.

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

It's horrible, but it could be worse

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

Click to see all the parameters you'll need

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

JS Nice is an easy way to render your code unreadable

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 = "";

Generate, the award winning conference for web designers, returns to NYC on April 24-25! To book tickets visit www.generateconf.com&nbsp;

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: