软件断点INT3

提到调试,很多人立刻会想到设置断点和单步执行。x86系列处理器从其第一代产品8086开始就提供了一条专门用来支持调试的指令,即INT 3。调试程序时,我们可以在可能有问题的地方插入一条INT3指令,使CPU执行到这一点时停下来。
这便是软件调试中经常用到的断点(breakpoint)功能,因此INT3指令又称为断点指令。

通过一个小程序感受一下INT3指令的工作原理
vc6建立一个hiInt控制台小程序
 

程序代码:

#include <stdio.h>

int main() {
    // manual breakpoint
    _asm int 3;
    printf("hello INT 3!\n");
    return 0;
}

运行程序,会进入调试状态,程序停留在INT 3 的指令位置。

打开反汇编和寄存器窗口,可以看到内存地址401028处确实是INT3指令

还可以看到int3指令正好是0xcc, 调试版程序,局部缓冲区就是用cc填充(前面栈溢出文章内存有这个现象),连续两个cc,中文就是“烫”,所以内存会显示成很多烫字,不可触碰,一碰就中断。很奇妙的巧合。

观察调试器埋下的断点指令
winDbg打开winmine.exe挖雷小程序。
0:000> x ntdll!*readfile*
771df00f ntdll!LdrpResReadFile = <no type information>
7714fce4 ntdll!ZwReadFileScatter = <no type information>
772150c6 ntdll!_ResReadFile = <no type information>
7714f8f0 ntdll!ZwReadFile = <no type information>
7714fce4 ntdll!NtReadFileScatter = <no type information>
7714f8f0 ntdll!NtReadFile = <no type information>
0:000> u ntdll!NtReadFile
ntdll!ZwReadFile:
7714f8f0 b803000000      mov     eax,3
7714f8f5 b91a000000      mov     ecx,1Ah
7714f8fa 8d542404        lea     edx,[esp+4]
7714f8fe 64ff15c0000000  call    dword ptr fs:[0C0h]
7714f905 83c404          add     esp,4
7714f908 c22400          ret     24h
7714f90b 90              nop
ntdll!ZwDeviceIoControlFile:
7714f90c b804000000      mov     eax,4
设置软件断点
0:000> bp 7714f8f0
0:000> bl
 0 e 7714f8f0     0001 (0001)  0:**** ntdll!ZwReadFile

继续运行断点命中
0:000> g
ModLoad: 769f0000 76a50000   C:\Windows\SysWOW64\IMM32.DLL
ModLoad: 767a0000 7686e000   C:\Windows\syswow64\MSCTF.dll
Breakpoint 0 hit
eax=000ceb8c ebx=00000000 ecx=7c990000 edx=0008e3c8 esi=0050d048 edi=0050d068
eip=7714f8f0 esp=000ceb38 ebp=000cebb0 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!ZwReadFile:
7714f8f0 b803000000      mov     eax,3

检查线程堆栈查看命中的函数栈
0:000> k
ChildEBP RetAddr  
000ceb34 7568efaf ntdll!ZwReadFile
000cebb0 7568f293 kernel32!BaseDllOpenIniFileOnDisk+0x341
000cebf0 7568e5d1 kernel32!BaseDllReadWriteIniFileOnDisk+0x2d
000cec08 7569187f kernel32!BaseDllReadWriteIniFile+0xed
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\SysWOW64\winhafnt.dll - 
000cec3c 10009826 kernel32!GetPrivateProfileStringA+0x34
WARNING: Stack unwind information not available. Following frames may be wrong.
000cfa1c 77169280 winhafnt!UninstallDetours+0x3696
000cfa3c 7716feb7 ntdll!LdrpCallInitRoutine+0x14
000cfb30 7717b474 ntdll!LdrpRunInitializeRoutines+0x26f
000cfcb0 77179f31 ntdll!LdrpInitializeProcess+0x1402
000cfd00 77169799 ntdll!_LdrpInitialize+0x78
000cfd10 00000000 ntdll!LdrInitializeThunk+0x10

线程堆栈先不管,回到断点
0:000> u 7714f8f0
ntdll!ZwReadFile:
7714f8f0 b803000000      mov     eax,3
7714f8f5 b91a000000      mov     ecx,1Ah
7714f8fa 8d542404        lea     edx,[esp+4]
7714f8fe 64ff15c0000000  call    dword ptr fs:[0C0h]
7714f905 83c404          add     esp,4
7714f908 c22400          ret     24h
7714f90b 90              nop
ntdll!ZwDeviceIoControlFile:
7714f90c b804000000      mov     eax,4

设置断点是一个int3 ,为何反汇编看不到int3?其实是调试器故意隐藏了int 3,不让大家看到。
那如何看呢?先让程序跑起来。g

启动第二个winDbg,以noinvasive的方式attack winmine进程,就可以只读方式调试


反汇编同样的地址:
0:000> u 7714f8f0
ntdll!NtReadFile:
7714f8f0 cc              int     3
7714f8f1 0300            add     eax,dword ptr [eax]
7714f8f3 0000            add     byte ptr [eax],al
7714f8f5 b91a000000      mov     ecx,1Ah
7714f8fa 8d542404        lea     edx,[esp+4]
7714f8fe 64ff15c0000000  call    dword ptr fs:[0C0h]
7714f905 83c404          add     esp,4
7714f908 c22400          ret     24h
这样可以清晰的看到int 3,而且因为x86是不等长的,后面的反汇编意外的翻译成add指令,胡乱翻译了
调试器中断的时候,调试器会把int3恢复成原来的指令。 go起来后反而是int 3指令。

恢复执行
当用户结束分析希望恢复被调试程序执行时,调试器通过调试API通知调试子系统,这会使系统内核的异常分发函数返回到异常处理例程,然后异常处理例程通过IRET/IRETD指令触发一个异常返回动作,使CPU恢复执行上下文,从发生异常的位置继续执行。注意,这时的程序指针指向断点所在的那条指令,此时刚才的断点已经被替换成本来的指令,于是程序会从断点位置的原来指令继续执行。
这里有一个问题,前面我们说当断点命中中断到调试器,调试器会把所有断点处的INT3指令恢复成本来的内容。因此,在用户发出恢复执行指令后,调试器在通知系统真正恢复执行前,需要将断点列表中的所有断点再落实一遍。但是对于刚刚命中的这个断点需要特殊对待,试想如果把这个断点处的指令也替换成INT3,那么程序一执行便又触发断点了。对于这个问题,大多数调试器的做法都是先单步执行一次。具体地说,就是先设置单步执行标志,然后恢复执行,将断点所在位置的指令执行完。因为设置了单步标志,所以CPU执行完断点位置的这条指令后会立刻再中断到调试器,这一次调试器不会通知用户,会做一些内部操作后便立刻恢复程序执行,而且将所有断点都落实(使用INT3替换),这个过程一般被称为“单步走出断点”
 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值