吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 16070|回复: 45
收起左侧

[调试逆向] 探秘INT3指令

  [复制链接]
镇北看雪 发表于 2020-6-3 19:06
本帖最后由 镇北看雪 于 2020-6-19 13:40 编辑

简介


INT3指令是专门用来支持调试的一条指令,它对应的机器码是0xCC。当cpu执行到这条指令是会产生异常并调用相应的异常处理程序(3号中断)进行进一步的处理。

详细分析


int3指令原理

cpu在执行完int3指令后会引发异常,此异常会使操作系统从中断向量表中调用3号中断处理程序,此中断处理程序即函数 nt!KiTrap03( ),此函数进行一些处理后又会继续调用nt!KiDisPatchException( )函数来进行异常的分发。而此函数会先去检查是否存在调试器(即程序是否正在被调试),如果存在调试器则把异常交给调试器,调试器处理完之后在返回到。如果调试器不处理或者就不存在调试器则异常会传递到程序自身的异常处理中(如果最后依然没处理就会进行异常的第二次分发)。
2020-06-19_125206.jpg

int3指令在调试器中的应用

我们都知道调试器中有int3断点,而int3断点就是基于int3指令实现的。以OD为例当我们在某一汇编指令处设下断点后,调试器会把所设断点地址处的第一个字节改为0xCC(即INT3指令),并把原字节保存。之所以我们看起来OD的此地址处字节没有发生任何变化是因为OD为了维持汇编代码的可读性并没有将改变后的指令进行重新反汇编。我们可以利用如下方法来查看其地址处的实际数据。

首先我们用OD随便加载一个程序,我们在入口点下发任意一条汇编指令处下断点,我们发现此地址处的数据并没有变化,这是OD为了维护代码可读性,实际此地址数据已经变为了0xCC

0I0F52T_7BBT][({C{1]Q97.png

我们接下来用把即将运行的第一条指令修改为 mov  al , byte ptr ds:[0x401830]。也就是将此断点地址处的值读到al中

[RRE8HJP]@1QY3E{}O4NLOV.png

F8执行此代码后,我们发现eax值为0x000000CC。证明此断点地址处的值已经被修改为0xCC。

TT4}OIYE~(QS~Y6B)MCJ4SP.png

所以当cpu执行到此断点时就会执行0xCC(即INT3指令),接着产生异常去执行函数nt!KiTrap03( ),接着会调用nt!KiDisPatchException( )函数并将异常分发给调试器,其刚执行完0xCC此时eip指向0xCC的下一个字节,调试器会让eip减一,然后eip重新指向0xCC(断点处)而OD调试器将先还原此断点处的原字节,然后使返回程序将停在此断点处等待用户的进一步操作。我们可以通过如下方法进行验证。

接着上一次我们分析的程序,我们把断点处的指令更改为mov  al , byte ptr ds:[0x401830]。

60}UYWTMJPE5$]B1$[))E9O.png

F8向下执行,当执行完断点处的指令后,我们发现al的值A0。说明断点处的字节已被修复。

5B@TZKBEGT[AR2E]R%D~XOJ.png
然后为了使下次运行到此处时断点还有效,程序会利用单步异常来把断点处的值在该为0xCC。其在执行完断点处的指令后,会产生单步异常从而被调试器捕捉,然后调试器会将此断点处的值更改为0xCC。我们可以利用如下方法验证。

我们将断点后的指令改为mov  al , byte ptr ds:[0x401830],然后F8执行代码后发现al的值为0xCC,说明断点已在执行完断点处指令后恢复。

