Search code examples
c#c.netdlldll-injection

C# dll injection not loading dll


I know there are a few similar questions here on stackoverflow, but none of them solved my problem.

So I am writing a c# framework for low level inter process operations including a dll injection feature.

Before calling my injector I am already attached to the target process (in this case notepad++.exe) using OpenProcess() with PROCESS_ALL_ACCESS permissions. The following is my injector code (I know readability suffered quite a lot due to all the debug prints):

public void Inject(string dllName, bool printDebugInfo)
{
    // Check if we are attached to the process.
    target.Assertions.AssertProcessAttached();
    target.Assertions.AssertInjectionPermissions();

    // searching for the address of LoadLibraryA and storing it in a pointer
    IntPtr kernel32Handle = WinAPI.GetModuleHandle("kernel32.dll");
    if (kernel32Handle == IntPtr.Zero)
    {
        uint errorCode = WinAPI.GetLastError();
        throw new Win32Exception((int)errorCode, "Encountered error " + errorCode.ToString() + " (0x" + errorCode.ToString("x") + ") - FATAL: Could not get handle of kernel32.dll: was NULL.");
    }
    UIntPtr loadLibraryAddr = WinAPI.GetProcAddress(kernel32Handle, "LoadLibraryA");
    if (loadLibraryAddr == UIntPtr.Zero)
    {
        uint errorCode = WinAPI.GetLastError();
        throw new Win32Exception((int)errorCode, "Encountered error " + errorCode.ToString() + " (0x" + errorCode.ToString("x") + ") - FATAL: Could not get address of LoadLibraryA: was NULL.");
    }
    HelperMethods.Debug("LoadLibraryA is at 0x" + loadLibraryAddr.ToUInt64().ToString("x"), printDebugInfo);

    // alocating some memory on the target process - enough to store the name of the dll
    // and storing its address in a pointer
    uint size = (uint)((dllName.Length + 1) * Marshal.SizeOf(typeof(char)));
    IntPtr allocMemAddress = WinAPI.VirtualAllocEx(target.Handle, IntPtr.Zero, size, (uint)Permissions.MemoryPermission.MEM_COMMIT | (uint)Permissions.MemoryPermission.MEM_RESERVE, (uint)Permissions.MemoryPermission.PAGE_READWRITE);
    HelperMethods.Debug("Allocated memory at 0x" + allocMemAddress.ToInt64().ToString("x"), printDebugInfo);

    int bytesWritten = 0;
    // writing the name of the dll there
    byte[] buffer = new byte[size];
    byte[] bytes = Encoding.ASCII.GetBytes(dllName);
    Array.Copy(bytes, 0, buffer, 0, bytes.Length);
    buffer[buffer.Length - 1] = 0;
    bool success = WinAPI.WriteProcessMemory((uint)target.Handle, allocMemAddress.ToInt64(), buffer, size, ref bytesWritten);
    if (success)
    {
        HelperMethods.Debug("Successfully wrote \"" + dllName + "\" to 0x" + allocMemAddress.ToInt64().ToString("x"), printDebugInfo);
    }
    else
    {
        HelperMethods.Debug("FAILED to write dll name!", printDebugInfo);
    }
    // creating a thread that will call LoadLibraryA with allocMemAddress as argument
    HelperMethods.Debug("Injecting dll ...", printDebugInfo);
    IntPtr threadHandle = WinAPI.CreateRemoteThread(target.Handle, IntPtr.Zero, 0, loadLibraryAddr, allocMemAddress, 0, out IntPtr threadId);
    HelperMethods.Debug("CreateRemoteThread returned the following handle: 0x" + threadHandle.ToInt32().ToString("x"), printDebugInfo);
    uint errCode = WinAPI.GetLastError();
    if (threadHandle == IntPtr.Zero)
    {
        throw new Win32Exception((int)errCode, "Encountered error " + errCode.ToString() + " (0x" + errCode.ToString("x") + ") - FATAL: CreateRemoteThread returned NULL pointer as handle.");
    }
    Console.WriteLine("CreateRemoteThread threw errorCode 0x" + errCode.ToString("x"));
    Console.WriteLine("Currently the following modules are LOADED:");
    ProcessModuleCollection processModules = target.Process.Modules;
    foreach (ProcessModule module in processModules)
    {
        Console.WriteLine("  - " + module.FileName);
    }
    uint waitExitCode = WinAPI.WaitForSingleObject(threadHandle, 10 * 1000);
    HelperMethods.Debug("Waiting for thread to exit ...", printDebugInfo);
    HelperMethods.Debug("WaitForSingleObject returned 0x" + waitExitCode.ToString("x"), printDebugInfo);
    Thread.Sleep(1000);
    Console.WriteLine("Currently the following modules are LOADED:");
    processModules = target.Process.Modules;
    foreach (ProcessModule module in processModules)
    {
        Console.WriteLine("  - " + module.FileName);
    }
    success = WinAPI.GetExitCodeThread(threadHandle, out uint exitCode);
    if (!success)
    {
        uint errorCode = WinAPI.GetLastError();
        throw new Win32Exception((int)errorCode, "Encountered error " + errorCode.ToString() + " (0x" + errorCode.ToString("x") + ") - FATAL: Non-zero exit code of GetExitCodeThread.");
    }
    Console.WriteLine("Currently the following modules are LOADED:");
    processModules = target.Process.Modules;
    foreach (ProcessModule module in processModules)
    {
        Console.WriteLine("  - " + module.FileName);
    }
    HelperMethods.Debug("Remote thread returned 0x" + exitCode.ToString("x"), printDebugInfo);
    success = WinAPI.CloseHandle(threadHandle);
    if (!success)
    {
        uint errorCode = WinAPI.GetLastError();
        throw new Win32Exception((int)errorCode, "Encountered error " + errorCode.ToString() + " (0x" + errorCode.ToString("x") + ") - FATAL: Failed calling CloseHandle on 0x" + threadHandle.ToInt64().ToString("x") + ".");
    }
    HelperMethods.Debug("Called CloseHandle on 0x" + threadHandle.ToInt64().ToString("x") + ".", printDebugInfo);
    success = WinAPI.VirtualFreeEx(target.Handle, allocMemAddress, 0, 0x8000);
    if (!success)
    {
        uint errorCode = WinAPI.GetLastError();
        throw new Win32Exception((int)errorCode, "Encountered error " + errorCode.ToString() + " (0x" + errorCode.ToString("x") + ") - FATAL: Failed calling VirtualFreeEx on 0x" + allocMemAddress.ToInt64().ToString("x") + ".");
    }
    HelperMethods.Debug("Released all previously allocated resources!", printDebugInfo);
}

