首页
社区
课程
招聘
[翻译]Windows调试内幕系列二之Windows本地(Native)调试内幕
发表于: 2014-6-8 10:58 11262

[翻译]Windows调试内幕系列二之Windows本地(Native)调试内幕

2014-6-8 10:58
11262

Windows Native Debugging Internals

Introduction

In part two of this three part article series, the native interface to Windows debugging is dissected in detail. The reader is expected to have some basic knowledge of C and general NT Kernel architecture and semantics. Also, this is not an introduction on what debugging is or how to write a debugger. It is meant as a reference for experienced debugger writers, or curious security experts.

Windows本地(Native)调试内幕

作者: AlexIonescu
原文链接:http://www.openrce.org/articles/full_view/25
系列的第一部分链接:http://bbs.pediy.com/showthread.php?t=188300

介绍

这篇文章是这个系列的第二部分,将要详细分析Windows调试的本地接口。读者应该具有C语言和一般的NT内核架构和语法的基本知识。同时,这篇文章不是介绍什么是调试,或者怎样编写一个调试器。它只是为有经验的调试器编写者,或者好奇的安全专家作一个参考。

Native Debugging

Now it's time to look at the native side of things, and how the wrapper layer inside ntdll.dll communicates with the kernel. The advantage of having the DbgUi layer is that it allows better separation between Win32 and the NT Kernel, which has always been a part of NT design. NTDLL and NTOSKRNL are built together, so it's normal for them to have intricate knowledge of each others. They share the same structures, they need to have the same system call IDs, etc. In a perfect world, the NT Kernel should have to know nothing about Win32.

本地调试

现在来看看Native内容的一面,以及位于ntdd.dll里的包装层是怎样和Windows内核通信的。具有DbgUi层的优点是,它允许Win32和NT内核之间更好地分离,这一直都是NT设计的一部分。NTDLL和NTOSKRNL是构建在一起的,所以它们彼此之间有复杂的联系,这是很正常的。它们共享相同的结构,它们需要有相同的系统调用身份,等等。最好的设计应该是,NT内核不应该知道关于Win32的任何细节。

Additionally, it helps anyone that wants to write debugging capabilities inside a native application, or to write a fully-featured native-mode debugger. Without DbgUi, one would have to call the Nt*DebugObject APIs manually, and do some extensive pre/post processing in some cases. DbgUi simplifies all this work to a simple call, and provides a clean interface to do it. If the kernel changes internally, DbgUi will probably stay the same, only its internal code would be modified.

此外,它可以帮助那些想要在本地应用程序中编写调试功能,或者编写一个全功能的本地模式调试器。如果没有DbgUi,他们将不得不手动调用Nt*DebugObject API,并在某些情况下,做一些扩展的预/后(pre/post)处理。DbgUi简化了所有这一切工作,通过一个简单的调用,并提供了一个干净的接口来完成。如果内核内部发生改变,DbgUi可能会保持不变,只有内部的代码将被修改。

We start our exploration with the function responsible for creating and associating a Debug Object with the current Process. Unlike in the Win32 world, there is a clear distinction between creating a Debug Object, and actually attaching to a process.

我们从一个函数开始探索,这个函数负责对当前进程创建并关联一个调试对象。不像在Win32的世界里,这里在创建一个调试对象和实际附加到进程之间有一个明显的区别。

NTSTATUS
NTAPI
DbgUiConnectToDbg(VOID)
{
    OBJECT_ATTRIBUTES ObjectAttributes;

    /* Don't connect twice */
    if (NtCurrentTeb()->DbgSsReserved[1]) return STATUS_SUCCESS;

    /* Setup the Attributes */
    InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, 0);

    /* Create the object */
    return ZwCreateDebugObject(&NtCurrentTeb()->DbgSsReserved[1],
                               DEBUG_OBJECT_ALL_ACCESS,
                               &ObjectAttributes,
                               TRUE);
}

As you can see, this is a trivial implementation, but it shows us two things. Firstly, a thread can only have one debug object associated to it, and secondly, the handle to this object is stored in the TEB's DbgSsReserved array field. Recall that in Win32, the first index, [0], is where the Thread Data was stored. We've now learnt that [1] is where the handle is stored.

