从 APC 调用 LdrLoadDll 会导致访问冲突

Calling LdrLoadDll from APC gives access violation

我已经制作内核级 DLL 注入器大约两周了。我已经与大量错误作斗争并从中学到了很多东西,但我仍然停留在最后一个问题上。当我将我的 UserMode APC 插入目标进程时,正常例程执行得很好并且正常工作,除了一行:调用 LdrLoadDll 例程将我的 DLL 加载到目标进程中。当 LdrLoadDll 被调用并出现访问冲突异常时,目标进程崩溃。

这是插入 APC 的代码:

PTITANIUM_INJECTION_INFO InjectionInfo = FindInjectionInfo(ProcessId);

wchar_t DllPathBuffer[] = L"C:\Users\alber\Desktop\TestDLL.dll";
RtlInitUnicodeString(&InjectionInfo->DllPath, DllPathBuffer);

DbgPrint("\nTarget DLL Path: %wZ\n", InjectionInfo->DllPath);
DbgPrint("LdrLoadDllRoutine is at 0x%p\n", InjectionInfo->LdrLoadDllRoutine);

PEPROCESS pProcess;
PsLookupProcessByProcessId(ProcessId, &pProcess);

PETHREAD AvailableThread = FindAvailableThread(ProcessId);
if (!AvailableThread)
{
    DbgPrint("Failed to find available thread in target process\n\n");
    return;
}

KeAttachProcess(pProcess);
DbgPrint("Attached to Process\n\n");

PVOID DllPathBufferAddress = NULL;
SIZE_T DllPathBufferAddressSize = 4096;

NTSTATUS status0 = ZwAllocateVirtualMemory(NtCurrentProcess(), (PVOID*)&DllPathBufferAddress, 0, &DllPathBufferAddressSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!NT_SUCCESS(status0))
{
    DbgPrint("[-] Failed to allocate memory for dll path buffer, error code: 0x%X [-]\n", status0);
    KeDetachProcess();
    DbgPrint("Detached from process\n\n");

    return;
}

DbgPrint("Allocated 4096 bytes for DLL Path Buffer\n");

wcscpy(DllPathBufferAddress, LocalDllPathBuffer);
DbgPrint("Local DLL Path Buffer copied into usermode space: %ws\n", DllPathBufferAddress);

PVOID  ContextAddress = NULL;
SIZE_T ContextAllocationSize = 4096;

NTSTATUS status = ZwAllocateVirtualMemory(NtCurrentProcess(), &ContextAddress, 0, &ContextAllocationSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!NT_SUCCESS(status))
{
    DbgPrint("[-] Failed to allocate memory for apc context, error code: 0x%X [-]\n", status);
    KeDetachProcess();
    DbgPrint("Detached from process\n\n");

    return;
}

DbgPrint("APC Context allocated in the target process at 0x%p\n", ContextAddress);

memcpy(ContextAddress, InjectionInfo, sizeof(TITANIUM_INJECTION_INFO));
DbgPrint("Context copied into the target process\n");

PVOID  NormalRoutineAddress = NULL;
SIZE_T NormalRoutineAllocationSize = (SIZE_T)((ULONG_PTR)NRStubFn - (ULONG_PTR)InjectorAPCNormalRoutine);
DbgPrint("Normal Routine function size: %i bytes\n", NormalRoutineAllocationSize);

status = ZwAllocateVirtualMemory(NtCurrentProcess(), &NormalRoutineAddress, 0, &NormalRoutineAllocationSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!NT_SUCCESS(status))
{
    DbgPrint("[-] Failed to allocate memory for apc normal routine [-]\n");
    KeDetachProcess();
    DbgPrint("Detached from process\n\n");

    return;
}

DbgPrint("APC Normal Routine allocated in the target process at 0x%p\n", NormalRoutineAddress);

memcpy(NormalRoutineAddress, InjectorAPCNormalRoutine, NormalRoutineAllocationSize);
DbgPrint("Normal Routine copied into the target process\n");

