Introduction #
In my journey of learning game hacking, I came across the topic of DLLs and how hackers use them to inject their own code into another running process.
What is a DLL? #
A DLL (Dynamic Link Library) is a file that contains code/functions and its purpose is to export functionality to an existing executable for the Windows OS.
What is a DLL Injector? #
A DLL Injector is a tool that loads a module into another processes virtual memory space.
Process #
To get a better understanding on how this all works under the hood. I wanted to make a DLL injector along with some code to inject into a game. Let’s begin!
Coding the Injector #
#include <Windows.h>
#include <TlHelp32.h>
#include <iostream>
DWORD getProcId(const char* procName)
{
DWORD pid = 0;
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap != INVALID_HANDLE_VALUE)
{
PROCESSENTRY32 procEntry;
procEntry.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hSnap, &procEntry))
{
do
{
if (!_stricmp(procEntry.szExeFile, procName))
{
pid = procEntry.th32ProcessID;
break;
}
} while (Process32Next(hSnap, &procEntry));
}
CloseHandle(hSnap);
}
return pid;
}
int main()
{
const char* dllPath = "C:DLL_PATH";
const char* procName = "TARGET_PROCESS";
DWORD pid = 0;
while (!pid)
{
pid = getProcId(procName);
Sleep(30);
}
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
if (hProc == NULL)
{
std::cout << "open proc fail\n";
return EXIT_FAILURE;
}
// alloc memory in target process
PVOID loc = VirtualAllocEx(hProc, NULL, strlen(dllPath) + 1, MEM_COMMIT, PAGE_READWRITE);
// write DLL into target process
WriteProcessMemory(hProc, loc, dllPath, strlen(dllPath) + 1, NULL);
// call DLL
HANDLE hThread = CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryA, loc, 0, NULL);
// Wait for the remote thread to complete
WaitForSingleObject(hThread, INFINITE);
// Get the exit code of the remote thread
DWORD exitCode = 0;
GetExitCodeThread(hThread, &exitCode);
VirtualFreeEx(hProc, loc, 0, MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProc);
std::cout << pid << '\n';
std::cin.get();
return 0;
}
Let’s examine the code to gain an understanding of how DLL injection works.
The program requires a path to the DLL file along with the name of the target process. A snapshot is taken of all processes running on the system and the program will iterate through the snapshot until it locates the target process. Once the process is found, we get a return value of the Process ID.
The Process ID is then used to get a HANDLE
. The HANDLE
data type is used a lot in the Windows API. You can think of a HANDLE
as a reference to an object.
Once we have the HANDLE
to our target process, we can allocate memory and then write our DLL in that memory region. To begin execution of code in the DLL, a Thread
needs to be created. This is done by making a call to CreateRemoteThread()
. Once the thread completes its execution in the DLL, it returns execution back to the injector and we then start to free resources.
DLL Test #
#include <Windows.h>
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBox(0,"DLL Loaded Into Memory", 0, 0);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
For this example, I will be using Notepad for my target process. Once the DLL is loaded into memory, code execution will begin inside the DLL and we will get a message from within the DLL.