Non-emulated APIs
Antivirus emulator engines only simulate the execution of most
common executable file formats and functions.
Knowing this, we can attempt to bypass detection with a function
that is either incorrectly emulated or is not emulated at all.
In general, there are two ways of locating non-emulated APIs.
The first is to reverse engineer the antivirus emulator,
but due to the highly complex software, this would be very time consuming.
A second, and perhaps simpler, way
is to test out various APIs against the AV engine.
The general concept is that when the AV emulator encounters a non-emulated API,
its execution will fail.
In these cases, our malicious program will have a chance to detect AV emulation
by simply testing the API result and comparing it with the expected result.
For example, consider the Win32 VirtualAllocExNuma API
The “Numa” suffix, which refers to a system design to optimize memory usage
on multi- processor servers, makes this a relatively uncommon API.
In essence, this API allocates memory just like VirtualAllocEx
but it is optimized to be used with a specific CPU.
Obviously, this type of optimization is not required
on a standard single-CPU workstation.
Because of this, some antivirus vendors do not emulate VirtualAllocExNuma
and, in this case, its execution by the AV emulator
will not result in a successful memory allocation.
Let’s try this out with a simple proof-of- concept by modifying the C# shellcode
runner that uses Caesar cipher encryption and the Sleep call.
Sadly, pinvoke.net does not contain an entry for VirtualAllocExNuma,
but we can compare the C type function prototype
of VirtualAllocEx and VirtualAllocExNuma.
Comparing the two function prototypes,
we find that VirtualAllocExNuma has an additional argument
and is a simple DWORD type
This means we can reuse the pinvoke import for VirtualAllocEx
and manually add an extra argument of type UInt32
As for VirtualALlocEx, the Numa version accepts as the first argument the handle
for the process in which we want to allocate memory as the first arugument.
In our case, we simply want to allocate memory
in the address space of the currently running process.
An easy way to obtain a handle to the current process
is with the Win32 GetCurrentProcess API.
This does not take arguments, so the import is rather simple.
The next four arguments for the Numa variant are similar to the
VirtualAllocEx API, which specify the allocated memory address,
the size of the allocation, the allocation type, and the type of memory protection.
We can reuse the values we used previously for VirtualAllocEx.
Lastly, we must specify the target NUMA node for the allocation.
In the case of a multiprocessing computer, this is essentially the CPU
where the physical memory for our allocation should reside.
Since we expect to be on a single CPU workstation,
we'll pass a value of “0” for the first node.
If the API is not emulated and the code is run by the AV emulator,
it will not return a valid address.
In this case, we simply exit from the application
without performing any malicious actions, similar to the implementation using Sleep.
Let’s compile this,
and check it against AntiScan.Me
Very nice! Our new code was flagged by 4 antivirus products.
We've managed to successfully bypass most antivirus product
supported by AntiScan.Me by combining simple encryption and non-emulated APIs.
Now that we’ve had success with our C# shellcode runner,
we can expand our tradecraft to the other attack vectors
including Microsoft Office documents and PowerShell.