DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • In-Depth Guide to Using useMemo() Hook in React
  • API Security: Best Practices and Patterns To Securing APIs
  • The Power of @ngrx/signalstore: A Deep Dive Into Task Management
  • Unveiling Real-Time Operating Systems (RTOS): The Heartbeat of Modern Embedded Systems

Trending

  • Teradata Performance and Skew Prevention Tips
  • AI's Dilemma: When to Retrain and When to Unlearn?
  • Rust and WebAssembly: Unlocking High-Performance Web Apps
  • Analyzing “java.lang.OutOfMemoryError: Failed to create a thread” Error
  1. DZone
  2. Software Design and Architecture
  3. Integration
  4. Windows API Hooking and DLL Injection

Windows API Hooking and DLL Injection

This article is devoted to an approach for setting up local Windows hooks in C/C++ using native API calls.

By 
Olena Lisnevych user avatar
Olena Lisnevych
·
Updated Oct. 27, 23 · Tutorial
Likes (4)
Comment
Save
Tweet
Share
47.7K Views

Join the DZone community and get the full member experience.

Join For Free

This article is devoted to an approach for setting up local Windows API hooks. This article will also provide you with a DLL (dynamic link library) injection example: we will demonstrate how you can easily hook the system network adapter enumerator API call to manipulate the returned network adapter info. 

Overview

Hooking covers a range of techniques for altering or augmenting the behavior of an operating system, application, or other software components by intercepting API function calls, messages, or events passed between software components. Code that handles such interception is called a hook.

At Plexteq, we develop complex networking and security applications for which we use low-level techniques such as hooking and injection. We would like to share our experience in this domain. 

Related Tutorial: Implementing Spring Boot Basic Security with Swagger 3 (OpenAPI 3).

Some of the software applications that utilize hooks are tools for programming (e.g. debugging), antimalware, application security solutions, and monitoring tools.  Malicious software often uses hooks as well; for example, to hide from the list of running processes or to intercept keypress events in order to steal sensitive inputs such as passwords, credit card data, etc. Further Reading: How to Generate Keystore and CSR using keytool commands.

There are two main ways to modify the behavior of an executable:

  • through a source modification approach, which involves modifying an executable binary prior to application start through reverse engineering and patching. Executable signing is utilized to defend against this, preventing code that isn’t properly signed from being loaded.
  • through runtime modification, which is implemented by the operating system’s APIs. Microsoft Windows provides appropriate harnesses for hooking the dialogs, buttons, menus, keyboard, mouse events, and various system calls.

API hooks can be divided into the following types:

  • Local hooks: these influence only specific applications.
  • Global hooks: these affect all system processes.

In this article, we'll go over the hook technique for Windows that belongs to the local type done through a runtime modification using C/C++ and native APIs.

Hooking internals

Injection

Local hooks implemented with the runtime modification approach have to be executed within the address space of the target program. A program that manipulates a target process and makes it load hook is called an injector. In our example, we imply that the hook setup code is contained within an external DLL resource that is an injection object. 

The overall flow for preparing the hook to be loaded and executed requires the injector to follow these steps:

  1. Obtain the target process handle.
  2. Allocate memory within a target process and write the external DLL path into it (here we mean writing the dynamic library path that contains the hook).
  3. Create a thread inside the target process that would load the library and set up the hook.

In our example, we imply the hook setup code is located in DllMain function of the external DLL so it will be automatically executed upon a successful library load. 

Microsoft Windows API provides several system calls that are suitable for implementing the injector. Let’s go through the steps and figure out the best way to implement them. 

Note that the approach below won’t work for processes that don’t use kernel32.dll as in the samples below we heavily rely on API functions exported by it. 

Suppose the target process is not running yet, and we would like to inject our hook right after the target program starts. To make this happen, the injector should first run the target process by making an API call to CreateProcess.

C
 




xxxxxxxxxx
1
12


 
1
BOOL CreateProcessA(
2
  LPCSTR                lpApplicationName,
3
  LPSTR                 lpCommandLine,
4
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
5
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
6
  BOOL                  bInheritHandles,
7
  DWORD                 dwCreationFlags,
8
  LPVOID                lpEnvironment,
9
  LPCSTR                lpCurrentDirectory,
10
  LPSTARTUPINFOA        lpStartupInfo,
11
  LPPROCESS_INFORMATION lpProcessInformation
12
);


To make our hook set right after our target process starts, the injector has to suspend the target by passing a CREATE_SUSPENDED flag (dwCreationFlags) and then, after injecting the hook, resume the target process by calling the ResumeThread API function.

