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

首先是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. 使用Ajax内容签名,减少流量浪费

    前端UI界面用Ajax获取数据内容的时候,一般是直接获取内容数据并填充,不管内容有无变化,不管数据量多大,都是直接重新加载数据,例如定时刷新公告等. 今天在浏览器控制台调试的时候,发现动态刷新内容,其 ...

  2. 专题训练之区间DP

    例题:以下例题部分的内容来自https://blog.csdn.net/my_sunshine26/article/details/77141398 一.石子合并问题 1.(NYOJ737)http: ...

  3. 题解 P2598 【[ZJOI2009]狼和羊的故事】

    P2598 [ZJOI2009]狼和羊的故事 题目描述 "狼爱上羊啊爱的疯狂,谁让他们真爱了一场:狼爱上羊啊并不荒唐,他们说有爱就有方向......" Orez听到这首歌,心想:狼 ...

  4. 《转》sklearn参数优化方法

    sklearn参数优化方法  http://www.cnblogs.com/nolonely/p/7007961.html   学习器模型中一般有两个参数:一类参数可以从数据中学习估计得到,还有一类参 ...

  5. 线上Redis偶发性链接失败排查记

    问题过程 输入法业务于12月12日上线了词库接受业务,对部分用户根据用户uuid判断进行回传,在12月17日早上8点多开始出现大量的php报错(Redis went away),报错导致了大量的链接积 ...

  6. 「七天自制PHP框架」应用:Model外键链接

    这里以行政区数据为例: 一级行政区数据范例: 二级行政区范例: 三级行政区范例: 在Model层建立三个Model class ProvinceModel extends Model{ public ...

  7. winform MDI子窗口闪动问题(本人测试100%有效解决闪屏问题)

    将下面的代码随便放到主窗体的任何一个地方 protected override CreateParams CreateParams //解决MDI闪屏 { get { CreateParams cp ...

  8. centos下搭建YII环境

      第一步:安装lnmp wget -c http://soft.vpser.net/lnmp/lnmp1.4-full.tar.gz 下载完lnmp后,进行解压文档 tar zxf lnmp1.4- ...

  9. 巧用margin/padding的百分比值实现高度自适应

    原文:https://segmentfault.com/a/1190000004231995 一个基础却又容易混淆的css知识点 本文依赖于一个基础却又容易混淆的css知识点:当margin/padd ...

  10. NYOJ 1022 合纵连横 (并查集)

    题目链接 描述 乱世天下,诸侯割据.每个诸侯王都有一片自己的领土.但是不是所有的诸侯王都是安分守己的,实力强大的诸侯国会设法吞并那些实力弱的,让自己的领土面积不断扩大.而实力弱的诸侯王为了不让自己的领 ...