Search code examples
c#windowsassemblyx86-64calling-convention

Access Violation inside LoadLibrary


I am using CreateRemoteProcess to inject some assembler code into a remote process (64-Bit), which then loads a dll, but I'm getting an C0000005 EXCEPTION_ACCESS_VIOLATION inside of LoadLibraryA for the call that loads my .dll file.

Here is the injected assembler code (Addresses are different in the screenshots below, but these are calculated relative to the remote memory address before being written):

MOV RCX,2A0DFF0020
MOV RAX,<kernel32.LoadLibraryA>
CALL RAX
MOV RCX,RAX
MOV RDX,2A0DFF0030
MOV RAX,<kernel32.GetProcAddress>
CALL RAX
MOV QWORD PTR DS:[2A0DFF0010],RAX
MOV RCX,2A0DFF0040
MOV RAX,<kernel32.LoadLibraryA>
CALL RAX
CMP RAX,0
JNZ 2A0DFF024D
XOR CL,CL
MOV RDX,2A0DFF00C0
MOV R8,2A0DFF00B0
MOV CL,10
MOV RAX,QWORD PTR DS:[2A0DFF0010]
CALL RAX
XOR CL,CL
MOV RAX,<kernel32.FatalExit>
CALL RAX
MOV QWORD PTR DS:[2A0DFF0000],RAX
MOV RCX,RAX
MOV RDX,2A0DFF00A0
MOV RAX,<kernel32.GetProcAddress>
CALL RAX
CMP RAX,0
JNZ 2A0DFF02A7
XOR CL,CL
MOV RDX,2A0DFF0140
MOV R8,2A0DFF00B0
MOV CL,10
MOV RAX,QWORD PTR DS:[2A0DFF0010]
CALL RAX
XOR CL,CL
MOV RAX,<kernel32.FatalExit>
CALL RAX
MOV RCX,2A0DFF0000
XOR DL,DL
MOV RAX,<kernel32.FreeLibraryAndExitThread>
CALL RAX  

Here is a screenshot of the registers and memory just before the CALL that crashes (and also the assembler code with highlighting!) and here is a screenshot of the actual crash


The full Dll I'm trying to inject right now (just a test):

#include <windows.h>
__declspec(dllexport) void init(void)
{
    return;
}


BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    return TRUE;
}

I compiled the dll with the latest tcc 64-Bit with the following command:

tcc -o "test.dll" -shared testdll.c


Here is the injecting code, I stripped out all the error handling:

//AlignedStream is a stream wrapper that supports aligning memory,
//in this case to 16 Byte borders
data = new AlignedStream(new MemoryStream());

//adding the strings to the stream (using Encoding.ASCII.GetBytes)

System.Diagnostics.Process.EnterDebugMode();

var process = Interop.OpenProcess
(
    Interop.ProcessAccessFlags.CreateThread |
    Interop.ProcessAccessFlags.VirtualMemoryOperation |
    Interop.ProcessAccessFlags.VirtualMemoryWrite |
    Interop.ProcessAccessFlags.QueryInformation |
    Interop.ProcessAccessFlags.QueryLimitedInformation,
    false,
    processId
);

var asmsize = GetAsmSize();
var RemoteMemory = Interop.VirtualAllocEx(
    process,
    IntPtr.Zero,
    new UIntPtr((ulong)asmsize + (ulong)data.Length),
    Interop.AllocationType.Commit | Interop.AllocationType.Reserve,
    Interop.MemoryProtection.ExecuteReadWrite
);

//Adding the assembler to the stream (as raw bytes in code)

var bytes = data.ToArray();

var oldProtect = Interop.VirtualProtectEx
(
    process,
    RemoteMemory,
    new UIntPtr((ulong)bytes.LongLength),
    Interop.MemoryProtection.ExecuteReadWrite
);

var written = Interop.WriteProcessMemory
(
    process,
    RemoteMemory,
    bytes,
    new UIntPtr((ulong)bytes.LongLength)
);

Interop.VirtualProtectEx
(
    process,
    RemoteMemory,
    new UIntPtr((ulong)bytes.LongLength),
    oldProtect
);

Interop.FlushInstructionCache
(
    process, 
    RemoteMemory,
    new UIntPtr((ulong)bytes.LongLength)
);

var thread = Interop.CreateRemoteThread
(
    process,
    IntPtr.Zero,
    UIntPtr.Zero,
    IntPtr.Add(RemoteMemory, asmOffset),
    IntPtr.Zero,
    Interop.ThreadCreation.Default
);
var result = Interop.WaitForSingleObject(thread, Interop.INFINITE);

Interop.VirtualFreeEx
(
    process, 
    RemoteMemory, 
    UIntPtr.Zero, 
    Interop.FreeType.Release
);

System.Diagnostics.Process.LeaveDebugMode();

Interop.CloseHandle(process);

I made sure that the .dll, the target process and the injector are all 64-Bit, and that the Injector is running with administrator rights. The dll loads fine when loading it with a test project that just calls LoadLibrary.

Things I tried to fix it:

  • I tried using LoadLibraryW instead and it crashes at the same point (From what I saw in the debugger, LoadLibraryA actually calls LoadLibraryExW at some point down the line, just like LoadLibraryW).

  • All previous questions I found with similar problems have boiled down to the data not being written to the target process, but as you can see in the screenshot it's definitely there.

  • I tried using a different process, to see if it was specific to notepad because its in the system directory, no success.

  • I tried using a dll built with vc++

I'm completely out of ideas. Maybe its a simple mistake in my assembler code that I just can't find (pretty inexperienced with assembler). Why does this crash occur and what can be done to prevent it?


Solution

  • You are not following the standard calling convention. In particular, but not limited to, you are not aligning the stack, thus the aligned SSE move instruction MOVAPS faults inside the LoadLibrary function. To fix the immediate problem, you can do AND RSP, -16 at the beginning of your code. You can also add the standard function prologue (PUSH RBP; MOV RBP, RSP) and epilogue (MOV RSP, RBP; POP RBP; RET).

    You should however take care to follow all the peculiarities of the calling convention (such as allocating spill space for the arguments) to create proper code, not just one that happens to work.