I decided to take some time out and prepare a detailed write up on how I went about solving the JavaScript Deobfuscation Challenge posted on BreakingPoint Systems' Blog recently.
You can read more about the contest and the scenario given in the challenge here:
First off, I would like to thank Ricky for setting up this challenge. It was interesting and it really makes you think beyond the normal capabilities of JavaScript language which we most are aware of.
As you proceed to solve this Obfuscated JavaScript, you will uncover certain facts about JavaScript which will come to you as a surprise.
The concept:
The exploit code is encoded using XOR encoding. In order to decode this XOR encoded code, we need a passphrase. There's a nice concept used here.
The first part of the script will detect the Victim's User Agent and based on that will return a Random PreGenerated Passphrase.
This passphrase, when XOR'ed with Encoded JavaScript, will yield the plain text.
It's not as easy at it sounds though. Keep reading to find out more about it.
Here's a list of the different tricks applied in order to obfuscate the code and make it difficult for the reversers to analyse it:
Strings represented as numbers using Base Conversion with Radix 32.
Here, the Radix 32 was represented as a bitwise XOR operation between 2 numbers.
A bitwise leftshift is essentially equivalent to multiplying the number with 2 (exponent the number of leftshifts)
In JavaScript, the toString() function is used to convert a Number to String. When supplied the radix value of 32 to this function, it essentially converts the Base to 32 from decimal.
An example would help to understand better.
720094129..toString(16<<1)+""
720094129 is in Decimal (base 10).
16<<1 = 32 as we have already seen above.
The number, 720094129 in Base 32 is equivalent to the String, length
720094129..toString(32)+""= length + "" = length
Representing Numbers as an Expression operating on Strings.
A clever trick was used to represent numbers, especially small numbers (0-9) as an Expression Operating on a String.
1 could be represented as the Length of a Single Char.
'C'[length] = 1
But we know from above analysis that the string "length" can also be written as: 720094129..toString(32)+""
so, 'C'[720094129..toString(32)+""] = 1
In the JavaScript given to us. There are several "if" expressions evaluated. Let's take one as an example and the same approach can be extended to the remaining.
Char Codes were represented in different formats, either base 10 (dec), base 8 (oct) and base 16 (hex)
String.fromCharCode(x)
This JavaScript function will return the Character Corresponding to the ASCII Code, x.
Important thing to note here is, the number 'x' passed as an argument to this function, should be in decimal format. It shouldn't be either hex or octal.
If we look at the above sample "if" expression, we can see some octal entries too.
I wrote a short Perl Script at the time of the contest which would automatically parse a comma seperated list of a mixture of numbers in different format and convert them all to decimal (base 10) format.
It turned out to be really useful and saved a lot of time. As you will see later when we come to the stage of XOR decoding. This script saves a lot of time.
For instance,
There was an "if" expression in the obfuscated javascript as given below:
If you input, 0157,112,0145,114,97 in the input box on the site, you will get some weird characters:
p‘ra
Similarly, try to input, 0x6d,0x61,0x54,0150,76,0114,0132,113,0x50,0155,114 ,0x72,0x46,0x53 and observe the result.
=6–Lr„q2›rH.5
This was the reason, I mentioned above. These values should be in base 10 decimal and not octal or hex. In the obfuscated javascript, a mixture of base 10, base 16 and base 8 numbers were included to trick the reverser.
This is where my script comes in handy and saves us the time.
It is important to understand this part of the code. Here, uUHIjMJVFJET variable will store the user agent (in lowercase) of the victim's browser. We check whether or not the victim is using an Opera Browser. If the victim is using Opera Browser then we return a Random PreGenerated Passphrase, "maThLLZqPmrrFS".
This is the passphrase which will be used to decode the XOR encoded payload which we will see later.
There are multiple if statements which will check for the different browser types and depending on that, will return the corresponding passphrase.
There were many places, where you can see, (function () { return x })() entries present. I hope it is obvious that this does nothing but returns the value x.
This section of the code, once again looks similar to our previous "if" expression. It returns the random passphrase, aFaQW if the victim's browser is Chrome.
Continuing this way, we can deobfuscate a considerable amount of the code to the following. I have added in appropriate comments wherever needed:
function wprcm()
{
var uUHIjMJVFJET = navigator.userAgent.toLowerCase(); // retrieves the Browser's User Agent and converts it to lowercase.
if(uUHIjMJVFJET.indexOf(opera) != -1) // is the browser Opera?
{
return maThLLZqPmrrFS; // the Random passphrase which is returned depending on the Browser Type
}
if(uUHIjMJVFJET.indexOf(firefox) != -1) // is the browser Firefox?
{
return (loMAYcXfkUsG);
}
if(uUHIjMJVFJET.indexOf(chrome) != -1) // is the browser Chrome?
{
return (aFaQW);
}
if(uUHIjMJVFJET.indexOf(safari) != -1) // is the browser Safari?
{
return (MjLMl);
}
if(uUHIjMJVFJET.indexOf(msie) != -1) // is the browser Microsoft Internet Explorer
{
return (nUHCmZfyQBLAg);
}
if(uUHIjMJVFJET.indexOf(netscape)) != -1) // is the browser Netscape?
{
return (IrNFOz);
}
if(uUHIjMJVFJET.indexOf(mozilla/5.0) != -1) // Is the browser Mozilla?
{
return VjtxHZHGKWT;
}
return HEeeeYBsTMItYY; // If none of the above User Agents were detected then return this value
for(i = 0; i < FSzQjtHkbuMDLW.length; i++)
{
VfoamYteBIveYp += String.fromCharCode(UMa[FSzQjtHkbuMDLW.substr(i, 1)] ^ UMa[pOtdvHbBav.substr(i, 1)]); // The XOR decoded result is stored in VfoamYteBIveYp
}
return VfoamYteBIveYp; // This returns the XOR decoded String to the eval function to execute
}
Further down in the script we can see a call made to the eval function.
Here, pjSkrbvs() function takes two arguments, one is the unescaped XOR encoded code and the other is the random passphrase required to decode.
It expands the passphrase according to the length of the XOR encoded code.
UMa[String.fromCharCode(i)] stores all the ASCII Values for the characters.
The XOR operation is performed between very character from the XOR encoded string and the expanded passphrase.
The entire decoded code is present inside the variable, VfoamYteBIveYp
This is returned to the eval() function which executes it in the context of the browser. In the next post, we discuss more about the code returned to the eval() function.
The decoded code obtained after running the above XOR decoding routine is once again heavily obfuscated. However, the deobfuscation technique used in the first section of the challenge could be extended here as well.
Here's what you get after deobfuscating the XOR decoded code:
function LcXiYjzTRSKyzv(jkPfjgfRwzD)
{
var yGTthPYIyl = document.createElement(jkPfjgfRwzD);
Selective Symbolic Execution(S2E)
Today, 08:33 AM in Reverse Engineering and Application Cracking