2026/2/9 17:22:11
网站建设
项目流程
网站推广方案范例,广州建站服务,网络营销方式和工具,阳江市招聘最新招聘前言
在之前的文章 中#xff0c;我们已经分析了#xff1a;内核如何在 KiDeliverApc 中识别用户 APC如何调用 KiInitializeUserApc以及它如何修改 TrapFrame 与用户栈#xff0c;为用户 APC 的执行提前“铺好路”但需要特别强调的是#xff1a;KiInitializeUserApc 并不执…前言在之前的文章 中我们已经分析了内核如何在 KiDeliverApc 中识别用户 APC如何调用 KiInitializeUserApc以及它如何修改 TrapFrame 与用户栈为用户 APC 的执行提前“铺好路”但需要特别强调的是KiInitializeUserApc 并不执行用户 APC。它只是为“下一次返回用户态”布置好执行环境。真正执行用户 APC 的地方发生在 Ring3并且位于 ntdll.dll 中。本文将从线程返回用户态开始完整分析用户 APC 是如何在 Ring3 中被执行又如何返回原执行流的。一、从内核返回用户态执行入口已经被篡改回顾上一篇文章KiInitializeUserApc 在用户栈上写入NormalRoutineNormalContextSystemArgument1SystemArgument2以及一整份 _CONTEXT 结构下面结合KiUserApcDispatcher汇编代码对这些步骤逐一说明:.text:77F06F58;S U B R O U T I N E.text:77F06F58.text:77F06F58;Attributes:noreturn.text:77F06F58.text:77F06F58;__stdcallKiUserApcDispatcher(x,x,x,x).text:77F06F58public_KiUserApcDispatcher16.text:77F06F58 _KiUserApcDispatcher16proc near;DATA XREF:.text:off_77EF61B8↑o.text:77F06F58.text:77F06F58 arg_Cbyte ptr10h.text:77F06F58 arg_2D8byte ptr2DCh.text:77F06F58.text:77F06F58 lea eax,[esp2DCh].text:77F06F5F mov ecx,large fs:_TEB;ecxTEB准确说是把 TEB 基址拿出来备用.text:77F06F66 mov edx,offset _KiUserApcExceptionHandler16;edxKiUserApcExceptionHandler 的地址.text:77F06F66;这是用户 APC 执行期间专用的异常处理函数SEH handler.text:77F06F6B mov[eax],ecx;构造一个 SEH 节点.text:77F06F6B;写入 Next 指针.text:77F06F6D mov[eax4],edx;写入 HandlerKiUserApcExceptionHandle.text:77F06F70 mov large fs:0,eax;fs:[0]就是 SEH 异常链表头NT_TIB.ExceptionList.text:77F06F70;这句就是把我们新构造的异常帧挂到 fs:[0]上.text:77F06F76 pop eax;eaxNormalRoutine.text:77F06F77 lea edi,[esp0Ch];edi 指向 Context 结构.text:77F06F7B call eax;直接调用 NormalRoutine.text:77F06F7D mov ecx,[edi2CCh];ecxContext.ExceptionList.text:77F06F83 mov large fs:0,ecx;恢复异常链.text:77F06F8A push1;TestAlert.text:77F06F8C push edi;CONTEXT.text:77F06F8D call _ZwContinue8;ZwContinue(x,x).text:77F06F92 mov esi,eax.text:77F06F94.text:77F06F94 loc_77F06F94:;CODE XREF:.text:77F06F9A↓j.text:77F06F94 push esi.text:77F06F95 call _RtlRaiseStatus4;RtlRaiseStatus(x).text:77F06F95 _KiUserApcDispatcher16endp;sp-analysis failed二、KiUserApcDispatcher 的第一步建立异常保护.text:77F06F58 lea eax,[esp2DCh].text:77F06F5F mov ecx,large fs:_TEB;ecxTEB准确说是把 TEB 基址拿出来备用.text:77F06F66 mov edx,offset _KiUserApcExceptionHandler16;edxKiUserApcExceptionHandler 的地址.text:77F06F66;这是用户 APC 执行期间专用的异常处理函数SEH handler.text:77F06F6B mov[eax],ecx;构造一个 SEH 节点.text:77F06F6B;写入 Next 指针.text:77F06F6D mov[eax4],edx;写入 HandlerKiUserApcExceptionHandle.text:77F06F70 mov large fs:0,eax;fs:[0]就是 SEH 异常链表头NT_TIB.ExceptionList.text:77F06F70;这句就是把我们新构造的异常帧挂到 fs:[0]上KiUserApcDispatcher 进入后做的第一件事并不是执行用户回调而是在用户栈上临时构造一个 SEH 异常处理节点并将其挂入当前线程的异常链表fs:[0]。该异常处理器专用于本次 APC 回调 用于捕获 NormalRoutine 执行过程中可能发生的异常防止异常直接破坏线程的原始执行上下文。在 NormalRoutine 执行结束后 该异常节点会被立即移除 不影响线程后续的异常处理行为。关于用户态 APC 回调期间的异常处理机制本文不再展开其设计目的仅在于保证 APC 回调对线程执行流的透明性。此阶段后用户栈结构三、执行用户 APC调用 NormalRoutine.text:77F06F76 pop eax;eaxNormalRoutine此行代码执行后堆栈结构如下.text:77F06F7B call eax;直接调用 NormalRoutine此行代码执行后堆栈结构如下可以看出在 KiInitializeUserApc 中内核已经按固定布局把参数巧妙的写到了用户栈栈布局本身就是为 KiUserApcDispatcher 量身定制的至此用户 APC 的 NormalRoutine 正式在 Ring3 中执行。四、用户 APC 执行完后并不会“直接回去”这是用户 APC 机制中最容易被误解的一点。当 NormalRoutine 执行结束后线程并不会直接回到原来的用户代码。原因是当前的寄存器状态当前的栈指针当前的执行位置全部都是为了 APC 临时构造的必须有一个步骤把执行现场恢复成“APC 发生前”的样子五、恢复异常链ZwContinue再次进入内核.text:77F06F7D mov ecx,[edi2CCh];ecxContext.ExceptionList.text:77F06F83 mov large fs:0,ecx;恢复异常链.text:77F06F8A push1;TestAlert.text:77F06F8C push edi;CONTEXT.text:77F06F8D call _ZwContinue8;ZwContinue(x,x)这里传入的 _CONTEXT正是当初在 KiInitializeUserApc 中由内核复制到用户栈上的那一份。这一步发生了什么ZwContinue 触发系统调用进入 Ring0内核中的 NtContinue 被执行内核根据 _CONTEXT恢复寄存器恢复栈恢复 EIP随后内核再次走返回路径NtContinue ↓ KiServiceExit ↓ iret这一次线程才真正回到 APC 发生前的用户执行流。六、执行链路总结完整闭环至此用户 APC 的完整执行流程可以总结为Ring0:KiDeliverApc → KiInitializeUserApc-构造用户栈-复制 CONTEXT-修改TrapFrame(EIP/ESP)→ KiServiceExit → iret Ring3:ntdll!KiUserApcDispatcher-建立 SEH-call NormalRoutine-恢复 SEH-ZwContinue(Context)Ring0:nt!NtContinue-从Context中恢复trapframe-返回内核出口 Ring3:原用户代码继续执行这是一次 Ring3 → Ring0 → Ring3 的完整闭环。