Detecting Process Injection using Microsoft Detour Hooks

This blog post discusses using Microsoft Detours to add hooks for common APIs in an attempt to detect and prevent process injection.

Introduction

Microsoft Detours "is a software package for monitoring and instrumenting API calls on Windows". The package provides a generic and safe way to implement hooks for x86 and x64 Windows APIs, allowing for monitoring, tampering, or anything else that you'd want to do with an API hook.

This blog post will cover how to get started with detours, with the objective of being able to detect and prevent a typical example of process injection used by common attack frameworks. We'll build a DLL that when loaded into a process will hook the relevant API calls, and then kill the host process if the sequence of API calls indicates that process injection is about to occur.

To put it another way, we will try and create the most basic, simplistic, and probably incorrect example of a "Next-Generation Anti-Virus" (NGAV). Suffice to say the content of this blog should not be taken with any seriousness in terms of detecting real threats; it does not factor in performance, stability, false positives, or any of the other factors that enterprise security products have to address.

Getting Started with Detours

Hooking MessageBoxA

Getting started with Detours is very simple, we can use NuGet to install the package and the we just need to include the library at the start of our project file:

After we've added the library to our project we can hook our first function. For this initial proof of concept we will build a DLL that when injected into a process will hook the MessageBoxA function:

#include "windows.h"
#include "detours.h"

// Remap MessageBoxA to a new function name
INT(WINAPI* oMessageBoxA)(HWND,LPCSTR,LPCSTR,UINT) = MessageBoxA;

// Define our hook, match return code and parameters
int WINAPI hookedMessageBoxA(HWND hWnd,LPCSTR lpText,LPCSTR lpCaption,UINT uType)
{
    // Do our "evil"
    // Change the text of the MessageBox
    lpText = "Hooked!";
    return oMessageBoxA(hWnd,lpText,lpCaption,uType);
}

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {
        // Initialise detours and commit them
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourAttach(&(PVOID&)oMessageBoxA, hookedMessageBoxA);

        LONG lError = DetourTransactionCommit();
        if (lError != NO_ERROR) {
            MessageBox(HWND_DESKTOP, L"Could not add detour", L"Detour Error", MB_OK);
            return FALSE;
        }
    }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

Implementing our first function hook is trivial as we can see from the code snippet above. The majority of the code is just boiler plate DLL code, but lines 5 - 13 are what we really care about. On line 5 we define a new function to map to the original MessageBoxA function, this allows us to call the original function after our hook has run. Note that it has the same return code and parameters as the real MessageBoxA function:

Lines 7 - 13 are where we define our "hook" code. We copy the return code and parameters of the real MessageBoxA function, do our "evil" (in this case changing the text displayed in the text box to 'Hooked!') and then return execution back to the original MessageBoxA function.

Finally, to implement the actual hook we call DetourTransactionBegin(), DetourUpdateThread(), DetourAttach() and DetourTransactionCommit() on lines 26 - 30. That's all that's needed to set up our hook, considerably easier that doing it all manually eh?

Now that we have a DLL that we can inject into a process to initiate our hook, let's test it out to make sure it works. In the screenshot below I created a simple program that will call MessageBoxA and display the text "Hi". It will then wait for user input and then display the message box again. In the break while it's waiting for user input, we can inject our DLL using Process Hacker, and if our hook works then the second call to MessageBoxA should have its text change to "Hooked!".

MessageBoxA Demo

Detecting Process Injection

Now that we know how to hook APIs using Detours we can try something a little more interesting than just hooking MessageBoxA. Instead, let's create a DLL that - when injected into a process - will hook the common API calls used for process injection, and then kill the host process if process injection is about to occur.

Process Injection Primer

Discussing process injection (and the different variations) in detail is outside of the scope of this blog post in an attempt to keep the length of this post reasonable. In a nutshell typical process injection utilises the following API calls:

  1. OpenProcess - Used to get a handle to the target process of the process injection.

  2. VirtualAllocEx - Used to allocation memory in the target process to hold the payload.

  3. WriteProcessMemory - Used to write the payload to the memory region in the target process.

  4. CreateRemoteThreadEx - Used o create a thread in the target process to execute the payload memory region.

Building our DLL

#include <windows.h>
#include "detours.h"

BOOL openProcessCalled = FALSE;
BOOL virtualAllocExCalled = FALSE;
BOOL writeProcessMemoryCalled = FALSE;
BOOL createRemoteThreadExCalled = FALSE;

