解析Windows2000的IDT扩展机制 下载本文配套源代码 typedef struct _idtr { //定义中断描述符表的限制,长度两字节; short IDTLimit; //定义中断描述服表的基址,长度四字节; unsigned int IDTBase; }IDTR,*PIDTR;当我们获得了IDT的入口后,就会在中断描述符表中检索我们需要处理的中断号对应的IDT单元,单元中包含了很多我们需要注意的数据结构,其中我们最为关心的是代码段选择器,中断代码执行的偏移量和特权等级等,那好我们先给出它的定义,在下文中我们将详细讨论它们的具体应用。 typedef struct _idtentry { //中断执行代码偏移量的底16位; unsigned short OffsetLow; //选择器,也就是寄存器; unsigned short Selector; //保留位,始终为零; unsigned char Reserved; //IDT中的门的类型:包括中断门,陷阱门和任务门; unsigned char Type:4; //段标识位; unsigned char SegmentFlag:1; //中断门的权限等级,0表示内核级,3表示用户级; unsigned char DPL:2; //呈现标志位; unsigned char Present:1; //中断执行代码偏移量的高16位; unsigned short OffsetHigh; }IDTENTRY,*PIDTENTRY; ![]() 作为普通的Windows程序员,或许您需要的是熟悉对系统基本功能的操作,以及对通用程序开发的熟练掌握。但对于一个有想法的Windows内核级分析开发人员来说,对系统底层的深入了解是非常必要的,同时也是非常重要的。Hook为我们创造了一个绝好的机会,它使我们了解系统内部运行机制的想法成为了一种可能。同时,书写一个系统相关的监视程序可以自动的对系统内部操作进行记录与分析。当然我们不能局限于对系统的了解,我们更渴望实施对系统的修改与扩展,改变系统原有的操作特性,注入我们需要的功能组件,让系统做更适合我们自己,也是我们最希望看到的操作。前面我们曾经谈到了创建系统服务调用的钩子来截获系统服务调用,同样在Windows2000下,系统服务是通过系统服务中断(System Service Interrupt,int 0x2e)来实现的,通过截获软件中断同样可以达到监视并修改系统服务调用的功能。在此我们主要讨论的是为软件中断创建钩子,不过对于硬件中断和异常也同样不例外,我们同样可以将本文提到的方法应用于硬件中断和异常。比如我们也可以通过截获键盘驱动的中断调用来书写内核级的键盘记录器,它可以直接对每次击键和释放进行操作,效果是非常的明显,不过这还需要使用到一些微软为我们提供的与硬件中断钩子相关的函数。 ![]() 其实创建软件中断钩子的过程应该是比较明显了,下面我们将先简要介绍一下创建Hook的过程,然后以实际代码进行具体的讲解。首先我们通过汇编指令sidt(sidt: Store Interrupt Descriptor Table Register;lidt: Load Interrupt Descriptor Table Register)来获取IDT的基地址IDTBase,然后我们在中断描述符表中搜寻我们需要HOOK的中断号HOOKINTID,它应该是在0-255内的一个整数,虽然最新的Intel处理器声称支持8192个中断描述符单元,但由于某些限制原因,仍然只能处理前256个中断描述门。在找到我们需要Hook的中断描述门后,将它原本的中断执行代码偏移量(32位)保存到一个全局变量OldISR中,以备我们在执行中断处理或恢复IDT时使用。这样新的IDT中对应中断号的执行代码偏移量就指向了我们自己的处理代码了。在我们的处理代码NewISR中,注意先要保存一些线程环境,在处理完我们额外添加的执行程序(Monitor,监视注册表相关的16个系统服务调用)后,恢复现场并执行中断门以前指向的程序代码。这样,对外就看不出我们对中断门做了什么额外的处理,感觉和以前没什么两样!如果我们只是处理了我们添加的代码而没有继续执行中断门对应的以前的程序代码,那么系统必将混乱甚至崩溃!同样在我们卸载我们的软件中断钩子时,就是进行了一个逆向工作。先获取IDT的基地址,然后将保存在全局变量中的旧的执行代码地址偏移量赋给对应中断号的偏移量单元(OffsetLow/OffsetHigh)。大概过程讲得差不多了,相关程序为T-HookInt,我们再看看代码吧! VOID HookInt(VOID) { //保存IDT入口的基地址和限制信息的数据结构; IDTR idtr; //记录IDT数组的指针,通过它可以查找到我们需要Hook中断号对应的中断门; PIDTENTRY IdtEntry; //汇编指令sidt,获取IDT入口信息; __asm sidt idtr; //赋予IDT基地址值; IdtEntry = (PIDTENTRY)idtr.IDTBase; //保存中断号HOOKINTID对应中断门所指向的执行代码偏移量,以备执行中断处理或恢复时使用; OldISR = ((unsigned int)IdtEntry[HOOKINTID].OffsetHigh << 16) │ (IdtEntry[HOOKINTID].OffsetLow); //关中断 __asm cli //更新执行代码偏移量的底16位; IdtEntry[HOOKINTID].OffsetLow = (unsigned short)NewISR; //更新执行代码偏移量的高16位; IdtEntry[HOOKINTID].OffsetHigh = (unsigned short)((unsigned int)NewISR >> 16); //开中断 __asm sti; } VOID UnhookInt(VOID) { IDTR idtr; PIDTENTRY IdtEntry; __asm sidt idtr; IdtEntry = (PIDTENTRY)idtr.IDTBase; __asm cli //恢复中断号HOOKINTID对应中断门执行代码偏移量的底16位; IdtEntry[HOOKINTID].OffsetLow = (unsigned short)OldISR; //恢复中断号HOOKINTID对应中断门执行代码偏移量的高16位; IdtEntry[HOOKINTID].OffsetHigh = (unsigned short)((unsigned int)OldISR >> 16); __asm sti; } VOID __fastcall Monitor() { …… //由于我们处理的中断号为0x2e, //对应于系统服务中断(System Service Interrupt), //通过获取eax寄存器中的数值来区分系统服务调用; __asm mov dwServiceId,eax; //执行内核函数获取当前进程的ID号; dwProcessId = (unsigned int)PsGetCurrentProcessId(); //提升当前IRQL,防止被中断; KeRaiseIrql(HIGH_LEVEL,&OldIrql); switch(dwServiceId) { //如果eax对应的数值为0x23, //则对应于Windows2000的ZwCreateKey系统服务调用; case 0x23: DbgPrint("ProcessId: %d ZwCreateKey/n",dwProcessId); break; …… default: break; } //恢复原始IRQL; KeLowerIrql(OldIrql); } ![]() 通过添加软件中断,我们可以扩展系统的功能,改变系统的很多操作行为。在前面我们介绍过为系统添加新的系统服务调用来扩展系统,通过添加新的软件中断同样可以到达添加系统服务调用的目的,并且我们可以在新添的中断处理程序中执行Ring 0级别的任意代码,那是何等的让人欣慰! 其实在IDT中,256个中断门单元并不是被完全利用的,还剩下一些流给将来扩展使用的中断门,我们可以自己给这些未使用的中断门添加一些机制为我所用。其实添加软件中断的过程和前面我们详细讲解的添加软件中断钩子有很多相似的地方,所以在此我就不做很详细的介绍了。同样是,首先获得IDT的基地址,然后在中断描述符表中查找我们将要添加的中断号对应的中断门描述符,之后给相关的参数赋值,使其成为名副其实的软件中断门。这时我们就可以在应用程序中使用中断指令int xx来调用我们自己中断门中的服务程序了。 ![]() 相关程序为T-ADDIG(Add Interrupt Gate),我们来看看代码哈~ NTSTATUS InstallIG() { …… //判断我们想要添加的中断是否已被占用; if(IdtEntry[ADDINTID].OffsetLow != 0 ││ IdtEntry[ADDINTID].OffsetHigh != 0) { return STATUS_UNSUCCESSFUL; } //复制原始的中断门描述信息; RtlCopyMemory(&OldIdtEntry,&IdtEntry[ADDINTID],sizeof(OldIdtEntry)); //关中断 __asm cli //更新执行代码偏移量的底16位; IdtEntry[ADDINTID].OffsetLow = (unsigned short)InterruptServiceRoutine; //目的代码段的段选择器,CS为8; IdtEntry[ADDINTID].Selector = 8; //保留位,始终为零; IdtEntry[ADDINTID].Reserved = 0; //门类型,0xe代表中断门; IdtEntry[ADDINTID].Type = 0xe; //SegmentFlag设置0代码为段; IdtEntry[ADDINTID].SegmentFlag = 0; //描述符权限等级为3,允许用户模式程序调用本中断; IdtEntry[ADDINTID].DPL = 3; //呈现标志位,设置为一; IdtEntry[ADDINTID].Present = 1; //更新执行代码偏移量的高16位; IdtEntry[ADDINTID].OffsetHigh = (unsigned short)((unsigned int)InterruptServiceRoutine >> 16); //开中断 __asm sti return STATUS_SUCCESS; } VOID RemoveIG() { …… __asm cli //恢复我们修改过的中断门描述符; RtlCopyMemory(&IdtEntry[ADDINTID],&OldIdtEntry,sizeof(OldIdtEntry)); __asm sti } extern void _cdecl InterruptServiceRoutine(VOID) { unsigned int Command; //获取eax寄存器中的数值,接受从用户模式传入的命令参数; __asm mov Command,eax; //执行内核代码,获取操作系统版本号; DbgPrint("NtBuildNumber == %d/n",(unsigned short)NtBuildNumber); //中断返回; __asm iretd; }后记 写到这儿,我们只是介绍了扩展IDT的一些基本方法,当然还有很多更深入的,更值得我们研究的课题需要大家努力去探索。比如我们可以将T-HookInt扩展,不仅仅是监视系统注册表操作相关的系统服务调用,不过在Windows XP/2003上由于其内在机制的一些变更,所以通过Hook int 0x2e来截获系统服务调用就不这么现实了。当然还有基于IDT的内核级后门,可以通过添加新的软件中断为任意用户提供SYSTEM权限级别的Command等。总之,探究Windows内核奥秘的旅行还未结束,或许这只能算是一次起航罢了。 附录: 由于本文相关的源代码比较多,所以在此就不帖了,欢迎有兴趣的朋友到我们主页下载,谢谢~ 关于我们: FZ5FZ 主要从事网络/系统安全的学习与研究,深入编程技术的剖析与探讨,坚持原创,追求共享。 FZ5FZ 主页:http://www.fz5fz.org |
解析Windows2000的IDT扩展机制[转贴]
最新推荐文章于 2022-10-12 13:15:18 发布