Здрасте. Размышляя над темой http://kitrap08.blogspot.com/2011/05/apc-dispatchera.html появилась мысль использовать V8086-mod. Автор того поста сказал что ничего не выйдет. Создаётся остановленный тред, взводится EFlags.VF и тред ресумится. APC-диспетчер не вызывается, управление получает непосредственно стартап-код, это ошибка в ядре: Код (Text): VOID KiInitializeUserApc ( IN PKEXCEPTION_FRAME ExceptionFrame, IN PKTRAP_FRAME TrapFrame, IN PKNORMAL_ROUTINE NormalRoutine, IN PVOID NormalContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2 ) /*++ Routine Description: This function is called to initialize the context for a user mode APC. Arguments: ExceptionFrame - Supplies a pointer to an exception frame. TrapFrame - Supplies a pointer to a trap frame. NormalRoutine - Supplies a pointer to the user mode APC routine. NormalContext - Supplies a pointer to the user context for the APC routine. SystemArgument1 - Supplies the first system supplied value. SystemArgument2 - Supplies the second system supplied value. Return Value: None. --*/ { EXCEPTION_RECORD ExceptionRecord; CONTEXT ContextFrame; LONG Length; ULONG UserStack, TopOfStack; PKAPC_RECORD ApcRecord; // // APCs are not defined for V86 mode; however, it is possible a // thread is trying to set it's context to V86 mode - this isn't // going to work, but we don't want to crash the system so we // check for the possibility before hand. // if (TrapFrame->EFlags & EFLAGS_V86_MASK) { return ; } // // Move machine state from trap and exception frames to the context frame. // ContextFrame.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; KeContextFromKframes(TrapFrame, ExceptionFrame, &ContextFrame); // // Transfer the context information to the user stack, initialize the // APC routine parameters, and modify the trap frame so execution will // continue in user mode at the user mode APC dispatch routine. // try { C_ASSERT (CONTEXT_ALIGN >= __alignof(EXCEPTION_REGISTRATION_RECORD)); C_ASSERT (CONTEXT_ALIGN >= __alignof(KAPC_RECORD)); ASSERT ((TrapFrame->SegCs & MODE_MASK) != KernelMode); // Assert usermode frame // // Compute length of context record and new aligned user stack pointer. // Make sure to include space for a double-word aligned exception registration // record as well. For compatibility the exception registration must follow the // context record. // TopOfStack = (ContextFrame.Esp & ~(__alignof(EXCEPTION_REGISTRATION_RECORD)-1)); Length = CONTEXT_ALIGNED_SIZE + sizeof(KAPC_RECORD); UserStack = ((TopOfStack - sizeof (EXCEPTION_REGISTRATION_RECORD)) & ~CONTEXT_ROUND) - Length; // // Probe user stack area for writability and then transfer the context // record to the user stack. Note the minimum alignment is used as // the above code ensures the proper alignment of the addresses. // ProbeForWrite ((PCHAR)UserStack, TopOfStack-UserStack, 1); NT_ASSERT (((ULONG_PTR)(UserStack + sizeof(KAPC_RECORD)) & CONTEXT_ROUND) == 0); RtlCopyMemory ((PULONG)(UserStack + sizeof(KAPC_RECORD)), &ContextFrame, sizeof(CONTEXT)); // // Force correct R3 selectors into TrapFrame. // TrapFrame->SegCs = SANITIZE_SEG(KGDT_R3_CODE, UserMode); TrapFrame->HardwareSegSs = SANITIZE_SEG(KGDT_R3_DATA, UserMode); TrapFrame->SegDs = SANITIZE_SEG(KGDT_R3_DATA, UserMode); TrapFrame->SegEs = SANITIZE_SEG(KGDT_R3_DATA, UserMode); TrapFrame->SegFs = SANITIZE_SEG(KGDT_R3_TEB, UserMode); TrapFrame->SegGs = 0; TrapFrame->EFlags = SANITIZE_FLAGS( ContextFrame.EFlags, UserMode ); // // If thread is supposed to have IOPL, then force it on in eflags // if (KeGetCurrentThread()->Iopl) { TrapFrame->EFlags |= (EFLAGS_IOPL_MASK & -1); // IOPL = 3 } // // Set the address of the user APC routine, the APC parameters, the // new frame pointer, and the new stack pointer in the current trap // frame. Set the continuation address so control will be transferred // to the user APC dispatcher. // TrapFrame->HardwareEsp = UserStack; TrapFrame->Eip = (ULONG)KeUserApcDispatcher; TrapFrame->ErrCode = 0; ApcRecord = (PKAPC_RECORD)UserStack; ApcRecord->NormalRoutine = NormalRoutine; ApcRecord->NormalContext = NormalContext; ApcRecord->SystemArgument1 = SystemArgument1; ApcRecord->SystemArgument2 = SystemArgument2; } except (KiCopyInformation(&ExceptionRecord, (GetExceptionInformation())->ExceptionRecord)) { // // Lower the IRQL to PASSIVE_LEVEL, set the exception address to // the current program address, and raise an exception by calling // the exception dispatcher. // // N.B. The IRQL is lowered to PASSIVE_LEVEL to allow APC interrupts // during the dispatching of the exception. The current thread // will be terminated during the dispatching of the exception, // but lowering of the IRQL is required to enable the debugger // to obtain the context of the current thread. // KeLowerIrql(PASSIVE_LEVEL); ExceptionRecord.ExceptionAddress = (PVOID)(TrapFrame->Eip); KiDispatchException(&ExceptionRecord, ExceptionFrame, TrapFrame, UserMode, TRUE); } return; } T-фрейм не инициализируется, а используется первичный, заполненный в Get/Set- контекст апи. Изза валидации TrapFrame->EFlags & EFLAGS_V86_MASK формирование обоих фреймов не выполняется. Из за мода адреса урезаются, тоесть Ip и стек - младшее слово 32-хбитных регистров. Для этого в нуле аллоцируется память, туда можно записать стартап код и расположить стек. Вопрос - как откатать состояние треда в нормальный мод.
Автор таки посмотрел более внимательно и увидел что трапфрейм используется прежний ( тот который был получен в начале работы ядерного апк диспатчера ), так что работать будет. Надо подумать =]
Вызов прерываний возможен на IOPL = 3. Вызов Sysenter допустим, но в ядре проверка на V8086 мод и генерация #UD. Можно попробовать использовать LDT, тогда шедулер настроит 0x21 гейт для вызова DPMI-сервисов, но это видимо ничего не даст. Остаётся только манипуляция контекстом из другого потока. Вот пруфкод, запускающий тред без вызова диспетчера APC(без проверок успешности выполнения апи): Код (Text): CreateHiddenThread proc uses ebx Ip:PVOID Local Context:CONTEXT Local RegionAddress:PVOID, RegionSize:ULONG Local ThreadHandle:HANDLE Local ClientId:CLIENT_ID mov RegionAddress,4 mov RegionSize,PAGE_SIZE invoke ZwAllocateVirtualMemory, NtCurrentProcess, addr RegionAddress, 0, addr RegionSize, MEM_COMMIT or MEM_RESERVE or MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE invoke RtlCreateUserThread, NtCurrentProcess, NULL, TRUE, 0, NULL, NULL, -1, 0, addr ThreadHandle, addr ClientId mov Context.ContextFlags,CONTEXT_CONTROL or CONTEXT_SEGMENTS invoke ZwGetContextThread, ThreadHandle, addr Context ; Cs:Ip mov Context.regEip,1 mov Context.regSegCs,0 mov Context.regEFlags,EFLAGS_VM or EFLAGS_MASK mov ebx,Context.regEsp mov dword ptr ds:[1],90FEEBF9H ; Stc/Jmp $ invoke ZwSetContextThread, ThreadHandle, addr Context invoke ZwResumeThread, ThreadHandle, NULL @@: invoke ZwGetContextThread, ThreadHandle, addr Context test Context.regEFlags,EFLAGS_CF jz @b invoke ZwSuspendThread, ThreadHandle, NULL mov ecx,Ip mov Context.regSegCs,KGDT_R3_CODE or RPL_MASK mov Context.regSegDs,KGDT_R3_DATA or RPL_MASK mov Context.regSegEs,KGDT_R3_DATA or RPL_MASK mov Context.regSegSs,KGDT_R3_DATA or RPL_MASK mov Context.regSegFs,KGDT_R3_TEB or RPL_MASK mov Context.regEsp,ebx mov Context.regEip,ecx mov Context.regEFlags,EFLAGS_MASK invoke ZwSetContextThread, ThreadHandle, addr Context invoke ZwFreeVirtualMemory, NtCurrentProcess, addr RegionAddress, addr RegionSize, MEM_RELEASE invoke ZwResumeThread, ThreadHandle, NULL ret CreateHiddenThread endp TSS :P
Хук юзермодного диспетчера исключений. Чтобы выполнить нормальный код нужно будет выбраться из V8086, сгенерировав исключение, ядерный диспетчер исключений передаст управление на юзермодный (т.к. v8086 код выполняется с cpl = 3).
TSS Обратите внимание на проверку в KiDispatchException(): Код (Text): if (TrapFrame->HardwareSegSs != (KGDT_R3_DATA | RPL_MASK) || TrapFrame->EFlags & EFLAGS_V86_MASK ) { ExceptionRecord2.ExceptionCode = STATUS_ACCESS_VIOLATION; ExceptionRecord2.ExceptionFlags = 0; ExceptionRecord2.NumberParameters = 0; ExRaiseException(&ExceptionRecord2); } Видимо поток будет крутиться в бесконечном цикле на одной инструкции. Будет возникать фолт, ядро возвращать управление на туже самую инструкцию. При этом кстате не сбрасывается VF.
Если такой поток не может выбраться из режима эмуляции, то зачем его ловить? Он ничего не сможет сделать
У потока есть другой поток-родитель. Он может выполнить определённые манипуляции с создаваемым потоком, как например код в #3.
Это понятно что может. Я говорю про то, что созданный таким образом поток ограничен в возможностях, заперт в своем 1Мб АП, сервисы звать не может, cpl остается юзермодным, привелегированные инструкции эмулируются через обработчик #GP. Что создали поток, что не создали, результат примерно один Поэтому и ловить бессмысленно. А с технической точки зрения интересно, да.
TSS В #3 после ZwResumeThread() поток обычный, а не V86. Ну разве что нужно зарегать в csrss, но тут это не важно. Оно работает же. Родительский тред выполняет необходимые настройки, например при создании треда(CreateThread()/RtlCreateUserThread()) стартап-контекст ядро не формирует, это делает поток-родитель.