Hello all. I was working on another post for this weekend, but I rapidly realized that I haven’t gone over the basics of DLL injection. This is really important as it is a required basis for a lot of Substack post ideas that I have in the near future.
But first…
What’s a DLL?
DLL stands for “Dynamic-Link Library”. Say, for example, that you have a huge file of code algorithms that you frequently use with a variety of different projects. Whenever you need to use this (static) library of algorithms, you #include
the file. Then you get a big chunk of code that is repetitively used across all of your projects. Wouldn’t it be neat if you could use all of those algorithms without bloating your project with code you frequently use? That is the premise of DLLs.
A DLL is a library that can be dynamically used by programs. It works by the program importing functions from the DLL, where the virtual address of each function is imported into the codebase, and references to the function jump out of the process’ memory space and into the DLL’s to execute the desired logic.
DLLs inherit the permissions and properties of the process that loads it which give it more dynamic power and control over the calling process. This inheriting aspect can be leveraged for speed, functionality, and even maliciousness.
Linux has a similar program. These are called “Shared Objects” or SO files.
But Why?
If you remember last week’s post, in order to dynamically patch, we had to iterate over all the processes and find the one we needed to patch. Then we had to grab the process’ base address start scanning for a byte signature. Lastly, we had to use the Windows API to write to the process’ memory.
For such a basic case as above, that is quite a process. Now imagine if we wanted to hook a function or call one manually? We would run into some pretty serious permissions issues. If we inject a DLL into the process, we can make our lives much, much easier.
Instead of writing an enumeration of processes every time we want to mess with a program, we just do it once and inject our own custom DLL.
How?
There are 2 (technically 3) main methods of DLL injection that I’m currently aware of.
Using the Registry
Insert your DLL into HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\AppCertDLLs
so that any process that uses the Win32 API will load the DLL automatically.
You can also insert it into HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs
so that any process that loads User32.dll will also load the DLL.
Manual DLL Injection
This is a much more preferred method for targeted attacks. Simply find the process you want to inject, allocate some memory within it, stick the path to your DLL into that allocated memory, and use the function CreateRemoteThread
to remotely have the process use LoadLibrary
to load your DLL.
Today, I will show you how to manually inject a DLL.
What You Need
Microsoft Visual Studio with the Desktop development for C++ package installed.
Literally any program you want to inject. I will not provide one this time. Use something like Notepad.
If you are running a third party antivirus that does DLL scanning, it may freak out and cause crashes.
Methodology
Find a program to inject (I will use Notepad.exe)
Write an injector EXE that scans for a program and injects your custom DLL
Write a basic DLL that does something (calling
MessageBox
for example)
Walkthrough
First, we will write the injector and come back to it later once we build our DLL.
Open Visual Studio and create a new, blank C++ project.
Make sure you set your build architecture to the same architecture as the process you are targeting. Cross architectural building does not work. Odds are, this is x64.
Create a new source file (I called mine Main.cpp).
Your logic is to enumerate all processes until you find the one you are aiming to inject. You will then allocate a small blob of memory within that process (with VirtualAlloc
). Then, you will write to this memory the path to your DLL (which we will write later). Lastly, you will call CreateRemoteThread
at that memory address to get the process to call LoadLibrary
on your DLL. I encourage you to try this yourself with limited assistance (which I will provide below).
I opted for a different process enumeration method using snapshots (it’s good to be familiar with multiple methods, especially if you’re a reverser). You will need to #include <TlHelp32.h>
.
DWORD ProcIDFromName(std::string procName)
{
DWORD dwProcID = 0;
// Snapshot current processes
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
std::wstring wProcName = std::wstring(procName.begin(), procName.end());
if (hSnap != INVALID_HANDLE_VALUE)
{
PROCESSENTRY32 entry{};
entry.dwSize = sizeof(entry);
if (Process32First(hSnap, &entry))
{
do
{
// If this is what we're looking for
if (!_wcsnicmp(entry.szExeFile, wProcName.c_str(), wProcName.size()))
{
dwProcID = entry.th32ProcessID;
break;
}
} while (Process32Next(hSnap, &entry));
}
}
CloseHandle(hSnap);
return dwProcID;
}
Note the discrepancies of messing with wide strings. We need to get the process ID from the snapshot, then from there we can use OpenProcess
.
int main(int argc, char** argv)
{
// Could preface string with L but w/e
const char* EXE_NAME = "Notepad.exe";
const char* DLL_PATH = "???";
DWORD dwID = ProcIDFromName(EXE_NAME);
if (dwID == 0)
{
std::cout << "Invalid proc" << std::endl;
return 0;
}
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwID);
if (hProc == NULL)
{
std::cout << "Could not open proc" << std::endl;
return 0;
}
For EXE_NAME
, I put Notepad.exe. Note that some programs have what is called “sandbox mode” and will not be able to be injected easily such as Chrome or calculator. It’s either that or ASLR. I don’t know, I didn’t look very hard. Just know that those 2 won’t work.
We haven’t written our DLL yet, so don’t worry about filling out DLL_PATH
.
Next, after opening the process. You must allocate the memory within it, then write DLL_PATH
to the buffer.
void* mem = VirtualAllocEx(hProc, 0, MAX_PATH, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (mem == NULL)
{
std::cout << "Could not allocate mem" << std::endl;
return 0;
}
if (!WriteProcessMemory(hProc, mem, DLL_PATH, MAX_PATH, 0))
{
std::cout << "Could not inject" << std::endl;
return 0;
}
Note the permissions you allocate the memory with. If you’re astute, you might be thinking “why don’t I just have the process use the DLL_PATH
pointer? Why does it have to be within the process’ memory?” And to that I say “great question”. Process memory space is very secure and tries to avoid outside reads and writes. If you simply supply your own address that is not within the target process’ virtual space, it will crash with a PAGE_READ
access error. So instead, you must (should) use the process’ memory space so that it automatically has the proper permissions.
Last step:
HANDLE hThread = CreateRemoteThread(hProc, 0, 0, (LPTHREAD_START_ROUTINE)LoadLibraryA, mem, 0, 0);
if (hThread == NULL)
{
std::cout << "Could not create remote thread" << std::endl;
return 0;
}
CloseHandle(hThread);
CloseHandle(hProc);
return 0;
The remote thread will call LoadLibraryA
within the target process, which will, of course, load the library that is your DLL.
Now, it’s time to write a DLL.
Create a new C++ project.
Once again, set the build architecture to same as the process you are targeting.
Set it as a DLL. (Properties → Configuration Properties → Configuration Type: DLL)
Now you can write your entrypoint.
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
if (fdwReason != DLL_PROCESS_ATTACH)
return TRUE;
CreateThread(NULL, 0x1000, &MessageBoxThread, NULL, 0, NULL);
return TRUE;
}
Note that DLL entrypoints are a bit different than the usual main
. There are a few extra rules you need to follow within a DllMain
function. You can Google those.
To run some logic without running into deadlocks, you need to make another thread. This is actually not advised by Windows, but this is something easy to prove that the process is loading your DLL. I simply made a function called MessageBoxThread
(be aware of its declaration) and had it call MessageBoxA
.
DWORD WINAPI MessageBoxThread(LPVOID lpParam)
{
char buffer[MAX_PATH]{};
GetModuleFileNameA(NULL, buffer, MAX_PATH);
char buffer2[MAX_PATH]{};
snprintf(buffer2, MAX_PATH, "Hello from %s", buffer);
// NULL means this is the current process aka US!
MessageBoxA(NULL, buffer2, "Hello", 0);
return S_OK;
}
Now, you can build your DLL. Once you do that, go to its build path and copy it. Remember DLL_PATH
from earlier? Now you get to fill that out.
const char* DLL_PATH = "C:\\malware\\dllinjection\\x64\\Debug\\dllinjection.dll";
Now you can build your injector.
You’re done coding! Hopefully…
Now you can open Notepad.exe, then run your injector.exe. You should receive an output similar to this:
You now have your own DLL injected into another, running process. Now you can do all kinds of shenanigans like function hooks, detours, and function calls and have a much easier time dealing with permissions.
Since future Substack posts will rely on injection, and if you followed along here, you now have your own injector you can use. If you didn’t follow along or gave up, you can also just Google one that someone else has written to make your life easier.
That’s all for now. I hope you got at least something out of this post even if you didn’t follow along writing your own injector. Thanks for reading.
Go!
-BowTiedCrawfish