Enter Sandbox – part 6: The Nullsoft hypothesis and other installers’ conundrums

Monitoring system services, Native APIs, Windows APIs and COM is a good start, but the monitoring capabilities can be always extended. In this post I will focus on one particular category of monitoring which I believe is often overlooked.

I am talking about installers and their plug-ins – with a main focus on the Nullsoft Installer (although I will talk about various installers in general).

I wrote about, or at least mentioned installers a couple of times before:

The reason why monitoring installers can be interesting is that it may provide an extra insight in the working of a binary (mind you, my focus in this series is on manual in-depth analysis more than automated analysis /although the latter could still benefit from discussed topics too/).

And why is that interesting?

  • There are classes of malware that rely on installers as a main way of infection
  • There are plug-ins (DLLs) dropped and loaded by installers that do stuff that may explain why certain things work/don’t work
  • Some plug-ins offer decoding/decompression/decryption/anti-sandboxing capabilities – intercepting the function calls responsible for this functionality can sched some more light on the internal working of the malware (and help to write behavioral rules)
  • Some installers use encryption and passwords can be only intercepted by analyzing the internals of the installer/plug-ins
  • Some plug-ins use novelty techniques to do stuff – they leverage COM, .NET, WMI to retrieve information about the system, download payloads, etc. – some of these cannot be intercepted on the Native/Windows API level and even if they are, the context is lost
  • etc.

Before we talk about the dynamic analysis of installers let’s look at a typical installer first.

Static analysis of installers

A typical installer contains a stub followed by a compressed/encrypted data blob. The stub is a legitimate program written by whoever designed the installer. As such, its detection cannot be used as a reliable mechanism to detect the malware using the installer since the signature (at least, not w/o looking at the appended data) would ‘hit’ many legitimate applications sharing the very same stub. In other words, the actual malware is never ‘seen’ as it is hidden in the package handled by ‘the always-clean’ stub. Static analysis of such installer files is hard, because it requires someone to write an unpacker – one that is dedicated to that particular installer (or, more specifically – its particular version), test it and then it may hopefully manage to handle a class of installers.

The problem of course is that:

  • there are lots of different installers
  • there are lots of very similar installers, but created either as a result of evolution (subsequent versions, localized versions, ANSI/Unicode, etc.), or private/customized versions that are spin-offs of legitimate repos available online (these f.ex. wipe out markers and change bits here and there to make the unpacking impossible w/o manual effort)

The net result is that it’s hard for the unpacker to handle it all and static analysis may simply not work (again, unless someone uses a dedicated naive algorithmic detection that could work f.ex. like this: detect known stub, check size of appended data, if matches the range, check some specific values in the appended data region, then ‘detect’; of course, such detection is useless since it is not generic as it only matches 1-1. i.e. each file requires a dedicated signature).

Detection of dropped files makes it a bit easier for the AV that can pick them up during run-time, but there is really nothing that could prevent malware author from keeping the main payload inside the actual installer and making it simply run persistently on the system. Having only the installer to look at may be then a relatively difficult target for manual analysis.

Luckily, the RCE community has addressed unpacking of many popular installers and there are plenty of tools that help with a task of a decompilation; including, but not limited to:

  • 7Z decompiles many files, including SFX files; earlier versions of 7z decompile even some versions of the Nullsoft installers
  • InnoSetup installers can be decompiled by Inno Setup Unpacker
  • AutoIt executables (not really installers, but kinda similar) can be unpacked with Exe2Aut
  • There are many decompilers for less popular installers (it’s easy to find them online) – there is also a dedicated project that focuses on decompiling all the possible goodness called Universal Extractor – at this stage project seems to be stalled, but it’s still a very good tool for many installers
  • Many installers can be decompiled by the Plugins for Total Commander: InstallExplorer 0.9.2, InstallExporer Port
  • RARSFX can be decompiled using winrar/rar
  • etc.

There are also ways to extract some data from files f.ex.:

  • 7ZSFX files
    • <installer filename>  -sfxconfig foo – ‘sfxconfig’ is a command that allows to extract comments from the sfx file and save it to a ‘foo’ file – an example of an extracted config file that instructs SFX to execute malware.exe file after the installation is shown below (GUIMode = “2” – hides the GUI/silent mode/, “hidcon:” prefix is used to hide the console window)
      ;!@Install@!UTF-8!
      GUIMode="2"
      InstallPath="%APPDATA%"
      RunProgram="hidcon:%APPDATA%\malware.exe"
      ;!@InstallEnd@!
  • RAR SFX files
    • rar cw foo – ‘cw’ is a command that allows to extract comments from the .rar archive (including sfx) and save it to a ‘foo’ file – an example of a SFX file that instructs SFX to execute malware.exe file after the installation is shown below
      ;The comment below contains SFX script commands
      Setup=malware.exe