355_3UKZ`E_{9RJ`A5IO{UG.png

int3与它的兄弟int 3

int3指令的机器码为0xCC ,而int 3也就时我们所了解的软中断int n的机器码为 0xCD 0x03。二者不光时机器码不同,系统会对int3指令一些特殊待遇而int  3却没有此待遇。
其不同在调试器调试过程中也有反映,我们下面利用OD来分析一下在调试器中其会有哪些不同。
在分析之前我们要设置一下OD,让OD忽略int3断点。这样OD就不会处理int3产生的异常了。

1XG2VCXR[8UKYP%{6)]MC4T.png

我们随便拿一个程序用OD分析,我们发程序入口的指令改为int3(即0xCC)

UA}(I98LI6N{PNRL@F0EJT5.png

然后我们运行程序我们发现eip还是指向此地址处。原因是产生异常后系统调用了在第一次请求调试器处理异常时异常没被处理(前面我们设置的忽略异常)然后异常进行第二次分发系统会自动让eip - 1(注意这里时系统做的,而不是调试器做的,这是时int3产生的异常不同于其他异常的一点),所以返回后eip还指向地址处。

@Y(8`EY8(5`FN0I{R$JOHX5.png

我们接下来把此地址处指令改为  int   3。即字节0xCD  0x03,其余的自动用nop填充。

AKCZX`W07YH5]8MEHDU]XZ5.png

然后我们运行代码发现eip指向了地址0x004010c3即入口地址+1处。原因是我们在执行完int   3指令后也产生异常,系统同样会在第二次分发异常时让eip - 1。但是int   3 指令是两个字节,减一后eip指向的是字节0x03。如此一来在返回后OD会重新对eip后面的代码进行组合和反汇编。

TE0PTE8H12)AM2401PT~0FR.png
如果我们继续执行代码则会发生意想不到的异常,因为后面的代码都是错误的反汇编代码。

对于上述所说的int3产生的异常如果第一次调试器不处理,第二次分发的时候系统会让eip - 1的操作我们进行验证
我专门写了个调试器进行验证。代码如下


#include <Windows.h>
#include <iostream>using namespace std;
int flag = 0;                              //标志是第一次断点异常,还是第二次断点异常
int main()
{
        char a[256] = {0};
        cout<<"请输入需要调试的目标程序的路径:";
        cin>>a;
        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<<"创建进程成功"<<endl;

        while(TRUE)
        {
                if(WaitForDebugEvent(&devent, INFINITE))
                {
                        switch(devent.dwDebugEventCode)
                        {
                        case EXCEPTION_DEBUG_EVENT:
                                switch(devent.u.Exception.ExceptionRecord.ExceptionCode)
                                {
                                case EXCEPTION_BREAKPOINT:                                 //断点异常

                                        if(devent.u.Exception.dwFirstChance == 1)          //第一次异常分发
                                        {
                                                cout<<"第一次异常。  ";
                                                cout<<"eip:";
                                                stContext.ContextFlags = CONTEXT_FULL;
                                                GetThreadContext(pi.hThread, &stContext);
                                                int n1 = GetLastError();
                                                cout<<stContext.Eip<<endl;
                                                flag = 1;
                                        }
                                        if(devent.u.Exception.dwFirstChance == 0)          //第二次异常分发
                                        {
                                                cout<<"第二次异常。  ";
                                                cout<<"eip:";
                                                GetThreadContext(pi.hThread, &stContext);
                                                cout<<stContext.Eip<<endl;
                                                flag = 0;
                                        }
                                        break;
                                }
                                break;
                        }

                }
                if(flag == 1)                                                //如果是第一次碰见int3断点异常,我们不处理他,让他直接继续进行第二次异常分发
                        ContinueDebugEvent(devent.dwProcessId, devent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
                else                                                         //如果是第二次碰见int3断点异常或者是其他调试事件我们直接返回让程序继续运行
                {
                        flag = 0;
                        ContinueDebugEvent(devent.dwProcessId, devent.dwThreadId, DBG_CONTINUE);
                }
        }
        return 0;
}

我们随便找一个程序,把第一条指令改为int3并保存到文件。来验证异常处理过程。

S(1RH[WQ[]GA5UVY]ND5)57.png

接着我们用我写的简单的调试器去分析此修改过的文件,分析结果如下。

}6~G)4N{0ACQ{MP9_$(I.png

int3指令在其他方面的应用

我们知道栈一般是用0xcc初始化,而堆一般使用0xcd初始化。而0xcc对应汉字编码为 烫,int3指令机器码也为0xcc。而这并非偶然,当发生指针栈溢出时eip会指向初始化时填充的0xcc从而引发int3断点异常,使程序中断。

int3断点的优缺点

  • 优点:数量没有限制,操作简单。
  • 缺点:因为改变机器码所以易被检测,只能在代码段中使用,而且因为其是基于中断的所以当中断描述符表被破坏时其将无效。

总结


int3指令在软件调试中被广泛运用。因为int3指令基于中断和异常处理,所以利用int3指令在软件的安全方面也有很大用处。
分析如有错误,请路过大牛指出。

参考:软件调试

免费评分

参与人数 13吾爱币 +11 热心值 +12 收起 理由
橘子哟6 + 1 + 1 谢谢@Thanks!
juan5201314 + 1 我很赞同!
evea + 1 + 1 鼓励转贴优秀软件安全工具和文档!
求求你们别学了 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
禁闭岛 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
红颜世家、 + 1 + 1 谢谢@Thanks!
LovenSar + 1 + 1 谢谢@Thanks!
kilkilo502 + 1 用心讨论,共获提升!
sunnylds7 + 1 热心回复!
fushanpupil + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
xxpl123 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
adime2018 + 1 + 1 用心讨论,共获提升!
Randolp欢 + 1 + 1 我很赞同!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| 镇北看雪 发表于 2020-6-19 20:27
q329973692 发表于 2020-6-19 20:02
奇怪了,第二次异常输出的EIP为什么会-1呢?
第一次异常不处理,EIP-1后还是int3 继续执行int3 之后 EIP应 ...

第一次异常不处理意思是执行完oxCC后eip加一了,而我们没有让调试器改变eip。这样异常会进行第二次分发,在第二次时发现eip减一了,这其实是系统做的。
一旧云 发表于 2022-10-6 12:22
接着上一次我们分析的程序,我们把断点处的指令更改为mov  al , byte ptr ds:[0x401830]。
F8向下执行,当执行完断点处的指令后,我们发现al的值A0。说明断点处的字节已被修复。
此处的A0不是自已修改的那条mov指令的机器码的第一个字节吗?并不是OD将原来的数据改回来吧,原来的数据不是83吗?
sam喵喵 发表于 2020-6-3 19:19
学习了,感谢楼主分享。
软件调试是张银奎那本吗
赛博 发表于 2020-6-3 19:57
学习了 谢谢 很详细啊
陆想想 发表于 2020-6-3 20:35
谢谢您 很感谢
玖公子 发表于 2020-6-3 20:41
很详细,谢谢分享!
xxpl123 发表于 2020-6-3 21:48
不错的文章,用另一种方式说明了
atonghao 发表于 2020-6-3 22:20
好复杂。。。头大
arthas75101 发表于 2020-6-3 23:59
不愧是大佬,只不过我看的是云里雾里
wapjcxz 发表于 2020-6-4 07:13
小白看得云里雾里……
rsice 发表于 2020-6-4 11:49
感谢分享,请楼主继续,消息断点的使用能否多讲讲
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-21 19:39

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表