本文主要分析内核中与调试相关的几个内核函数。

首先是NtCreateDebugObject函数,用于创建一个内核调试对象,分析程序可知,其实只是一层对ObCreateObject的封装,并初始化一些结构成员而已。

我后面会写一些与window对象管理方面的笔记,会分析到对象的创建过程。

来自WRK1.2
NTSTATUS NtCreateDebugObject (
OUT PHANDLE DebugObjectHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN ULONG Flags
) {
NTSTATUS Status;
HANDLE Handle;
KPROCESSOR_MODE PreviousMode;
PDEBUG_OBJECT DebugObject;
//检测这个宏所在位置的IRQL级别,相当于一个断言,确定当前IRQL允许分页
PAGED_CODE();
//获取当前操作模式是内核还是用户
PreviousMode = KeGetPreviousMode(); try
{
if (PreviousMode != KernelMode)
{
//验证是否可读写(验证参数正确性)
ProbeForWriteHandle (DebugObjectHandle);
}
*DebugObjectHandle = NULL; } except (ExSystemExceptionFilter ())
{
return GetExceptionCode ();
} if (Flags & ~DEBUG_KILL_ON_CLOSE) {
return STATUS_INVALID_PARAMETER;
} //
// 创建调试对象
// Status = ObCreateObject (PreviousMode,
DbgkDebugObjectType,
ObjectAttributes,
PreviousMode,
NULL,
sizeof (DEBUG_OBJECT),
0,
0,
&DebugObject); if (!NT_SUCCESS (Status))
{
return Status;
} ExInitializeFastMutex (&DebugObject->Mutex);
//初始化调试内核对象中的调试事件链表
InitializeListHead (&DebugObject->EventList);
KeInitializeEvent (&DebugObject->EventsPresent, NotificationEvent, FALSE); if (Flags & DEBUG_KILL_ON_CLOSE)
{
DebugObject->Flags = DEBUG_OBJECT_KILL_ON_CLOSE;
}
else
{
DebugObject->Flags = 0;
} // 调试对象插入当前进程的句柄表
Status = ObInsertObject (DebugObject,
NULL,
DesiredAccess,
0,
NULL,
&Handle/*返回一个句柄*/); if (!NT_SUCCESS (Status))
{
return Status;
}
//用异常处理进行安全复制
try
{
*DebugObjectHandle = Handle;
}
except(ExSystemExceptionFilter ())
{ Status = GetExceptionCode ();
} return Status;
}

29号写的windows调试机制学习笔记中分析了DebugActiveProcess()函数,这个函数会调用到ZwDebugActiveProcess(),而这个函数最后又会执行到NtDebugActiveProcess()

而这个函数里关键的调用就是DbgkpPostFakeProcessCreateMessages()和DbgkpSetProcessDebugObject()

NTSTATUS
NtDebugActiveProcess (
IN HANDLE ProcessHandle,
IN HANDLE DebugObjectHandle
)
{
NTSTATUS Status;
KPROCESSOR_MODE PreviousMode;
PDEBUG_OBJECT DebugObject;//返回调试对象的值
PEPROCESS Process;
PETHREAD LastThread;
//检测这个宏所在位置的IRQL级别,相当于一个断言,确定当前IRQL允许分页
PAGED_CODE (); PreviousMode = KeGetPreviousMode();
//用句柄使用进程内核对象
Status = ObReferenceObjectByHandle (ProcessHandle,
PROCESS_SET_PORT,
PsProcessType,
PreviousMode,
&Process,
NULL);
if (!NT_SUCCESS (Status))
{
return Status;
} // Don't let us debug ourselves or the system process.
//验证参数合法性
if (Process == PsGetCurrentProcess () /*是否要调试自己*/|| Process == PsInitialSystemProcess/*不能调试system进程,pid为4*/)
{
ObDereferenceObject (Process);//消除计数
return STATUS_ACCESS_DENIED;
} //用句柄使用调试内核对象
Status = ObReferenceObjectByHandle (DebugObjectHandle,
DEBUG_PROCESS_ASSIGN,
DbgkDebugObjectType,
PreviousMode,
&DebugObject,
NULL); if (NT_SUCCESS (Status))
{ // We will be touching process address space. Block process rundown.
//加锁,防止进程在操作过程中退出
if (ExAcquireRundownProtection (&Process->RundownProtect))
{ //这是个关键的函数,下面会针对分析
//作用是发送伪造的调试信息
//根据字面意思是发送伪造的进程创建调试信息
Status = DbgkpPostFakeProcessCreateMessages (Process,
DebugObject,
&LastThread); // Set the debug port. If this fails it will remove any faked messages.
//上面的是WRK自带的注释
Status = DbgkpSetProcessDebugObject (Process,
DebugObject,
Status,
LastThread);
//释放锁
ExReleaseRundownProtection (&Process->RundownProtect);
}
else
{
Status = STATUS_PROCESS_IS_TERMINATING;
} ObDereferenceObject (DebugObject);
}
ObDereferenceObject (Process); return Status;
}

