Let's now discuss API monitoring in more detail.
API monitoring usually consists of injecting a monitoring
DLL inside newly created processes.
The injection is usually performed by Kernel‑mode driver, as explained
previously. The DLL will leverage an API hooking library, such as Microsoft
Detours, to hook frequently abused APIs. By hooking an API, the antivirus can
ensure that if the monitored API is called,
the call will be proxied by the monitoring DLL, which gives the
antivirus an opportunity to log the parameters which were passed to
the API. Antivirus and EDR solutions will usually monitor APIs which
are regularly used by evasive malware.
We saw previously that the reflective DLL injection library used by
Meterpreter makes use of the GetProcAddress, VirtualAlloc, and LoadLibraryA
APIs, so one could expect those APIs to be monitored.
Other APIs which can be used to inject malicious code into other
processes, such as WriteProcessMemory and CreateRemoteThread, are also
interesting targets for antivirus and EDR solutions.
Here is an example of what API monitoring looks like.
On the left‑hand side we have a process which is
being monitored by an antivirus.
The antivirus driver has loaded and launched its API monitoring DLL inside the
process, which in turn has hooked all functions which it needs to monitor.
When an API call is performed by the process, all the parameters are passed to
the API monitoring DLL, which can send them to the antivirus so that it can
perform heuristic analysis.
The API monitoring DLL will then call the original API on behalf
of the caller and return the result to him,
which once again gives the antivirus an opportunity to
analyze the values returned by the API call.
On the right‑hand side, you can see what this process looks like
when no API monitoring is being performed.
As you can see, when the calls are made, the antivirus has no visibility inside
the process and therefore cannot perform heuristic analysis.
You may at this point be wondering why this monitoring cannot
be performed inside the kernel, this would avoid having to
inject a monitoring DLL into every user mode process running on
the system. Unfortunately, or fortunately,
depending on the way you look at it,
Microsoft enforces a technology called Kernel Patch Protection,
also known as Patch Guard,
which prevents antivirus companies from performing this kind of monitoring
from inside the Kernel. This technology was originally meant to protect
systems from rootkits, but has the unfortunate side effect of forcing
antivirus monitoring inside user mode,
which as we will discover in later modules, makes this
approach somewhat flawed by design.
Let's take a closer look at API monitoring in practice.
I have placed the shellcode runner, which we wrote during the
Meterpreter deep dive module, on the desktop.
I have also placed a folder on the desktop called EDR, which contains two
monitoring DLLs, as well as the driver which will register the callback
routines and trigger the DLL injections when new processes are started on the
machine. I have configured the monitoring DLLs to hook the VirtualAlloc and
LoadLibraryA APIs, which if you recall, were used in our shellcode runner, as
well as the stager itself.
When calls are made to these two functions,
the monitoring DLLs will print the recorded activity to a
debugger. Real antivirus or EDR would of course send this data to
its engine to perform analysis on the data.
To start the driver, I ran the sc start command from an elevated command prompt.
Once started,
I opened Debug view to capture the debug messages which
are recorded by the monitoring DLLs.
I then launched the stager shellcode runner, which is located on the desktop.
If we take a look at the running processes and search for our shellcode runner,
you will notice it has the process idea of 936. Since this
was compiled as a console application,
you will also find a child process which was spawned, called conhost.exe,
which has a process ID of 8968. If we observe the
loaded modules in Process Explorer,
we can also see that our monitoring DLL has indeed been injected.
If we now move over to Debug view,
we can see that the driver's callback routine was notified by the
kernel that these two processes were being spawned and was able to log
the command line used to start them. Next, we can see that the DLL was
successfully injected into both the conhost and shellcode runner
processes by the driver.
We can also see that the hooking library which the DLLs are using was able to
hook the LoadLibraryA and VirtualAlloc APIs successfully.
We can then see that a call to VirtualAlloc was made
by the shellcode runner's process.
You may recall that our shellcode runner does indeed allocate memory using
this API in order to place the stager's shellcode into it.
In this case, I chose to log the start address of the memory to be allocated,
which in our case is null. If we take a look at the prototype on MSDN for
VirtualAlloc, we can see that this is normal and this means that the system
will choose where the memory is allocated.
I also logged the memory protection value which was passed to the API.
In our case, we can see that the value was hex 40.
If we look this up on MSDN once again, we can see that this
corresponds to readwrite execute memory permissions.
We can then see that a call to LoadLibrary was performed
by the shellcode runner's process.
You may recall that the reverse HTTP Meterpreter stager's shellcode makes
use of this API to load the WinINet library in order to leverage some of
its exported functions to communicate over HTTP. We can then see a few
more calls to VirtualAlloc. These were probably performed by the
reflective DLL injection library when execution was transferred to the
payload by the stager. We can then see that several calls were made to
LoadLibrary.
This is also due to the reflective loader, which went ahead
and loaded the payload's dependencies.
As you can imagine,
the monitoring DLLs could also have logged the return parameters
of the various API calls which were performed.
A good example would be the calls to VirtualAlloc, which return
a pointer to the allocated memory buffer.
An antivirus could trigger a scan of that memory after it
has been allocated to check if it contains specific byte
sequences at various offsets.
A good example of a static byte sequence are the Rot13
hashes we talked about previously,
which are commonly used by antivirus solutions.
Another big giveaway is the fact that the calls to VirtualAlloc all
requested readwrite and execute memory pages,
which is a good sign that code might be obfuscated and will
be deobfuscated in memory at runtime.