The Basics of DLL Injection Part I

Hello... sorry for not posting in a while.  I've been going through some recent career changes and haven't really come across any IT-related inspiration lately.  I thought I'd post a simple, bare-bones technique of injecting a DLL into a running process on a Windows computer.  It's really simple.

  1. Verify that the PID supplied is valid.
  2. Get a handle to the remote process.
  3. Allocate memory in the remote process.
  4. Write the DLL path into the remote process's memory.
  5. Call CreateRemoteThread to induce LoadLibrary in the remote process. 

As long as the process does not already have the DLL loaded, the remote process will automatically call DllMain (the injected DLL's entry point,) which can contain all sorts of fun code.  For instance, now that you have injected a DLL into the remote process, you are well on your way to performing something like API hooking, for example. You could modify the Import Address Table of the remote process... but I'm getting ahead of myself.  First things first.  Without further ado:

// InjectDLL.cpp
// Joseph Ryan Ries - 2015
// Injects a DLL into another process on the system.

#include 
#include 
#include 

// Returns true if the supplied file path exists and is a file.
// Returns false if the supplied path does not exist, or is a directory.
BOOL FileExists(LPCTSTR FilePath)
{
	DWORD FileAttributes = GetFileAttributes(FilePath);

	return (FileAttributes != INVALID_FILE_ATTRIBUTES && !(FileAttributes & FILE_ATTRIBUTE_DIRECTORY));
}

// Entry point. Returns 0 at the end if everything was successful.
// Returns 1 if something failed. Outputs messages to console.
int wmain(int argc, wchar_t *argv[])
{
	if (argc != 3)
	{
		wprintf_s(L"\nUsage: %s C:\\Temp\\MyDLL.dll 1337\n", argv[0]);
		wprintf_s(L"\nUse the full path to the DLL you want to inject and supply the\n");
		wprintf_s(L"process ID (PID) of the process you want to inject it in to.\n");
		return(1);
	}

	if (!FileExists(argv[1]))
	{
		wprintf_s(L"\nERROR: Unable to find file %s.\n", argv[1]);
		return(1);
	}

	wchar_t DLLPath[MAX_PATH] = { 0 };

	wcscpy_s(DLLPath, argv[1]);

	DWORD Pid = _wtoi(argv[2]);

	if (Pid == 0)
	{
		wprintf_s(L"\nERROR: Unable to interpret the supplied process ID.\n");
		return(1);
	}

	wprintf_s(L"Searching for PID %d.\n", Pid);

	DWORD CurrentProcesses[2048] = { 0 };
	DWORD ProcessListSize = 0;
	DWORD ProcessCount = 0;

	if (EnumProcesses(CurrentProcesses, sizeof(CurrentProcesses), &ProcessListSize) == 0)
	{
		wprintf_s(L"\nERROR: Unable to enumerate currently running processes!\nLastError: 0x%x.\n", GetLastError());
		return(1);
	}

	ProcessCount = ProcessListSize / sizeof(DWORD);

	wprintf_s(L"%d processes currently on the system.\n", ProcessCount - 1);

	BOOL ProcessFound = FALSE;

	for (unsigned int ProcessCounter = 0; ProcessCounter < ProcessCount; ProcessCounter++)
	{
		if (CurrentProcesses[ProcessCounter] == 0)
		{
			continue;
		}
		if (CurrentProcesses[ProcessCounter] == Pid)
		{
			ProcessFound = TRUE;
			break;
		}
	}

	if (ProcessFound == FALSE)
	{
		wprintf_s(L"\nERROR: A process with PID %d could not be found.\n", Pid);
		return(1);
	}

	HANDLE ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);

	if (ProcessHandle == NULL)
	{
		wprintf_s(L"\nERROR: Unable to open handle to process %d. LastError 0x%x.\n", Pid, GetLastError());
		return(1);
	}

	wprintf_s(L"Process with PID %d successfully opened.\n", Pid);

	void* RemoteDLLPathMemory = NULL;		// The DLL entry address in the remote process

	// VirtualAlloc will probably not give us less than 4k. Whatever. We only need ~520 bytes for MAX_PATH * 2.
	RemoteDLLPathMemory = VirtualAllocEx(ProcessHandle, NULL, (MAX_PATH * sizeof(wchar_t)), MEM_COMMIT, PAGE_READWRITE);

	if (RemoteDLLPathMemory == NULL)
	{
		wprintf_s(L"\nERROR: Unable to allocate memory in remote process %d. LastError: 0x%x.\n", Pid, GetLastError());
		return(1);
	}

	wprintf_s(L"Memory allocated in process %d.\n", Pid);

	SIZE_T BytesWritten = 0;

	if (WriteProcessMemory(ProcessHandle, RemoteDLLPathMemory, (void*)DLLPath, sizeof(DLLPath), &BytesWritten) == 0)
	{
		wprintf_s(L"\nERROR: Unable to write DLL path into remote process memory. LastError: 0x%x.\n", GetLastError());
		return(1);
	}

	wprintf_s(L"%I64d bytes written into the memory of process %d.\n", BytesWritten, Pid);
	
	HANDLE ThreadHandle = CreateRemoteThread(
		ProcessHandle,
		NULL,
		NULL,
		(LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW"),
		RemoteDLLPathMemory,
		NULL,
		NULL);

	if (ThreadHandle == NULL)
	{
		wprintf_s(L"\nERROR: Unable to start remote thread in process %d. LastError: 0x%x.\n", Pid, GetLastError());
		return(1);
	}

	wprintf_s(L"Successfully loaded %s into process %d.\n", DLLPath, Pid);

	wprintf_s(L"Note: DllMain will only run if the DLL has not already been loaded by the process.\n");
	
	return(0);
}

Ignore the very last line (</psapi.h></windows.h></stdio.h>), the code editor wigged out.

Comments are closed