各位新年快乐:rose :rose 本文成型感谢shoooo大牛,下文还是称为大S 大S问偶 int3具体是怎么执行的 偶无法回答.于是认真学习了1下 我们执行int3 立刻会进入系统中断门
引用:
什么是中断(interrupt) "中断被定义为当一个事件发生时,改变处理器的指令序列。这样的事件可由CPU芯片 内部或者外部硬件产生电信号产生" (摘自: "Understanding the Linux kernel," O'Reilly publishing.)Intel参考手册上指出“同步中断”(在一个指令执行完成后,由CPU控制单元产生的)作为“异常”。 异步中断(可能会在任意时刻由其他硬件产生的)才称为“中断”。中断被外部的I/O设备产生。 但是异常是由编程错误或者是由反常情况(必须由内核来处理)触发的。在该文档中, 术语“中断信号”既指异常又指中断。 中断分为两种类型:可屏蔽中断--它在短时间片段里可被忽略;不可屏蔽中断--它必须被立即处理。 不可屏蔽中断是由紧急事件产生例如硬件失败。著名的IRQS(中断请求)失败划为可屏蔽中断。 异常被分为不同的两类:处理器产生的异常(Faults, Traps, Aborts)和编程安排的 异常(用汇编指令int or int3 触发)。后一种就是我们经常说到的软中断 异常列表 --------------------------------------------------------------------------+ number | Exception | Exception Handler | --------------------------------------------------------------------------+ 0 | Divide Error | divide_error() | 1 | Debug | debug() | 2 | Nonmaskable Interrupt | nmi() | 3 | Break Point | int3() | 4 | Overflow | overflow() | 5 | Boundary verification | bounds() | 6 | Invalid operation code | invalid_op() | 7 | Device not available | device_not_available() | 8 | Double Fault | double_fault() | 9 | Coprocessor segment overrun | coprocesseur_segment_overrun() | 10 | TSS not valid | invalid_tss() | 11 | Segment not present | segment_no_present() | 12 | stack exception | stack_segment() | 13 | General Protection | general_protection() | 14 | Page Fault | page_fault() | 15 | Reserved by Intel | none | 16 | Calcul Error with float virgul| coprocessor_error() | 17 | Alignement check | alignement_check() | 18 | Machine Check | machine_check() | --------------------------------------------------------------------------+ 3 | Break Point | int3() | 我们关心的就是这个 当异常出现时会发生什么 ? 当一个中断发生,当前中断的中断处理函数被执行。该处理函数不是真正的处理异常函数, 它仅仅做个跳转,跳转到更好的处理函数。 异常就像中断,不管是什么原因(“软异常”除外)所引起,一旦发生首先进入的是内核中的异常响应/处理程序的入口,这就是类似于KiTrap0()那样的底层内核函数,只是因为引起异常的原因不同而进入不同的入口,就像对于不同的中断向量有不同的入口一样。在内核中,仍以页面异常为例,正如读者已经看到, CPU会从KiTrap14()进入函数KiPageFaultHandler()。在那儿,如果所发生的并非如“缺页”或“写时复制(Copy-On- Write)”那样的“正常”异常,就要根据CPU在发生异常时所处的空间而分别调用 KiKernelTrapHandler()或 KiUserTrapHandler()。如果调用的是KiKernelTrapHandler(),就会顺着KPCR数据结构中的“异常(处理)队列”、即ExceptionList,依次让各个节点认领。如果被认领,就会通过SEHLongJmp()长程跳转到当初通过_SEH_HANDLE{} 给定的代码中 对于发生于用户空间的异常,这里应该做些什么。显然,用户空间的异常不应靠内核里面的程序处理,应用软件理应为此作好了准备。前面讲过,Windows的 SEH机制并不是仅为内核而设计的,用户空间的程序同样可以使用类似于_SEH_TRY{} _SEH_HANDLE{} _SEH_END那样的手段为应用程序提供保护。事实上,在通过NtCreateThread()创建的线程首次被调度运行时,整个线程的执行都是作为一个SEH域而受到保护的 用户空间的每个线程都有一个ExceptionList,只不过这个队列在每个线程的TEB中,而不是在 KPCR中。既然内核中的 ExceptionList是由KiDispatchException()加以处理的,用户空间就应该有个类似于 KiDispatchException()的函数。事实上,动态连接库ntdll.dll中的KiUserExceptionDispatcher ()就是用户空间SEH处理的总入口。 KiUserExceptionDispatcher 位于 NTDLL.DLL 中,它是异常发生后执行的起点。这样说也不是百分之百的准确。例如,在 Intel 体系下,异常会使控制转到一个 ring 0 (内核模式)的处理程序。此处理程序由对应此异常的中断描述符表表项所定义。我将跳过所有的内核模式代码并假设发生异常时 CPU 直接执行 KiUserExceptionDispatcher。 KiUserExceptionDispatcher 的关键就是对 RtlDispatchException 的调用。这个调用启动了对注册的异常处理程序的查找。如果处理程序处理了异常并继续执行,则对 RtlDispatchException 的调用不再返回。如果 RtlDispatchException 返回了,则有两种可能:要么调用了 NtContinue 使进程继续,要么就是产生了另一个异常。若是后者,异常就不能再继续了,进程必须结束。接着说 RtlDispatchExceptionCode,这就是遍历异常帧的代码。函数获得一个指向 EXCEPTION_REGISTRATIONs 链表的指针并遍历每一个节点查找处理程序。因为堆栈可能崩溃掉,这个函数非常谨慎。在调用每个 EXCEPTION_REGISTRATION 指定的处理程序之前,代码要保证在线程堆栈中 EXCEPTION_REGISTRATION 是 DWORD 对齐的且前面的 EXCEPTION_REGISTRATION 的地址高。 RtlDispatchException 并不直接调用 EXCEPTION_REGISTRATION 结构体中指定的地址,而是调用 RtlpExecuteHandlerForException 来做这个脏累活儿。根据 RtlpExecuteHandlerForException 内部发生的情况,RtlDispatchException 要么继续遍历异常帧要么产生另一个异常。这个二级异常指示异常回调函数中出现问题不能继续执行。RtlpExecuteHandlerForException 的代码和另一个函数 RtlpExecutehandlerForUnwind 紧密相关。我在前面讲 unwinding 时曾提到这个函数。这两个函数都在将控制送到 ExecuteHandler 函数之前用不同的值加载 EDX 寄存器。换种说法就是 RtlpExecuteHandlerForException 和 RtlpExecutehandlerForUnwind 是同一个 ExecuteHandler 函数的不同的前端。 ExecuteHandler就是 EXCEPTION_REGISTRATION 的 handler 域被取出和执行的地方。也许看上去有些奇怪,对异常回调函数的调用本身也被一个结构化异常处理程序封装了起来。在这里使用 SEH 尽管有点儿怪,但认真考虑一下还是合理的。如果异常回调引起了另一个异常,操作系统需要知道此事件。根据异常是发生在初始的回调还是 unwind 中的回调,ExecuteHandler 返回 DISPOSITION_NESTED_ EXCEPTION 或 DISPOSITION_COLLIDED_UNWIND。这两个可都是“红色警戒!立即关闭!”级别的代号。 发生异常时系统的处理顺序(by Jeremy Gordon): 1.系统首先判断异常是否应发送给目标程序的异常处理例程,如果决定应该发送,并且目标程序正在被调试,则系统 挂起程序并向调试器发送EXCEPTION_DEBUG_EVENT消息.呵呵,这不是正好可以用来探测调试器的存在吗? 2.如果你的程序没有被调试或者调试器未能处理异常,系统就会继续查找你是否安装了线程相关的异常处理例程,如果 你安装了线程相关的异常处理例程,系统就把异常发送给你的程序seh处理例程,交由其处理. 3.每个线程相关的异常处理例程可以处理或者不处理这个异常,如果他不处理并且安装了多个线程相关的异常处理例程, 可交由链起来的其他例程处理. 4.如果这些例程均选择不处理异常,如果程序处于被调试状态,操作系统仍会再次挂起程序通知debugger. 5.如果程序未处于被调试状态或者debugger没有能够处理,并且你调用SetUnhandledExceptionFilter安装了最后异 常处理例程的话,系统转向对它的调用. 6.如果你没有安装最后异常处理例程或者他没有处理这个异常,系统会调用默认的系统处理程序,通常显示一个对话框, 你可以选择关闭或者最后将其附加到调试器上的调试按钮.如果没有调试器能被附加于其上或者调试器也处理不了,系统 就调用ExitProcess终结程序. 7.不过在终结之前,系统仍然对发生异常的线程异常处理句柄来一次展开,这是线程异常处理例程最后清理的机会. 很长的介绍文字,希望没有看晕 简单的说,就是我们执行int3 之后系统会让我们进入int3处理函数,KiUserExceptionDispatcher就是我们的处理函数 还记得softworm大侠的那个脱壳机吗? KiUserExceptionDispatcher是一个永远永远都不会返回的过程,它将要调用NtContinue或者是NtRaiseException。我们看看都发生了什么: - 异常发生 - ntoskrnl.exe通过KiTrapXX接手控制权 - KiTraps实际上是IDT的入口(?),并且根据不同的异常在KiTrapXX的入口处有两种可能的堆栈布局: +---------------+ +---------------+ | EFLAGS | | EFLAGS | +---------------+ +---------------+ | CS | | CS | +---------------+ +---------------+ | EIP | | EIP | +---------------+ +---------------+ | Error Code | +---------------+ 因为一些异常并不引发错误,同时也为了从ring0中退出来时更容易些,不管什么异常发生了一些KiTrapXX都将0压入堆栈模仿代码,比如KiTrap01和KiTrap03: _KiTrap01 0008:804D8D7C PUSH 00 <--- dummy Error Code 0008:804D8D7E MOV WORD PTR [ESP+02],0000 0008:804D8D85 PUSH EBP 0008:804D8D86 PUSH EBX 0008:804D8D87 PUSH ESI 0008:804D8D88 PUSH EDI 0008:804D8D89 PUSH FS _KiTrap03 0008:804D915B PUSH 00 <--- dummy Error Code 0008:804D915D MOV WORD PTR [ESP+02],0000 0008:804D9164 PUSH EBP 0008:804D9165 PUSH EBX 0008:804D9166 PUSH ESI 0008:804D9167 PUSH EDI 0008:804D9168 PUSH FS 但是KiTrap0E (内存页错误处理程序) 并没有将0压入堆栈因为错误代码存在了堆栈中。 _KiTrap0E 0008:804DAF25 MOV WORD PTR [ESP+02],0000 0008:804DAF2C PUSH EBP 0008:804DAF2D PUSH EBX 0008:804DAF2E PUSH ESI 0008:804DAF2F PUSH EDI 0008:804DAF30 PUSH FS 0008:804DAF32 MOV EBX,00000030 从中断中返回是由一个简单的IRETD指令完成的,它与ret指令相近,也是跳转到堆栈中所保存的EIP。异常处理完毕之后,ring0确定要调用 KiUserExceptionDispatcher时它就会将KiUserExceptionDispatcher的地址存储在堆栈中,所以IRETD 只是简单的返回了KiUserExceptionDispatcher : 0008:804F5A0F MOV EAX,[_KeUserExceptionDispatcher] 0008:804F5A14 MOV [EBX+68],EAX :dd ebx+68 0010:EEC21DCC 7C90EAEC 0000001B 00000246 0013FCD0 ìê |....F....... 0010:EEC21DDC 00000023 00000000 00000000 00000000 #............... 正如你所看到的,EIP被KiUserExceptionDispatcher的地址覆盖了以及堆栈中保存的CS,Eflags,esp 和 SS。因为我们要做的是hook这些指令,所以他会指向ntdll.dll中其他的代码,就是我们使用yates展示的方法所存储的那些代码。 同样,也有更好的方法应该尽量不要扫描磁盘上的ntdll.dll,而是使用内存中已经载入的文件直接重新引导至UserSharedData,在用户模式下被设置成了只读: kd> ? SharedUserData Evaluate expression: 2147352576 = 7ffe0000 kd> 但是在ring0它被映射到了: #define KI_USER_SHARED_DATA 0xffdf0000 Windows NT 的IDT中所有的异常处理程序地址都在0x80000000之上。0x80000000之上的地址被Windows NT保留用于特权级(Ring 0)访问。尽管从图上看可能不明显,但是确实几乎所有的异常处理程序地址都在NTOSKRNL.EXE中,它是Windows NT中运行于Ring 0的核心组件。由于我事先已经从NTOSKRNL的DBG文件中加载了调试符号,所以SoftICE查找异常处理程序地址并且找到了大部分异常处理程序的名称。前 0x20个异常被一系列名字为_KiTrap00,_KiTrap01等的例程处理。“Ki”代表内核中断(Kernel Interrupt) 还有一个应该注意的是IDT中的描述符特权级(Descriptor Privilege Level,DPL)域。它指定了允许调用特定软件中断的最低特权级。例如,INT 2EH 可以被从Ring 3(最低特权级)到Ring 0(最高特权级)中任何一级调用。同样,用于断点的INT 3H,也可以被Ring 3及更高特权级的代码调用。 从0x2A到0x2E的异常被NTOSKRNL.EXE中的其它例程处理。例如,在我1996年八月的文章 “Poking Around Under the Hood: A Programmer’s View of Windows NT 4.0”,我讲到了Ring 3级的应用程序代码传递控制权到Ring 0级的系统代码以完成诸如创建一个新进程之类的特殊操作的机制,那就是调用INT 2E。 INT 2E被系统DLL,例如NTDLL.DLL、USER32.DLL和GDI32.DLL从Ring 3调用。看一下IDT的0x2E这一项,你会看到它的地址指向NTOSKRNL中的_KiSystemService函数。正是这个函数把控制权转到了相应的代码 很多很复杂的东西,我们知道win32异常处理机制其实是很复杂,我们来看看和我们相关的 如果我们 hook system exception handler,所有你在代码中设的 INT3 中断,都能拦下,在NT下,这个handler就是NT.DLL中 的 KiUserExceptionDispatcher __asm int 3 // 普通的断点指令 这个就是EXCEPTION_BREAKPOINT __asm int 1 EXCEPTION_SINGLE_STEP 他们对应的就是的1号和3号异常处理, 设置int3断点,当运行到这里 系统异常触发,系统自动调用处理函数KiTrap03, 下发KiUserExceptionDispatcher,然后枚举下方的异常处理链表 应该是程序自己的异常处理过程,od就在这里进行异常处理 但是softice不同,是直接修改系统异常处理函数完成的 壳经常玩的把戏之一SEH就是这个了,因为SEH异常能够改变流程,然而异常处理过程 比较复杂,我们不能od跟踪,只能找准壳设置的函数下断,巧妙利用SEH可以极大 的干扰调试,另外,系统调用int2e其实也是这个机制 关于xp下得系统调用,系统调用由2k下得int2e换乘了sysenter ,是为了提高效率,但是你单步跟踪一下,就会发现其实他还是会转到int2e得入口地址去处理。 不知谁说得xp就不用int2e了,实际sysenter之后再执行几句特殊得语句就转到int2e得地址 入口了,完全可以用softice验证 异常机制偶不打算继续深入下去了,如果idt被替换了 我们需要使用1个反汇编引擎来搜索代码,寻找jmp掉邪恶代码 当然更换系统异常处理分支KiTrap03也行,通用unhook手段很多 只要本质上也是unhook,那么就和SSDT ,shodow sSDT 的unhook 没有多少本质区别 最后祝各位新年快乐:rose :rose