Драйвер ядра Windows: ZwAllocateVirtualMemory, вызывающий прерывание потока
Я пытаюсь написать драйвер APC для инъекций DLL, я нашел этот пример и подумал, чтобы изменить его в соответствии с моими потребностями.
Я использую PcreateProcessNotifyRoutineEx, чтобы получить ProcessId для конкретных приложений, на которые я нацеливаюсь, в данном случае "iexplore.exe", а затем использую PloadImageNotifyRoutine, чтобы проверить, загружен ли ntdll.dll и инициализирован (из этой рекомендации), а также загружен ли ntdll.dll. Я называю "мою" функцию InjectDLL.
Это функция PloadImageNotifyRoutine, которая вызывает InjectDll:
VOID PloadImageNotifyRoutine(
_In_ PUNICODE_STRING FullImageName,
_In_ HANDLE ProcessId,
_In_ PIMAGE_INFO ImageInfo
)
{
PEPROCESS Process = NULL;
PETHREAD Thread = NULL;
PCHAR pTeb = nullptr;
DWORD ArbitraryUserPointer = 0;
PCHAR pszProcessNameA = nullptr;
pTeb = (PCHAR)__readfsdword(0x18);
ArbitraryUserPointer = *(DWORD*)(pTeb + 0x014);
// If ArbitraryUserPointer points to kernel32.dll it means ntdll.dll is done loading.
if (FALSE == IsStringEndWith((wchar_t*)ArbitraryUserPointer, L"\\kernel32.dll"))
{
return;
}
if (!NT_SUCCESS(PsLookupProcessByProcessId(ProcessId, &Process)))
{
return;
}
pszProcessNameA = (PCHAR)PsGetProcessImageFileName(Process);
if (FALSE == StringNCompare(pszProcessNameA, "iexplore.exe", GetStringLength("iexplore.exe")))
{
return;
}
Thread = KeGetCurrentThread();
InjectDll(MODULE_PATH, Process, Thread);
ObDereferenceObject(Process);
}
И это функция InjectDll:
BOOLEAN InjectDll(PWCHAR pModulePath, PEPROCESS Process, PETHREAD Thread)
{
PKINJECT mem;
ULONG size;
mem = NULL;
size = 4096;
if (!NT_SUCCESS(ZwAllocateVirtualMemory(NtCurrentProcess(), (PVOID*)&mem, 0, &size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)))
{
return FALSE;
}
//more code ...
}
Я убрал здесь некоторые проверки, чтобы было понятнее.
Я попытался немного отладить его, но похоже, что ZwAllocateVirtualMemory пытается выделить память, в какой-то момент происходит сбой и завершает поток.
После того, как он вызывает ExAllocatePoolWithTag, он начинает освобождать память, размечать разделы и завершать поток (<= вызовы, которые я видел в стеке во время отладки - на самом деле не отслеживал каждый вызов, но просматривал его в общем виде).
Стек до завершения потока:
nt!ExAllocatePoolWithTag+0x195
nt!NtAllocateVirtualMemory+0x1066
nt!KiSystemServicePostCall
nt!ZwAllocateVirtualMemory+0x11
kernel_apc_dll_injector!InjectDll+0x54
kernel_apc_dll_injector!PloadImageNotifyRoutine+0x2b0
nt!PsCallImageNotifyRoutines+0x62
Процесс по-прежнему виден из диспетчера задач, но его память составляет 92 КБ и не использует ЦП, возможно, из-за того, что он "не очищен" должным образом.
Я не знаю, прав ли мой анализ, и, возможно, он даже не нужен для этой проблемы.
1 ответ
С первой стороны заметка - не звонить PsLookupProcessByProcessId
из изображения уведомить рутины. это просто не нужно. Проверь это ProcessId == PsGetCurrentProcessId()
, и если да - используйте текущий указатель процесса (как вы и используете в вызове ZwAllocateVirtualMemory
- NtCurrentProcess()
) иначе просто существует.
Теперь о главном - "ZwAllocateVirtualMemory, вызывающий прерывание потока" - конечно нет. нить не прекращается. оно висело сначала, если поток завершается - потому что на данном этапе это единственный поток в процессе - весь процесс завершается. но вы сами говорите, что процесс все еще виден из диспетчера задач. также, как вы просматриваете стек вызовов завершенного потока? это также говорит о том, что поток не завершен, но ждать внутри ExAllocatePoolWithTag
проблема в том, что обратный вызов выполняется в некоторой критической области, и действия, которые вы можете выполнить в этой подпрограмме, ограничены (операционная система вызывает процедуру уведомления-процесса драйвера по адресу PASSIVE_LEVEL внутри критической области с отключенными обычными APC ядра). одно из ограничений - это звонок ZwAllocateVirtualMemory
- он завис в обратном вызове, который вы и можете увидеть.
так что нельзя звонить ZwAllocateVirtualMemory
и делать инъекции прямо из обратного вызова. но решение существует. вставить нормальное ядро apc в текущий поток. это не будет выполнено на месте, потому что - нормальные APC ядра отключены в обратном вызове. но сразу после выхода из callback и apc будет включен - ваш apc выполнен. а здесь (в обычном порядке) вы уже можете позвонить ZwAllocateVirtualMemory
и делать инъекции dll