简介
INT3指令是专门用来支持调试的一条指令,它对应的机器码是0xCC。当cpu实行到这条指令是会产生非常并调用相应的非常处理步伐(3号停止)进行进一步的处理。
详细分析
int3指令原理
cpu在实行完int3指令后会引发非常,此非常会使操作系统从停止向量表中调用3号停止处理步伐,此停止处理步伐即函数 nt!KiTrap03( ),此函数进行一些处理后又会继续调用nt!KiDisPatchException( )函数来进行非常的分发。而此函数会先去检查是否存在调试器(即步伐是否正在被调试),如果存在调试器则把非常交给调试器,调试器处理完之后在返回到。如果调试器不处理或者就不存在调试器则非常会通报到步伐自身的非常处理中(如果末了依然没处理就会进行非常的第二次分发)。
int3指令在调试器中的应用
我们都知道调试器中有int3断点,而int3断点就是基于int3指令实现的。以OD为例当我们在某一汇编指令处设下断点后,调试器会把所设断点地点处的第一个字节改为0xCC(即INT3指令),并把原字节保存。之所以我们看起来OD的此地点处字节没有发生任何变化是由于OD为了维持汇编代码的可读性并没有将改变后的指令进行重新反汇编。我们可以使用如下方法来查看其地点处的实际数据。
起首我们用OD随便加载一个步伐,我们在入口点下发任意一条汇编指令处下断点,我们发现此地点处的数据并没有变化,这是OD为了维护代码可读性,实际此地点数据已经变为了0xCC
我们接下来用把即将运行的第一条指令修改为 mov al , byte ptr ds:[0x401830]。也就是将此断点地点处的值读到al中
F8实行此代码后,我们发现eax值为0x000000CC。证明此断点地点处的值已经被修改为0xCC。
所以当cpu实行到此断点时就会实行0xCC(即INT3指令),接着产生非常去实行函数nt!KiTrap03( ),接着会调用nt!KiDisPatchException( )函数并将非常分发给调试器,其刚实行完0xCC此时eip指向0xCC的下一个字节,调试器会让eip减一,然后eip重新指向0xCC(断点处)而OD调试器将先还原此断点处的原字节,然后使返回步伐将停在此断点处等候用户的进一步操作。我们可以通过如下方法进行验证。
接着上一次我们分析的步伐,我们把断点处的指令更改为mov al , byte ptr ds:[0x401830]。
F8向下实行,当实行完断点处的指令后,我们发现al的值A0。分析断点处的字节已被修复。
然后为了使下次运行到此处时断点还有用,步伐会使用单步非常来把断点处的值在该为0xCC。其在实行完断点处的指令后,会产生单步非常从而被调试器捕获,然后调试器会将此断点处的值更改为0xCC。我们可以使用如下方法验证。
我们将断点后的指令改为mov al , byte ptr ds:[0x401830],然后F8实行代码后发现al的值为0xCC,分析断点已在实行完断点处指令后恢复。
int3与它的兄弟int 3
int3指令的机器码为0xCC ,而int 3也就时我们所了解的软停止int n的机器码为 0xCD 0x03。二者不但时机器码不同,系统会对int3指令一些特殊待遇而int 3却没有此待遇。
其不同在调试器调试过程中也有反映,我们下面使用OD来分析一下在调试器中其会有哪些不同。
在分析之前我们要设置一下OD,让OD忽略int3断点。这样OD就不会处理int3产生的非常了。
我们随便拿一个步伐用OD分析,我们发步伐入口的指令改为int3(即0xCC)
然后我们运行步伐我们发现eip还是指向此地点处。原因是产生非常后系统调用了在第一次请求调试器处理非常时非常没被处理(前面我们设置的忽略非常)然后非常进行第二次分发系统会自动让eip - 1(留意这里时系统做的,而不是调试器做的,这是时int3产生的非常不同于其他非常的一点),所以返回后eip还指向地点处。
我们接下来把此地点处指令改为 int 3。即字节0xCD 0x03,其余的自动用nop添补。
然后我们运行代码发现eip指向了地点0x004010c3即入口地点+1处。原因是我们在实行完int 3指令后也产生非常,系统同样会在第二次分发非常时让eip - 1。但是int 3 指令是两个字节,减一后eip指向的是字节0x03。云云一来在返回后OD会重新对eip背面的代码进行组合和反汇编。
如果我们继续实行代码则会发生意想不到的非常,由于背面的代码都是错误的反汇编代码。
对于上述所说的int3产生的非常如果第一次调试器不处理,第二次分发的时候系统会让eip - 1的操作我们进行验证
我专门写了个调试器进行验证。代码如下
#include #include using namespace std;int flag = 0; //标记是第一次断点非常,还是第二次断点非常int main(){ char a[256] = {0}; couta; PROCESS_INFORMATION pi; //接受新进程的一些有关信息 STARTUPINFO si; //指定新进程的主窗体如何显示 DEBUG_EVENT devent; //消息事件 CONTEXT stContext; //线程信息块 GetStartupInfo(&si); CreateProcessA( a, NULL, NULL, NULL, FALSE, // 不可继续 DEBUG_ONLY_THIS_PROCESS | DEBUG_PROCESS, // 调试模式启动 NULL, NULL, &si, &pi ); cout |