正如你所看到的,这是一个简单的实现,但它告诉我们两件事。首先,一个线程只能有一个调试对象与之关联,其次,该对象的句柄存储在当前线程环境块(TEB)的DbgSsReserved数组域中。记得在Win32中,该数组的第0号索引,即DbgSsReserved[0]存储了线程数据结构(DBGSS_THREAD_DATA),我们现在已经知道该数组的第1号索引,即DbgSsReserved[1]存储了该对象的句柄。

Now let's see how attaching and detaching are done:

现在让我们来看看,附加和分离是怎样完成的:

NTSTATUS
NTAPI
DbgUiDebugActiveProcess(IN HANDLE Process)
{
    NTSTATUS Status;

    /* Tell the kernel to start debugging */
    Status = NtDebugActiveProcess(Process, NtCurrentTeb()->DbgSsReserved[1]);
    if (NT_SUCCESS(Status))
    {
        /* Now break-in the process */
        Status = DbgUiIssueRemoteBreakin(Process);
        if (!NT_SUCCESS(Status))
        {
            /* We couldn't break-in, cancel debugging */
            DbgUiStopDebugging(Process);
        }
    }

    /* Return status */
    return Status;
}

NTSTATUS
NTAPI
DbgUiStopDebugging(IN HANDLE Process)
{
    /* Call the kernel to remove the debug object */
    return NtRemoveProcessDebug(Process, NtCurrentTeb()->DbgSsReserved[1]);
}

Again, these are very simple implementations. We can learn, however, that the kernel is not responsible for actually breaking inside the remote process, but that this is done by the native layer. This DbgUiIssueRemoteBreakin API is also used by Win32 when calling DebugBreakProcess, so let's look at it:

再次,这是非常简单的实现。我们能够知道,然而,这个内核不负责实际中断远程进程,而是由本地层(native layer)来完成的。在Win32中,当调用DebugBreakProcess函数时,DbgUiIssueRemoteBreakin API也被使用,让我们来看看它:

NTSTATUS
NTAPI
DbgUiIssueRemoteBreakin(IN HANDLE Process)
{
    HANDLE hThread;
    CLIENT_ID ClientId;
    NTSTATUS Status;

    /* Create the thread that will do the breakin */
    Status = RtlCreateUserThread(Process,
                                 NULL,
                                 FALSE,
                                 0,
                                 0,
                                 PAGE_SIZE,
                                 (PVOID)DbgUiRemoteBreakin,
                                 NULL,
                                 &hThread,
                                 &ClientId);

    /* Close the handle on success */
    if(NT_SUCCESS(Status)) NtClose(hThread);

    /* Return status */
    return Status;
}

All it does is create a remote thread inside the process, and then return to the caller. Does that remote thread do anything magic? Let's see:

它的作用就是在目标进程中创建一个远程线程,然后返回至它的调用者。那么,远程线程做了什么魔法?让我们来看看:

VOID
NTAPI
DbgUiRemoteBreakin(VOID)
{
    /* Make sure a debugger is enabled; if so, breakpoint */
    if (NtCurrentPeb()->BeingDebugged) DbgBreakPoint();

    /* Exit the thread */
    RtlExitUserThread(STATUS_SUCCESS);
}

Nothing special at all; the thread makes sure that the process is really being debugged, and then issues a breakpoint. And, because this API is exported, you can call it locally from your own process to issue a debug break (but note that you will kill your own thread). In our look at the Win32 Debugging implementation, we've noticed that the actual debug handle is never used, and that calls always go through DbgUi. Then the NtSetInformationDebugObject system call was called, a special DbgUi API was called before, to actually get the debug object associated with the thread. This API also has a counterpart, so let's see both in action:

根本没有什么特别之处,这个线程确保目标进程正在被调试,然后产生一个断点。而且,因为DbgBreakPoint这个API已经导出,你可以在你自己的进程中本地调用它,用来产生一个调试断点(注意,这样将结束你自己的线程)。我们从Win32调试实现中看到,注意到,实际的调试句柄从来没有被使用过,并且这种调用总是通过DbgUi函数。之后NtSetInformationDebugObject系统调用被调用,在一个特殊的DbgUi API被调用之前,用来实际获取线程相关联的调试对象。这也有一个对应的API,让我们来看看这两个API的实现:

