zoukankan      html  css  js  c++  java
  • windows下的用户态调试的底层与上层实现

    操作系统:windows XP

    调试器通过CreateProcess传入带有DEBUG_PROCESS和DEBUG_ONLY_THIS_PROCESS的dwCreationFlags创建被调试进程。这种情况下,进程创建的早期(执行NtCreateProcess或NtCreateProcessEx之前),调用DbgUiConnectToDbg()使调用线程和调试子系统建立连接。DbgUiConnectToDbg()内部调用ZwCreateDebugObject创建一个DEBUG_OBJECT内核对象,并将其句柄保存在当前线程环境块(TEB)的的DbgSsReserved[1]中。调试进程与被调试进程就是通过DEBUG_OBJECT进行交互的。之后该线程便被认作是调试工作线程。
     
    调用NtCreateProcess或NtCreateProcessEx时,
    NTSYSAPI
    NTSTATUS
    NTAPI
    NtCreateProcess(

    OUT PHANDLE ProcessHandle,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
    IN HANDLE ParentProcess,
    IN BOOLEAN InheritObjectTable,
    IN HANDLE SectionHandle OPTIONAL,
    IN HANDLE DebugPort OPTIONAL,
    IN HANDLE ExceptionPort OPTIONAL );
    DbgSsReserved[1]中的句柄将传入,内核中进程创建函数PspCreateProcess会检查它是否为空,若不为空,则取得该句柄的对象指针(PDEBUG_OBJECT),并将其存储于新创建进程EPROCESS的DebugPort中。之后新创建进程便被认作是被调试进程。
     
    之后PspCreateProcess调用MmCreatePeb创建进程环境块(PEB)时,MmCreatePeb会根据DebugPort是否为空,相应设置PEB中BeingDebugged字段。
     
    另一种情况是调试器附加到被调试进程。通过调用 BOOL DebugActiveProcess(DWORD dwProcessId)完成。首先会调用DebugUiConnectToDbg使调用进程与调试子系统建立连接。之后调用ProcessIdToHandle取得句柄。之后调用NTDLL中的DbgUiDebugActiveProcess。该函数调用NtDebugActiveProcess并传入ProcessHandle和DebugObject的handle。NtDebugActiveProcess内部根据句柄取得要被调试进程的EPROCESS和用于调试的调试对象DEBUG_OBJECT,之后向调试对象发送杜撰的调试事件(在调试循环中再说),之后调用DbgkpSetProcessDebugObject将调试对象的指针存储到被调试进程EPROCESS的DebugPort中,并调用DbgkpMarkProcessPeb设置被调试进程PEB的BeingDebugged字段。当NtDebugActiveProcess成功返回后,DbgUiDebugActiveProcess调用DbgUiIssueRemoteBreakIn在被调试进程中创建远程中断线程(后述),使被调试进程中断到调试器中。
     
    之后调试器和被调试程序便建立起了调试会话。
     
    之后调试线程进入了调试循环。调试线程使用WaitForDebugEvent等待调试事件。
     
    调试事件即被调试进程所发生的某些特殊动作。例如线程、进程的创建和销毁等,当然还有非常重要的异常事件。被调试进程通过调试子系统发送给调试器,此时,调试器的WaitForDebugEvent将结束等待,并相应处理调试事件。具体如下:
     
    调试子系统内核部分设计了一系列的函数来采集调试事件,并将收集到的调试事件以DBGKM_APIMSG这样的结构存储在调试子系统的调试消息队列中。当被调试进程创建新的进程或线程时,将产生相应的调试事件。在进程管理器创建新线程时,最后将调用PspUserThreadStartup准备启动线程。在该函数中,会调用调试子系统内核函数DbgkCreateThread,DbgkCreateThread将检查DebugPort是否为空,若为空,则立即返回,若不为空,继续检查进程的用户态运行时间(UserTime)是否为0,从而判断是否为进程中第一个线程。若是,若是,则通过DbgkpSendApiMessage()向DebugPort发送DbgKmCreateProcessApi消息,若不是,则发送DbgKmCreateThreadApi消息。
     
    NTSTATUS DbgkpSendApiMessage(IN OUT PDGBKM_APIMSG ApiMsg, IN PVOID Port, IN BOOLEAN SuspendProcess)
    其中Port一般是DebugPort中的指向DEBUG_OBJECT的指针。SuspendProcess说明是否要挂起被调试进程。若SuspendProcess为真,则该函数调用DbgkpSuspendProcess挂起被调试进程。之后调用DbgkpQueueMessage,该函数根据参数决定是否需要等待,若需要,则通过等待函数,直到收到调试器回复后返回。若是发送杜撰消息,则无需等待直接返回。之后若挂起了调试进程,则调用DbgkpResumeProcess唤醒被调试进程。
     
    DbgkpSuspendProcess调用KeFreezeAllThreads()冻结除被调试进程中产生调试事件的线程的所有线程。之后如上文,该线程调用DbgkpQueueMessage发送消息并在大部分情况下调用KeWaitForSingleObject等待调试器的返回。之后调试子系统会通知调试器读取调试事件。调试器调试线程WaitForDebugObject返回,调试线程处理调试事件。完成之后调用ContinueDebugEvent。之后调试子系统唤醒被调试进程在等待的那个线程。该线程醒来后,会调用KeThawAllThreads()唤醒被调试进程中的所有线程。
     

  • 相关阅读:
    Excel两列查找重复值
    Docker容器的数据卷(data volume),数据卷容器,数据卷的备份和还原
    Ubuntu 搭建 GitLab 笔记 ***
    JIRA使用
    Android.mk文件LOCAL_MODULE_TAGS 说明
    Git Reset 三种模式
    Android常用的编译命令
    Android源码编译
    AOSP---"Android Open-Source Project"
    理解 Android Build 系统
  • 原文地址:https://www.cnblogs.com/foohack/p/3582305.html
Copyright © 2011-2022 走看看