> Windows陷阱机制简介 陷阱(Trap)是Windows系统中一种不可缺少的系统机制当系统中发生中断(硬件中断或软件中断)异常时处理器会捕捉这个动作并将系统的控制转移到一个固定的处理程序处进行相应的操作处理在处理器开始处理发生的中断或异常前必须保存一些处理器环境参数到堆栈中以备系统还原时使用系统是通过一种称为陷阱帧(Trap Frame)的方式来实现的它将系统中全部线程的环境数据保存到内核堆栈(Kernel Stack)中在执行完后通过堆栈的出栈机制来恢复系统控制流程中的执行点内核中的陷阱机制分为中断和异常中断是系统中随即发生的异步事件与当前系统的处理器状态无关同时系统中的中断可分为可屏蔽中断和不可屏蔽中断而异常则是一种同步事件在特定情况下异常可以重现而中断不可以中断又可以分为硬件中断和软件中断很明显硬件中断是与硬件相关的比如I/O设备执行的某些操作处理器时钟或硬件端口上的处理等软件中断则是通过中断指令int xx引入的它往往是应用程序在用户模式执行后进入操作系统的代码这时系统为用户提供了各种各样的系统服务比如我们上次提到的系统服务调用(System Service Call)在Windows NT/下就是通过软件中断int xe(System Service Interrupt)来实现的虽然在Windows XP/下微软使用了一种称为快速系统调用接口来为用户提供系统服务不过大量的中断服务仍然存在与系统之中的 > 中断处理及其相关流程 此处我们讨论的是与特定处理器相关的数据结构所以会有一些移植方面的问题本文仅针对Intel的x Family处理器并且本文附带的程序也只支持在Intel x处理器上正常执行何为IDT?IDT(Interrupt Descriptor Table)称为中断描述符表它是可容纳个单元的数组数组中的每个成员是称之为门的长度为字节的段描述符在IDT中门可分为三种中断门(Interrrupt Gate)陷阱门(Trap Gate)和任务门(Task Gate)但主要的是中断门和陷阱门而它们两者之间也只有少许差别我们在此只关心IDT中的中断门如果您对这方面比较感兴趣请查阅Intel处理器的相关文档《Intel Architecture Software Developers ManualVolume 》同时在系统中存在一个中断描述符表寄存器(IDTR)它包含了系统中断描述符表的基地址和IDT的限制信息它于一条汇编指令sidt息息相关在下文中我们将看到它是我们实现各种中断描述符表扩展的基础和关键!还有一点是需要注意的在Windows系统中引入了分页分段和虚拟存储机制后就存在这一种调度机制将需要执行的代码和数据调入内存将不需要的数据调到外存(辅助存储器如硬盘等)如果我们在执行某些代码时发现了我们需要的数据不在内存中时就会发出一个缺页中断这时系统就会在IDT中搜寻这个中断的ISR(Interrupt Service Routine中断服务例程)执行相应的调入工作大家可以想象如果我们的中断描述符表被调出到外存后会是什么样的结果?那时系统将无法定位缺页中断的服务例程至此系统将会崩溃掉! 在中断描述符表中我们刚才提到了一个感兴趣的寄存器IDTR当然我们更关心对我们来说更直接的数据IDT中的代码段选择器(Code Segment Selector)中断执行代码的偏移量(Offset)和中断描述符的权限等级(Descriptor Privilege Level)参数下面我们看看中断指令的执行流程我们应该知道应用程序执行在用户模式(Ring )下而中断描述符表则是存在于内核模式(Ring )才可以访问的系统地址空间内的在软件中断发生后也就是应用程序调用了某条软件中断指令后处理器首先在IDT中检索传入的中断号参数找到响应的入口单元后就检查中断门的权限等级参数看是否允许Ring 下的应用程序调用这样操作系统就为我们保留了对软件中断调用控制的权力然而硬件中断和异常是不会关注权限方面的信息如果当前权限等级(Current Privilge LevelCPL)数值大于中断门描述符需要的权限(Descriptor Privilege Level)也就是权限不够时会引发一个通用保护故障(General Protection Fault)反之则进行处理器的切换从用户堆栈到内核堆栈现在是保存线程环境的时候了处理器将在用户模式下的堆栈指针(SS:ESP)和标准的中断帧(EFLAGS和CS:EIP)压入堆栈之后处理器进入我们的中断服务例程执行相关的代码处理后通过汇编指令iretd返回到调用的应用程序在指令iretd执行时系统将存储在堆栈中的线程环境数据出栈还原待系统恢复中断指令执行前的环境后就接着执行应用程序的后续代码 > 中断相关数据结构 首先我们介绍一下前面我们提到的一条关键汇编指令sidt的相关数据结构在执行指令sidt后系统将会把中断描述符表的基地址和限制(总共长六字节)保存在指令中指向的变量指针中这就是我们进行IDT操作的入门口 typedef struct _idtr { //定义中断描述符表的限制长度两字节 short IDTLimit; //定义中断描述服表的基址长度四字节 unsigned int IDTBase; }IDTR*PIDTR; 当我们获得了IDT的入口后就会在中断描述符表中检索我们需要处理的中断号对应的IDT单元单元中包含了很多我们需要注意的数据结构其中我们最为关心的是代码段选择器中断代码执行的偏移量和特权等级等那好我们先给出它的定义在下文中我们将详细讨论它们的具体应用 typedef struct _idtentry { //中断执行代码偏移量的底位 unsigned short OffsetLow; //选择器也就是寄存器 unsigned short Selector; //保留位始终为零 unsigned char Reserved; //IDT中的门的类型包括中断门陷阱门和任务门 unsigned char Type:; //段标识位 unsigned char SegmentFlag:; //中断门的权限等级表示内核级表示用户级 unsigned char DPL:; //呈现标志位 unsigned char Present:; //中断执行代码偏移量的高位 unsigned short OffsetHigh; }IDTENTRY*PIDTENTRY; > 创建软件中断钩子的作用 作为普通的Windows程序员或许您需要的是熟悉对系统基本功能的操作以及对通用程序开发的熟练掌握但对于一个有想法的Windows内核级分析开发人员来说对系统底层的深入了解是非常必要的同时也是非常重要的Hook为我们创造了一个绝好的机会它使我们了解系统内部运行机制的想法成为了一种可能同时书写一个系统相关的监视程序可以自动的对系统内部操作进行记录与分析当然我们不能局限于对系统的了解我们更渴望实施对系统的修改与扩展改变系统原有的操作特性注入我们需要的功能组件让系统做更适合我们自己也是我们最希望看到的操作前面我们曾经谈到了创建系统服务调用的钩子来截获系统服务调用同样在Windows下系统服务是通过系统服务中断(System Service Interruptint xe)来实现的通过截获软件中断同样可以达到监视并修改系统服务调用的功能在此我们主要讨论的是为软件中断创建钩子不过对于硬件中断和异常也同样不例外我们同样可以将本文提到的方法应用于硬件中断和异常比如我们也可以通过截获键盘驱动的中断调用来书写内核级的键盘记录器它可以直接对每次击键和释放进行操作效果是非常的明显不过这还需要使用到一些微软为我们提供的与硬件中断钩子相关的函数 > 如何创建软件中断钩子? 其实创建软件中断钩子的过程应该是比较明显了下面我们将先简要介绍一下创建Hook的过程然后以实际代码进行具体的讲解首先我们通过汇编指令sidt(sidt: Store Interrupt Descriptor Table Registerlidt: Load Interrupt Descriptor Table Register)来获取IDT的基地址IDTBase然后我们在中断描述符表中搜寻我们需要HOOK的中断号HOOKINTID它应该是在内的一个整数虽然最新的Intel处理器声称支持个中断描述符单元但由于某些限制原因仍然只能处理前个中断描述门在找到我们需要Hook的中断描述门后将它原本的中断执行代码偏移量(位)保存到一个全局变量OldISR中以备我们在执行中断处理或恢复IDT时使用这样新的IDT中对应中断号的执行代码偏移量就指向了我们自己的处理代码了在我们的处理代码NewISR中注意先要保存一些线程环境在处理完我们额外添加的执行程序(Monitor监视注册表相关的个系统服务调用)后恢复现场并执行中断门以前指向的程序代码这样对外就看不出我们对中断门做了什么额外的处理感觉和以前没什么两样!如果我们只是处理了我们添加的代码而没有继续执行中断门对应的以前的程序代码那么系统必将混乱甚至崩溃!同样在我们卸载我们的软件中断钩子时就是进行了一个逆向工作先获取IDT的基地址然后将保存在全局变量中的旧的执行代码地址偏移量赋给对应中断号的偏移量单元(OffsetLow/OffsetHigh)大概过程讲得差不多了相关程序为THookInt我们再看看代码吧! VOID HookInt(VOID) { //保存IDT入口的基地址和限制信息的数据结构 IDTR idtr; //记录IDT数组的指针通过它可以查找到我们需要Hook中断号对应的中断门 PIDTENTRY IdtEntry; //汇编指令sidt获取IDT入口信息 __asm sidt idtr; //赋予IDT基地址值 IdtEntry = (PIDTENTRY)idtrIDTBase; //保存中断号HOOKINTID对应 |