HANDLE
NTAPI
DbgUiGetThreadDebugObject(VOID)
{
    /* Just return the handle from the TEB */
    return NtCurrentTeb()->DbgSsReserved[1];
}

VOID
NTAPI
DbgUiSetThreadDebugObject(HANDLE DebugObject)
{
    /* Just set the handle in the TEB */
    NtCurrentTeb()->DbgSsReserved[1] = DebugObject;
}

For those familiar with object-oriented programming, this will seem similar to the concept of accessor and mutator methods. Even though Win32 has perfect access to this handle and could simply read it on its own, the NT developers decided to make DbgUi much like a class, and make sure access to the handle goes through these public methods. This design allows the debug handle to be stored anywhere else if necessary, and only these two APIs will require changes, instead of multiple DLLs in Win32.

对于那些熟悉面向对象编程的,这似乎类似于访问器和突变方法的概念(get和set方法)。即使Win32针对这个句柄具有完善的访问,并且它自己可以简单地读取句柄,NT开发者还是决定把DbgUi设计得像一个类,并确保通过这些公有方法来访问这个句柄。这种设计允许调试句柄根据需要可以存储在任何地方,且只有这两个API将需要改变,而不是Win32里多个DLL。

Now for a visit of the wait/continue functions, which under Win32 were simply wrappers:

现在来看看等待和继续(wait/continue)函数,在上层Win32它们是简单包装者:

NTSTATUS
NTAPI
DbgUiContinue(IN PCLIENT_ID ClientId,
              IN NTSTATUS ContinueStatus)
{
    /* Tell the kernel object to continue */
    return ZwDebugContinue(NtCurrentTeb()->DbgSsReserved[1],
                           ClientId,
                           ContinueStatus);
}

NTSTATUS
NTAPI
DbgUiWaitStateChange(OUT PDBGUI_WAIT_STATE_CHANGE DbgUiWaitStateCange,
                     IN PLARGE_INTEGER TimeOut OPTIONAL)
{
    /* Tell the kernel to wait */
    return NtWaitForDebugEvent(NtCurrentTeb()->DbgSsReserved[1],
                               TRUE,
                               TimeOut,
                               DbgUiWaitStateCange);
}

Not surprisingly, these functions are also wrappers in DbgUi. However, this is where things start to get interesting, since if you'll recall, DbgUi uses a completely different structure for debug events, called DBGUI_WAIT_STATE_CHANGE. There is one API that we have left to look at, which does the conversion, so first, let's look at the documentation for this structure:

毫不奇怪,在DbgUi层这些函数同样也是包装者。然而,这就是事情开始变得有趣了,如果你回忆一下,DbgUi为调试事件使用一个完全不同的结构,称为DBGUI_WAIT_STATE_CHANGE。这里有个API我们留到之后再看,它完成转换功能,首先,我们来看看这个结构的文档说明:

//
// User-Mode Debug State Change Structure
//
typedef struct _DBGUI_WAIT_STATE_CHANGE
{
    DBG_STATE NewState;
    CLIENT_ID AppClientId;
    union
    {
        struct
        {
            HANDLE HandleToThread;
            DBGKM_CREATE_THREAD NewThread;
        } CreateThread;
        struct
        {
            HANDLE HandleToProcess;
            HANDLE HandleToThread;
            DBGKM_CREATE_PROCESS NewProcess;
        } CreateProcessInfo;
        DBGKM_EXIT_THREAD ExitThread;
        DBGKM_EXIT_PROCESS ExitProcess;
        DBGKM_EXCEPTION Exception;
        DBGKM_LOAD_DLL LoadDll;
        DBGKM_UNLOAD_DLL UnloadDll;
    } StateInfo;
} DBGUI_WAIT_STATE_CHANGE, *PDBGUI_WAIT_STATE_CHANGE;

The fields should be pretty self-explanatory, so let's look at the DBG_STATE enumeration:

这个结构各个域应该是很好的自我解释,所以我们来看看DBG_STATE枚举:

//
// Debug States
//
typedef enum _DBG_STATE
{
    DbgIdle,
    DbgReplyPending,
    DbgCreateThreadStateChange,
    DbgCreateProcessStateChange,
    DbgExitThreadStateChange,
    DbgExitProcessStateChange,
    DbgExceptionStateChange,
    DbgBreakpointStateChange,
    DbgSingleStepStateChange,
    DbgLoadDllStateChange,
    DbgUnloadDllStateChange
} DBG_STATE, *PDBG_STATE;

If you take a look at the Win32 DEBUG_EVENT structure and associated debug event types, you'll notice some differences which might be useful to you. For starters, Exceptions, Breakpoints and Single Step exceptions are handled differently. In the Win32 world, only two distinctions are made: RIP_EVENT for exceptions, and EXCEPTION_DEBUG_EVENT for a debug event. Although code can later figure out if this was a breakpoint or single step, this information comes directly in the native structure. You will also notice that OUTPUT_DEBUG_STRING event is missing. Here, it's DbgUi that's at a disadvantage, since the information is sent as an Exception, and post-processing is required (which we'll take a look at soon). There are also two more states that Win32 does not support, which is the Idle state and the Reply Pending state. These don't offer much information from the point of view of a debugger, so they are ignored.

如果你对比一下Win32 DEBUG_EVENT结构和相关的调试事件类型,你将会注意到一些差异,这些差异可能对你有用(我把DEBUG_EVENT结构定义加到下面)。首先,异常,断点和单步异常的处理方式不同。在Win32世界里,只有两个区别:RIP_EVENT异常和EXCEPTION_DEBUG_EVENT调试事件。虽然,代码在后面能够搞清楚这是否是一个断点或者单步,这个信息直接来自本地结构。你同时也会注意到OUTPUT_DEBUG_STRING事件消失了。在这里,这是DbgUi处于劣势,因为这信息是作为一个异常发送的,同时后处理是必需的(我们很快就会看到)。还有两个状态是Win32不支持的,那是空闲(Idle)状态和回复未决(Reply Pending)状态。从一个调试器的角度来看,这些没有提供太多的信息,所以它们被忽略了。

typedef struct _DEBUG_EVENT {
        DWORD dwDebugEventCode;
        DWORD dwProcessId;  DWORD dwThreadId;
        union {
                EXCEPTION_DEBUG_INFO Exception;
                CREATE_THREAD_DEBUG_INFO CreateThread;
                CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
                EXIT_THREAD_DEBUG_INFO ExitThread;
                EXIT_PROCESS_DEBUG_INFO ExitProcess;
                LOAD_DLL_DEBUG_INFO LoadDll;
                UNLOAD_DLL_DEBUG_INFO UnloadDll;
                OUTPUT_DEBUG_STRING_INFO DebugString;
                RIP_INFO RipInfo;
        }u;
} DEBUG_EVENT,  *LPDEBUG_EVENT;

Now let's take a look at the actual structures seen in the unions:

现在来看看在共同体中的那些实际结构体定义:

//
// Debug Message Structures
//
typedef struct _DBGKM_EXCEPTION
{
    EXCEPTION_RECORD ExceptionRecord;
    ULONG FirstChance;
} DBGKM_EXCEPTION, *PDBGKM_EXCEPTION;

typedef struct _DBGKM_CREATE_THREAD
{
    ULONG SubSystemKey;
    PVOID StartAddress;
} DBGKM_CREATE_THREAD, *PDBGKM_CREATE_THREAD;

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;

typedef struct _DBGKM_EXIT_THREAD
{
    NTSTATUS ExitStatus;
} DBGKM_EXIT_THREAD, *PDBGKM_EXIT_THREAD;

typedef struct _DBGKM_EXIT_PROCESS
{
    NTSTATUS ExitStatus;
} DBGKM_EXIT_PROCESS, *PDBGKM_EXIT_PROCESS;

typedef struct _DBGKM_LOAD_DLL
{
    HANDLE FileHandle;
    PVOID BaseOfDll;
    ULONG DebugInfoFileOffset;
    ULONG DebugInfoSize;
    PVOID NamePointer;
} DBGKM_LOAD_DLL, *PDBGKM_LOAD_DLL;

typedef struct _DBGKM_UNLOAD_DLL
{
    PVOID BaseAddress;
} DBGKM_UNLOAD_DLL, *PDBGKM_UNLOAD_DLL;

