By: Shiran Grinberg
A story about the usage of Integral OS features to Intercept data while evading detection.
The Windows operating system, or virtually any other OS for that matter, is composed of various compartmentalized software components, operating in tandem to provide the end-user experience. Many times, third-party software also relies on these components to function. In order to allow these various components to correctly interact with each other without causing conflicts, Application Programming Interfaces (or APIs for short) are used. When software A requires a certain functionality offered by OS component B, it invokes one of B’s available functions, defined in its API.
In Windows, the core set of functions that the OS makes accessible to software components is handled through the Windows API, also known as WinAPI. WinAPI provides access to base services like filesystem, processes, and threads; and to higher level services such as UI control, and networking (for example NetBIOS and RPC)
As powerful as WinAPI is, sometimes developers want to modify its functions’ code flow to extend or alter software functionality. For example, as I am writing this article and pressing the keys on my keyboard, the OS is generating WM_KEYDOWN messages and delivers them, using an API, to my text editor for processing. But what if I were to install a third-party software that spell-checks my input as I type? The software would have to receive the message intended for my text editor, process it, and then hand the processes’ result over to the text editor—acting as a man-in-the-middle, of a sort.
This is called Hooking—the process by which an application intercepts an API call between two other applications. In the example above, the intercepting function (called a hook procedure) altered the data passed onto the recipient (the text editor), but that is not always the case.
To enable this behavior, Windows supplies developers with a mechanism for intercepting events, messages, and certain API calls, called hooks. For each hook, Windows handles a separate hook chain, listing the pointers to all of the hook procedures associated with it. To use a hook, developers call the WinAPI SetWindowsHookEX function. When an event occurs that is monitored by a hook, such as the aforementioned keystroke event, the OS calls the first procedure in the hook chain. This procedure executes, then either invokes the next procedure in the hook chain or breaks it. Notice that some hooks only enable the chain to monitor the data flow but not to change it. These hooks also do not enable subordinate procedures to break the chain.
Hooks can either be global—meaning they apply to all relevant events within the scope of their desktop, or thread-specific. If the hook is global, the hooked procedure must be stored in a separate DLL file, so different threads can share it.
In the description above, the Windows SetWindowsHookEX was used to hook a function, but other methods exist that do not rely on this function.
As executables run, they may require loading shared DLL modules to extend their functionality. However, the memory addresses of these modules and the required functions within them are bound to change across different endpoints and OS releases. Because of that, executables need a way to dynamically locate their required DLLs during runtime.
To achieve this, an array called an Import Address Table (IAT) is included in the executable’s code, listing all of the required DLL files and specific functions to import from them. Upon execution, the Windows Dynamic Linker scans this array, locates the DLLs’ memory addresses, and then adds them to it.
However, the IAT can be used to hook functions listed within it. When performing IAT hooking, the pointers in the table are replaced with pointers to hook procedures. That way, all calls from the executable to the original function are actually directed to the hooked procedure, which now runs as a man-in-the-middle between the executable and the hook.
Inline Hooking is mostly relevant to user-space processes. When performing an inline hook, the first several bytes of the target function are overwritten in memory with a jump command to the hook procedure. That way, whenever the target function is called, the hook procedure runs first.
Generally, the hook procedure executes and then invokes a function containing the overwritten bytes. Afterwards, the rest of the target function executes. In this case, the hook procedure is usually called a Detour Function, and the function containing the overwritten bytes is called a Trampoline.
API hooking is a vital part of how many operating systems work, and it enables developers to extend software functionality and to debug it. Obviously, the ability to alter the code flow of APIs, and to intercept system messages and events has many legitimate uses.
Nonetheless, these very same tools can be exploited by malware to intercept victim data, evade detection mechanisms and maintain persistence over victim machines. For example, by globally hooking to WH_KEYBOARD_LL, adversaries may intercept private correspondence and credentials (see MITRE T1056.004: Input Capture: API Hooking). Hooking can also be used to hide malicious processes and C&C network ports from showing up by hooking functions such as NtQuerySystemInformation.
Accordingly, many malware kits and pen-testing tools alike exploit API hooking. For example, Netripper—a network collection tool ported in the Empire post-exploitation framework—uses API hooking to intercept network traffic. Trickbot, a trojan malware that first arose in 2016 in the banking sector, captures RDP credentials by hooking the CredEnumerateA API.
For our first demonstration, we will write the foundation hook for a keylogger. To keep it as simple as possible, we shall not write the entire keylogger, but just the hook function.
Our keylogger hooks to WinAPI’s WH_KEYBOARD_LL. This enables us to monitor keyboard input events before they are posted to the active window’s input queue. When keystroke events occur, WH_KEYBOARD_LL calls a placeholder function called KeyboardProc. We implement this function and then hook our implementation.
We must import <Windows.h>, in which our target function is stored:
As mentioned above, WH_KEYBOARD_LL‘s hooked procedures are implementations of the KeyboardProc function. Let us first see its declaration from the WinAPI documentation:
So, the hook procedure roughly looks as follows:
In a real-world scenario, instead of simply writing “Hook called”, wParam and lParam are parsed and stored. Notice, we want our keylogger hook to function globally, so we save it to a DLL file called “hook.dll”, which is loaded in the next step.
Now, we can install our hook handler using SetWindowsHookEx. First, we load our KeyboardProc DLL, then obtain its address, and finally hook it using SetWindowsHookEx. This function receives four parameters:
And here is the hook code:
In this demonstration, we prevent the task from displaying cmd.exe processes. To do so, we inject it with a DLL. Upon execution, the DLL locates the task manager’s Import Address Table and then hooks to the NtQuerySystemInformation function, which is used to gather information on running processes. Our hook procedure invokes NtQuerySystemInformation, eliminates the cmd.exe entry from its results, then returns the modified version to the task manager.
This demonstration is based on an excellent series of tutorials on PE headers from guidedhacking.com.
Beyond some basic libraries, we import <winternl.h>, which contains our target function.
The main function simply checks if the DLL is injected by comparing dwReason to DLL_PROCESS_ATTACH. If it is, it invokes our hook function Hook().
To locate the IAT, we must first obtain some information for the module in which we are running. To do so, we use GetModuleHandle(0) and GetModuleInformation. Passing zero to GetModuleHandle returns the handle of the current module.
The import table that we must change is located inside a section of the executable called the optional headers. All in all, we locate the current DLL’s base of memory, use this pointer to locate the DOS header at the base memory, then locate the optional headers through their offset from the DOS header. Finally,, we find the import table’s address based on the optional headers. The following code is added inside Hook():
Now that we have the import table descriptor, we can iterate it to find the entry for the NtQuerySystemInformation, then modify it. First, we locate the DLL containing the hook, ntdll:
Now that pID points to the right DLL, we can iterate thunks, which broadly speaking are export functions in the DLL, and locate NtQuerySystemInformation. Notice that the loop only handles functions that are imported by name:
We hopefully located the target thunk, and now we can edit it to point to our hook procedure instead of the original function (after making sure that we have write permissions):
Now, we implement the hook procedure. As mentioned above, our function adhere to the declaration of NtQuerySystemInformation, but it hides the cmd.exe process from its results.
First, we must paste some items that the original function relies on, as detailed in the WinAPI documentation:
And below is the hook procedure, which relies on these items. The function simply calls NtQuerySystemInformation, searches for cmd.exe, and hides the pointer to it, so when the task manager iterates the results, it is unable to read it.
API hooking is an integral part of the Windows operating system, has legitimate uses, and can be implemented in numerous ways. Therefore, complete mitigation and prevention are difficult to accomplish. Yet, measures can be taken to detect and monitor hooks, and to classify malicious attempts from legitimate ones. Some techniques to consider are: