Javascript deobfuscation the easy way

JD
3 min readMar 22, 2024

--

Lately, I have been ripping my hair out with obfuscated javascript. It makes static analysis almost impossible, and dynamic analysis nearly impossible. The de-obfuscation process can be long and arduous. I set out to make it a little easier.

Let's start by examining how javascript is obfuscated. Often, there will be an array of strings such as

const G = [
"Firefox",
"modified",
"continue",
"beforeunload",
"userLanguage",
"tempKey",
"screenshotHandler",
"isHDR",
"previousSibling",
"keyboardEvent",
"numberKeys",
"lyy7U",
"isSardineStringEntry",
"RTCEncodedAudioFrame",
"script[src*=extension]",
"atanh",
...
"dev"...

There is also often a function which manipulates this array in some way. The function will often look like this

function a3q(u, R) {
const G = a3b();
return (
(a3q = function (b, q) {
b = b - 0x77;
let K = G[b];
return K;
}),
a3q(u, R)
);
}
(function (u, R) {
const uZ = a3q,
G = u();
while (!![]) {
try {
const b =
parseInt(uZ(0xdf)) / 0x1 +
(-parseInt(uZ(0x2e5)) / 0x2) * (-parseInt(uZ(0x273)) / 0x3) +
-parseInt(uZ(0x221)) / 0x4 +
(-parseInt(uZ(0x86)) / 0x5) * (-parseInt(uZ(0x18f)) / 0x6) +
-parseInt(uZ(0x242)) / 0x7 +
parseInt(uZ(0xa0)) / 0x8 +
(-parseInt(uZ(0x1df)) / 0x9) * (parseInt(uZ(0x227)) / 0xa);
if (b === R) break;
else G["push"](G["shift"]());
} catch (q) {
G["push"](G["shift"]());
}
}
})(a3b, 0xa6ef0),

What this is doing is initially shifting (moving the first value to the end)

G["push"](G["shift"]()

the array 0xa6ef0 number of times (which is hex for 683760) THEN subtracting 0x77 (or 119) from that. So if you run a3q(0x77) it should give you the FIRST value of the new array that is created.

To test this, I typed in a3q(0x77) into your console with this script running, I got a value (‘dev’) in this case. Typing in a3q(0x76) I got undefined (since ‘dev’ is the first item in the NEW array, and the function is subtracting 0x77 from the parameter).

Now that we know that ‘dev’ is the first item in the array, all you have to do is find it in the ORIGINAL array to find out what the shift is (even without knowing ANY javascript).

copy the array into a python script. and write code similar to this

myArray = []// this is where you copy the array
print(myArray.index('dev'))

The output will be the shift.

NOW you have enough to make a deobfuscation script.

import re
for i in range(467):
myarray.append(myarray.pop(0))

def findArrayNumber(index):
index = index - 0x77
return myarray[index]

with open('purify.js', 'r') as r:
data = r.read()
regex = re.compile(r'(([a-zA-Z0-9]+)\((0x[0-9a-z]+)\))')
matches = regex.findall(data)
for match in matches:
index = int(match[2], 16)
realResult = findArrayNumber(index)
wholeMatch = match[0]
data = data.replace(wholeMatch, realResult)
with open('final.js', 'w') as w:
w.write(data)

What this script does is FIRST shifts the array in the same way as the initial file shifted it THEN makes a function which subtracts 0x77 from your parameter, just like the obfuscated file did. Finally, the script finds all references to a function which takes hex as the only parameter, and replaces that with the string from the new sorted array.

Why any function and not just a3q? because often there is FURTHER obfuscation such as this

function P() {
const uC = a3q;
return new window[uC(0x13d)]["BrowserClient"]({
dsn: D,
release: H["version"],
environment: H[uC(0x226)],
integrations: z
? window[uC(0x13d)][uC(0x2a5)]
: window["Sentry"]["defaultIntegrations"][uC(0xe3)](
(E) => ![uC(0x2a6), uC(0x24c)][uC(0x15e)](E["name"])
),
});
}

here, the function is setting uC to 3aq before calling it.

A few things to consider:

  1. my script assumes that you are SUBTRACTING 0x77 from the parameter, but sometimes you are ADDING it. Make sure to look for this in the original function
  2. my script assumes that the array is being pushed then shifted, so the first item is going to the end, but sometimes they do other things, like push then shift TWICE or take the last item and put it in the beginning

I hope this helps.

--

--

JD

Resident at Thomas Jefferson University Hospital