If you're familiar with the DEBUG_EVENT structure, you should notice some subtle differences. First of all, no indication of the process name, which explains why MSDN documents this field being optional and not used by Win32. You will also notice the lack of a pointer to the TEB in the thread structure. Finally, unlike new processes, Win32 does display the name of any new DLL loaded, but this also seems to be missing in the Load DLL structure; we'll see how this and other changes are dealt with soon. As far as extra information goes however, we have the "SubsystemKey" field. Because NT was designed to support multiple subsystems, this field is critical to identifying from which subsystem the new thread or process was created from. Windows 2003 SP1 adds support for debugging POSIX applications, and while I haven't looked at the POSIX debug APIs, I'm convinced they're built around the DbgUi implementation, and that this field is used differently by the POSIX library (much like Win32 ignores it).

如果你熟悉DEBUG_EVENT结构,你应该注意到一些细微的差异。首先,没有迹象表明该进程的名称,这解释了为什么MSDN文档说明这个字段是可选的,同时不被Win32所使用。你同时也应注意到在线程结构中缺少指向TEB结构的指针。最后,不像新进程,Win32显示任何新加载的DLL的名称,但这似乎也不存在于Load DLL结构中;我们将很快看到这个和其它一些变化是怎样被处理的。至于额外的信息,我们有“SubsystemKey”这个字段。因为NT被设计成支持多个子系统,这个字段是关键,用于标明新线程或者新进程是由那个子系统所创建的。Windows 2003 SP1添加了对POSIX应用程序的调试支持,而我还没看到POSIX调试API,我确信它们就是建立在DbgUi的实现上,这个字段被POSIX库不同地使用(很像Win32忽略它)。

Now that we've seen the differences, the final API to look at is DbgUiConvertStateChangeStructure, which is responsible for doing these modifications and fixups:

现在我们已经看到了这些差异,最后要看的一个API是DbgUiConvertStateChangeStructure,它是负责做这些修改和修正:

