Windows native debugging internal components

The detailed analysis of the Windows debugging native interface. I hope the reader has some basic knowledge of C and common NT kernel architecture and semantics. In addition, this does not explain what or how to write debugging debugger. It can refer to persons or write security expert curious as experienced debugger.

The machine debugging

Now is the time to see what aspects of the machine, and how to package layer ntdll.dll in communication with the kernel. Layer DbgUi advantage is that it allows better separation Win32 and NT kernel, the kernel has been NT NT part of the design. NTDLL and NTOSKRNL are building together, so they have a sophisticated understanding of each other is normal. They share the same structure, need to have the same system ID, etc. calls. In a perfect world, NT kernel should be ignorant of Win32.

In addition, it can help anyone want to write debugging functions or write a full-featured debugger in native mode native application. If not DbgUi, you must manually call Nt * DebugObject api, and a lot of pre / post-processing in some cases. DbgUi all these work reduced to a simple call, and provide a clean interface to complete. If a change occurs within the kernel, DbgUi may remain the same, only modifies its internal code.

We create and debug object from the beginning it is responsible for the current exploration and functions associated with the process. And Win32 different, to create a clear distinction between the debugger and the actual objects attach to the process.

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 to implement, but it shows us two things. First of all, there is only one thread debugging object associated Secondly, this object DbgSsReserved handle storage array in the field of TEB. Recall that in Win32, the first index [0] is stored in the position data of the thread. We now know that [1] is a place to store the handle.
Now let's look at how connected and disconnected:

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 implementation. However, we can understand, the kernel is not responsible for the real interruption in the remote process, but done by the native layer. The DbgUiIssueRemoteBreakin API also be used when calling Win32 DebugBreakProcess, so let's look at it:

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 in the process, and then returned to the caller. The remote thread has nothing magical about it? let us see:

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

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

It is not special; thread ensure real progress is being debugged, then issue a breakpoint. Moreover, because the API is exported, so you can call it from your local process to issue a debug break (but please note that you will kill yourself thread). When we look Win32 debugging implement, we note that the actual debug handler never used, but calls are always carried out by DbgUi. Then call NtSetInformationDebugObject system call, before calling a special DbgUi API, in order to obtain the actual thread debugging associated with the object. The API also has a corresponding API, so let us look at the role of these two 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 seems like the notion of access control and variation methods. Although Win32 have perfect access to the handle, and can be simply read it yourself, NT developers decided to let DbgUi more like a class, and ensure public access to the handle by using these methods. This design allows, if necessary, the handle is stored in the debugging anywhere else, and only two api need to change, rather than multiple dll Win32 in.
Now access wait / continue function, it is only under Win32 wrapper:

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 DbgUi the wrapper. However, this is where things start to get interesting, because if you remember, DbgUi completely different structure of the debug event, called DbgUi-WAIT-STATE-CHANGE. We also have a look at API, which is responsible for the conversion, so first of all, let us look at the structure of this document:

//
// 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;

These fields should be very simple, so let's see DBG_STATE enumeration:

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

If you look at the debug event structure and event type associated with debugging Win32, you'll notice some of the differences that might be useful to you. For beginners, the different anomalies, breakpoints and single-step mode handling exceptions. In the Win32 world, there are only two differences: for RIP_ abnormal events and events for EXCEPTION_DEBUG_ debug events. Although the code can be determined later, this is a break or a step, but the information directly from the structure of the machine. You will also notice the lack of OUTPUT_DEBUG_STRING event. Here, DbgUi at a disadvantage, because the information is sent as an exception, and the need for post-treatment (we will soon study it). Win32 does not support the addition of two states, namely idle and answer pending. From the debugger's point of view, they do not provide too much information, so it is ignored.

Now let's look at the actual structure:

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

typedef struct _DBGKM_CREATE_THREAD
{
    ULONG SubSystemKey;
    PVOID Home Address;
} 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 Exit Status;
} DBGKM_EXIT_THREAD, *PDBGKM_EXIT_THREAD;

typedef struct _DBGKM_EXIT_PROCESS
{
    NTSTATUS Exit Status;
} 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 are familiar DEBUG_EVENT structure, you should notice some subtle differences. First of all, there is no indication of the name of the process, which explains why this MSDN record field is an optional field, and Win32 do not use it. You will also notice the lack of a thread structure points to TEB pointer. Finally, the new process is different, Win32 does show the name of any new DLL is loaded, but the DLL is loaded structure seems to lack this name; we will soon see how to deal with this and other changes. However, for additional information, we have "SubsystemKey" field. Since NT is designed, so the field is critical subsystems which create new threads or processes to support from a number of subsystems for identification. Windows2003SP1 adds support for debugging POSIX applications, although I have not studied the debugging POSIX API, but I am sure they are built around DbgUi achieve, and POSIX libraries use this field to different (much like Win32 ignore it).

Now that we have seen these differences, the final look of the API is DbgUiConvertStateChangeStructure, which is responsible for the implementation of these changes and amendments:

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 look at these interesting decor. First, by using ThreadBasicInformation type of call NtQueryInformationThread easy to fix deficiencies TEB pointer, which returns a pointer to TEB type of pointer, and then save it in Win32 structure. As debug strings, exception code and find the API analysis DBG_PRINTEXCEPTION_C, it has a specific exception record, which is parsed and converted to the debug output string.

So far pretty good, but the code is loaded DLL that may occur in the worst hacker attacks. Because the DLL is not loaded or EPROCESS ETHREAD such structures in kernel memory, but in private Ldr structure ntdll, the only section that identifies its objects in memory memory-mapped files. When the kernel receives a request to create an executable section of memory-mapped files, it will save the file name in TEB (or TIB, more precisely, TIB) in a field, the field is called arbiryuserpointer.

This function then know that a string located there, and the pointer lpImageName members set to debug events. Since the first building, the hacker has been in the NT, as far as I know, it still exists in Vista. It would be so difficult to solve?

Once again, our discussion is over, in part because ntdll process to be debugged is running out. The following is an overview of this series discussed in this section content:

  • DbgUi between Win32 or other kernel subsystems and provides a degree of separation. It is a completely separate class as written, and even access control and variation method, rather than open its handle.
  • Handle storage thread debugging object in the array of TEB in DbgSsReserved the second field.
  • DbgUi allow a thread has a DebugObject, but the use of this system call allows you to do anything you want to do.
  • Most dbguiapi is a simple wrapper NtXxxDebugObject system calls, and use the handle to communicate TEB.
  • DbgUi responsible for entering additional process, not the kernel.
  • DbgUi use their own debug event structure, the kernel can understand this structure. In some respects, this structure provides more information about certain events (for example, subsystem, and this is one-step or break exception), but in other respects, the lack of certain information (such as a pointer pointing to a thread TEB alone or debug string structure).
  • TIB (arbitration pointer member located in TEB) contains the name of the DLL is loaded during debugging event.

Guess you like

Origin www.cnblogs.com/yilang/p/11854251.html