加密与解密(第四版)第9章 win32调试API

调试相关函数

  1. ContinueDebugEvent:允许调试器恢复先前由于调试事件而挂起的线程
  2. DebugActiveProcess:允许调试器捆绑到一个正在允许的进程上
  3. DebugActiveProcessStop:允许调试器从一个正在运行的进程上卸载
  4. DebugBreak:在当前进程中产生一个断点异常,如果进程未处于调试状态,异常将被系统阶段,多数情况下会直接终止进程
  5. DebugBreakProcess:在指定的进程中产生一个断点异常
  6. FatalExit:将使调用进程强制退出,将控制权转交给调试器,与ExitProcess不同是,此函数在退出前会调用一个INT 3断点
  7. FlushInstructionCache:刷新指令高速缓存
  8. GetThreadContext:获取指定线程的执行环境
  9. GetThreadSelectorEntry:获取指定选择器和线程的描述符表的入口地址
  10. IsDebuggerPresent:判断调用进程是否处于调试环境中
  11. OutputDebugString:输出一个字符串传递给调试器显示
  12. ReadProcessMemory:获取指定进程某区域内的数据
  13. SetThreadContext:设置线程的执行环境
  14. WaitForDebugEvent:等待被调试进程发生调试事件
  15. WriteProcessMemory:在指定进程的某区域内写入数据

调试相关事件

被调试进程中发生一个调试事件后,系统将通知调试器来处理这个事件,调试器利用WaitForDebugEvent函数获取目标进程的相关环境信息

常用调试事件如下:

当WaitForDebugEvent收到一个调试事件后,将把调试事件的信息写入DEBUG_EVENT结构中返回,结构定义如下:

  • dwDebugEventCode:所发生的调试事件的类型
  • dwProcessID:调试事件所发生的进程的标识符
  • dwThreadID:调试事件所发生的线程的标识符
  • union:联合体结构包含了关于调试事件的更多信息,根据dwDebugEventCode的不同,可以是如下的结构:


    一般流程:
    假设调试程序调用了WaitForDebugEvnet并得到返回,要做的第一件事就是查看dwDebugEventCode的值,根据调试事件类型来决定union联合体中的结构包含哪些内容

调试器原理

创建一个新进程以供调试
在调用CreateProcess创建进程时,在dwCreationFlag标志字段中指定DEBUG_PROCESS或DEBUG_ONLY_THIS_PROCESS标志,将创建一个用于调试的新进程。

将调试器捆绑到一个正在运行的进程上
利用DebugActiveProcess函数可以将调试器捆绑到一个正在运行的进程上,如果执行成功,则效果类似于调用CreateProcess时,传入DEBUG_ONLY_THIS_PROCESS标志位创建的进程。

在NT内核中,当试图调用DebugActiveProcess函数将调试器捆绑到一个创建时带有安全描述符的进程上时,将被拒绝。

进入正式调试阶段(调试循环体)
使用一个while循环,不停调用WaitForDebugEvent函数,获取调试事件进行处理,处理完毕后调用和ContinueDebugEvent恢复被调试进程的运行,然后继续调用WaitForDebugEvent等待下一个调试事件的到来。

示例代码:

处理调试事件
当WaitForDebugEvent拿到调试事件后,根据dwDebugEventCode检查union联合体,
union联合体的开头包含一个EXCEPTION_DEBUG_INFO结构

其中,EXCEPTION_RECORD结构包含了异常的很多信息,内容如下:

  • ExceptionCode:描述异常类型的代码
  • ExceptionFlag:0表示可继续异常,反之,值为EXCEPTION_NONCONTINUABLE
  • ExceptionRecord:指向_EXCEPTION_RECORD结构的指针
  • ExceptionAddress:异常发生的地址
  • NumberParameters:ExceptionInformation队列中定义的32位参数的数目
  • ExceptionInformation:额外的32位消息队列,主要在嵌套异常时使用,多数异常情况下没有定义

在发生一个EXCEPTION_NONCONTINUABLE的异常后,如果继续执行,将产生一个EXCEPTION_NONCONTINUABLE_EXCEPTION异常

常见异常情况:
EXCPETION_BREAKPOINTEXCPETION_SINGLE_STEP,当遇到一个INT 3断点时会产生EXCPETION_BREAKPOINT异常,如果设置了单步执行标志,那么执行完一条指令后会产生一个EXCPETION_SINGLE_STEP异常。

当调试事件处理完毕后,调用ContinueDebugEvent让线程继续运行。ContinueDebugEvent的dwContinueStatus参数有两个取值,分别是DBG_EXCEPTION_NOT_HANDLEDDBG_CONTINUE。对于除了EXCEPTION_DEBUG_EVENT调试事件以外的事件,两者无区别,都让线程继续运行。

当调试事件是EXCEPTION_DEBUG_EVENT时,意味着被调试线程中发生了一个异常。
如果指定了DBG_CONTINUE,线程将忽略它自己的异常处理函数并继续执行,并引发后续由于该异常没有被处理而导致一系列更大的异常,此时调试器的表现为不停的收到EXCEPTION_DEBUG_EVENT调试事件,直到异常导致程序终止。
如果指定了DBG_EXCEPTION_NOT_HANDLED就是告诉系统,调试器不对该异常负责,不会处理它。此时系统将使用调试线程的默认异常处理函数来处理异常。

调用ContinueDebugEvent的一般规则:

  1. 对进程被载入后发生的第一个EXCEPTION_DEBUG_EVENT,必须以DBG_CONTINUE为标志继续
  2. 如果程序调用了DebugBreakhh函数,或者成功插入了INT 3断点,并将内存恢复,都应该以DBG_CONTINUE为标志继续
  3. 如果在程序中发生了不确定的异常(特别是在调试带壳程序时),问题多半由于外壳的SEH引起的,此时应该以DBG_EXCEPTION_NOT_HANDLE为标志继续,让被调试程序本身的异常处理机制来处理。

线程运行环境

当前的线程运行状态保存于一个CONTEXT的结构中,包含线程执行所用的寄存器,系统栈和用户栈,以及线程所使用的描述符等其他状态信息。这样,当该线程再次运行时,Windows就可以恢复最近线程的运行环境,好像中间什么都没有发生一样。

获取指定线程的执行环境:GetThreadContext

设置指定线程的执行环境:SetThreadContext

调试器将代码注入进程

具体步骤如下:

  1. 利用CreateProcess获得要注入进程的进程句柄
  2. 建立由WaitForDebugEvent和ContinueDebugEvent函数构成的调试循环体
  3. 利用SuspendThread函数挂起目标线程
  4. 利用VirtualProtectEx函数修改目标内存页的读写权限
  5. 利用ReadProcessMemory函数读取目标页
  6. 利用GetThreadContext函数保存线程环境
  7. 利用WaitProcessMemory函数写入新的代码页
  8. 确认新写入的指令中的最后一个指令是INT 3
  9. 当INT 3断点被触发后,调试器保存一份CONTEXT结构的临时拷贝
  10. 在这份临时拷贝中设置新的EIP值
  11. 恢复原线程的执行,此时线程将执行注入的代码,直到INT 3指令被执行为止,此时将被调试器捕获
  12. 调试器利用WriteProcessMemory函数恢复原始代码页
  13. 恢复原始代码页的读写属性
  14. 利用SetThreadContext函数恢复线程的原始环境
  15. 恢复线程的执行

将某个区块的相对地址转化为线性虚拟地址,可以使用GetThreadSelectorEntry函数

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值