Recently, in response to a customer incident we needed to reverse engineer a malware sample of WhiteRabbit ransomware that proved to be tricker than expected. As we’ll see, this sample maps a PE into memory with a stomped header, making it hard to reverse engineer.

A screenshot of a computer Description automatically generated

The fix-stomped-imports Binary Ninja plugin introduced below will allow us to reconstruct the Import Address Table so that we can statically see what API calls are being made by the malware.

github GitHub: https://github.com/nettitude/binja-fix-stomped-imports

Upon receiving the sample, we started with the basics and after some simple triage looked to see if the sample was packed. We open it up in x64dbg and placed a breakpoint on VirtualAlloc & friends after hitting the PE entry point and sure enough, we saw a Read-Write-Execute (RWX) allocation that gets called into from the source PE.

A screenshot of a computerDescription automatically generated

Our VirtualAlloc breakpoint to check for any basic memory allocations. Note also the fourth argument for the requested allocation is 0x40 (RWX permissions).

A screenshot of a computer programDescription automatically generated

The return value of VirtualAlloc in EAX after the function was invoked. The allocated memory region is at 0x8a0000.

A screenshot of a computer programDescription automatically generated

Continuing debugging, the sample PE calls into the new region at offset 0x3f60.

Dumping the memory region to a file however produces an interesting result, as the region doesn’t initially look like a PE as it has no recognisable headers such as the MZ header or section table. Scrolling through the region however sets the spidey-senses tingling as it smells like a PE. There are areas with bytes that start at page-aligned offsets with null padding between them, as well as a region with some strings that looks like an Import Table.

A screenshot of a computerDescription automatically generated

The start of the allocation has some bytes, but nothing that looks like a PE MZ header.

A screenshot of a computerDescription automatically generated

Further into the region we find a number of strings with DLL and function names. This looks like an Import Table.

We dump the allocated memory region to a file so we can statically reverse engineer the second stage.

We can open up and rebase the dumped file in a decompiler like Binary Ninja to start to better understand what the second stage is doing, however as the dump is not a valid PE we have to load it as straight up shellcode.

A screenshot of a computer programDescription automatically generated

We open up the dumped file in Binary Ninja, setting the base address and entry point with the offset the sample used when it called into the region (0x3f60).

Obviously, we don’t have any sections like a PE, but the sample doesn’t appear to be simple shellcode either as it makes calls to offsets similar to the way a PE does, but without the headers we have no way of knowing what these calls are.

A screenshot of a computerDescription automatically generated

There are no PE sections as the file is not a valid PE.

A screenshot of a computer programDescription automatically generated

There are calls to functions in external memory regions that match where system DLLs are likely to be loaded (0x7…….).

Looking at the offsets we see something that resembles an Import Address Table (IAT).

A screenshot of a computerDescription automatically generated

The data variables all point to external memory addresses in similar memory regions, like in an IAT.

If we return to debugging the sample after it calls into the allocated region, we can see that there are indeed calls to API functions, and following the offset we see what looks like the IAT.

A screenshot of a computer programDescription automatically generated

The sample does indeed make direct API calls from a pointer at a data variable – like an IAT.

A screenshot of a computerDescription automatically generated

We can follow this data variable to see where it leads.

A screenshot of a computer programDescription automatically generated

Viewing the resultant location in the dump as an address shows it is an IAT.

A screenshot of a computer programDescription automatically generated

The IAT when viewed as an address.

We want to apply this table to our dump in Binary Ninja, luckily the Binary Ninja API is great to work with, and it provides easy functions to add segments, sections, update data variables as well re-perform the analysis.

To that end, we created a plugin for Binary Ninja that does exactly that. Fix-stomped-imports will take an IAT dump and then create the relevant sections and fix the imports before updating the analysis.

A black screen with textDescription automatically generated

The top level of the plugin, which runs as a background thread to avoid locking the UI.

parse_iat_dump will take a paste of the IAT from x64dbg (or similar tools) and parse it to extract the imports and their offsets.

A screen shot of a computerDescription automatically generated

Parsing an IAT dump to extract the function names, offsets and addresses.

create_memory_regions creates the relevant segments and sections for the imported functions at the correct memory regions by iterating over the values and creating a section that is page aligned with the lowest address in the imports and the appropriate length. We then need to update the analysis so that when we add imports Binary Ninja knows what we are updating in this new section.

A computer screen shot of a black screenDescription automatically generated

Creating the new section for the external functions.

fix_imports then gets the function from the platform type libraries, if it exists, and update the data variable at the import address with the function prototype so that the correct arguments, etc, are shown. The name of the data variable is also updated to that of the function, as in a real IAT.

A black screen with colorful linesDescription automatically generated

Updating the data variables to build the IAT.

Once this has completed, it should be much easier to see what is going on in the sample. Trying this out, we copy the lines from the IAT in x64dbg.

A screenshot of a computer programDescription automatically generated

Select the IAT in x64dbg and copy the lines.

We invoke our plugin from the plugins menu and then just paste in the IAT dump from x64dbg:

A screen shot of a computerDescription automatically generated

Pasting the lines into the plugin dialog.

As expected, this successfully creates the externs section and the IAT.

A screenshot of a computerDescription automatically generated

A new externs section is created.

A screenshot of a computer programDescription automatically generated

The IAT has been built.

The function bodies of the imports are still empty, but that doesn’t matter for our analysis as the function name and arguments are populated in our sample and we can now much more easily reverse engineer exactly what it is doing.

A screen shot of a computer screenDescription automatically generated

The external functions.

A screenshot of a computer programDescription automatically generated

The updated analysis showing the API call and arguments.

A screenshot of a computer programDescription automatically generated

Another API call with more arguments.

A screenshot of a computer programDescription automatically generated

One more for good measure.

The code for this plugin is available on GitHub and the plugin itself has been submitted to the Binary Ninja team for approval.

github GitHub: https://github.com/nettitude/binja-fix-stomped-imports

Many thanks to the Binary Ninja team for their assistance in writing the plugin as well as to @herrcore from OALabs for his assistance with a similar sample. @herrcore was also able to determine that similar samples do in fact use a modified, obfuscated PE header, and has content available to go over this.