Open Menu
DLL INJECTION
developer

DLL INJECTION

User Name

Escrito por:

Nacho García

31 de Octubre de 2018

What is DLL Injection?

DLL injection is a technique used for executing code within the space of a program, by forcing it to load and run a dynamic library that was not considered by its original design.
Although this may sound malicious, and indeed many malware use this kind of techniques, the truth is that it has many legit usages such as debugging or monitoring the target process.
To achieve this, this technique is usually complemented with function hooking which involves intercepting the call of a specific function and redirecting this call to our own implementation into the injected library.

For such legit usages, Unix-like operating systems (as Linux) provide built-in capabilities for dynamic library injection, for example via LD_PRELOAD environment variable:


LD_PRELOAD="./tobeinjected.so" targetProgram

Since the library, the process and the environment are owned and executed by the same user, this is not considered a security risk at all and it's used everyday for many development tools.
However, Windows operating systems do not provide any built-in mechanisms to facilitate this.
Several techniques were developed over the years, some of them trying to alter the OS itself or even exploiting security flaws in a specific program.
We’ll focus here on today’s standard method for DLL injection, which has been successfully tested with all the available Windows versions and works with software that doesn’t feature any special protection against debugging.
Let’s call it “Standard Injection Method”. This method requires to execute the LoadLibraryA function provided by the kernel32.dll in the target process, to load and execute our DLL from it.
We are going to discuss this step by step.

Acquire debug privileges

First of all, we need acquire ‘debug’ privileges on the process used to inject the DLL.


bool grantDebugPrivileges() {
	TOKEN_PRIVILEGES priv = {0};
	HANDLE hToken = NULL;
	bool success=false;
	if (OpenProcessToken(GetCurrentProcess(),
		TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
			priv.PrivilegeCount = 1;
			priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
			if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &priv.Privileges[0].Luid)) {
				success=AdjustTokenPrivileges(hToken, FALSE, &priv, 0, NULL, NULL);
			}
		CloseHandle(hToken);		
	}
	return success;
}

In this example code, we first get the access token of the current process by calling OpenProcessToken.
The access token contains the security context for the process, that was, in principle, inherited from our logon as user in the machine.
Then we ask for the locally unique identifier (LUID) of the privilege named SE_DEBUG_NAME at the local machine, using LookupPrivilegeValue.
SE_DEBUG_NAME is the exact privilege that we’ll use later to debug and adjust the memory of the process to be injected.
Then we call AdjustTokenPrivileges to enable this privilege into the process token.
If the privilege can’t be granted, we may use the administrator account (which has the Debug privilege enabled by default) or grant to our account this privilege, by setting the user’s security context using an account with administrator permissions.
For a default Windows installation, all users have the debug privilege enabled.

Get a handle of the target process


HANDLE handle;
handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);

We’ll need to pass the process identifier (PID) to OpenProcess, which can be obtained through many methods that fall outside the scope of this paper. Anyway, keep in mind that 32-bit processes are unable to open 64-bit processes.
To avoid some inconsistencies between Windows versions, it’s better to ask for ALL_ACCESS, but we can also be more specific and, for Windows Vista and higher, ask for:

PROCESS_CREATE_THREAD |  PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ

Wait for the target process to be idle

This may sound weird, but experience has shown us that it's better to wait for the process become idle, using WaitForInputIdle, before going ahead with the injection process.
It seems that if OpenProcess is called during the process start-up we can get unexpected issues.


// Wait until the process becomes idle
switch (WaitForInputIdle(processHandle, TIMEOUT_FOR_IDLE_MILISECONDS)) {
	case 0:
	//Success
	break;
	case WAIT_TIMEOUT:
	case WAIT_FAILED:
	// ...
	// Unable to inject yet
	// ...
	break;
	default:
	// ...
	// Some undocumented return value
	// ...
	break;
}

Initialize the debug symbol loading


// Initialize debug symbol loading
if (!SymInitialize(processHandle, NULL, FALSE)) {
//Something went wrong
}

We need to call SymInitialize to initialize the symbol handler in the target process, as if we were going to debug it.
The reason is that we need to find the exact address of LoadLibraryA in the process, and we need the symbols to search it by name.
As we already have debug privileges we should succeed at this.

Load symbols for kernel32.dll

Almost every Windows process has kernel32.dll loaded, the dynamic library where “LoadLibraryA” resides.
If this is not the case of our target process, this method won’t work.
For the other 99%, the following example should work:


bool LoadSymbolModule(const char *name,  HANDLE processHandle) {
  MODULEENTRY32 modEntry = {0};
  HANDLE handle;
  DWORD64 returnAddress = 0;
  handle = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32,
                                      GetProcessId(processHandle));
  if (handle != INVALID_HANDLE_VALUE) {
      modEntry.dwSize = sizeof(modEntry);
      if (Module32First(handle, &modEntry)) {
        do {
          if (_stricmp(modEntry.szModule, name) == 0) {
            returnAddress = SymLoadModuleEx(
                processHandle, NULL, modEntry.szExePath, modEntry.szModule,
                (DWORD64)modEntry.modBaseAddr, modEntry.modBaseSize, NULL, 0);
            break;
          }
        } while (Module32Next(handle, &modEntry));
      }
      CloseHandle(handle);     
    }
  return returnAddress!=0;
}
//........................
if (LoadSymbolModule("kernel32.dll",processHandle)){
//Success
}
//.........................

