最近由于某个程序需要,探索了下int3指令,目的是不通过idt中断表,直接访问KiTrap03函数.写此文的目的是
1,询问几个问题,
2,分享下探索过程 



以下是整个探索过程:
首先 开启双机调试(VM WIN7 + WINDBG),ctrl+break中断后,栈回溯如图:
名称:  1.png查看次数: 4文件大小:  6.7 KB
 
可以看到断点的由来是RtlpBreakWithStatusInstruction里的int3,
从这个名称开头中的p可以看出来它是一个内部函数(原因参见<windows内核原理与实现>40页),所以怀疑栈回溯显示的符号不完全准确,验证一下


反汇编KdCheckForDebugBreak函数如图:
点击图片以查看大图图片名称: 2.png查看次数: 13文件大小: 14.7 KB文件 ID : 86470
 
可以看到KdCheckForDebugBreak中并没有对RtlpBreakWithStatusInstruction进行访问,唯一有点像断点的函数的就是DbgBreakPointWithStatus


反汇编DbgBreakPointWithStatus如图:
名称:  3.png查看次数: 1文件大小:  4.0 KB
 
这里可以看到RtlpBreakWithStatusInstruction其实就是DbgBreakPointWithStatus函数中的一个标签.



接下来就是写代码实现功能,首先是HOOK DbgBreakPointWithStatus函数,部分代码如下:
代码:
//得到内核中的KiTrap03函数地址 gNewKiTrap03Addr=GetOriginalProcAddr(L"KiTrap03");      //定位内核中DbgBreakPointWithStatus函数地址 ulNewFunAddr=GetOriginalProcAddr(L"DbgBreakPointWithStatus");  //让DbgBreakPointWithStatus函数转向到MY函数中 ReplaceProcAddr((ULONG)MyDbgBreakPointWithStatus, ulNewFunAddr); 


第一次实验的MyDbgBreakPointWithStatus代码如下:
代码:
//MY DbgBreakPointWithStatus函数 void __declspec(naked) MyDbgBreakPointWithStatus()   /*++      Routine Description:   此函数用于替换函数DbgBreakPointWithStatus, 模拟其中的int3指令,让其不经过中断表    --*/ {     //反汇编DbgBreakPointWithStatus如下:   //83ecf0cc 8b442404        mov     eax,dword ptr [esp+4]   //83ecf0d0 cc              int     3   //83ecf0d1 c20400          ret     4   //把int3 直接替换为跳转到 jmp KiTrap03   __asm   {     mov     eax,dword ptr [esp+4]          //跳向KiTrap03函数     jmp gNewKiTrap03Addr      ret 4   } } 
由于先前没有过多了解int3指令,只知道它会根据中断表访问相应例程,所以就想直接把int3 替换为jmp KiTrap03的指令,这样一来多方便一条指令搞定,编译放到虚拟机运行,然后在windbg中ctrl+break 断下后分析结果如下:

点击图片以查看大图图片名称: 4.png查看次数: 7文件大小: 20.3 KB文件 ID : 86472
 
可以看到
1.  断在了一个未知的地方,
2.  观察esp是无效的,
3.  观察栈回溯代码也是无效,
4.  对比正常情况下和此时的命令行提示符也发现不一样,现在是16.0: kd> 正常情况是 1: kd>,说明不是同一个进程
5.  继续F5运行下去就出现系统错误,最终虚拟机自动重启了如图:

  点击图片以查看大图图片名称: 5.png查看次数: 2文件大小: 5.6 KB文件 ID : 86473

通过以上结果,怀疑是不是到了其他进程环境中呢?再想想对int3的替换比较草率,没想其原理,所以先看看其原理.



查阅<软件调试>278页:
  名称:  6.png查看次数: 2文件大小:  82.2 KB

Int3属于中断门,遵循以上原理,由于进入前后代码都是在ring0,所以CPU不需要堆栈切换,int3做的就是EFLAGS CS EIP 压栈,流程自然就到KiTrap03例程中了,修改实验代码.



第二次实验的MyDbgBreakPointWithStatus代码如下:
代码:
//MY DbgBreakPointWithStatus函数 void __declspec(naked) MyDbgBreakPointWithStatus() {     __asm   {     mov     eax,dword ptr [esp+4]      //志寄存器入栈     pushf      //CS入栈     push cs  //EIP入栈     push offset _ret1        //跳向KiTrap03函数     jmp gNewKiTrap03Addr  _ret1:     ret 4   } } 
编译放入虚拟机运行结果如下:

点击图片以查看大图图片名称: 7.png查看次数: 4文件大小: 17.4 KB文件 ID : 86475
 
