用户 APC 的执行过程(下)

前言

在之前的文章 中,我们已经分析了:

  • 内核如何在 KiDeliverApc 中识别用户 APC

  • 如何调用 KiInitializeUserApc

  • 以及它如何修改 TrapFrame 与用户栈,为用户 APC 的执行提前“铺好路”

但需要特别强调的是:

KiInitializeUserApc 并不执行用户 APC。
它只是为“下一次返回用户态”布置好执行环境。

真正执行用户 APC 的地方,发生在 Ring3,并且位于 ntdll.dll 中。

本文将从线程返回用户态开始,完整分析
用户 APC 是如何在 Ring3 中被执行,又如何返回原执行流的。

一、从内核返回用户态:执行入口已经被篡改

回顾上一篇文章KiInitializeUserApc 在用户栈上写入:

  • NormalRoutine

  • NormalContext

  • SystemArgument1

  • SystemArgument2

  • 以及一整份 _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_KiUserApcDispatcher@16.text:77F06F58 _KiUserApcDispatcher@16proc near;DATA XREF:.text:off_77EF61B8↑o.text:77F06F58.text:77F06F58 arg_C=byte ptr10h.text:77F06F58 arg_2D8=byte ptr2DCh.text:77F06F58.text:77F06F58 lea eax,[esp+2DCh].text:77F06F5F mov ecx,large fs:_TEB;ecx=TEB(准确说是把 TEB 基址拿出来备用).text:77F06F66 mov edx,offset _KiUserApcExceptionHandler@16;edx=KiUserApcExceptionHandler 的地址.text:77F06F66;这是用户 APC 执行期间专用的异常处理函数(SEH handler).text:77F06F6B mov[eax],ecx;构造一个 SEH 节点.text:77F06F6B;写入 Next 指针.text:77F06F6D mov[eax+4],edx;写入 Handler=KiUserApcExceptionHandle.text:77F06F70 mov large fs:0,eax;fs:[0]就是 SEH 异常链表头(NT_TIB.ExceptionList).text:77F06F70;这句就是:把我们新构造的异常帧挂到 fs:[0].text:77F06F76 pop eax;eax=NormalRoutine.text:77F06F77 lea edi,[esp+0Ch];edi 指向 Context 结构.text:77F06F7B call eax;直接调用 NormalRoutine.text:77F06F7D mov ecx,[edi+2CCh];ecx=Context.ExceptionList.text:77F06F83 mov large fs:0,ecx;恢复异常链.text:77F06F8A push1;TestAlert.text:77F06F8C push edi;CONTEXT.text:77F06F8D call _ZwContinue@8;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 _RtlRaiseStatus@4;RtlRaiseStatus(x).text:77F06F95 _KiUserApcDispatcher@16endp;sp-analysis failed

二、KiUserApcDispatcher 的第一步:建立异常保护

.text:77F06F58 lea eax,[esp+2DCh].text:77F06F5F mov ecx,large fs:_TEB;ecx=TEB(准确说是把 TEB 基址拿出来备用).text:77F06F66 mov edx,offset _KiUserApcExceptionHandler@16;edx=KiUserApcExceptionHandler 的地址.text:77F06F66;这是用户 APC 执行期间专用的异常处理函数(SEH handler).text:77F06F6B mov[eax],ecx;构造一个 SEH 节点.text:77F06F6B;写入 Next 指针.text:77F06F6D mov[eax+4],edx;写入 Handler=KiUserApcExceptionHandle.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;eax=NormalRoutine

此行代码执行后堆栈结构如下:

.text:77F06F7B call eax;直接调用 NormalRoutine

此行代码执行后堆栈结构如下:

可以看出:

  • 在 KiInitializeUserApc 中,内核已经按固定布局把参数巧妙的写到了用户栈
  • 栈布局本身就是为 KiUserApcDispatcher 量身定制的

至此,用户 APC 的 NormalRoutine 正式在 Ring3 中执行。

四、用户 APC 执行完后:并不会“直接回去”

这是用户 APC 机制中最容易被误解的一点。

当 NormalRoutine 执行结束后:

线程并不会直接回到原来的用户代码。

