Guide DLL Injection Methods

Hexui Undetected CSGO Cheats Sinkicheat PUBG Cheat

Rake

Cesspool Admin
Administrator
Jan 21, 2014
12,383
78,998
2,414
DLL injection is the act of loading a dynamic link library into an external process, from your own process. It is the easiest to perform, easiest to manage and most portable method to get execution in an external process.

The best source to learn the most common DLL Injection methods is the GH Injector Source Code but the code is not noob friendly, you need to have experience before you can dive into it. You can get all the experience you need by completing the first 2 books of the Guided Hacking Bible.

There are really two parts to DLL injection, getting execution in the target process and then the actual loading of the DLL inside the target process. These methods increase in their complexity to avoid antivirus & anticheat detection.

In the context of the GH Injector these methods are:

Code Execution Methods
  • CreateRemoteThread
  • NtCreateThreadEx
  • ThreadHijacking
  • SetWindowsHookEx
  • QueueUserAPC
DLL Injection Methods
  • LoadLibrary
  • LdrLoadDLL
  • Manual Map

Execution Methods

CreateRemoteThread

CreateRemoteThread or CRT is the standard method provided by the Windows API which lets you create a thread in an external process. You pass it the address of a function where you want execution to begin.

C++:
HANDLE WINAPI CreateRemoteThread(HANDLE hProcess, LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId);

NtCreateThreadEx
This is a undocumented function exported by ntdll with allows remote threads to be created across sessions, something regular CreateRemoteThread cannot do.

C++:
NTSYSCALLAPI NTSTATUS NTAPI
NtCreateThreadEx(
_Out_ PHANDLE ThreadHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_opt_ POBJECT_ATTRIBUTES ObjectAttributes,
_In_ HANDLE ProcessHandle,
_In_ PVOID StartRoutine, // PUSER_THREAD_START_ROUTINE
_In_opt_ PVOID Argument,
_In_ ULONG CreateFlags, // THREAD_CREATE_FLAGS_*
_In_ SIZE_T ZeroBits,
_In_ SIZE_T StackSize,
_In_ SIZE_T MaximumStackSize,
_In_opt_ PPS_ATTRIBUTE_LIST AttributeList
);

There are a lot more arguments but it's pretty easy to use:
C++:
struct NtCreateThreadExBuffer
{
    SIZE_T Size;
    SIZE_T Unknown1;
    SIZE_T Unknown2;
    PULONG Unknown3;
    SIZE_T Unknown4;
    SIZE_T Unknown5;
    SIZE_T Unknown6;
    PULONG Unknown7;
    SIZE_T Unknown8;
};

#pragma comment(lib, "ntdll.lib")
EXTERN_C NTSYSAPI NTSTATUS NTAPI NtCreateThreadEx(PHANDLE,
    ACCESS_MASK, LPVOID, HANDLE, LPTHREAD_START_ROUTINE, LPVOID,
    BOOL, SIZE_T, SIZE_T, SIZE_T, LPVOID);
  
NtCreateThreadExBuffer ntbuffer;

memset(&ntbuffer, 0, sizeof(NtCreateThreadExBuffer));
DWORD temp1 = 0;
DWORD temp2 = 0;

ntbuffer.Size = sizeof(NtCreateThreadExBuffer);
ntbuffer.Unknown1 = 0x10003;
ntbuffer.Unknown2 = 0x8;
ntbuffer.Unknown3 = (DWORD*)&temp2;
ntbuffer.Unknown4 = 0;
ntbuffer.Unknown5 = 0x10004;
ntbuffer.Unknown6 = 4;
ntbuffer.Unknown7 = &temp1;
ntbuffer.Unknown8 = 0;

HANDLE proc = OpenProcess(GENERIC_ALL, 0, dwPid);
HANDLE hThread;

wchar_t path[] = L"C:\\dll.dll";

