Skip to main content

Bypassing IAT Hooking via Dynamic Resolution

Let's suppose we’re conducting an offensive security exercise and need to bypass a security appliance--and need to call VirtualAlloc. We could do it in a less than optimal way, like this:

#include <windows.h>

int main() {
    void* mem = VirtualAlloc(NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    return 0;
}

Why is this suboptimal? If we were to load the compiled binary into a tool like PE-bear, or if a security appliance or EDR were monitoring Import Address Table (IAT) entries, the VirtualAlloc function call would be trivial to detect, since the IAT contains pointers to all statically imported functions, making them easy to inspect or hook.

Various security solutions and EDRs occasionally inspect these IAT functions by first scanning the binary and seeing what functions are exported. It then hooks those functions. But we can conceal this information (at least from a static point of view) and avoid such hooks.

A better idiom is to perform dynamic resolution of functions. I used this in a separate example on another blog post, "Bypassing Windows Defender." In that example, I also called the underlying NT function, NtAllocateVirtualMemory, rather than VirtualAlloc.

Instead of doing a raw VirtualAlloc call that tells the compiler to import the VirtualAlloc function during compile time, we can use a different programming idiom that allows us to dynamically resolve functions. This avoids detection based on the IAT, bypassing IAT hooks used by security appliances and EDRs. It also makes static analysis harder.

The programming idiom below allows us to dynamically resolve VirtualAlloc within kernel32.dll, rather than being resolved by the compiler and hardcoded into the IAT:

HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
FARPROC pVirtualAlloc = GetProcAddress(hKernel32, "VirtualAlloc");

In this way, we can use function pointers to dynamically resolve any functions we need from kernel32.dll:

#include <windows.h>
#include <stdio.h>

int main() {
    // Get handle to kernel32.dll
    HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
    if (!hKernel32) {
        printf("Failed to get handle to kernel32.dll\n");
        return 1;
    }

    // Get pointer to VirtualAlloc 
    FARPROC pVirtualAlloc = GetProcAddress(hKernel32, "VirtualAlloc");
    if (!pVirtualAlloc) {
        printf("Failed to resolve VirtualAlloc\n");
        return 1;
    }

    // Cast to correct function signature
    LPVOID (*MyVirtualAlloc)(LPVOID, SIZE_T, DWORD, DWORD) =
        (LPVOID (*)(LPVOID, SIZE_T, DWORD, DWORD))pVirtualAlloc;

    // Call VirtualAlloc via function pointer
    LPVOID mem = MyVirtualAlloc(NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (!mem) {
        printf("VirtualAlloc failed\n");
        return 1;
    }

    printf("Memory allocated at: %p\n", mem);
    return 0;
}

One could go even further by resolving functions without relying on GetProcAddress, such as by walking the Process Environment Block (PEB). That is, by manually parsing Windows structures to locate DLL and function addresses without calling standard APIs.

Static Import; No Bypass

Here, I'm using the C program from above. We can plainly see that the IAT includes a call to VirtualAlloc.

Dynamically Resolved IAT; IAT Hooking Bypassed!

This binary is from the C program I wrote for the "Bypassing Windows Defender" post, which uses dynamic IAT resolution.

Calls to VirtualAlloc--and various other dynamically resolved calls--don't show up during static analysis. Only the ones that were statically imported at compile time do.

Comments

Popular posts from this blog

yt-dlp Archiving, Improved

One annoying thing about YouTube is that, by default, some videos are now served in .webm format or use VP9 encoding. However, I prefer storing media in more widely supported codecs and formats, like .mp4, which has broader support and runs on more devices than .webm files. And sometimes I prefer AVC1 MP4 encoding because it just works out of the box on OSX with QuickTime, as QuickTime doesn't natively support VP9/VPO9. AVC1-encoded MP4s are still the most portable video format. AVC1 ... is by far the most commonly used format for the recording, compression, and distribution of video content, used by 91% of video industry developers as of September 2019. [ 1 ] yt-dlp , the command-line audio/video downloader for YouTube videos, is a great project. But between YouTube supporting various codecs and compatibility issues with various video players, this can make getting what you want out of yt-dlp a bit more challenging: $ yt-dlp -f "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best...

Latin1 vs UTF8

Latin1 was the early default character set for encoding documents delivered via HTTP for MIME types beginning with /text . Today, only around only 1.1% of websites on the internet use the encoding, along with some older applications. However, it is still the most popular single-byte character encoding scheme in use today. A funny thing about Latin1 encoding is that it maps every byte from 0 to 255 to a valid character. This means that literally any sequence of bytes can be interpreted as a valid string. The main drawback is that it only supports characters from Western European languages. The same is not true for UTF8. Unlike Latin1, UTF8 supports a vastly broader range of characters from different languages and scripts. But as a consequence, not every byte sequence is valid. This fact is due to UTF8's added complexity, using multi-byte sequences for characters beyond the general ASCII range. This is also why you can't just throw any sequence of bytes at it and ex...

Too much efficiency makes everything worse

From "Overfitting and the strong version of Goodhart's law" : Increased efficiency can sometimes, counterintuitively, lead to worse outcomes. This is true almost everywhere. We will name this phenomenon the strong version of Goodhart's law. As one example, more efficient centralized tracking of student progress by standardized testing seems like such a good idea that well-intentioned laws mandate it. However, testing also incentivizes schools to focus more on teaching students to test well, and less on teaching broadly useful skills. As a result, it can cause overall educational outcomes to become worse. Similar examples abound, in politics, economics, health, science, and many other fields. [...] This same counterintuitive relationship between efficiency and outcome occurs in machine learning, where it is called overfitting. [...] If we keep on optimizing the proxy objective, even after our goal stops improving, something more worrying happens. The goal often sta...