NTSTATUS
NTAPI
DbgUiConvertStateChangeStructure(IN PDBGUI_WAIT_STATE_CHANGE WaitStateChange,
                                 OUT PVOID Win32DebugEvent)
{
    NTSTATUS Status;
    OBJECT_ATTRIBUTES ObjectAttributes;
    THREAD_BASIC_INFORMATION ThreadBasicInfo;
    LPDEBUG_EVENT DebugEvent = Win32DebugEvent;
    HANDLE ThreadHandle;

    /* Write common data */
    DebugEvent->dwProcessId = (DWORD)WaitStateChange->
                                     AppClientId.UniqueProcess;
    DebugEvent->dwThreadId = (DWORD)WaitStateChange->AppClientId.UniqueThread;

    /* Check what kind of even this is */
    switch (WaitStateChange->NewState)
    {
        /* New thread */
        case DbgCreateThreadStateChange:

            /* Setup Win32 code */
            DebugEvent->dwDebugEventCode = CREATE_THREAD_DEBUG_EVENT;

            /* Copy data over */
            DebugEvent->u.CreateThread.hThread =
                WaitStateChange->StateInfo.CreateThread.HandleToThread;
            DebugEvent->u.CreateThread.lpStartAddress =
                WaitStateChange->StateInfo.CreateThread.NewThread.StartAddress;

            /* Query the TEB */
            Status = NtQueryInformationThread(WaitStateChange->StateInfo.
                                              CreateThread.HandleToThread,
                                              ThreadBasicInformation,
                                              &ThreadBasicInfo,
                                              sizeof(ThreadBasicInfo),
                                              NULL);
            if (!NT_SUCCESS(Status))
            {
                /* Failed to get PEB address */
                DebugEvent->u.CreateThread.lpThreadLocalBase = NULL;
            }
            else
            {
                /* Write PEB Address */
                DebugEvent->u.CreateThread.lpThreadLocalBase =
                    ThreadBasicInfo.TebBaseAddress;
            }
            break;

        /* New process */
        case DbgCreateProcessStateChange:

            /* Write Win32 debug code */
            DebugEvent->dwDebugEventCode = CREATE_PROCESS_DEBUG_EVENT;

            /* Copy data over */
            DebugEvent->u.CreateProcessInfo.hProcess =
                WaitStateChange->StateInfo.CreateProcessInfo.HandleToProcess;
            DebugEvent->u.CreateProcessInfo.hThread =
                WaitStateChange->StateInfo.CreateProcessInfo.HandleToThread;
            DebugEvent->u.CreateProcessInfo.hFile =
                WaitStateChange->StateInfo.CreateProcessInfo.NewProcess.
                FileHandle;
            DebugEvent->u.CreateProcessInfo.lpBaseOfImage =
                WaitStateChange->StateInfo.CreateProcessInfo.NewProcess.
                BaseOfImage;
            DebugEvent->u.CreateProcessInfo.dwDebugInfoFileOffset =
                WaitStateChange->StateInfo.CreateProcessInfo.NewProcess.
                DebugInfoFileOffset;
            DebugEvent->u.CreateProcessInfo.nDebugInfoSize =
                WaitStateChange->StateInfo.CreateProcessInfo.NewProcess.
                DebugInfoSize;
            DebugEvent->u.CreateProcessInfo.lpStartAddress =
                WaitStateChange->StateInfo.CreateProcessInfo.NewProcess.
                InitialThread.StartAddress;

            /* Query TEB address */
            Status = NtQueryInformationThread(WaitStateChange->StateInfo.
                                              CreateProcessInfo.HandleToThread,
                                              ThreadBasicInformation,
                                              &ThreadBasicInfo,
                                              sizeof(ThreadBasicInfo),
                                              NULL);
            if (!NT_SUCCESS(Status))
            {
                /* Failed to get PEB address */
                DebugEvent->u.CreateThread.lpThreadLocalBase = NULL;
            }
            else
            {
                /* Write PEB Address */
                DebugEvent->u.CreateThread.lpThreadLocalBase =
                    ThreadBasicInfo.TebBaseAddress;
            }

            /* Clear image name */
            DebugEvent->u.CreateProcessInfo.lpImageName = NULL;
            DebugEvent->u.CreateProcessInfo.fUnicode = TRUE;
            break;

        /* Thread exited */
        case DbgExitThreadStateChange:

            /* Write the Win32 debug code and the exit status */
            DebugEvent->dwDebugEventCode = EXIT_THREAD_DEBUG_EVENT;
            DebugEvent->u.ExitThread.dwExitCode =
                WaitStateChange->StateInfo.ExitThread.ExitStatus;
            break;

        /* Process exited */
        case DbgExitProcessStateChange:

            /* Write the Win32 debug code and the exit status */
            DebugEvent->dwDebugEventCode = EXIT_PROCESS_DEBUG_EVENT;
            DebugEvent->u.ExitProcess.dwExitCode =
                WaitStateChange->StateInfo.ExitProcess.ExitStatus;
            break;

        /* Any sort of exception */
        case DbgExceptionStateChange:
        case DbgBreakpointStateChange:
        case DbgSingleStepStateChange:

            /* Check if this was a debug print */
            if (WaitStateChange->StateInfo.Exception.ExceptionRecord.
                ExceptionCode == DBG_PRINTEXCEPTION_C)
            {
                /* Set the Win32 code */
                DebugEvent->dwDebugEventCode = OUTPUT_DEBUG_STRING_EVENT;

                /* Copy debug string information */
                DebugEvent->u.DebugString.lpDebugStringData =
                    (PVOID)WaitStateChange->
                           StateInfo.Exception.ExceptionRecord.
                           ExceptionInformation[1];
                DebugEvent->u.DebugString.nDebugStringLength =
                    WaitStateChange->StateInfo.Exception.ExceptionRecord.
                    ExceptionInformation[0];
                DebugEvent->u.DebugString.fUnicode = FALSE;
            }
            else if (WaitStateChange->StateInfo.Exception.ExceptionRecord.
                     ExceptionCode == DBG_RIPEXCEPTION)
            {
                /* Set the Win32 code */
                DebugEvent->dwDebugEventCode = RIP_EVENT;

                /* Set exception information */
                DebugEvent->u.RipInfo.dwType =
                    WaitStateChange->StateInfo.Exception.ExceptionRecord.
                    ExceptionInformation[1];
                DebugEvent->u.RipInfo.dwError =
                    WaitStateChange->StateInfo.Exception.ExceptionRecord.
                    ExceptionInformation[0];
            }
            else
            {
                /* Otherwise, this is a debug event, copy info over */
                DebugEvent->dwDebugEventCode = EXCEPTION_DEBUG_EVENT;
                DebugEvent->u.Exception.ExceptionRecord =
                    WaitStateChange->StateInfo.Exception.ExceptionRecord;
                DebugEvent->u.Exception.dwFirstChance =
                    WaitStateChange->StateInfo.Exception.FirstChance;
            }
            break;

        /* DLL Load */
        case DbgLoadDllStateChange :

            /* Set the Win32 debug code */
            DebugEvent->dwDebugEventCode = LOAD_DLL_DEBUG_EVENT;

            /* Copy the rest of the data */
            DebugEvent->u.LoadDll.lpBaseOfDll =
                WaitStateChange->StateInfo.LoadDll.BaseOfDll;
            DebugEvent->u.LoadDll.hFile =
                WaitStateChange->StateInfo.LoadDll.FileHandle;
            DebugEvent->u.LoadDll.dwDebugInfoFileOffset =
                WaitStateChange->StateInfo.LoadDll.DebugInfoFileOffset;
            DebugEvent->u.LoadDll.nDebugInfoSize =
                WaitStateChange->StateInfo.LoadDll.DebugInfoSize;

            /* Open the thread */
            InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL);
            Status = NtOpenThread(&ThreadHandle,
                                 THREAD_QUERY_INFORMATION,
                                 &ObjectAttributes,
                                 &WaitStateChange->AppClientId);
            if (NT_SUCCESS(Status))
            {
                /* Query thread information */
                Status = NtQueryInformationThread(ThreadHandle,
                                                  ThreadBasicInformation,
                                                  &ThreadBasicInfo,
                                                  sizeof(ThreadBasicInfo),
                                                  NULL);
                NtClose(ThreadHandle);
            }

            /* Check if we got thread information */
            if (NT_SUCCESS(Status))
            {
                /* Save the image name from the TIB */
                DebugEvent->u.LoadDll.lpImageName =
                    &((PTEB)ThreadBasicInfo.TebBaseAddress)->
                    Tib.ArbitraryUserPointer;
            }
            else
            {
                /* Otherwise, no name */
                DebugEvent->u.LoadDll.lpImageName = NULL;
            }

            /* It's Unicode */
            DebugEvent->u.LoadDll.fUnicode = TRUE;
            break;

        /* DLL Unload */
        case DbgUnloadDllStateChange:

            /* Set Win32 code and DLL Base */
            DebugEvent->dwDebugEventCode = UNLOAD_DLL_DEBUG_EVENT;
            DebugEvent->u.UnloadDll.lpBaseOfDll =
                WaitStateChange->StateInfo.UnloadDll.BaseAddress;
            break;

        /* Anything else, fail */
        default: return STATUS_UNSUCCESSFUL;
    }

    /* Return success */
    return STATUS_SUCCESS;
}

