Written by Toby Gray and Ratnesh Pandey.
Endpoint detection and response (EDR) tools rely on operating system events to detect malicious activity that is generated when malware is run. These events are later correlated and analysed to detect anomalous and suspicious behaviour. One of the sources for such events are application program interface (API) hooks that help EDR solutions to trace interesting API calls. We recently came across a phishing campaign delivering the Agent Tesla password-stealing Trojan. While analysing the forensic data captured by Bromium Secure Platform, we noticed memory tampering events in the address space of ntdll.dll, the dynamic-link library (DLL) that exports the Windows Native API. The payload was isolated by Bromium Secure Platform and captured the malware.
The Agent Tesla downloader arrived as a .xls file which drops and executes the primary payload. In this blog post we cover the unhooking of APIs by the dropper to evade detection by tools such as EDR that rely on hooking. In a subsequent blog post, we provide an in-depth analysis of the campaign.
A system call is a function in the kernel of an operating system that services requests from users and provides a barrier so that underlying high-privilege resources cannot be directly accessed by the user. On Windows systems, the ntdll.dll library contains user mode system calls. Information about these system calls are stored in an array of function pointers and the System Service Descriptor Table (SSDT).
Figure 1 – The SERVICE_TABLE_DESCRIPTOR data structure contains a pointer to array of system calls.
The “Base” points to the function pointer array and the system call number is an index into this array. These functions are used to request the kernel to perform some action, such as allocating virtual memory in the case of NtAllocateVirtualMemory. For the rest of this discussion we’ll focus on NtProtectVirtualMemory, which is an undocumented system call that’s used to change the permissions of memory.
32-bit code on 64-bit Windows
When a 32-bit program is run on a 64-bit Windows machine, it runs under a system known as Windows on Windows 64 (or WoW64 for short). Because the kernel is running in 64-bit mode, system calls from 32-bit programs all go via a wrapper function, Wow64SystemServiceCall, which is at a known location in memory. This means that ntdll.dll, which contains many system call functions, has a very repetitive structure:
Figure 2 – Disassembly of NtProtectVirtualMemory.
The four lines of the disassembly code of NtProtectVirtualMemory are broken down as:
- Load the system call number 0x50 into the eax register
- Put the location of Wow64Transition (0x77BC2430 in the above screenshot) into the edx register
- Call the function at edx
- Return from this function
The next system call function, ZwQuerySection, is immediately after this one and follows the same structure, the only difference being loading 0x51 as the system call number rather than 0x50.
Security products use API hooking to intercept and record system API calls from software. One way of accomplishing this is to modify the in-memory code for the API that is to be hooked.
When hooked, the first instruction is replaced by an instruction that jumps to the trampoline code generated by the hooking software.
Figure 3 – Original function of NtProtectVirtualMemory is overwritten with 5-byte jmp instruction.
In figure 3, the first instruction of NtProtectVirtualMemory no longer loads 0x50 into eax; instead it redirects execution of the code (or jumps) to the address 0x004F0012. The hooking code will have generated code at that address which:
- Performs the action that hooking was added for, this could be a combination of:
- Recording the API call to monitor activity
- Modifying the API call to prevent certain actions
- Blocking the API to stop malicious activity
- Performs the replace