JavaScript Obfuscation Techniques by Example
trickster.dev
trickster.dev
[1] view-source:https://www.freeslots.com/slot515.min.js?v=84
I'm not sure how many levels it was applied but my general strategy on it was the first obfuscation method seemed to be an IIFE that triggers eval on a big string that has had some transformations applied which then calls eval on the next level and so on so I would take the IIFE, turn it into a function definition stuck in a variable like "decodeFunction1" so I could just recursively call directly and change the ending to return the string instead of eval it directly. There is probably something that could be done with debugger breakpoints here but I don't know enough about how that plays out inside eval. Anyways if I were evil I'd make sure at some point in this chain there is a subtle change in what's happening that breaks this approach so I wouldn't be surprised if someone told me that was the case :).
It'd be interesting to see what some real JS devs could get to, both from a "how hard is it" perspective but also just to see what different obfuscations are used in the real world.
With it I get https://ghostbin.me/62d52999cc217 , from there it's decoding UTF-16 and at least one more decoding step (parts of decoded UTF-16 are mangled) to get the string j and the function o and resolving the original function with it.
Comment at the top also points to https://freeslots.com/libs/mersenne-twister.js
Using those two together to decode the RNG function, and searching around for some more local pointers (with some judicious renaming of functions for legibility, and some comments):
// polyfill for IE??
function trunc(a) {
return a < 0 ? Math.ceil(a) : Math.floor(a)
}
var genrand_res53 = function() {
var a = new MersenneTwister((new Date).getTime() + Math.floor(Math.random() * Math.pow(2, 32)));
return function() {
// Generate a random float by varying the 53-bit mantissa.
var b = genrand_int32(a) >>> 5,
c = genrand_int32(a) >>> 6;
// (b << 26 | c) / 2 ** 53
return (b * 67108864 + c) * 1.1102230246251565E-16
}
}();
function rand_int(a, b) {
var c = genrand_res53();
if (a === undefined) return c;
if (b === undefined) return trunc(c * a);
if (b > a) return a + trunc(c * (b - a));
return a;
}
function Kma() {
// credits is `Lma`; if you want to cheat, you can edit that variable directly
credits < 500 && (Ika = Ika - Ika % 1E6 + rand_int(890001) + 1E5)
}
So, we're generating a random integer from 0 to 890,000 (inclusive, with negligible chance of generating 890001) with each spin, and using that to update the state (seeded with current time; the top 1e6 of the value is determined via mixing of Date.now() and Math.random() and is fixed for a given session).There's then more to determine whether your state is a winner (based on the game mode), but I didn't feel like looking into that specifically.
I have had to deal with client that thought they could keep some bit of code secret on a browser before. I have had to explain many many times that anything the browser can do a human can do. So if a browser can run the code, at some point a human can too.
Meaning, effectively, it can be de-obfuscated into code with control flow that's readily understood by a human, even if it would take some patience and practice (and the right tools) to perform the de-obfuscation.
Re: the FreeSlots.com program, https://deobfuscate.io shows that most of the obfuscation is related to decoding characters per some algorithm of their devising and eventually eval'ing the string as a JS program. There are likely several tricky rounds of that technique (and others) used at layers within the obfuscated code.
If the FreeSlots devs are clever, then they likely have a scheme to randomly generate the code they want (producing their desired result in terms of odds), where the random part is w.r.t. how the obfuscation layers are composed. Done well, that could make it rather difficult to mechanically de-obfuscate their code changing over time, i.e. without a human intervening to help identify the distinct layers because... parsers are hard.
I gave it a quick shot with some spare time, was selfishly hoping someone else had done the work when I checked back :p.
I don't feel so incentivized at present, sorry if I'm letting you down.
Figuring out the odds generator...is a task I will leave to someone else :)
The code looked like:
var O11IOOO1I011 = 1<<1,
II00001OOO1O = 1<<2;---
This one binary encoded with tabs and spaces:
For example, FileFields.js:
const FF = proxyFieldNames('FF', { foo: null, bar: null })
// DEV: FF.foo → FF_foo
// PROD: FF.foo → 'a'
https://github.com/uxtely/js-utils/tree/main/proxy-fields-ob...As a bonus, it's helpful for renaming, autocompleting, and finding usages.
You can get around this by intercepting the request and returning a copy of the js with this check patched out, but it's just another hurdle in the way of casual inspection.
The more aggressive they make patent law the less useful it seems to become for protecting any actual investment, so here we are, clinging to wooden totems...
If you publish your code on github, it's more likely to be compromised than if it's just in the webapp, very well obfuscated.
A sufficiently motivated actor will break it, and frankly break almost anything else, so it's a game of probabilities etc..
Obfuscation probably does make sense so long as it's not obviously getting in the way of dev. and, with the key understanding that 'it can be broken'.
Physical security at most companies can be thwarted with enough effort, it doesn't mean we don't do it.
Regard it as an intermediate representation (IR) of your code, a stage between your readable source code and browser bytecode/jit.
The "shit" is still readable since webpack also generates source maps.
Terser transforms non-global identifiers lexically and does some simple substitutions.
Normally you want to bundle modules with their dependencies anyway, maybe transpile code... Then why not minify?
Et voila, some completely unreadable shit.
Take it one step further: hire sufficiently terrible spaghetti coders that nobody, not you or even they know what the code does, and any hacker trying to make sense of it will feel ill.
reversing is one of the few classic skills from the home computing era that is alive and well in 2022. it's kind of nice.
Android 12 with Chrome v103.
Interesting, I have Bromite set up to disable JIT by default, so if it's because it weird JS, it's a bug in both the JIT engine and in the interpreter.
Edit: I doubt it's JS related because this site doesn't seem to use JS (unless there's some UA sniffing going on). I'm guessing this is a Chrome bug, hopefully not an exploitable one!
Android 11, Chrome 103.
Sure, you can de-obfuscate JS but you can also reverse engineer other software.
Very few users may even want to do this (perhaps none), but in theory it's a nice thing that has historically been made possible by the web. Unlike binaries or backend code, the user gets the source themselves to run in their browser... fine, if you want to obfuscate it you can, but I think it's fair for users to also dislike websites that do this.
I'm not talking about minifiers/bundlers which are used to make the content more user-friendly, I'm specifically talking about steps taken to make the web less accessible and less free.
I can understand the desire to be able to vet code that a website wants to run on your device. I don't see why that preference should create an imperative for websites to either accommodate you or be banned from the internet.