首页
社区
课程
招聘
[原创]对模拟int3的探索
发表于: 2014-1-11 18:14 12259

[原创]对模拟int3的探索

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

以下是整个探索过程:
首先 开启双机调试(VM WIN7 + WINDBG),ctrl+break中断后,栈回溯如图:

 
可以看到断点的由来是RtlpBreakWithStatusInstruction里的int3,
从这个名称开头中的p可以看出来它是一个内部函数(原因参见<windows内核原理与实现>40页),所以怀疑栈回溯显示的符号不完全准确,验证一下

反汇编KdCheckForDebugBreak函数如图:

 
可以看到KdCheckForDebugBreak中并没有对RtlpBreakWithStatusInstruction进行访问,唯一有点像断点的函数的就是DbgBreakPointWithStatus

反汇编DbgBreakPointWithStatus如图:

 
这里可以看到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 断下后分析结果如下:


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

 

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

查阅<软件调试>278页:
 

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


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


 
----<软件调试>76页


----<软件调试>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断下后结果如图:


 
得到以下结果:
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,如图


 
发现结果:
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以前是一样的,栈回溯也能正确显示,反复测试也可以正确跑起来,至此目的就达到了,没有经过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的一点探索,最后的问题还请各位朋友讲解以下,谢谢先

[课程]Android-CTF解题方法汇总!

上传的附件:
收藏
免费 5
支持
分享
最新回复 (19)
雪    币: 19
活跃值: (1086)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
不错 可以绕过IDT了
2014-1-11 18:35
0
雪    币: 257
活跃值: (67)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
3
LZ不要异想天开了
不过VT虚拟化指令监控可以拦截任何指令,包括int 3
2014-1-11 18:41
0
雪    币: 257
活跃值: (67)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
4
另外,LZ的代码纯粹是Ring0层的int 3而不是Ring3层的
所以ss和cs不需要push
2014-1-11 19:16
0
雪    币: 100
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
不错,算是绕过了int3
2014-1-11 21:15
0
雪    币: 3836
活跃值: (4142)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
LZ的思路不错。。。。。赞一个
2014-1-11 23:07
0
雪    币: 95
活跃值: (119)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
楼主写得很详细,非常不错的思路
2014-1-11 23:17
0
雪    币: 55
活跃值: (519)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
8
吓死我了,我还以为是ring3的int3
2014-1-12 15:45
0
雪    币: 55
活跃值: (519)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
9
iret指令本身就有恢复堆栈的功能吧。不知道Add Esp在这里起到什么作用。
2014-1-12 16:05
0
雪    币: 19
活跃值: (1086)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
是不是因为声明的函数是纯汇编的?编译器没有生成那些调整堆栈的代码 要自己来调整吧
2014-1-12 16:13
0
雪    币: 92
活跃值: (70)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
11
这个问题我也很纳闷,但是如果不加这句指令,栈有错乱,所以加上只是为了让程序正确,至于在KiTrap03例程中,是什么地方把这个几个字节拿去了就不知道了,
2014-1-12 16:33
0
雪    币: 92
活跃值: (70)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
12
SS可以忽略,但是去掉 push CS 程序段下来后会出错,环境有问题,
2014-1-12 16:35
0
雪    币: 19
活跃值: (1086)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
_declspec( naked ) void interrupt3( void )
{
        __asm{
                //iretd //return

                cmp [DebuggedProcessID],0 //there's currently no debugging gong on so quit
                je Original

                PUSHAD        //32               
                push ds //4
                push es //4
                push gs //4
                push fs //4

                mov ax,0x23
                mov ds,ax
                mov es,ax
                mov gs,ax
                mov ax,0x30
                mov fs,ax

                mov eax,esp
                add eax,48
                push eax //the location of the original stack
                PUSH 3 //int 3 identifier
            CALL GeneralHandler //call my regular int handler
                cmp eax,1 //if 1 then do no handle the original handler
                je Exit
                pop fs
                pop gs
                pop es
                pop ds
                POPAD
Original:
            JMP [Int3Address]

Exit:
                pop fs       
                pop gs
                pop es
                pop ds
                POPAD               
                IRETD
        };

}
2014-1-12 16:46
0
雪    币: 257
活跃值: (67)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
14
那要改CPU的MicroCode,即微码
据说指令译码器这东西,不过太底层了,几乎没有这方面的资料
2014-1-13 00:07
0
雪    币: 110
活跃值: (303)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
mark.
2014-1-13 00:57
0
雪    币: 55
活跃值: (519)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
16
以前就看到某人说自己能hook MicroCode,当时吓尿了
2014-1-13 09:17
0
雪    币: 257
活跃值: (67)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
17
MicroCode从BIOS加载到CPU,要hook也得从BIOS下手
系统引导阶段不是那么容易控制的,看看国外论坛应该有,再对照CPU手册
2014-1-13 14:07
0
雪    币: 199
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
mark.
2014-1-16 08:35
0
雪    币: 549
活跃值: (451)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
19
楼主把pushf换成pushfd,就不用push esp 也不用add esp,2了。
2014-2-24 17:52
0
雪    币: 249
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
20
只能膜拜。。。
2014-3-4 10:41
0
游客
登录 | 注册 方可回帖
返回
//