Here’s an example of how to start a process in a suspended state:

C
 




x
10


 
1
STARTUPINFO             startupInfo;
2
PROCESS_INFORMATION     processInformation;
3

          
4
// starting a new process
5
if (!CreateProcess(targetPath, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &startupInfo, &processInformation))
6
{
7
    PrintError(TEXT("CreateProcess failed"));
8
    return FALSE;
9
}
10

          


Allocating and writing memory in the target process is performed using a combination of system calls VirtualAllocEx and WriteProcessMemory.

The injector should first allocate memory with VirtualAllocEx.

C
 




xxxxxxxxxx
1


 
1
LPVOID VirtualAllocEx(
2
  HANDLE hProcess,
3
  LPVOID lpAddress,
4
  SIZE_T dwSize,
5
  DWORD  flAllocationType,
6
  DWORD  flProtect
7
);


And then write data to it with WriteProcessMemory.

C
 




xxxxxxxxxx
1


 
1
BOOL WriteProcessMemory(
2
  HANDLE  hProcess,
3
  LPVOID  lpBaseAddress,
4
  LPCVOID lpBuffer,
5
  SIZE_T  nSize,
6
  SIZE_T  *lpNumberOfBytesWritten
7
);


Here’s an example of allocating and writing memory to a target process:

C
 




xxxxxxxxxx
1
28


 
1
// lpcwszDll is a string that contains path to a DLL hook
2
nLength = wcslen(lpcwszDll) * sizeof(WCHAR);
3

          
4
// allocate mem for dll name
5
lpRemoteString = VirtualAllocEx(processInformation.hProcess, NULL, nLength + 1, MEM_COMMIT, PAGE_READWRITE);
6
if (!lpRemoteString)
7
{
8
    PrintError(TEXT("Failed to allocate memory in the target process"));
9

          
10
    // close process handle
11
    CloseHandle(processInformation.hProcess);
12

          
13
    return FALSE;
14
}
15

          
16
// write DLL hook name
17
if (!WriteProcessMemory(processInformation.hProcess, lpRemoteString, lpcwszDll, nLength, NULL)) {
18

          
19
    PrintError(TEXT("Failed to write memory to the target process"));
20
    // free allocated memory
21
    VirtualFreeEx(processInformation.hProcess, lpRemoteString, 0, MEM_RELEASE);
22

          
23
    // close process handle
24
    CloseHandle(processInformation.hProcess);
25

          
26
    return FALSE;
27
}


The next step is to create a thread inside the target process that loads the library with the hook. Microsoft Windows API provides a CreateRemoteThread API call for that purpose: 

C
 




xxxxxxxxxx
1
10


 
1
HANDLE CreateRemoteThread(
2
  HANDLE                 hProcess,
3
  LPSECURITY_ATTRIBUTES  lpThreadAttributes,
4
  SIZE_T                 dwStackSize,
5
  LPTHREAD_START_ROUTINE lpStartAddress,
6
  LPVOID                 lpParameter,
7
  DWORD                  dwCreationFlags,
8
  LPDWORD                lpThreadId
9
);
10

          


So CreateRemoteThread creates a new thread with state parameters dwCreationFlags in the target remote process specified by a hProcess handle. The newly created thread will execute a function pointed by lpStartAddress and pass lpParameter to it as a first argument.

Our plan now is to use this API function to start a thread and make it load our DLL, which we will accomplish by:

  1. Passing a pointer to the Windows API function LoadLibrary as a lpStartAddress.
  2. Passing a pointer to the DLL hook (the one we initialized using VirtualAllocEx and WriteProcessMemory) as a lpParameter.

To find a pointer to the LoadLibrary function in a target process, we will use GetProcAddress.

C
 




xxxxxxxxxx
1


 
1
FARPROC GetProcAddress(
2
  HMODULE hModule,
3
  LPCSTR  lpProcName
4
);


where hModule is a reference to a DLL that exports the LoadLibrary. HMODULE pointer could be obtained with the help of GetModuleHandle.

C
 




xxxxxxxxxx
1


 
1
HMODULE GetModuleHandleA(
2
  LPCSTR lpModuleName
3
);


Here’s an example of loading a DLL with the hook into the target process:

C
 




