最近由于某个程序需要,探索了下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的一点探索,最后的问题还请各位朋友讲解以下,谢谢先