PKAPC apc = (PKAPC)ExAllocatePool(NonPagedPool, sizeof(KAPC));
if (!apc)
{
    DbgPrint("Error: Unable to allocate the APC object.");
    KeDetachProcess();
    DbgPrint("Detached from process\n");
    return;
}

KeInitializeApc(apc, AvailableThread, OriginalApcEnvironment, KernelApc, NULL, (PKNORMAL_ROUTINE)NormalRoutineAddress, UserMode, ContextAddress);
DbgPrint("APC initialized\n");

KeInsertQueueApc(apc, NULL, NULL, IO_NO_INCREMENT);
DbgPrint("APC inserted into the queue\n");

/*LARGE_INTEGER delay;
delay.QuadPart = -200 * 10000;

for (int i = 1; i <= 20; i++)
{
    DbgPrint("[***] InjectedInfo->Inject Real Value: %i [***]\n", ((PTITANIUM_INJECTION_INFO)ContextAddress)->Injected);

    if (!((PTITANIUM_INJECTION_INFO)ContextAddress)->Injected)
    {
        KeDelayExecutionThread(KernelMode, FALSE, &delay);
        DbgPrint("[*] InjectedInfo->Inject: FALSE | Retrying: %i [*]\n", i);
    }
    else
    {
        DbgPrint("[!] InjectedInfo->Inject: TRUE | DllBase: 0x%p [!]\n", ((PTITANIUM_INJECTION_INFO)ContextAddress)->DllBase);
        break;
    }
}*/

KeDetachProcess();
DbgPrint("Detached from process\n");

DbgPrint("\n");

这是我的日常安排:

VOID InjectorAPCNormalRoutine(PVOID Context, PVOID SysArg1, PVOID SysArg2)
{
    PTITANIUM_INJECTION_INFO InjectionInfo = (PTITANIUM_INJECTION_INFO)Context;
    
    InjectionInfo->LdrLoadDllRoutine(NULL, 0, &InjectionInfo->DllPath, &InjectionInfo->DllBase);
    InjectionInfo->Injected = TRUE;
}

我尝试从一个单独的用户模式项目手动调用 LdrLoadDll,它运行良好。我还验证了 LdrLoadDll 例程地址在两种情况下都是相同的。

我真的不知道在任何地方都可以做什么,相信我,我已经浏览了所有我能想到的 google 搜索。

以下是调试和反汇编的一些图像,以备不时之需:

我设法自己找到了解决问题的办法。显然我的代码没有任何问题,但编译器生成了一条有问题的指令。在断点 call 指令正上方的第一个屏幕截图中,有一条 mov 指令,它将正确的 LdrLoadDll 函数地址移动到 rax 寄存器中,但下一条指令调用了一些奇怪的地址rax 寄存器中的正确地址。 我通过创建一个单独的 .asm 文件并在那里编写自己组装的正常例程来解决我的问题。如果有人感兴趣,这是汇编代码:

NormalRoutineNativeAssembly proc

    mov         qword ptr [rsp+18h], r8
    mov         qword ptr [rsp+10h], rdx  
    sub         rsp, 48h  
    mov         rax, qword ptr [rsp+50h]  
    mov         qword ptr [rsp+30h], rax  
    mov         rax, qword ptr [rsp+30h]  
    add         rax, 30h  
    mov         rcx, qword ptr [rsp+30h]  
    add         rcx, 20h  
    mov         rdx, qword ptr [rsp+30h]  
    mov         rdx, qword ptr [rdx+18h]  
    mov         qword ptr [rsp+38h], rdx  
    mov         r9, rax  
    mov         r8, rcx  
    xor         edx, edx  
    xor         ecx, ecx  
    mov         rax, qword ptr [rsp+38h]  
    call        rax
    mov         rax, qword ptr [rsp+30h]  
    mov         byte ptr [rax+38h], 1  
    add         rsp, 48h  
    ret

NormalRoutineNativeAssembly endp

感谢所有帮助我尝试解决此问题的人。