BOOK THIS SPACE FOR AD
ARTICLE ADLately, 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",
"focusEvent"...
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 function is doing is initially shifting (moving the first value to the end) 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, just type in a3q(0x77) into your console with this script running, you will get a value (‘dev’) in this case. if you type in a3q(0x76) you will get undefined (since ‘dev’ is the first item in the 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 arrayprint(myArray.index('dev'))
The output will be the shift.
NOW you have enough to make a deobfuscation script.
import refor 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:
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 functionmy 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 beginningI hope this helps.