All the WinAPI functions being called as specified by official Microsoft documentation (triple-checked that).

I am calling my code as follows

Target target = Target.CreateFromName("notepad++");
target.Attach(Permissions.ProcessPermission.PROCESS_ALL_ACCESS);
target.Injector.Inject(@"L:\Programming\C\test\newdll.dll",true);

The full source code of the Target class is on GitHub but shouldn't be relevant for this question.

probably the most interesting here being the newdll.dll that is written in native C as follows:

#include<Windows.h>
#include<stdbool.h>
__declspec(dllexport) bool WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason)
    {
        case DLL_PROCESS_ATTACH:
        {
            break;
        }

        case DLL_PROCESS_DETACH:
        {
            break;
        }

        case DLL_THREAD_ATTACH:
        {
            break;
        }

        case DLL_THREAD_DETACH:
        {
            break;
        }
    }
    return true;
}

This code obviously doesn't do much, but as spawning something like a messagebox from the DllMain is apparently considered "bad" I left it like this. However if the injection was working the dll would be listed in Process.Modules (which it isn't).

However when running my code I get the following output from all the debug prints:

LoadLibraryA is at 0x778f60b0
Allocated memory at 0x9c0000
Successfully wrote "L:\Programming\C\test\newdll.dll" to 0x9c0000
Injecting dll ...
CreateRemoteThread returned the following handle: 0x22c
CreateRemoteThread threw errorCode 0x0
Currently the following modules are LOADED:
  - J:\TOOLS\Notepad++\notepad++.exe
  - C:\Windows\SYSTEM32\ntdll.dll
  - C:\Windows\SYSTEM32\wow64.dll
  - C:\Windows\SYSTEM32\wow64win.dll
  - C:\Windows\SYSTEM32\wow64cpu.dll
Waiting for thread to exit ...
WaitForSingleObject returned 0x0
Currently the following modules are LOADED:
  - J:\TOOLS\Notepad++\notepad++.exe
  - C:\Windows\SYSTEM32\ntdll.dll
  - C:\Windows\SYSTEM32\wow64.dll
  - C:\Windows\SYSTEM32\wow64win.dll
  - C:\Windows\SYSTEM32\wow64cpu.dll
Currently the following modules are LOADED:
  - J:\TOOLS\Notepad++\notepad++.exe
  - C:\Windows\SYSTEM32\ntdll.dll
  - C:\Windows\SYSTEM32\wow64.dll
  - C:\Windows\SYSTEM32\wow64win.dll
  - C:\Windows\SYSTEM32\wow64cpu.dll
Remote thread returned 0x0
Called CloseHandle on 0x22c.
Released all previously allocated resources!
Press any key to continue . . .

As can be seen there are no error codes or any indications really that the injection went wrong, except that the newdll.dll was never loaded, as it doesn't show up in the loaded modules in Process.Modules.

So what's wrong with my code?

Quick overview: I do follow the procedure of:

  • OpenProcess() with PROCESS_ALL_ACCESS
  • GetModuleHandle("kernel32.dll")
  • GetProcAddress(kernel32Handle, "LoadLibraryA")
  • VirtualAllocEx(...) and WriteProcessMemory() to write my dll name and path.
  • CreateRemoteThread() to load the dll
  • WaitForSingleObject() to wait for the dll to be loaded
  • Free all the resources previously allocated

Solution

  • As was correctly pointed out this injection technique only works when injecting from 32 bit process --> 32 bit process or from 64 bit process --> 64 bit process. I was compiling my code as 64 bit executable, trying to inject my dll into the 32 bit notepad++.exe.

    Also I had to adapt the WriteProcessMemory call to comply with 32 bit memory space. So changing from bool success = WinAPI.WriteProcessMemory((uint)target.Handle, allocMemAddress.ToInt64(), buffer, size, ref bytesWritten); to bool success = WinAPI.WriteProcessMemory((uint)target.Handle, allocMemAddress.ToInt32(), buffer, size, ref bytesWritten); did the trick.