This code needs a brief explanation. First we need to call CreateToolhelp32Snapshot to “take a snapshot” of the process. This will create an image of all running modules, heaps and threads.
On success, we initialize a MODULE32ENTRY structure and begin to iterate the list of modules loaded by calling Module32First (to get the first module) and Module32Next (to get the remaining modules), until we find a module whose name matches the module we are looking for. This is where we should stop, because we don’t need symbols from any other module.
Despite their names, these functions also work with 64-bit processors (there is no Module64 version)

Find the address of LoadLibraryA

Now that we have the symbols loaded, let’s find the address of LoadLibraryA
This is quite straightforward using SymFromName:


  SYMBOL_INFO symbol = {0};
  symbol.SizeOfStruct = sizeof(symbol);
  if (!SymFromName(processHandle, "LoadLibraryA", &symbol) || symbol.Address == 0)
  {
	  // Failure
  }
  LPTHREAD_START_ROUTINE loadLibraryExAddress = reinterpret_cast <LPTHREAD_START_ROUTINE>(symbol.Address);

Allocate space for the function’s arguments

Now we’ll use VirtualAllocEx to allocate space for the arguments to LoadLibraryA into the target’s memory.
Arguments to this function are, of course, the full-qualified path and filename of the DLL that we want to load.


void *pArgumentAddress  =
VirtualAllocEx(processHandle, NULL, szfullDllNameSize,
MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (pArgumentAddress == nullptr) {
// Failure
}

“szfullDllNameSize” will be the length of the full path of your library, plus the NULL terminator.
On success pArgumentAddress will point somewhere in the target’s memory. This is not your process memory, so you can’t write directly there, just take the address for the next step.

Write the argument into the target’s memory

With pArgumentAddress pointing to the target’s memory obtained in the previous code, we’ll call WriteProcessMemory to write into the actual path and filename:


if (!WriteProcessMemory(processHandle, pArgumentAddress, szfullDllName,
szfullDllNameSize, NULL)){
// Failure
}

Create a remote thread

Now that we have the function’s argument in a known memory position, it’s time to work the real magic:
By using CreateRemoteThread, we will execute LoadLibraryA into the target’s process (whose address we have obtained before) passing to it the full path of the library that we want to load.

This will happen before your very eyes:


HANDLE remoteThread =
      CreateRemoteThread(processHandle, NULL, 0x100000, loadLibraryExAddress, pArgumentAddress	,NULL);

if everything goes well, within some nanoseconds LoadLibraryA will be executed on the target process, and DllMain will be called.

Check for completion

We can’t get the return value of LoadLibraryA, so in a real world application we recommend the usage of some IPC signaling (for example, an IPC event via OpenEvent ) to control the success of the DLL initialization on the other side. The specific code needed depends greatly on what you want to check, so we won’t reproduce it here.
Also, you should return from DllMain as soon as possible, creating another thread local to the process from DllMain to continue the work from there.
From the injection code point of view, now it’s time to just call WaitForSingleObject to wait for the remote thread to be terminated:


DWORD result = WaitForSingleObject(remoteThread, TIMEOUT_MILISECONDS);
if (result != WAIT_OBJECT_0) {
// Error
}
// Check here your IPC event or whatever other method that
// you implemented to check if your injected DLL has been succeed 

Clean up and exit

To finish, even if they were only a few bytes, we want to be polite and clean up unneeded memory and the process handle.


if (processHandle)  
	CloseHandle(processHandle); 
if (pArgumentAddress)
	VirtualFreeEx(processHandle, pArgumentAddress, 0, MEM_RELEASE);

Conclusions

Even tough Windows doesn’t provide a standard method, nor a well documented procedure to perform a proper DLL injection, the indicated steps should work for the vast majority of applications.
You can take it as a good base to take on the implementation of a DLL injector on your own.
However, there are some applications hostile to this technique; particularly, some anti-debugger code will quickly complain because the start of the DLL can be detected very easily.
Or maybe, the LoadLibraryA function that you are calling has been patched (hooked) and will refuse the loading of any DLL that is unknown to the application.
There are hundreds of scenarios where one application may block your “standard” injection attempt.
To circumvent this counter-measures, there are more powerful techniques such as the so-called “reflective DLL loading”, which basically avoids calling the LoadLibraryA function by implementing alternative custom loaders.
This way, the injection process is more stealth and more difficult to be detected and blocked.
But techniques that fall into the dark side are a horse of a different color -and may be subject to a whole new article ;)

Da el salto con tus experiencias multimedia

Navega a través de nuestra lista de códecs de audio y video. Entiende por qué ofrecemos servicios de alta calidad para industrias de todos los tamaños.

¡Contacta con un experto!
Grid
Decor