ApiScout: Painless Windows API information recovery

After hacking away for some days in the code chamber, I'm finally satisfied with the outcome and happy to announce the release of my new library: "ApiScout".
The main goal of ApiScout is to allow a faster migration from memory dumps to effective static analysis.

While reverse engineering "things" (especially malware), analysts often find themselves in a position where no API information is immediately available for use in IDA or other disassemblers.
This is pretty unfortunate, since API information is probably the single most useful feature for orientation in unknown binary code and a prime resource for recovery of meaning.
Usually, this information has to be recovered first: for example by rebuilding the PE ("clean unpacking", using ImpRec, Scylla, or similar) or by recording information about DLLs/APIs from the live process to be able to apply it later on (see Alex Hanel's blog post).

Both methods are potentially time-consuming and require manual effort to achieve their results. From my experience, clean unpacked files are often not even needed to conduct an efficient analysis of a target.
As I did a lot of dumping when reversing malware over the last years (and especially for malpedia - project outlook slides here), I craved for a more efficient solution.
Initially, I used a very hacky idapython script to "guess" imports in a given dump versus an offline DB - the limitations: 32bit and a single reference OS only.

After talking to some folks who liked the approach, I decided to refactor it properly and also integrate support for 64bit including ASLR.

TL;DR (Repository): ApiScout

To show the usefulness of this library, I have written both a command line tool and IDA plugin, which are explained in the remainder of this blog post.

First, let's have a look at a more or less common situation.

A Wild Dump Appears

For the purpose of illustration we use 1e647bca836cccad3c3880da926e49e4eefe5c6b8e3effcb141ac9eccdc17b80, a pretty random Asprox sample.

Executing it yields a very suspicious new svchost.exe process.

Running the Asprox sample results in a new suspicious scvhost.exe process.

Inspecting the memory of this new process reveals a not less suspicious memory section with RWX access rights and a decent size of 0x80000 bytes.
However, apparently the PE header got lost as can be seen on the left:

Looking closer at the process memory, we find a RWX segment @0x008D0000.

Luckily the import information is readily available:

Left (Hex view) /Right (Address view): Import Address Table (IAT) as found inside of the RWX segment.

With ImpRec or Scylla, we would now have to point to the correct IAT instead of using the handy IAT autosearch, because autosearch would identify the IAT of svchost.exe instead of Asprox' (see comparison left vs. right).

Left: Scylla IAT Autosearch gives IAT of svchost.exe, but we want ...
Right: IAT of Asprox - which we can't dump since PE header is missing.

But we now encounter another issue: Because there is no PE header available, Scylla fails to rebuild the binary and with that, the imports.
Granted, many injected memory sections will have more or less correct PE headers or we could write one from scratch...
But remember, I promised "painless" recovery in this blog post's title.

ApiScout: command-line mode

As I explained before, if we have all relevant API information available, we can directly locate IATs like the one of the above example.
So let's first build an API DB:

Running DatabaseBuilder.py to collect Windows API information from a running system.

While DatabaseBuilder.py is fully configurable, using Auto-Mode should yield good results already.

Next we can use the database to directly extract API information from our dump of memory section 0x008D0000:

Resultof running scout.py with the freshly build API DB against a memory dump of our injected Asprox.

Since this cmdline tool is just a demo for using the library, this should give you an idea of what can be achieved here.
For our example memory dump (76kb), I timed the full recovery (loading API DB, searching, shell output) on my system at about 0.3 seconds, so it's actually quite fast.

I am aware that this may occasionally lead to False Positives but there is also a filter option as a simple but effective measure: It requires that there is at least another identified API address within n bytes of neighbourhood - from my experience this is already enough to reduce the already very few FPs to an absolute minimum.

IDA ApiScout: fast-tracking import recovery

In this section, I want to showcase the beautified version of my old hacky script.
I assume it can be similarly adapted for others disassemblers like radare2, Hopper, or BinaryNinja.

Loading ida_scout.py as a script in IDA shows the following dialog in which an appropriate API DB can be selected.
Note that imports are not resolved as we loaded the memory as a binary (not PE) at fixed offset 0x008D0000:

ida_scout.py shows the available API DBs or can be used to load a DB from another place.

Executing the search with the WinXP profile from which Asprox was dumped, we now get a preview of the APIs that can be annotated:

Selection/Filter step of identified API candidates.

Aaaaand here we go, annotated API information:

Yay, annotated offsets in IDA as if we had a proper import table!

And yes, it's just as fast as it seems, clicking through both windows and having API information ready to go took less than 10 seconds.

That's what I call painless. :)

Dealing with ASLR

For simplicity's sake the above example was executed on WinXP 32bit, with no ASLR available.
However, it works just as fine for more recent versions (I use Windows 7 64bit), both for 64bit dumps or 32bit compatibility mode dumps.
In case you haven't disabled ASLR on your reference system, this section explains how ASLR offsets are obtained for all DLLs that are later stored in the DB.

I will skip explaining ASLR in detail, but feel free to read up on it, e.g. this report by Symantec.

The first step of DLL discovery is identical to non-ASLR systems and performed by DatabaseBuilder.py.
At the end of the crawling process (which involves collecting the ImageBase addresses as stated in the PE headers of all DLLs), we perform a heuristic check if ASLR is activated: We obtain a handle (which equals the in-memory BaseAddress) to three DLLs (user32.dll, kernel32.dll, and ntdll.dll) via GetModuleHandle() and check if the respective corresponding file as identified with GetModuleFileName() shows an identical ImageBase. If at least one DLL differs, we assume ASLR is active.

Since every DLL receives a individual ASLR offset, we will have to make sure that every DLL of interest has been loaded at least once.
For this purpose, I wrote a little helper binary "DllBaseChecker[32|64].exe" which simply performs a LoadLibrary() on a given DLL path and returns the load address.
Iterating through all DLLs identified in the discovery step, we are now able to determine each individual ASLR offset by subtracting file ImageBase and load address.

Closing Note

While this approach probably is certainly no magic or rocket science, I haven't seen it published in this form elsewhere yet. At least to me, it provides great convenience in several ways and I hope that one or the other can benefit from it as well.

For future use, I imagine it being used manually as shown in the post or potentially in automated analysis post-processing chains, where this functionality may come in handy.

I have to admit that I misjudged the effort to do code this in a nice way (by about a week of release-time) but I want to thank @herrcore for motivating me to rewrite and release it and @_jsoo_ for pushing me to address ASLR properly with the initial release version.

Code is here: ApiScout

As I want this to become a tradition: this blog post was written while listening to deadmau5's new album "stuff I used to do". :)

Keine Kommentare:

Kommentar veröffentlichen