对模拟int3的探索

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



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


反汇编KdCheckForDebugBreak函数如图:
对模拟int3的探索_第2张图片
 
可以看到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 断下后分析结果如下:

对模拟int3的探索_第3张图片
 
可以看到
1.  断在了一个未知的地方,
2.  观察esp是无效的,
3.  观察栈回溯代码也是无效,
4.  对比正常情况下和此时的命令行提示符也发现不一样,现在是16.0: kd> 正常情况是 1: kd>,说明不是同一个进程
5.  继续F5运行下去就出现系统错误,最终虚拟机自动重启了如图:

  对模拟int3的探索_第4张图片

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



查阅<软件调试>278页:
  对模拟int3的探索_第5张图片

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  
}
}
编译放入虚拟机运行结果如下:

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


对模拟int3的探索_第7张图片
 
----<软件调试>76页


对模拟int3的探索_第8张图片
----<软件调试>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断下后结果如图:

对模拟int3的探索_第9张图片
 
得到以下结果:
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,如图

对模拟int3的探索_第10张图片
 
发现结果:
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运行,结果如图:
对模拟int3的探索_第11张图片
 
可以发现已经正确了,栈的位置及值都和没有执行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的一点探索,最后的问题还请各位朋友讲解以下,谢谢先

你可能感兴趣的:(对模拟int3的探索)