原因是:

  • 当前的寄存器状态

  • 当前的栈指针

  • 当前的执行位置

全部都是为了 APC 临时构造的

必须有一个步骤:

把执行现场恢复成“APC 发生前”的样子

五、恢复异常链,ZwContinue:再次进入内核

.text:77F06F7D mov ecx,[edi+2CCh];ecx=Context.ExceptionList.text:77F06F83 mov large fs:0,ecx;恢复异常链.text:77F06F8A push1;TestAlert.text:77F06F8C push edi;CONTEXT.text:77F06F8D call _ZwContinue@8;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 的完整闭环。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1156284.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Keil+STM32调试多任务系统:实战案例分享

用Keil玩转STM32多任务系统:从崩溃现场到稳定运行的实战之路你有没有遇到过这样的场景?代码明明逻辑清晰、编译通过,烧进去后却在某个莫名其妙的时刻突然“死机”,串口啥也没输出,或者只留下一句孤零零的HardFault。更…

STM32CubeMX点亮LED灯:超详细版入门实战指南

用STM32CubeMX点亮LED灯:从零开始的实战入门课你有没有试过,手握一块STM32开发板,电脑上装好了IDE,却卡在第一步——连个LED都点不亮?别急,这几乎是每个嵌入式新手都会踩的坑。寄存器不会配、时钟树看不懂、…

PTL仓储亮灯系统如何实现拣选“零”误差运营?

仓储运营中,拣选环节的速度和精准,直接影响整体物流成本与客户体验。随着仓储管理向智能化、数字化演进,PTL(Pick-to-Light)亮灯拣选系统凭借其直观、高效的作业方式,逐渐成为高精度拣选场景下的重要工具。…

⚡_实时系统性能优化:从毫秒到微秒的突破[20260113173734]

作为一名专注于实时系统性能优化的工程师,我在过去的项目中积累了丰富的低延迟优化经验。实时系统对性能的要求极其严格,任何微小的延迟都可能影响系统的正确性和用户体验。今天我要分享的是在实时系统中实现从毫秒到微秒级性能突破的实战经验。 &#…

Keil与ST-Link连接配置:新手友好型指南

Keil与ST-Link连接配置:从“连不上”到“一按就跑”的实战指南 你有没有过这样的经历? 代码写得信心满满,点击Keil的“Download”按钮——结果弹出一个无情提示:“ Cortex-M device not responding. ” 或者更糟:…

[特殊字符]_安全性能平衡术:如何在保证安全的前提下提升性能[20260113174726]

作为一名经历过多次安全事件的工程师,我深知在Web应用开发中安全与性能的平衡是多么重要。最近,我参与了一个金融级应用的开发,这个项目让我重新思考了安全机制对性能的影响。今天我要分享的是如何在保证安全的前提下提升Web应用性能的经验。…

STM32芯片调试:JLink仿真器时序控制全面讲解

深入STM32调试核心:JLink仿真器时序控制实战全解析你有没有遇到过这样的场景?代码烧录到STM32H7上,JLink连接失败,反复提示“Cannot connect to target”;或者刚进入单步调试,定时器却在疯狂输出PWM波&…

解决 smb 签名漏洞 SMB Signing not required

检测代码,两个都为 True 就是修复了 Get-SmbServerConfiguration | Select-Object RequireSecuritySignature, EnableSecuritySignature修复代码 Write-Host "--- Enabling SMB Signing (Mandatory) ---" -ForegroundColor Cyan# 1. 针对服务端 (Server) …

高压电缆故障预警与定位:基于行波与北斗的高精度监测系统解析

高压电缆是电力输送的“大动脉”,一旦发生故障,不仅影响供电稳定性,还可能引发安全事故。如何在故障发生后迅速定位、快速修复,是电力运维中的一大难题。近年来,随着电力物联网和北斗技术的发展,基于行波定…

python 代码扫描 icmp 时间戳漏洞 ICMP Timestamp Request Remote Date Disclosure