注意DbgkpPostFakeProcessCreateMessages()是用于向调试器发生伪造的调试信息的。为什么要进行伪造调试信息呢?因为是为了获取完整的调试信息。

想象这样一个场景,你附加一个正在运行的进程。如果没有伪造调试信息那么调试器是无法正常工作的,因为你附加时进程已经运行起来了,之前的调试信息你是收不到的,

调试器也就无法正常工作了。伪造调试信息就是为了完整的把调试信息重新发送一遍给调试器而诞生的。

我们来看DbgkpPostFakeProcessCreateMessages()这个函数,我们可以看到这个其实也只是一层封装而已。有作用的是DbgkpPostFakeThreadMessages和DbgkpPostFakeModuleMessages这两个函数。

NTSTATUS
DbgkpPostFakeProcessCreateMessages (
IN PEPROCESS Process,
IN PDEBUG_OBJECT DebugObject,
IN PETHREAD *pLastThread
)
{
NTSTATUS Status;
KAPC_STATE ApcState;
PETHREAD Thread;
PETHREAD LastThread; PAGED_CODE (); //附加到目标进程上
KeStackAttachProcess(&Process->Pcb, &ApcState);
//发送假的线程创建信息(主要功能在这里实现)
Status = DbgkpPostFakeThreadMessages (Process,
DebugObject,
NULL,
&Thread,
&LastThread); if (NT_SUCCESS (Status))
{
//发送模块消息
Status = DbgkpPostFakeModuleMessages (Process, Thread, DebugObject);
if (!NT_SUCCESS (Status))
{
ObDereferenceObject (LastThread);
LastThread = NULL;
}
ObDereferenceObject (Thread);
}
else
{
LastThread = NULL;
}
//解除附加
KeUnstackDetachProcess(&ApcState); *pLastThread = LastThread; return Status;
}

DbgkpPostFakeThreadMessages函数分析如下

1.发送进程创建伪造调试信息

2.依次发送每个线程的创建伪造调试信息

3.把每个信息放入调试对象的链表中的调试事件中

这里的伪造调试信息使用的是

DBGKM_APIMSG结构

typedef struct _DBGKM_APIMSG {
    PORT_MESSAGE h;
    DBGKM_APINUMBER ApiNumber;
    NTSTATUS ReturnedStatus;
    union {
        DBGKM_EXCEPTION Exception;
        DBGKM_CREATE_THREAD CreateThread;
        DBGKM_CREATE_PROCESS CreateProcessInfo;
        DBGKM_EXIT_THREAD ExitThread;
        DBGKM_EXIT_PROCESS ExitProcess;
        DBGKM_LOAD_DLL LoadDll;
        DBGKM_UNLOAD_DLL UnloadDll;
    } u;
} DBGKM_APIMSG, *PDBGKM_APIMSG;