xxxxxxxxxx
1
29


 
1
LPVOID lpLoadLibraryW = NULL;
2
lpLoadLibraryW = GetProcAddress(GetModuleHandle(L"KERNEL32.DLL"), "LoadLibraryW");
3

          
4
if (!lpLoadLibraryW)
5
{
6
    PrintError(TEXT("GetProcAddress failed"));
7

          
8
    // close process handle
9
    CloseHandle(processInformation.hProcess);
10
    return FALSE;
11
}
12

          
13
// call LoadLibraryW
14
HANDLE hThread = CreateRemoteThread(processInformation.hProcess, NULL, NULL,
15
                                    (LPTHREAD_START_ROUTINE)lpLoadLibraryW,
16
                                    lpRemoteString, NULL, NULL);
17

          
18
if (!hThread) {
19
    PrintError(TEXT("CreateRemoteThread failed"));
20
  
21
      // close process handle
22
    CloseHandle(processInformation.hProcess);
23
    return FALSE; 
24
} else {
25
    WaitForSingleObject(hThread, 4000);
26

          
27
    //resume suspended process
28
    ResumeThread(processInformation.hThread);
29
}


These are all the necessary steps for injecting the hook library. If the injection went well, the hook library is loaded in the target process, and the DllMain function is executed so that we can set any hooks we want.

Hook Engine

To implement the hooking itself, we recommend using one of the many already existing solutions. There are a lot of them available as open-source, free, or partially free solutions. For example, Microsoft Detour, a powerful hooking engine, has support for the x86 architecture in a free version (it requires a paid subscription for hooking on x64). Another popular engine is NtHookEngine, which supports both x86 and x64 and has a well-designed and very straightforward API. Actually, this engine exports just three simple-to-use functions:

C
 




xxxxxxxxxx
1


 
1
BOOL (__cdecl *HookFunction)(ULONG_PTR OriginalFunction, ULONG_PTR NewFunction);
2
VOID (__cdecl *UnhookFunction)(ULONG_PTR Function);
3
ULONG_PTR (__cdecl *GetOriginalFunction)(ULONG_PTR Hook);


HookFunction - sets a hook. This one may be used to hook any function that exists in the current process's virtual address space.

UnhookFunction - removes a specific hook.

GetOriginalFunction - returns a pointer to the original function. This is very useful when the original function needs to be called inside a hook function.

Further, in the sample implementation, we’ll be using NtHookEngine. 

Sample Implementation

To demonstrate the injection and hooking in action, we’ve developed a test project that consists of an injector, hook library, and simple target. All sources can be found on GitHub.

Hook Library

Our library hooks the GetAdaptersInfo method and fakes the network adaptor name and its MAC-address values.

C
 




xxxxxxxxxx
1
16


 
1
void HooksManager::hookFunctions() {
2
  
3
    if (HookFunction == NULL 
4
        || UnhookFunction == NULL 
5
        || GetOriginalFunction == NULL) { 
6
      return;
7
    }
8
  
9
    hLibrary = LoadLibrary(L"Iphlpapi.dll");
10
    if (hLibrary == NULL) { 
11
      return;
12
    }
13
  
14
    HookFunction((ULONG_PTR)GetProcAddress(hLibrary, "GetAdaptersInfo"),
15
                 (ULONG_PTR)FakeGetAdaptersInfo);
16
}


So eventually we hook GetAdaptersInfo with FakeGetAdaptersInfo. Inside the FakeGetAdaptersInfo, we use GetOriginalFunction to get the actual adapter info. Next, we replace the first adapter info with fake values.

C++
 




xxxxxxxxxx
1
22


 
1
DWORD FakeGetAdaptersInfo(PIP_ADAPTER_INFO pAdapterInfo, PULONG pOutBufLen)
2
{
3
    DWORD(*OriginalGetAdaptersInfo)(PIP_ADAPTER_INFO pAdapterInfo, PULONG pOutBufLen);
4

          
5
    OriginalGetAdaptersInfo = (DWORD(*)(PIP_ADAPTER_INFO pAdapterInfo, PULONG pOutBufLen)) HooksManager::GetOriginalFunction((ULONG_PTR)FakeGetAdaptersInfo);
6

          
7
    DWORD result = OriginalGetAdaptersInfo(pAdapterInfo, pOutBufLen);
8
    std::string fakeAdapterName = "{11111111-2222-3333-4444-555555555555}";
9
    std::string fakeAdapterDescription = "Fake Adapter 0001";
10

          
11
    if (pAdapterInfo != NULL)
12
    {
13
        strcpy_s(pAdapterInfo->AdapterName, sizeof(pAdapterInfo->AdapterName), fakeAdapterName.c_str());
14
        strcpy_s(pAdapterInfo->Description, sizeof(pAdapterInfo->Description), fakeAdapterDescription.c_str());
15

          
16
        for (int i = 0; i < sizeof(pAdapterInfo->Address); i++)
17
        {
18
            pAdapterInfo->Address[i] = (BYTE)i;
19
        }
20
    }
21
    return result;
22
}
5
    OriginalGetAdaptersInfo = (DWORD(*)(PIP_ADAPTER_INFO pAdapterInfo, PULONG pOutBufLen)) HooksManager::GetOriginalFunction((ULONG_PTR)FakeGetAdaptersInfo);