So, static analysis are often possible to certain extent and all the available tools can make it relatively painless. I think it would definitely interesting to see some of these commands and tools deployed by commercial sandboxes as well – the reporting capabilities would increase a lot.

Dynamic analysis of installers

Coming back to the dynamic analysis of installers – you may still wonder why would we even go this path if there are so many tools and tricks available on the static level.

Here is why – the example below shows some logs from my PoC monitor for some of the Nullsoft installer Plug-Ins:

Call::in (*(i,i,i,i)i.r1)
Call::out=
Call::in (USER32::GetWindowRect(ir2,ir1))
Call::out=131418
Call::in (USER32::MapWindowPoints(i0,ir0,ir1,i1))
Call::in (*1545016(i.r6,i.r7))
Call::in (USER32::GetClientRect(ir2,ir1))
Call::in (*1545016(i,i,i.r8,i.r9))
Call::in (*1545016(i,i,i.r3,i.r4))
Call::in (USER32::SetWindowPos(ir2,i,i,i,ir3,ir4,i6))
Call::in (USER32::CreateWindowEx(i0,t "Button",t "Make JeezBar my default search engine",i 0x40000000|0x10000000|0x04000000|0x00010000|0x00000000|0x00000C00|0x00000003|0x00002000,ir6,ir7,ir8,ir9,ir0,i666,i0,i0)i.r2)
Call::in (USER32::CreateWindowEx(i0,t "Button",t "Make JeezBar my home page",i 0x40000000|0x10000000|0x04000000|0x00010000|0x00000000|0x00000C00|0x00000003|0x00002000,ir6,ir7,ir8,ir9,ir0,i667,i0,i0)i.r3)
Call::in (USER32::CreateWindowEx(i0,t "Button",t "Restart IE (if running)",i 0x40000000|0x10000000|0x04000000|0x00010000|0x00000000|0x00000C00|0x00000003|0x00002000,ir6,ir7,ir8,ir9,ir0,i668,i0,i0)i.r4)
Another example:
Call::in (kernel32::GetTickCount()i .r0)
Call::out=4847734
get (szURL=hxxp://dyn.flingstone.com/trackedevent.aspx?ver=0.0.1.0&rnd=4847734)
Call::in (kernel32::CreateMutexA(i 0, i 0, t "thlp_mutex") ?e)
Call::out=SendRequest Error
Call::out=1706281
get (szURL=hxxp://dyn.flingstone.com/vic.aspx?ver=0.0.1.0&rnd=1706281)

As you can see, you can instantly discover the internal working of the Nullsoft Script used by the installer. None of these can be seen using standard API monitoring. If you do offline analysis, intercepting some of these calls may give you clues that otherwise would be very difficult to obtain (e.g. URLs, sequence of internal instructions, possible log messages, etc.).

Now, how would you go about monitoring these calls?

Since I mentioned I am going to talk about Nullsoft Installer I will focus only on this particular installer here.

At this moment of time, there are at least 430+ versions of Nullsoft Installer in my repository. These come in various forms and flavors – lots of various versions, localized, etc. there is probably more than I counted, since many of the custom ones are modified in a way that makes it harder to distinguish them in a generic fashion. In any case, it’s quite a variety.

What is common about all these varieties is that they share plug-ins; there are a couple of plug-ins that are very popular f.ex. system, or inetc, execcmd, nsprocess, etc., but this is just a tip of the iceberg…

After running some scripts and hacking things around I counted over 5K different Nullsoft plugins and it was quite a clumsy work, so I bet there is at least twice as much 🙂

Where do we take it from here? Pick up the most common ones, recognize them when they are loaded, hook their functions and … log them.

Just in case it was not obvious yet – note that despite sharing the same name you may find out that many of them are different between each other – some process ANSI, some Unicode. Some implement additional functionality and all they share is a name and… a list of exported functions.

In any case, for the most popular ones it may add an extra layer of information and help in creating behavioral rules as well as getting to understand the inner workings of the samples.

Using race conditions as an antisandbox trick

I have had this idea for a while and today I finally implemented it. My implementation is pretty lame and only affects those monitoring solutions that rely on hooking or intercepting user-mode APIs, but after all – it’s just a proof of concept (plus, there is a possibility of implementing similar thingie in the kernel mode too).

So the idea goes as follows:

In a typical scenario, when an execution flow hits the address of the API (the one that sample is calling), the API hook takes over, or the monitor intercepts the fact API was called by recognizing that instruction pointer hits the known address of the API. Once it happens the monitor intercepts the function arguments and these will end up in the logs. Some monitors also intercept the moment when the API returns – this is handy as it allows to intercept buffers modified by the APIs.

Of course, the need to either hook APIs, or recognize when they are called is a subject to many evasions f.ex.:

  • one can detect hooks
    • checking if functions are starting with JMPs or short privileged instructions
    • cross-referencing their code with a code read directly from DLL’s image (a file)
    • one can use a legitimately looking call and trace it (using f.ex. TF=1) to see if the code is going through any region not mapped to a legitimate OS DLL (this usually means either malware, or some monitor is hooking APIs)
  • one can bypass hooks/tracers/monitors
    • using stolen bytes (length disassembler to copy code to a diff. buffer and call API via such a trampoline)
    • one can call the address a bit earlier than API (typically APIs are preceded by a series of NOPs) – this is not a strong evasion per se, but it may fool a tracer/emulator trying to match call instruction operand with the list of actual addresses of APIs that this operand is potentially pointing to
    • one can call the address a bit further than the API start (‘mov edi, edi’ is usually there and can be skipped; some common  instructions f.ex. ‘push’ can be emulated)
  • one can use a different API (lots of them do the very same thing)
  • one can use functions inline
  • etc.

It crossed my mind that one could instrument the code execution in a way that it would allow the monitor to intercept calls to APIs and their arguments, while it would quietly swap arguments ‘on the fly’ after it passed the monitoring stage.

So it would look like this:

  • program calls an API
  • monitor/interceptor takes control/recognizes API & logs its arguments
  • right before the control is passed to OS to actually execute the API and do the thing it is supposed to do (e.g. create file), the argument (f.ex. file name) would get swapped to something else

The result would be that the report would contain a reference to incorrect argument(s) – suffering from falsified reality, because it failed to recognize a race condition.

One may need to choose carefully when the code needs to swap the arguments of the potentially monitored function. It’s not an easy task since there is a lot of things going on and finding an internal point in the food chain where this data could be swapped may be tricky. Many internal functions copy data from one buffer to another and once the data is copied it cannot be controlled/modified anymore in an easy way.

Assuming we can control the data we can come up with some ideas on how to mod it.

The simplest way could be to choose a ‘lower’ API in the food chain so f.ex. program could be calling Sleep API, while at the same time hooking NtDelayExecution to patch the data passed to Sleep with something else. Monitors logging Sleep would get fooled. Obviously, most of sandboxes nowadays monitor NtDelayExecution so it wouldn’t work in real life, but it’s just one of the ideas.

Another way to detect access to the data could rely on leveraging page access rights (e.g. page guard, or no access), or – alternatively – a null selector. This could guarantee that access to data (either specific one that we want to monitor, or _any_ data if null selector was used) would generate an exception. An exception handler could then swap the data somewhere ‘inside’ of the monitored API – still, it would be _after_ its arguments have been already logged.

In my PoC I used a simple method of monitoring execution of the API via tracing (TF=1); once the tracing exception handler finds the code transitioning execution to kernel mode (f.ex. sysenter instruction) it checks if the argument on the stack is coming from the API call we monitor; it it is the case, it swaps the data.

So, the flow goes like this:

  • Set up an exception handler
  • Enable tracing (TF=1)
  • Execute NtCreateFile API with the file name \??\c:\good
  • Monitor execution via tracing; the moment sysenter is detected, check if it is indeed our monitored NtCreateFile – if it is, swap the file name to \??\c:\evil
  • OS should create \??\c:\evil file while monitors should report \??\c:\good was created

When the below test program is ran on the normal system (or inside VM) it creates the file \??\c:\evil.

What happens if it is monitored? I submitted it to a couple of sandboxes and ran it via some of the available API monitors and I got mixed results. Some of the API monitors got fooled. Some sandboxes I tested it with didn’t really show any results. Some monitors made the program create \??\c:\good file (which is an unexpected behavior; could be a bug in my PoC 🙂 ), and some sandboxes reported \??\c:\good as well, but I have no way of checking if the actual file was created, or was it my PoC actually fooling the monitor…

If you want to play around, here is the file.

Note: when you run it you need to kill it manually. This is because seeing that sandboxes didn’t report anything, I added to it a dummy loop that never ends. It creates 〰〰〰 file and then is sleeping for 70 miliseconds not to steal all the CPU cycles.