typedef struct _DBGKM_CREATE_PROCESS {
    ULONG SubSystemKey;
    HANDLE FileHandle;
    PVOID BaseOfImage;
    ULONG DebugInfoFileOffset;
    ULONG DebugInfoSize;
    DBGKM_CREATE_THREAD InitialThread;
} DBGKM_CREATE_PROCESS, *PDBGKM_CREATE_PROCESS;

注意这个结构最后被写入到调试事件中

NTSTATUS
DbgkpPostFakeThreadMessages (
IN PEPROCESS Process,
IN PDEBUG_OBJECT DebugObject,
IN PETHREAD StartThread,
OUT PETHREAD *pFirstThread,
OUT PETHREAD *pLastThread
) {
NTSTATUS Status;
PETHREAD Thread, FirstThread, LastThread;
DBGKM_APIMSG ApiMsg;
BOOLEAN First = TRUE;
BOOLEAN IsFirstThread;
PIMAGE_NT_HEADERS NtHeaders;
ULONG Flags;
NTSTATUS Status1;
//验证IRQL
PAGED_CODE (); LastThread = FirstThread = NULL; Status = STATUS_UNSUCCESSFUL;
   //注意,上面传过来的就是NULL!!!
if (StartThread != NULL) {
//StartThread!=NULL说明当前线程有ID即当前线程不是初始线程
First = FALSE;//不是第一个
FirstThread = StartThread;
ObReferenceObject (FirstThread);
}
else
{
//==0说明当前线程是初始线程。也说明是在创建进程。
StartThread = PsGetNextProcessThread (Process, NULL);//这里获得的就是初始线程
First = TRUE;//是第一个
} for (Thread = StartThread;
Thread != NULL;
//遍历进程的每一个线程
Thread = PsGetNextProcessThread (Process, Thread))
{
//设置调试事件不等待
Flags = DEBUG_EVENT_NOWAIT;
if (LastThread != NULL) {
ObDereferenceObject (LastThread);
}
//用来记录最后一个线程
LastThread = Thread;
ObReferenceObject (LastThread);
//锁住线程,防止线程终止
if (ExAcquireRundownProtection (&Thread->RundownProtect))
{
Flags |= DEBUG_EVENT_RELEASE;
//判断获得的线程是否是系统的线程
if (!IS_SYSTEM_THREAD (Thread))
{ //暂停线程
Status1 = PsSuspendThread (Thread, NULL);
if (NT_SUCCESS (Status1))
{
//暂停成功,加一个暂停标记
Flags |= DEBUG_EVENT_SUSPEND;
}
}
}
else
{
//获取锁失败,加上标记
Flags |= DEBUG_EVENT_PROTECT_FAILED;
}
//构造一个ApiMsg结构(DBGKM_APIMSG类型)
RtlZeroMemory (&ApiMsg, sizeof (ApiMsg)); //如果申请成功,并且这个线程是第一个线程
//说明是进程创建
//这里会发生进程创建伪造消息
if (First && (Flags&DEBUG_EVENT_PROTECT_FAILED) == 0 &&
!IS_SYSTEM_THREAD (Thread) && Thread->GrantedAccess != 0)
{
IsFirstThread = TRUE;//说明是第一线程创建兼进程创建
}
else
{
IsFirstThread = FALSE;
} if (IsFirstThread)
{
//这里设置了进程创建伪造消息的结构
ApiMsg.ApiNumber = DbgKmCreateProcessApi;
if (Process->SectionObject != NULL) //
{
//把进程主模块的文件句柄保存在伪造消息的结构中
ApiMsg.u.CreateProcessInfo.FileHandle = DbgkpSectionToFileHandle (Process->SectionObject);
}
else
{
ApiMsg.u.CreateProcessInfo.FileHandle = NULL;
} //把进程主模块基址保存在伪造信息的结构中
ApiMsg.u.CreateProcessInfo.BaseOfImage = Process->SectionBaseAddress;
//用异常处理增强稳定性
try
{
//获得PE结构的NT头部
NtHeaders = RtlImageNtHeader(Process->SectionBaseAddress);
if (NtHeaders)
{
ApiMsg.u.CreateProcessInfo.InitialThread.StartAddress = NULL; // Filling this in breaks MSDEV!
//解析NT头部中的调试信息,放入伪造信息的结构中
ApiMsg.u.CreateProcessInfo.DebugInfoFileOffset = NtHeaders->FileHeader.PointerToSymbolTable;
ApiMsg.u.CreateProcessInfo.DebugInfoSize = NtHeaders->FileHeader.NumberOfSymbols;
}
}
except (EXCEPTION_EXECUTE_HANDLER)
{
ApiMsg.u.CreateProcessInfo.InitialThread.StartAddress = NULL;
ApiMsg.u.CreateProcessInfo.DebugInfoFileOffset = 0;
ApiMsg.u.CreateProcessInfo.DebugInfoSize = 0;
}
}
else
{ //不是第一个,说明是线程创建,设置一个线程创建伪造信息结构
ApiMsg.ApiNumber = DbgKmCreateThreadApi;
ApiMsg.u.CreateThread.StartAddress = Thread->StartAddress;
} //把上面构造的消息包插入到队列中
Status = DbgkpQueueMessage (Process,
Thread,
&ApiMsg,
Flags,
DebugObject);
//错误处理
if (!NT_SUCCESS (Status))
{
if (Flags&DEBUG_EVENT_SUSPEND)
{
PsResumeThread (Thread, NULL);
}
if (Flags&DEBUG_EVENT_RELEASE)
{
ExReleaseRundownProtection (&Thread->RundownProtect);
}
if (ApiMsg.ApiNumber == DbgKmCreateProcessApi && ApiMsg.u.CreateProcessInfo.FileHandle != NULL)
{
ObCloseHandle (ApiMsg.u.CreateProcessInfo.FileHandle, KernelMode);
}
PsQuitNextProcessThread (Thread);
break;
}
else if (IsFirstThread) {
First = FALSE;//已经处理完第一次了
ObReferenceObject (Thread);
FirstThread = Thread;
}
} if (!NT_SUCCESS (Status)) {
if (FirstThread) {
ObDereferenceObject (FirstThread);
}
if (LastThread != NULL) {
ObDereferenceObject (LastThread);
}
} else {
if (FirstThread) {
*pFirstThread = FirstThread;
*pLastThread = LastThread;
} else {
Status = STATUS_UNSUCCESSFUL;
}
}
return Status;
}