from scapy.all import * import timedef verify_fix(ip):# 构造请求pkt IP(dstip) / ICMP(type13)print(f"正在发送 Type 13 请求到 {ip}...")# 发送包并设置严格的超时时间(2秒)reply sr1(pkt, timeout2, verboseFalse)if reply is None:p…

License Plate Detection Dataset (10,125 Images) 车牌检测数据集(10,125张图像)

该数据集是一个面向自动车牌识别(ANPR)系统的高质量目标检测数据集,可直接用于计算机视觉模型的训练与评估,具体核心信息如下: 数据规模与划分 总计10,125张高分辨率图像,格式为JPEG/PNG,原始分…

企业AI平台运营的关键密码,AI应用架构师独家解读

企业AI平台运营的关键密码:AI应用架构师独家解读 一、引言 (Introduction) 钩子 (The Hook) “我们投入了3000万建设AI平台,上线3年只落地了2个应用,ROI不足10%。”——这是某大型制造企业CIO在一次行业峰会上的无奈吐槽。另一组数据更触目惊心:Gartner报告显示,85%的企…

摩擦纳米发电机高效波浪能收集方法研究:基于光学动作捕捉浮子俯仰角及摇摆性能提供高精度验证数据 |中科院一区期刊王中林院士团队案例

导语随着清洁能源需求不断增加,海洋波浪能作为一种可再生能源受到广泛关注。中国科学院北京纳米能源与系统研究所王中林院士、曹南颖副研究员团队提出了一种摩擦纳米发电机(TENG),结合导电3D打印与浮力-重力优化,实现高…

multisim14.3安装与破解步骤:初学者实用教程

Multisim 14.3 安装与配置实战指南:从零构建稳定仿真环境当你的电路还没焊上电烙铁,它已经在虚拟世界里跑起来了你有没有过这样的经历?花了一周时间设计一个滤波器,制板、焊接、通电……结果一测,频率响应完全不对。回…

【期货量化入门】Python获取期货实时行情(TqSdk完整代码)

获取期货实时行情的基本步骤安装TqSdk库 确保Python环境已安装TqSdk库,可通过pip命令安装:pip install tqsd导入必要模块 需要从tqsdk模块导入TqApi和TqAuth:from tqsd import TqApi, TqAuth初始化连接与账户验证创建API实例时需要提供账户信…

浏览器插件到底安全不?教你几招快速检测方法

随着我们日常上网的频率越来越高,浏览器插件已经成为不少人的必备工具。无论是广告拦截、密码管理,还是视频下载,插件确实能让我们的网络生活更方便。 不过,你有没有想过,你安装的插件到底安全不安全?有些…

如何检测并清除Linux系统中的恶意软件并进行预防?

在Linux系统中,尽管其安全性较高,但仍可能受恶意软件(如病毒、木马、勒索软件和后门程序)攻击。以下是检测、清除和预防Linux系统中恶意软件的完整指南。一、检测Linux系统中的恶意软件1. 检查异常行为(1) 高CPU或内存占用使用以下…

ICRA 2025 南理工团队| 基于光学动作捕捉系统提供高精度位姿数据的腱驱动连续体机械臂(TDCM)的阻抗-容错控制实现高精度轨迹跟踪与柔顺力控

导语南京理工大学郭毓教授团队在 ICRA 2025 上发表了关于腱驱动连续体机械臂(TDCM)的研究论文《Command Filtered Cartesian Impedance Control for Tendon Driven Continuum Manipulators with Actuator Fault Compensation》。本文提出结合阻抗控制与容…

ST7735与MCU通信优化:智能穿戴设备操作指南

如何让ST7735在智能穿戴设备中“又快又省”?——深度优化MCU通信实战指南 你有没有遇到过这样的场景: 手环屏幕刷新慢半拍,滑动菜单卡成幻灯片; CPU一直在跑显示任务,心率数据却来不及处理; 电池明明不小…

CCS20新手教程:手把手带你熟悉开发环境

掌握TI嵌入式开发的钥匙:CCS20实战入门指南你是否曾在启动一个C2000项目时,面对Code Composer Studio那复杂的界面无从下手?是否下载程序失败、变量监视失效、断点无法命中,反复重启却找不到原因?别担心——这几乎是每…