Injector

The injector in this example can be built for either x86 or x64 architectures; however, keep in mind that an injector built for x86 won’t run in an x64 environment. 

Because hooks run in the context of an application, they must match the "bitness" of the application.

Our injector runs the target application by itself, and that’s why it doesn’t search for the target process ID in the active process lists. 

Finally, the injector code:

C++
 




x


 
1
BOOL WINAPI InjectDll(__in LPCWSTR lpcwszDll, __in LPCWSTR targetPath)
2
{
3
    SIZE_T nLength;
4
    LPVOID lpLoadLibraryW = NULL;
5
    LPVOID lpRemoteString;
6
    STARTUPINFO             startupInfo;
7
    PROCESS_INFORMATION     processInformation;
8

          
9
    memset(&startupInfo, 0, sizeof(startupInfo));
10
    startupInfo.cb = sizeof(STARTUPINFO);
11

          
12
    if (!CreateProcess(targetPath, NULL, NULL, NULL, FALSE,
13
        CREATE_SUSPENDED, NULL, NULL, &startupInfo, &processInformation))
14
    {
15
        PrintError(TEXT("Target process is failed to start"));
16
        return FALSE;
17
    }
18

          
19
    lpLoadLibraryW = GetProcAddress(GetModuleHandle(L"KERNEL32.DLL"), "LoadLibraryW");
20

          
21
    if (!lpLoadLibraryW)
22
    {
23
        PrintError(TEXT("GetProcAddress failed"));
24
      
25
        // close process handle
26
        CloseHandle( processInformation.hProcess);
27
        return FALSE;
28
    }
29

          
30
    nLength = wcslen(lpcwszDll) * sizeof(WCHAR);
31

          
32
    // allocate mem for dll name
33
    lpRemoteString = VirtualAllocEx(processInformation.hProcess, NULL, nLength + 1, MEM_COMMIT, PAGE_READWRITE);
34
    if (!lpRemoteString)
35
    {
36
        PrintError(TEXT("VirtualAllocEx failed"));
37

          
38
        // close process handle
39
        CloseHandle(processInformation.hProcess);
40

          
41
        return FALSE;
42
    }
43

          
44
    // write dll name
45
    if (!WriteProcessMemory(processInformation.hProcess, lpRemoteString, lpcwszDll, nLength, NULL)) {
46

          
47
        PrintError(TEXT("WriteProcessMemory failed"));
48
      
49
        // free allocated memory
50
        VirtualFreeEx(processInformation.hProcess, lpRemoteString, 0, MEM_RELEASE);
51

          
52
        // close process handle
53
        CloseHandle(processInformation.hProcess);
54

          
55
        return FALSE;
56
    }
57

          
58
    // call loadlibraryw
59
    HANDLE hThread = CreateRemoteThread(processInformation.hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)lpLoadLibraryW, lpRemoteString, NULL, NULL);
60

          
61
    if (!hThread) {
62
        PrintError(TEXT("CreateRemoteThread failed"));
63
    }
64
    else {
65
        WaitForSingleObject(hThread, 4000);
66

          
67
        //resume suspended process
68
        ResumeThread(processInformation.hThread);
69
    }
70

          
71
    //  free allocated memory
72
    VirtualFreeEx(processInformation.hProcess, lpRemoteString, 0, MEM_RELEASE);
73

          
74
    // close process handle
75
    CloseHandle(processInformation.hProcess);
76

          
77
    return TRUE;
78
}


Results

To check if the hook library is working, we compare the output of the target application, first without the hooking and then with it.

First, we run a target app without the hook applied:

Target app without hook

Now let’s check the output with the hook loaded. Please note the changed “adapter name” and “adapter addr” fields for the first adapter in the list.

App with hook


Happy hooking!

API Hook Injection operating system Library Microsoft Windows

Opinions expressed by DZone contributors are their own.

Related

  • In-Depth Guide to Using useMemo() Hook in React
  • API Security: Best Practices and Patterns To Securing APIs
  • The Power of @ngrx/signalstore: A Deep Dive Into Task Management
  • Unveiling Real-Time Operating Systems (RTOS): The Heartbeat of Modern Embedded Systems

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!