这个就是上面说的针对调试对象的调试事件链表的操作了

NTSTATUS
DbgkpQueueMessage (
IN PEPROCESS Process,
IN PETHREAD Thread,
IN OUT PDBGKM_APIMSG ApiMsg,
IN ULONG Flags,
IN PDEBUG_OBJECT TargetDebugObject
)
{
PDEBUG_EVENT DebugEvent;//ApiMsg最后会封装入这个结构内
DEBUG_EVENT StaticDebugEvent;
PDEBUG_OBJECT DebugObject;
NTSTATUS Status; PAGED_CODE ();
//判断是同步事件还是异步事件……
if (Flags&DEBUG_EVENT_NOWAIT)
{
//异步事件这样处理
//给调试事件分配空间
DebugEvent = ExAllocatePoolWithQuotaTag (NonPagedPool|POOL_QUOTA_FAIL_INSTEAD_OF_RAISE,
sizeof (*DebugEvent),
'EgbD');
if (DebugEvent == NULL)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
//设置DEBUG_EVENT结构的信息
DebugEvent->Flags = Flags|DEBUG_EVENT_INACTIVE;
ObReferenceObject (Process);
ObReferenceObject (Thread);
DebugEvent->BackoutThread = PsGetCurrentThread ();
DebugObject = TargetDebugObject;
}
else
{
//同步事件这样处理
//同步处理时没有为结构开辟pool内存
DebugEvent = &StaticDebugEvent;
//直接给定一个局部变量,因为在函数内就处理完成了,局部变量就可以满足条件
DebugEvent->Flags = Flags;
//获取同步锁
ExAcquireFastMutex (&DbgkpProcessDebugPortMutex); DebugObject = Process->DebugPort; //
// See if this create message has already been sent.
//
if (ApiMsg->ApiNumber == DbgKmCreateThreadApi ||
ApiMsg->ApiNumber == DbgKmCreateProcessApi)
{
if (Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_SKIP_CREATION_MSG)
{
DebugObject = NULL;
}
} //
// See if this exit message is for a thread that never had a create
//
if (ApiMsg->ApiNumber == DbgKmExitThreadApi ||
ApiMsg->ApiNumber == DbgKmExitProcessApi)
{
if (Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_SKIP_TERMINATION_MSG)
{
DebugObject = NULL;
}
}
} KeInitializeEvent (&DebugEvent->ContinueEvent, SynchronizationEvent, FALSE);
//填充DebugEvent
DebugEvent->Process = Process;
DebugEvent->Thread = Thread;
DebugEvent->ApiMsg = *ApiMsg;
DebugEvent->ClientId = Thread->Cid; if (DebugObject == NULL) {
Status = STATUS_PORT_NOT_SET;
} else { //
// We must not use a debug port thats got no handles left.
//
//获取一个调试对象的锁
ExAcquireFastMutex (&DebugObject->Mutex); //
// If the object is delete pending then don't use this object.
//
if ((DebugObject->Flags&DEBUG_OBJECT_DELETE_PENDING) == 0)
{
//把调试事件插入调试对象中的链表里
InsertTailList (&DebugObject->EventList, &DebugEvent->EventList);
//
// Set the event to say there is an unread event in the object
//
if ((Flags&DEBUG_EVENT_NOWAIT) == 0)
{
//如果是同步的,通知调试器去处理了
KeSetEvent (&DebugObject->EventsPresent, 0, FALSE);
}
Status = STATUS_SUCCESS;
} else
{
Status = STATUS_DEBUGGER_INACTIVE;
} ExReleaseFastMutex (&DebugObject->Mutex);
} if ((Flags&DEBUG_EVENT_NOWAIT) == 0)
{
ExReleaseFastMutex (&DbgkpProcessDebugPortMutex); if (NT_SUCCESS (Status))
{
//等待调试器返回信息
KeWaitForSingleObject (&DebugEvent->ContinueEvent,
Executive,
KernelMode,
FALSE,
NULL); Status = DebugEvent->Status;
*ApiMsg = DebugEvent->ApiMsg;
}
}
else
{
if (!NT_SUCCESS (Status)) {
ObDereferenceObject (Process);
ObDereferenceObject (Thread);
ExFreePool (DebugEvent);
}
} return Status;
}

