As part of a recent commercial project which included a paid software download, I had a mandate to "protect the software from unauthorized distribution".
That's one of those requirements that's easier said than done.
But even in cases where code obfuscation successfully discourages reverse engineering (or "beautification" as it is called), obfuscated code doesn't generally offer much protection against code theft. Since most obfuscated code runs equally well on any domain, "borrowed" code may still run on unauthorized websites regardless of its readability.
From a publisher perspective, it's often overlooked point that obfuscated code can be just as tempting a target as non-obfuscated code.
A survey of obfuscation methods is beyond the scope of this post, and endless how-to's have already been written about the various approaches used to render code human-unreadable. These approaches include identifier mangling, dead code injection, XOR encryption, a complete renaming of globals and string literals, code flattening, unicode escaping, self defending code and more. I have even seen code hidden in images, stored as pixel information. Suffice to say that for every convolution, there's a way to undo it -- and to restore the code to some level of readability.
In this case I had employed at least half of the above techniques and had already warped the code into a deviously complicated mess. Within that code I had embedded a simple comparison of the current domain to the authorized one, whose string representation was in-turn scrambled into unrecognizable oblivion.
But then what?
The two problems that immediately hit me were how to fail invisibly and how to create a universal piece of protective code which could be dropped into any pre-existing .js file to "domain lock" the script to an authorized domain.
Invisible failure is important because the point of failure can betray important clues as to where the domain-lock is taking place.
Universal code is often important when one doesn't know the specific script needing protection.
There were two ways to go that satisfied both of those requirements. I'll discuss the first one here, and the second in a forthcoming post.
As stated above, the general problem with most methods of script termination (including stopping code execution, intentionally throwing errors, launching into an infinite loop or redirecting the page) all leave an obvious console trail which would allow any determined programmer to quickly zero in on the offending piece of code and neutralize it.
I needed something sneakier.
My first solution -- which still works pretty well -- and has the advantage of being a truly universal solution -- is to damage other functions on the page. The reasoning behind this approach (which you can play with at a simple generator I made at DomainLockJS) is that the script must execute without an error, but cause a cascade of errors in other, unrelated functions. This approach leads the unauthorized user down a rabbit hole of errors in seemingly well formed code.
The resulting console error-trail is a frustrating sea of red, pointing to a host of confusingly unrelated functions which should otherwise be operating normally.
That solution is going to have to wait until next week when I have more time ...