Let's take a look at the interesting fixups. First of all, the lack of a TEB pointer is easily fixed by calling NtQueryInformationThread with the ThreadBasicInformation type, which returns, among other things, a pointer to the TEB, which is then saved in the Win32 structure. As for Debug Strings, the API analyzes the exception code and looks for DBG_PRINTEXCEPTION_C, which has a specific exception record that is parsed and converted into a debug string output.

我们来看看有趣的修正。首先,很容易地修正了缺少指向TEB结构的指针,通过调用NtQueryInformationThread函数并传递ThreadBasicInformation类型参数,在它返回的内容之间,有一个指向TEB的指针,然后它被保存在Win32结构中。至于调试字符串,这个API分析了异常代码并寻找DBG_PRINTEXCEPTION_C类型,如果匹配,其中有一个特定的异常记录,被解析并转换为一个调试字符串输出。

So far so good, but perhaps the nastiest hack is present in the code for DLL loading. Because a loaded DLL doesn't have a structure like EPROCESS or ETHREAD in kernel memory, but in ntdll's private Ldr structures, the only thing that identifies it is a Section Object in memory for its memory mapped file. When the kernel gets a request to create a section for an executable memory mapped file, it saves the name of the file in a field inside the TEB (or TIB, rather) called ArbitraryUserPointer.