Windows内核分析——内核调试机制的实现(NtCreateDebugObject、DbgkpPostFakeProcessCreateMessages、DbgkpPostFakeThreadMessages分析)的更多相关文章

  1. Windows内核开发-6-内核机制 Kernel Mechanisms

    Windows内核开发-6-内核机制 Kernel Mechanisms 一部分Windows的内核机制对于驱动开发很有帮助,还有一部分对于内核理解和调试也很有帮助. Interrupt Reques ...

  2. kernel 3.10内核源码分析--hung task机制

    kernel 3.10内核源码分析--hung task机制 一.相关知识: 长期以来,处于D状态(TASK_UNINTERRUPTIBLE状态)的进程 都是让人比较烦恼的问题,处于D状态的进程不能接 ...

  3. 内核用户模式调试支持(Dbgk)

    简介 将详细分析Windows调试的内核模式接口.希望读者对C和通用NT内核体系结构和语义有一些基本的了解.此外,这并不是介绍什么是调试或如何编写调试器.它可以作为经验丰富的调试器编写人员或好奇的安全 ...

  4. 初探Windows用户态调试机制

    我们在感叹Onlydbg强大与便利的同时,是否考虑过它实现的原理呢? 作为一个技术人员知其然必知其所以然,这才是我们追求的本心. 最近在学习张银奎老师的<软件调试>,获益良多.熟悉Wind ...

  5. Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7)

    http://blog.chinaunix.net/uid-20543672-id-3157283.html Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3 ...

  6. LINUX内核CPU负载均衡机制【转】

    转自:http://oenhan.com/cpu-load-balance 还是神奇的进程调度问题引发的,参看Linux进程组调度机制分析,组调度机制是看清楚了,发现在重启过程中,很多内核调用栈阻塞在 ...

  7. v75.01 鸿蒙内核源码分析(远程登录篇) | 内核如何接待远方的客人 | 百篇博客分析OpenHarmony源码

    子曰:"不学礼,无以立 ; 不学诗,无以言 " <论语>:季氏篇 百篇博客分析.本篇为: (远程登录篇) | 内核如何接待远方的客人 设备驱动相关篇为: v67.03 ...

  8. v80.01 鸿蒙内核源码分析(内核态锁篇) | 如何实现快锁Futex(下) | 百篇博客分析OpenHarmony源码

    百篇博客分析|本篇为:(内核态锁篇) | 如何实现快锁Futex(下) 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁) ...

  9. 【Windows 操作系统】 内核对象|句柄

    内核对象简介 内核对象就是 一些数据结构该结构用来描述存储内核中的一个内存块中的数据信息.   内存块是一种数据结构,其中的数据成员负责维护该对象的相应信息,这个数据结构以及其中的数据成员只能由内核访 ...