// Hooked API calls related to standard process injection
LPVOID(WINAPI* oOpenProcess)(DWORD, BOOL, DWORD) = OpenProcess;
LPVOID(WINAPI* oVirtualAllocEx)(HANDLE, LPVOID, SIZE_T, DWORD, DWORD) = VirtualAllocEx;
BOOL(WINAPI* oWriteProcessMemory)(HANDLE, LPVOID,LPCVOID,SIZE_T,SIZE_T*) = WriteProcessMemory;
HANDLE(WINAPI* oCreateRemoteThreadEx)(HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPPROC_THREAD_ATTRIBUTE_LIST, LPDWORD) = CreateRemoteThreadEx;

// Our hook function for OpenProcess
LPVOID WINAPI hookedOpenProcess(DWORD dwDesiredAccess,BOOL bInheritHandle,DWORD dwProcessId)
{
    openProcessCalled = TRUE;
    return oOpenProcess(dwDesiredAccess,bInheritHandle,dwProcessId);
}

// Our hook function for VirtualAllocEx
LPVOID WINAPI hookedVirtualAllocEx(HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect)
{
    virtualAllocExCalled = TRUE;
    return oVirtualAllocEx(hProcess, lpAddress, dwSize, flAllocationType, flProtect);
}

// Our hook function for WriteProcessMemory
BOOL WINAPI hookedWriteProcessMemory(HANDLE hProcess, LPVOID lpBaseAddress, LPCVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesWritten)
{
    writeProcessMemoryCalled = TRUE;
    return oWriteProcessMemory(hProcess,lpBaseAddress,lpBuffer,nSize,lpNumberOfBytesWritten);
}

// Our hook function for CreateRemoteThreadEx
HANDLE WINAPI hookedCreateRemoteThreadEx(HANDLE hProcess, LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, LPDWORD lpThreadId)
{
    // Naive logic to see if the APIs related process injection have been called
    // This is a very hacky PoC chaeck 
    if (openProcessCalled && virtualAllocExCalled && writeProcessMemoryCalled) {
    
        // Setup to reflective injection detected. Kill the process
		    MessageBox(HWND_DESKTOP, L"Reflective injection detected. Killing Process.", L"Poor Mans NGAV", MB_OK);
        DWORD killPid = GetCurrentProcessId();
        HANDLE hKillProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, killPid);
        TerminateProcess(hKillProcess, 0);
    }
    return oCreateRemoteThreadEx(hProcess, lpThreadAttributes, dwStackSize, lpStartAddress, lpParameter, dwCreationFlags, lpAttributeList,lpThreadId);
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {
        // Apply our hooks
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourAttach(&(PVOID&)oOpenProcess, hookedOpenProcess);
        DetourAttach(&(PVOID&)oVirtualAllocEx, hookedVirtualAllocEx);
        DetourAttach(&(PVOID&)oWriteProcessMemory, hookedWriteProcessMemory);
        DetourAttach(&(PVOID&)oCreateRemoteThreadEx, hookedCreateRemoteThreadEx);

        LONG lError = DetourTransactionCommit();
        if (lError != NO_ERROR) {
            MessageBox(HWND_DESKTOP, L"Could not add detour", L"Detour Error", MB_OK);
            return FALSE;
        }
    }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

The code above is by no means safe to use in any meaningful way. It is simply an example of how hooking could be used to detect/prevent process injection. False positives and performance issues have not even slightly been considered.

The code above is a fairly straight forward extension to the MessageBoxA example that we built previously, except instead of just hooking the MessageBoxA function we are hooking the API calls that take place in the lead up to process injection.

For the most part the majority of our API hooks just toggle a boolean which allows us to track which APIs have been called. Then in line 41 - in the hook for the CreateRemoteThreadEx API - we have some simple logic that checks whether all of the other APIs have been called. If they have, then we get a handle to the current process and kill it. All going well, when we inject this DLL into a process, if that process attempts to perform process injection we should be able to catch the behaviour and kill the host process before the injection occurs.

Does it Work?

To test whether our DLL will work correctly we need a simple way to perform process injection. Fortunately we can stand on the shoulders of other people's research and use the Invoke-ReflectivePEInjection Powershell script from PowerSploit:

Before we use the process injection script we need to load our DLL into the Powershell process memory space. To simplify this process and to emulate a poor man's NGAV, I built a simple "supervisor" program that will inject our DLL into any powershell processes that are created.

Process Injection without our Hook

Process Injection with our Hook

As you can see from the GIF above, our supervisor service injected our DLL into the powershell process that was created. When we then tried to use the Invoke-ReflectivePEInjection script to inject into another process, our hooks detected the lead up to the process injection and then ultimately killed the process!

References

Last updated