到目前为止还挺顺利的,但也许在代码中目前最难理解的就是DLL加载了。因为一个加载的DLL在内核内存中没有一个像EPROCESS或ETHREAD的结构,但是在ntdll私有的Ldr结构里,唯一确定在内存中内存映射文件的就是一个区段对象(Section Object)。当内核接收到一个请求,为一个可执行的内存映射文件创建一个区段,在TEB(或者TIB)结构中,称为ArbitraryUserPointer的字段,保存这个文件的名称。

This function then knows that a string is located there, and sets it as the pointer for the debug event's lpImageName member. This hack has been in NT every since the first builds, and as far as I know, it's still there in Vista. Could it be that hard to solve?

这个函数已经知道字符串位于那里,并将其设置成指针,赋值给调试事件的lpImageName成员。自从NT的第一个版本,这个方法就已经存在,据我所知,它仍然存在于Vista中。可能是难以解决的问题?

Once again, we come to an end in our discussion, since there isn't much left in ntdll that deals with the Debug Object. Here's an overview of what was discussed in this part of the series:
•DbgUi provides a level of separation between the kernel and Win32 or other subsystems. It's written as a fully independent class, even having accessor and mutator methods instead of exposing its handles.
•The handle to a thread's Debug Object is stored in the second field of the DbgSsReserved array in the TEB.
•DbgUi allows a thread to have a single DebugObject, but using the native system calls allows you to do as many as you want.
•Most DbgUi APIs are simple wrappers around the NtXxxDebugObject system calls, and use the TEB handle to communicate.
•DbgUi is responsible for breaking into the attached process, not the kernel.
•DbgUi uses its own structure for debug events, which the kernel understands. In some ways, this structure provides more information about some events (such as the subsystem and whether this was a single step or a breakpoint exception), but in others, some information is missing (such as a pointer to the thread's TEB or a separate debug string structure).
•The TIB (located inside the TEB)'s ArbitraryPointer member contains the name of the loaded DLL during a Debug Event.

再次,要结束我们的讨论了,因为没有遗留太多在ntdll中关于调试对象(Debug Object)的处理。接下来是对这篇文章所讨论的做个总结:
1、DbgUi在内核和Win32或者其他子系统之间提供一层分离。它被设计成为一个完全独立的类,有访问器和突变方法来代替暴露它的具体处理。
2、一个线程的调试对象句柄存储在TEB结构中DbgSsReserved数组的第二个字段里。
3、DbgUi允许一个线程拥有一个单一的调试对象,但是使用本地系统调用允许你做很多你想要的。
4、多数DbgUi API都只是简单地对NtXxxDebugObject系统调用进行包装,且使用TEB来处理通信。
5、DbgUi负责中断被附加调试的进程,而不是内核负责中断。
6、DbgUi使用它自己的结构,针对内核能够理解的调试事件。在某些方面,这种结构提供了关于一些事件的更多的信息(比如子系统,这是否是一个单步或者断点异常),但在其他情况下,缺少某些信息(比如一个指向线程TEB结构的指针或者一个单独的调试字符串结构)。
7、在一个调试事件期间,TIB(位于TEB中)的ArbitraryPointer成员包含了加载的DLL的名称。

系列一Windows用户模式调试内幕的word附件:
Windows用户模式调试内幕.rar

系列二Windows本地(Native)调试内幕的word附件:
Windows本地(Native)调试内幕.rar


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 3
支持
分享
最新回复 (4)
雪    币: 7048
活跃值: (3527)
能力值: ( LV12,RANK:340 )
在线值:
发帖
回帖
粉丝
2
沙发~
2014-6-8 12:18
0
雪    币: 413
活跃值: (637)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
3
谢谢,下载来学习。
2014-6-9 08:13
0
雪    币: 719
活跃值: (777)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
4
学习学习
2014-12-28 11:07
0
雪    币: 188
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
多谢楼主分享,学习学习。
2014-12-28 11:11
0
游客
登录 | 注册 方可回帖
返回
//