随机推荐

  1. jqury中关于ajax的几个常用的函数

    一: AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术. 什么是 AJAX ? AJAX = 异步 JavaScript 和 XML. AJAX 是一种用于创建快速动态网页的技术 ...

  2. 【bzoj4195】【NOI2015】程序自动分析

    4195: [Noi2015]程序自动分析 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 3470  Solved: 1626[Submit][Sta ...

  3. JSP2 特性

    JSP2 新特性 1.直接配置 JSP 属性 2.表达式语言 3.简化的自定义标签 API 4.Tag 文件语法 如果要使用 JSP2 语法,web.xml 文件必须使用 Servlet2.4 以上版 ...

  4. codeforces 55D 数位dp

    D. Beautiful numbers time limit per test 4 seconds memory limit per test 256 megabytes input standar ...

  5. 1.UiDevice API 详细介绍

    1.UiDevice按键与keycode使用 返回值 方法名 说明 boolean pressBack() 模拟短按返回back键 boolean pressDPadCenter() 模拟按轨迹球中点 ...

  6. 阳/阴性预测值Positive/negative Predictive Value(推荐AA)

    sklearn实战-乳腺癌细胞数据挖掘(博主亲自录制视频教程) https://study.163.com/course/introduction.htm?courseId=1005269003&am ...

  7. MySQL语句查看各个数据库占用空间

    select table_schema, sum(DATA_LENGTH)+sum(INDEX_LENGTH) from information_schema.tables group by tabl ...

  8. 最短路+找规律 Samara University ACM ICPC 2016-2017 Quarterfinal Qualification Contest L. Right Build

    题目链接:http://codeforces.com/gym/101149/problem/L 题目大意:有n个点(其实是n+1个点,因为编号是0~n),m条有向边.起点是0,到a和b两个节点,所经过 ...

  9. HDU 4778 状压DP

    一看就是状压,由于是类似博弈的游戏.游戏里的两人都是绝对聪明,那么先手的选择是能够确定最终局面的. 实际上是枚举最终局面情况,0代表是被Bob拿走的,1为Alice拿走的,当时Alice拿走且满足变换 ...

  10. 强大的jQuery网格插件 ParamQuery

    ParamQuery是一种轻量级的jQuery网格插件,基于用于用户界面控制.具有一致API的优秀设计模式jQueryUI Widget factory创建,能够在网页上展示各种类似于Excel和Go ...