LPVOID allocAddr = VirtualAllocEx(proc, 0, sizeof(path), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

WriteProcessMemory(proc, allocAddr, path, sizeof(path), nullptr);

NTSTATUS status = NtCreateThreadEx(
    &hThread, GENERIC_ALL, NULL, proc,
    (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW"),
    allocAddr, FALSE, NULL, NULL, NULL, &ntbuffer);

Learn more from this discussion: Solved - Use NtCreateThreadEx with 64-bit processes


ThreadHijacking

If creating a new thread is detected, you can hijack a thread which already exists to be more stealthy. This process involves, getting a list of the threads typically using NtQuerySystemInformation() to get a SYSTEM_PROCESS_INFORMATION structure which contains a SYSTEM_THREAD_INFORMATION structure which contains a CLIENT_ID structure which contains the UniqueThreadId. Once you have the thread's ID, you call OpenThread() and then SuspendThread(). Once the thread is suspended you change the instruction pointer (EIP/RIP) to point wherever you want it to point. Typically you point it at shellcode which then does your DLL loading.

SetWindowsHookEx

This function is a standard windows function which installs an application-defined hook procedure into a hook chain. Hooks are frequently used by normal software in this manner, to execute additional code when certain events are processed. SetWindowsHookEx takes a Hook ID of which there are several types (such as keyboard or WndProc hook), a pointer to your hook function and the thread Id which your hook will be associated with.

C++:
HHOOK SetWindowsHookExA(int idHook, HOOKPROC lpfn, HINSTANCE hmod, DWORD dwThreadId);

When your hook event is executed, execution will pass to your hook function, and then you can load your DLL.

QueueUserAPC

The QueueUserAPC function adds a user-mode asynchronous procedure call object to the APC queue of the specified thread. An asynchronous procedure call is simply a function that executes asynchronously in the context of a particular thread. When an APC is queued to a thread, the system issues a software interrupt. The next time the thread is scheduled, it will run the APC function

This function takes a pointer to your APC function which it typically shellcode you write to the process, a handle to the thread which you get by calling OpenThread() with THREAD_SET_CONTEXT, and a single argument you can pass to the function.

C++:
DWORD QueueUserAPC(PAPCFUNC pfnAPC, HANDLE hThread, ULONG_PTR dwData);

After calling it, you have only queued your APC, you now need to process it by calling PostThreadMessage() in that thread id. When it is processed, it will begin executing at whatever address you pointed it at, in most cases shellcode which will load your DLL.

DLL Loading Methods

LoadLibrary

The classic and simplest DLL injection method is WriteProcessMemory + CreateRemoteThread + LoadLibrary, this method is taught in this tutorial:


You call CreateRemoteThread() to create a thread, you pass in the address of LoadLibrary(), so it starts executing that function. You pass it a string which represents the path of the DLL you want to load, but that string must exist in the target process's memory, so you use WriteProcessMemory to place it there. You allocate the memory for this string using VirtualAllocEx.

It looks like this:
C++:
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, 0, procId);

if (hProc && hProc != INVALID_HANDLE_VALUE)
{
    void* loc = VirtualAllocEx(hProc, 0, MAX_PATH, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

    WriteProcessMemory(hProc, loc, dllPath, strlen(dllPath) + 1, 0);

    HANDLE hThread = CreateRemoteThread(hProc, 0, 0, (LPTHREAD_START_ROUTINE)LoadLibraryA, loc, 0, 0);

    if (hThread)
    {
        CloseHandle(hThread);
    }
}

LoadLibraryEx

LoadLibraryEx is the same as LoadLibrary except it offers two additional arguments which can be used to customize how the DLL is loaded. It takes the path to your DLL, and the other arguments you typically just set to NULL as they are not necessary. The second argument is reserved, and the third allows you to take certain actions when loading your DLL, such as disabling the loading of additional referenced libraries. So why use this function? It may bypass regular LoadLibrary hooks.

C++:
HMODULE LoadLibraryExW(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);

LdrLoadDLL

Next down the chain for LoadLibrary() is LdrLoadDLL. LoadLibrary() calls LoadLibraryEx and LoadLibraryEx in turn calls LdrLoadDLL. So again, we're just going deeper to avoid LoadLibrary and LoadLibraryEx hooks. LdrLoadDLL is an undocumented Windows function, unlike the other functions.

LdrLoadDll itself calls these functions:
  • LdrpCheckForLoadedDll
  • LdrpMapDll
  • LdrpWalkImportDescriptor
  • LdrpUpdateLoadCount
  • LdrpRunInitializeRoutines
  • LdrpClearLoadInProgress
But essentially LdrLoadDLL maps the module into memory, walks the modules import descriptor table, updates the module count and initialized the module. Using LdrLoadDLL is a bit more complicated than the previous 2 functions.

C++:
NTSTATUS NTAPI LdrLoadDll    (    _In_opt_ PWSTR     SearchPath,
_In_opt_ PULONG     LoadFlags,
_In_ PUNICODE_STRING     Name,
_Out_opt_ PVOID *     BaseAddress
)

Manual Mapping

Manual mapping avoids all the regular DLL loading functions, essentially bypassing them all and manually performing all the same steps:
  • Load raw binary data
  • Map sections into target process
  • Inject loader shellcode
  • Do relocation
  • Fix imports
  • Execute TLS callbacks
  • Call DllMain
  • Cleanup
This DLL injection method will always be the most undetectable method. All Windows API functions that can be used to detect DLLs such as ToolHelp32Snapshot or walking the PEB module list will be unable to find your module.

This is what the process typically looks like:
  1. Get the process id of the target
  2. Read the DLL file
  3. Allocate memory in target process the same size as ImageBase from the PE header
  4. Loop through PE sections after parsing the PE header
  5. Write the section to memory in the correct relative address
  6. Write shellcode into target process
  7. Call CreateRemoteThread and set your shellcode to execute
  8. Your shellcode fixes imports and does relocations
  9. Your Shellcode Execute TLS callbacks
  10. The above 2 steps are done parsing the DataDirectory in the optional header
  11. Call DllMain(), with DLL_PROCESS_ATTACH argument
Now your DLL is loaded and the DLL_PROCESS_ATTACH switch case is executing. Obivously it's more complicated than that, but that's the idea.

To learn how to do this watch our video tutorial Video Tutorial - Manual Mapping DLL Injection Tutorial - How To Manual Map


DLL Hijacking

Not exactly injection but we'll talk about it briefly. A process is vulnerable to DLL hijacking when you can replace one of it's dependency DLLs which it loads, with your own. This happens when the DLL path is not hard coded. The following is from @timb3r:

There are two types of paths in Windows:
  1. Relative (Ex: "kernel32.dll")
  2. Absolute (Ex: "C:\Windows\System32\kernel32.dll")
A relative path is resolved according to the directory where the process is running from (and some environment variables). If you specify a relative path when calling LoadLibrary Windows applies the following search logic:
  1. If the DLL is on the list of known DLLs for the version of Windows on which the application is running, the system uses its copy of the known DLL (and the known DLL's dependent DLLs, if any). The system does not search for the DLL. For a list of known DLLs on the current system, see the following registry key: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs.
  2. The directory from which the application loaded.
  3. The system directory. Use the GetSystemDirectory function to get the path of this directory.
  4. The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched.
  5. The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
  6. The current directory.
  7. The directories that are listed in the PATH environment variable. Note that this does not include the per-application path specified by the App Paths registry key. The App Paths key is not used when computing the DLL search path.
If your DLL is located in a path earlier in the load order than the real DLL and it's susceptible to DLL Hijacking it will load yours instead. But the process will still need the original code from the original DLL, so you have to make a proxy DLL to route the execution flow to the original DLL so it doesn't crash.

DLL Hijacking Resources

C# DLL Injection
DLL Injection Methods using C# are the same as C++.

Learn how to make a simple LoadLibrary + WriteProcessMemory + CreateRemoteThread injector in C#:

Additional C# Injection Resources

Exotic Injection Methods

Atom Bombing

AtomBombing is named after Atom tables, the Windows mechanism it exploits. An Atom table is used for sharing/exchanging data, i.e. atoms, amongst processes, utilizing shared system memory. Atom tables are useful when processes need to access the same strings. Microsoft defines Atom tables as follows: "An atom table is a system-defined table that stores strings and corresponding identifiers. An application places a string in an atom table and receives a 16-bit integer, called an atom that can be used to access the string. A string that has been placed in an atom table is called an atom name". The idea behind the attack is that someone can use a malicious process to create an atom name by writing malicious code in the form of a string instead of writing a legitimate string, and then get the target process to load the generated atom name and force it to execute the malicious code.

PROPagate

Adam (hexacorn on twitter) has discovered that he can abuse legitimate GUI window properties (UxSubclassInfo and CC32SubclassInfo) utilized internally by SetWindowSubclass function to load and execute malicious code inside other (legitimate) applications.

"Not all processes can be injected," Adam told Bleeping Computer in a private conversation today. "Only [applications] that use Windows GUI controls and popular GUI frameworks."

"That is not really a limitation though," Adam added, "the bug covers [the] majority of popular applications including Windows Explorer - a popular target for code injection."

DLL Injection & Anticheat

As you can see, we have all these different methods available because in the arms race that is security, the good guys and the bad guys are constantly countering each other. The bad guys started using LoadLibrary and CreateRemoteThread because the nice guys at Microsoft gave us that ability, but the anticheat got wise and started hooking LoadLibrary. Then we started using LoadLibraryEx, they hooked that too, then we went to LdrLoadDLL, well they hooked that too.

Then we said, the hell with it, we'll just manually map our shit which is relatively difficult to detect, but new threads started being detected, so we started using SetWindowsHookEx, then QueueUserAPC, & thread hijacking to avoid that.

If that wasn't enough, more techniques to avoid detection were deployed: handle hijacking, thread cloaking, erasing PE headers & more.

Manual mapping is the best technique, but they can still detect you, they can scan all memory pages and if any executable pages are found that are not mapped to a file on disk, then they know it's a manually mapped module. It's not enough just to manual map now, if the anticheat is strong you will have to hook all their usermode checks to prevent them from finding your modules.

In many cases, injection requires a process or thread handle, they can easily scan these as well to detect rogue handles & threads.

Additional resources

Special thanks @Broihon, without the Glorious GH Injector this guide would not exist
 
Last edited:

KF1337

*copies code from tutorials, then breaks it.*
Dank Tier Donator
Full Member
Nobleman
Jan 30, 2020
152
3,603
0
This is legitimately the best tutorial about injection techniques so far.
Covers literally every method with enough info so that one can research on this easily.

 
  • Like
Reactions: RyccoSN and Rake

RyccoSN

Dank Tier Donator
Sep 14, 2019
33
2,308
0
Nice to read about the cat and mouse history regarding detection of LoadLibrary and friends. Must have been pretty easy to bypass anything back in 2014.
 

Rake

Cesspool Admin
Administrator
Jan 21, 2014
12,383
78,998
2,414
Nice to read about the cat and mouse history regarding detection of LoadLibrary and friends. Must have been pretty easy to bypass anything back in 2014.
The very first Anticheats I'm familiar with are from 2002 for UT99 and TacOps, Anticheat were named TOST, UTDC, UTPure and Top Protect and I think there was another one named ACE actually. They were wicked basic

I wonder if there are older ones for Quake or Doom
 
Attention! Before you post:

Read the How to Ask Questions Guide
99% of questions are answered in the Beginner's Guide, do it before asking a question.

No Hack Requests. Post in the correct section.  Search the forum first. Read the rules.

How to make a good post:

  • Fill out the form correctly
  • Tell us the game name & coding language
  • Post everything we need to know to help you
  • Ask specific questions, be descriptive
  • Post errors, line numbers & screenshots
  • Post code snippets using code tags
  • If it's a large project, zip it up and attach it

If you do not comply, your post may be deleted.  We want to help, please make a good post and we will do our best to help you.

Community Mods