• 调试器目的:允许开发者监控程序的内部状态和运行
  • 程序运行时,调试器提供了查看程序动态视图的功能,查看或改变内存地址值的变化、内容,寄存器的内容以及每个函数的参数

1、源代码级与汇编级的调试器

  • 源代码级:一般内置于IDE中,允许设置断点
  • 汇编级调试器(底层调试器):操作对象是汇编代码,在某行汇编代码上设置断点中断程序的运行查看内存内容

2、内核模式和用户模式调试

  • 内核调试需要两个不同的系统,如果内核中有一个断点,就没有任何应用程序可以在该系统上运行。内核调试时,一个系统运行被调试代码,一个运行调试器。还需要配置操作系统使其开启内核调试功能,并将两个系统联通。
  • windbg是唯一支持内核调试的调试器

3、使用调试器

  • 方法一:利用调试器启动程序,入口点之前停止
  • 方法二:附加到已经运行的程序上
  • 单步调试:跳转多,查看内存内容如何逐步改变
  • 单步跳过(Stepping-Over)和单步跳入(stepping into)
    • 单步跳过:跳过一条函数调用指令,查看的下一条是函数调用返回后的第一条指令
      • 如果跳过了不会返回的函数,将导致调用错误,可以使用呢VMware record/reply功能重放调试回话
    • 单步跳入:看到的下一条指令是被调用函数的第一条指令
  • 跳出功能(Step-out):运行到返回指令时停止
  • 用断点暂停执行
    • 中断查看寄存器的值和内存地址的值(例;call eax;中断查看eax的值)
    • 使用调试器解决有加密例程的问题,加密前中断查看明文
    • 软件执行断点:特定指令执行后会停止程序运行
      • 使用0xCC(INT 3的机器码)重写指令的首个字节来实现软件断点。操作系统产生一个异常,控制权转到调试器。
      • 程序执行过程中字节发生变化断点就不会发生
    • 硬件执行断点
      • 利用专门的硬件寄存器
      • 处理器每执行一个指令,硬件会检测指令指针与断点地址是否相同。硬中断不关系内存中的内容
      • 可以设置访问中断的断点(无论断点处的内存被读还是被写都引起中断)
      • 缺点:只有四个硬件寄存器存储断点的地址(DR0~DR3);DR7存储DR0~DR3寄存器中的值是否有效,恶意代码能通过修改这些寄存器干扰调试器。(x86芯片组具有防御该干扰的功能,执行mov指令访问调试器的操作都会触发中断)
    • 条件断点:
      • 软件断点的一种,某些条件满足时才会被触发(例:在传递给函数GetProcAddress的参数值为RegSetValue时触发中断)
      • 缺点:减慢运行速度甚至不能正常结束运行

4、异常

  • 调试器获得运行程序控制权的基本方式
  • 首次和二次异常处理
    • 首次处理异常:异常后调试器获得控制权,如果应用程序注册了一个异常处理函数,会在调试器处理异常后获得处理异常的权限
    • 二次:应用程序没有处理异常,调试器必须处理异常(调试恶意代码时遇到可能意味着恶意代码不想在当前环境中运行)
  • 常见异常
    • 单步调试:标志寄存器中的陷阱标志(trap flag)用于单步调试,陷阱标志置位处理器每执行一条指令就会产生异常
    • 内存访问冲突:程序访问其无权访问的内存地址(内存地址无效或受保护)【特权模式=内核模式;非特权模式=用户模式】

5、使用调试器修改可执行文件

  • 改变函数跳转
  • 修改指令指针