这次结果可以发现,
1.  虽然还是没有正确,但是栈回溯可以查看了,而且也可以看到的确进入MY函数
2.  提示符变成了VM.1 这和VM虚拟机相符合,但是和正常状态下的提示符还是不一样
3.  我们设定的EIP应该是ret 4指令,应该是9a97a392但是现在EIP减了一,实际是9a97a391
4.  继续F5依然是错误,虚拟机自动重启
通过上面的第3点可以印证一个原理:


名称:  8.png查看次数: 1文件大小:  27.7 KB
 
----<软件调试>76页


名称:  9.png查看次数: 1文件大小:  36.3 KB
----<软件调试>78页

由此可见压入EIP这个代码需要修改,通过其他结果,发现还是和环境有关 但是明显的比第一次实验要好一点了,既然和环境有关,这里唯一没做的就是CPU堆栈相关的切换代码,当然抱着试一试的心态,加上了相关代码 修改后的代码下




第三次实验的MyDbgBreakPointWithStatus代码如下:
代码:
//MY DbgBreakPointWithStatus函数 void __declspec(naked) MyDbgBreakPointWithStatus() {     __asm   {       mov     eax,dword ptr [esp+4]      //段选择子 堆栈指针入栈     push ss     push esp        //志寄存器入栈     pushf      //CS、EIP入栈     push cs     push offset _ret1      //跳向KiTrap03函数     jmp gNewKiTrap03Addr      //弥补windows对INT3指令的特殊减一     nop _ret1:     ret 4 } } 
编译后虚拟机中运行,windbg中ctrl+break断下后结果如图:

点击图片以查看大图图片名称: 10.png查看次数: 2文件大小: 21.9 KB文件 ID : 86478
 
得到以下结果:
1.  EIP按照我的的想法到达了指定位置,这一点应该是修改正确了
2.  环境基本正确了,能正确显示断点地址,而且windbg命令提示符也正常了,这两点都表明环境已经基本正确了
3.  查看栈回溯发现最后一个访问点有问题,0x10807e不是一个有效地址,
4.  查看栈,发现esp不是在一个正确地址上的,因为esp地址应该是4的倍数才对,但现在最后一位是E 也就是十进制的14
5.  继续F5同样出现错误,VM自动重启

通过上面几点可以发现ESP有明显问题,栈不平衡,这点需要修改,继续以下实验代码




第四次实验在MyDbgBreakPointWithStatus 函数头添加断点DbgBreakPoint(),目的是为了查看调用 自定义的INT3 前后栈是不是一样,同样编译放入VM中运行 windbg中ctrl+break断下显示在DbgBreakPoint处,记录现在ESP,继续F5,再一次断下来,这一次是执行了 自定义的int3 段下来的,对比两次的esp,如图

点击图片以查看大图图片名称: 11.png查看次数: 4文件大小: 30.0 KB文件 ID : 86479
 
发现结果:
1.发现两次ESP相差了6字节,第一次是正确的,第二次是错误的
2.运行到第二次时,查看正确地址的ESP和第一次是完全一样的,这点说明栈内容还是对了的
3.反汇编返回地址发现的确也是上一层的调用函数

此时果断的想出直接调整下ESP看看是否正常,修改实验代码如下:





第五次实验的MyDbgBreakPointWithStatus代码如下:
代码:
//MY DbgBreakPointWithStatus函数 void __declspec(naked) MyDbgBreakPointWithStatus() {     __asm   {     mov     eax,dword ptr [esp+4]      //段选择子 堆栈指针入栈     push ss     push esp        //志寄存器入栈     pushf      //CS、EIP入栈     push cs     push offset _ret1      //跳向KiTrap03函数     jmp gNewKiTrap03Addr      //弥补windows对INT3指令的特殊减一     nop _ret1:     //调整堆栈     add esp,6          ret 4 } } 
编译后VM运行,结果如图:
名称:  12.png查看次数: 2文件大小:  24.5 KB
 
可以发现已经正确了,栈的位置及值都和没有执行int3以前是一样的,栈回溯也能正确显示,反复测试也可以正确跑起来,至此目的就达到了,没有经过idt表,但也实现了其功能,



问题列表:功能虽然实现但有些问题还是没搞懂,

第一点:都是ring0为什么需要加上CPU切换栈的代码呢,?

第二点:当我把push ss删除以后 把栈平衡该为add esp,2 程序依然是正确的,这是为什么呢?具体代码如下:
代码:
  mov     eax,dword ptr [esp+4]   push esp     pushf   push cs   push offset _ret1   jmp gNewKiTrap03Addr     nop _ret1:   add esp,2       ret 4 
第三点:如果保留push ss 删除push esp则会出现错误 难道是<软件调试>上以XP为例的和现在的WIN7有差异吗?

第四点:按照原理来写的代码,应该不会出现栈不平衡的情况呀,虽然自己可以调整ESP,但是这明显是一种治标不治本的方法,出现这样的情况是为什么呢?

以上就是我对int3的一点探索,最后的问题还请各位朋友讲解以下,谢谢先