Привет! Вот возникла задача создать поток из ядра в произвольном процессе. Чтобы было понятнее, нужно реализовать аналог RtlCreateUserThread(), ибо сама RtlCreateUserThread(), к сожалению, не экспортируется. Сейчас делаю так: 0. Вешаю callback на создание процессов и пытаюсь внедриться в каждый из них. 1. Создаю системный поток - PsCreateSystemThread(). 2. В потоке открываю процесс по ID и получаю его хендл. 3. Создаю стек потока. Примерно так: Код (Text): typedef struct _INITIAL_TEB { PVOID PreviousStackBase; PVOID PreviousStackLimit; PVOID StackBase; PVOID StackLimit; PVOID StackAllocationBase; PVOID StackCommit; PVOID StackCommitMax; PVOID StackReserved; } INITIAL_TEB, *PINITIAL_TEB; ... PBYTE pStack = NULL; SIZE_T RegionSize = 0; if (CommittedStackSize == 0) CommittedStackSize = PAGE_SIZE; if (MaximumStackSize == 0) MaximumStackSize = MM_ALLOCATION_GRANULARITY; if (CommittedStackSize >= MaximumStackSize) { MaximumStackSize = ROUND_UP (CommittedStackSize, (1024 * 1024)); } CommittedStackSize = ROUND_UP (CommittedStackSize, PAGE_SIZE); MaximumStackSize = ROUND_UP (MaximumStackSize, MM_ALLOCATION_GRANULARITY); pStack = NULL; status = ZwAllocateVirtualMemory ( hProcess, (PVOID*) &pStack, 0, &MaximumStackSize, MEM_RESERVE, PAGE_READWRITE); if (! NT_SUCCESS (status)) { ... } pInitialTeb -> PreviousStackBase = NULL; pInitialTeb -> PreviousStackLimit = NULL; pInitialTeb -> StackAllocationBase = pStack; pInitialTeb -> StackBase = pStack + MaximumStackSize; pStack += (MaximumStackSize - CommittedStackSize); if (MaximumStackSize > CommittedStackSize) { pStack -= PAGE_SIZE; CommittedStackSize += PAGE_SIZE; } status = ZwAllocateVirtualMemory ( hProcess, (PVOID*) &pStack, 0, &CommittedStackSize, MEM_COMMIT, PAGE_READWRITE); if (! NT_SUCCESS (status)) { ... } pInitialTeb -> StackLimit = pStack; В итоге в pInitialTeb -> StackBase имеем начало стека. 4. Затем создаю контекст: Код (Text): PCONTEXT pContext = NULL; PCONTEXT pMappedContext = NULL; SIZE_T uRegionSize = sizeof (CONTEXT); PMDL pMdl = NULL; pContext = (PCONTEXT) ExAllocatePool (NonPagedPool, sizeof (CONTEXT)); if (! pContext) { ... } pMdl = IoAllocateMdl (pContext, sizeof (CONTEXT), FALSE, FALSE, NULL); if (! pMdl) { ... } MmBuildMdlForNonPagedPool (pMdl); pMappedContext = (PCONTEXT) MmMapLockedPagesSpecifyCache (pMdl, UserMode, MmCached, NULL, 0, NormalPagePriority); if (! pMappedContext) { ... } pContext -> Eax = 0; pContext -> Ebx = 0; pContext -> Ecx = 0; pContext -> Edx = 0; pContext -> Esi = 0; pContext -> Edi = 0; pContext -> Ebp = 0; pContext -> SegGs = 0; pContext -> SegFs = 0x38; pContext -> SegEs = 0x23; pContext -> SegDs = 0x23; pContext -> SegSs = 0x23; pContext -> SegCs = 0x1B; pContext -> EFlags = 0x3000; pContext -> Esp = (ULONG) pStackBase; pContext -> Eip = (ULONG) pStartAddress; pContext -> ContextFlags = CONTEXT_FULL; pContext -> Esp -= sizeof (pParameter); status = NtWriteVirtualMemory ( hProcess, (PVOID) pContext -> Esp, (PVOID) &pParameter, sizeof (pParameter), NULL); if (! NT_SUCCESS (status)) { ... } pContext -> Esp -= sizeof (pParameter); pMappedContext в итоге передаётся в NtCreateThread(). 5. Записываю код в а.п. целевого процесса. Пока пишу тупо однобайтовый ret, чтобы хотя бы проверить работоспособность кода: Код (Text): PBYTE pCode = NULL; BYTE Code [1] = { 0xC3 /* ret */ }; SIZE_T uRegionSize = PAGE_SIZE; status = ZwAllocateVirtualMemory ( hProcess, &pCode, 0, &uRegionSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (! NT_SUCCESS (status)) { ... } status = NtWriteVirtualMemory ( hProcess, pCode, Code, sizeof (Code), NULL); if (! NT_SUCCESS (status)) { ... } 6. Создаю поток: Код (Text): CLIENT_ID ClientId = {0}; HANDLE hThread = NULL; OBJECT_ATTRIBUTES ThreadAttributes = {0}; InitializeObjectAttributes ( &ThreadAttributes, NULL, OBJ_KERNEL_HANDLE, NULL, NULL); status = NtCreateThread ( &hThread, THREAD_ALL_ACCESS, &ThreadAttributes, hProcess, &ClientId, pContext, pInitialTeb, FALSE); if (! NT_SUCCESS (status)) { ... } 7. Задержка примерно полсекунды. 8. Завершение потока - NtTerminateThread(). 9. Освобождение стека, кода и контекста. Обратите внимание, что на шаге 6 поток создаётся в подвешенном состоянии и не начинает исполнятся. При этом, заметьте, все вызовы во всех случаях отрабатывают успешно, возвращая STATUS_SUCCESS. Что ожидалось в случае, если всё было бы запрограммировано правильно: приложения бы падали с access violation, т.к. записанная инструкция ret возвращала бы управление на невалидный адрес, по-хорошему если, то в потоке нужно вызывать что-то вроде NtTerminateThread (NtCurrentThread ());, но это пока тестовый код, думаю, сейчас это делать необязательно. Сейчас в результате получаю крайне странное поведение. На Windows Server 2003 SP1 процессы запускаются и сразу же тихо падают, хотя по идее код ещё даже не начал выполнятся! На Windows XP SP3 всё работает, вроде бы, как и полагается - процессы запускаются и выполняются далее нормально. Далее если я таки запускаю поток после создания, то ситуация немного меняется. На Windows XP примерно в 10% случаев иногда приложения таки запускаются, а в остальном вылазит вот такое сообщение об ошибке: "Ошибка при инициализации приложения (0xC0000142). Для выхода из приложения нажмите кнопку ОК.". На Windows Server 2003 по-прежнему либо тихо падает сразу при запуске либо тоже самое сообщение об ошибке. Код 0xC0000142 - это Код (Text): // {DLL Initialization Failed} // Initialization of the dynamic link library %hs failed. The process is terminating abnormally. // #define STATUS_DLL_INIT_FAILED ((NTSTATUS)0xC0000142L) В WinDbg ставил бряк на виртуальный адрес кода, записанного на шаге 5, и бряк ни разу не сработал, значит код вообще не вызывается. С чем это может быть связано - не понимаю. Вроде всё сделал как вот тут советуют - http://www.wasm.ru/forum/viewtopic.php?id=24967 и всё равно не пашет. Может кто знает, что не так?
Трудно сказать почему не работает, могу предположить. Ядро при создании потока ставит в его очередь событие, поток начнёт его обрабатывать - с этой точки он и начнёт исполняться в юзермоде. Это KiUserApcDispatcher(), а событие LdrInitializeThunk(): Код (Text): PspUserThreadStartup( [...] KiInitializeUserApc (PspGetBaseExceptionFrame (Thread), PspGetBaseTrapFrame (Thread), PspSystemDll.LoaderInitRoutine, ;LdrInitializeThunk NULL, PspSystemDll.DllBase, NULL); В XPSP2 немного иначе, но это не меняет сути. Именно этот код и будет выполнен вначале. Код (Text): KiUserApcDispatcher: lea edi,dword ptr [esp+10] pop eax call eax push 1 push edi call ZwContinue В стеке будут параметры, переданные в KiInitializeUserApc. Тоесть в регистр Eax будет загружен указатель на LdrInitializeThunk(), а эта функция весьма тяжёлая, именно из неё поток обходит все EntryPoint модулей с причиной DLL_THREAD_ATTACH и подгружает kernel32.dll. Затем только по возврату из этой функции(если успешно прошло) поток перейдёт посредством NtContinue на код, указатель на который был в регистре Eip контекста. Так как гдето возникает ошибка, возврат из процедуры не происходит, поэтому не срабатывает бряк. Следует поставить точку останова ниже, например в самом начале на KiUserApcDispatcher() и посмотреть состояние контекста потока и стека.
zorro Это не важно, RPL = 0, но в 7-м дескрипторе GDT DPL = 3, поэтому юзермодный код свободно обращатсо может, темболее что первый сервис перезагрузит селектор в 0x3B.
ну указано то место с неточностью)) если бы должно было быть 38, то не указывали бы. У 38 уровень привилегий в селекторе 0, а у 3B - 3. Хорошо хоть индексы одинаковые но как сказал CrystalIC, это не важно
Great Из селектора игнорируетсо если PL_GDT > PL_SEL, проверь сам. Great: исправил у себя "ошибка" на "неточность", но по-моему, ты слишком придираешься к словам )
В асе топикстартера просил бряки на NtTerminateThread + call stack, бряк на KiUserExceptionDispatcher + call stack. Это в тему того, что в 2003 сервер у него молча вылетают новоиспеченные процессы после загрузки дрова. В XP у него заработало, как выяснилось, если его чудные махинации с reserve/commit заменить на полный коммит всего стека.
NDIS Можешь сделать так: 1. Внедрить код в ап целевого процесса 2. В kernel32 поменять в таблице импорта указатели на ф-ии из ntdll на свой код. При первом же вызове ф-ий ntdll из kernel32 внедрённый код получит управление. Из него уже и запускай поток обычными юзермодными ф-ми.
asd Хорошая кстати мысль.) Но с топикстартером мы вроде разобрались, вроде поток запускается.) нативный, правда.
CrystalIC ? пробема была у него в том, что он забыл сделать аттач к процессу когда проецировал в юзермод
Еще один момент. В качестве отправной точки пытаюсь вызвать RtlCreateUserThread (адрес подсмотрел в IDA). Вызов происходит в обработчике DeviceControlIo. RtlCreateUserThread возвращает 0 (т.е., все в порядке), но на самом деле ничего не происходит. Чего еще не хватает? Код (Text): RtlCreateUserThread(NtCurrentProcess(), NULL, FALSE, 0, 0, 0, (PVOID)0x00418B20, // функция вызывающая MessageBox NULL, NULL, NULL);