Windows user-mode debugging internal components

Brief introduction

Allow user-mode debugging internal mechanisms are rarely fully explained. Worse, these mechanisms in Windows XP has changed radically, when a lot of support to be re-written, but also through the ntdll most of the routine as part of the local API, so that more subsystems become It was portable. 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.

Win32 Debugging

NT Win32 subsystem allows to start the process of debugging from the first version, the subsequent version adds more related symbols and other properties of PE and debugging information to help libraries. However, the API for external users, in addition to stopping the process of debugging process without killing the added functionality in Windows XP, relatively few other changes. NT This version also includes a thorough examination of several underlying implementation, we will discuss in detail. However, a major side effect of these changes are no longer in use LPC (and csrss.exe), which allows the debugger to the binary file (previously, this debug binaries is not possible, because it is responsible to notify the user of processing cores).
Basic win32api process debugging process is very simple: DebugActiveProcess, to attach, WaitForDebugEvent, wait for the debug event occurs, so that the debugger can handle them, ContinueDebugEvent, resume thread. WindowsXP API release adds three more useful: Debug active process, it allows you to stop debug a process (Debug), Debug GestPosikIon exit, which allows you to run a process to continue even after it was split and Debug treatment, it It allows you to perform remote DebugBreak, without having to manually create a remote thread. In Windows XP Service Pack 1, we have added a API, CheckRemoteDebuggerPresent. And IsDebuggerPresent similar, this API allows you to check the connection of the debugger in another process, without the need for remote reading PEB.
Since the NT architecture, which api in the latest version of Windows (2003 will be used as an example, but the information also applies to XP) itself can not do much work. Instead, they typically work is required to perform the function Native calls, then the process output, so it is possible to use caller Win32 compatible with the Win32 API Win9x and defining an original format. Let's look at these very simple implementation:

BOOL
WINAPI
DebugActiveProcess(IN DWORD dwProcessId)
{
    NTSTATUS Status;
    HANDLE Handle;

    /* Connect to the debugger */
    Status = DbgUiConnectToDbg();
    if (!NT_SUCCESS(Status))
    {
        SetLastErrorByStatus(Status);
        return FALSE;
    }

    /* Get the process handle */
    Handle = ProcessIdToHandle(dwProcessId);
    if (!Handle) return FALSE;

    /* Now debug the process */
    Status = DbgUiDebugActiveProcess(Handle);
    NtClose(Handle);

    /* Check if debugging worked */
    if (!NT_SUCCESS(Status))
    {
        /* Fail */
        SetLastErrorByStatus(Status);
        return FALSE;
    }

    /* Success */
    return TRUE;
}

As you can see, the only work done here is to create a user-mode debugger to the initial connection component, which is located by the API set DbgUi Native ntdll is completed, we will see later. Since DbgUi use the handle instead of PIDs, you must first use a simple helper function converts PID:

HANDLE
WINAPI
ProcessIdToHandle(IN DWORD dwProcessId)
{
    NTSTATUS Status;
    OBJECT_ATTRIBUTES ObjectAttributes;
    HANDLE Handle;
    CLIENT_ID ClientId;

    /* If we don't have a PID, look it up */
    if (dwProcessId == -1) dwProcessId = (DWORD)CsrGetProcessId();

    /* Open a handle to the process */
    ClientId.UniqueProcess = (HANDLE)dwProcessId;
    InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL);
    Status = NtOpenProcess(&Handle,
                           PROCESS_ALL_ACCESS,
                           &ObjectAttributes,
                           &ClientId);
    if (!NT_SUCCESS(Status))
    {
        /* Fail */
        SetLastErrorByStatus(Status);
        return 0;
    }

    /* Return the handle */
    return Handle;
}

If you are not familiar with the Native API, we can say that this code is equivalent to OpenProcess on PID, so you get a handle. Back DebugActiveProcess, last call is DbgUiDebugActiveProcess, it is again located in the Native API. After the connection is completed, we can turn off the handle obtained from PID before. Api other features are also similar. Let's look at two new XP:

BOOL
WINAPI
DebugBreakProcess(IN HANDLE Process)
{
    NTSTATUS Status;

    /* Send the breakin request */
    Status = DbgUiIssueRemoteBreakin(Process);
    if(!NT_SUCCESS(Status))
    {
        /* Failure */
        SetLastErrorByStatus(Status);
        return FALSE;
    }

    /* Success */
    return TRUE;
}

BOOL
WINAPI
DebugSetProcessKillOnExit(IN BOOL KillOnExit)
{
    HANDLE Handle;
    NTSTATUS Status;
    ULONG State;

    /* Get the debug object */
    Handle = DbgUiGetThreadDebugObject();
    if (!Handle)
    {
        /* Fail */
        SetLastErrorByStatus(STATUS_INVALID_HANDLE);
        return FALSE;
    }

    /* Now set the kill-on-exit state */
    State = KillOnExit;
    Status = NtSetInformationDebugObject(Handle,
                                       DebugObjectKillProcessOnExitInformation,
                                         &State,
                                         sizeof(State),
                                         NULL);
    if (!NT_SUCCESS(Status))
    {
        /* Fail */
        SetLastError(Status);
        return FALSE;
    }

    /* Success */
    return TRUE;
}

The first wish not need to explain, because it is a simple package, but let's look at the second. If you are familiar with Native API, you will immediately recognize the familiar NtSetInformationXxx type of API, which is used to set the various settings on NT different types of objects (such as files, processes, threads, etc.). Here Interestingly, XP is not familiar with the commissioning itself now also use the debug target to complete. However, this object will be discussed in detail later. Now, let's look at the function.
The first API DbgUiGetThreadDebugObject is another call to DbgUi, it will return a handle debugging object associated with the thread (Later we'll see where it is stored). Once we have a handle, we call a direct and Dbgk (rather than DbgUi) Native API to communicate, it will simply change a sign kernel debugger object structure. We will see that this flag will be read at the time of the separation kernel.
Similar to this function is CheckRemoteDebuggerPresent, it uses the same type of NT semantics to obtain information about the process:

BOOL
WINAPI
CheckRemoteDebuggerPresent(IN HANDLE hProcess,
                           OUT PBOOL pbDebuggerPresent)
{
    HANDLE DebugPort;
    NTSTATUS Status;

    /* Make sure we have an output and process*/
    if (!(pbDebuggerPresent) || !(hProcess))
    {
        /* Fail */
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    /* Check if the process has a debug object/port */
    Status = NtQueryInformationProcess(hProcess,
                                       ProcessDebugPort,
                                       (PVOID)&DebugPort,
                                       sizeof(HANDLE),
                                       NULL);
    if (NT_SUCCESS(Status))
    {
        /* Return the current state */
        *pbDebuggerPresent = (DebugPort) ? TRUE : FALSE;
        return TRUE;
    }

    /* Otherwise, fail */
    SetLastErrorByStatus(Status);
    return FALSE;
}

As you can see, it is being used by another NtQuery / SetInformationXxx API, but this is a process. While it is possible to detect debugging, but can simply check NtCurrTeNPEB () -> BeingDebugged, if there is another way to do this, this is achieved by querying the kernel. Since the kernel needs to communicate with user-mode debugging in the event, so it needs some way to achieve this. Before XP, which is usually done by the LPC port, by now a Debug object (However, it shares the same pointer).
Owing EPROCESS structure in kernel mode, we use DebugPort informational queries. If EPROCESS-> DebugPort set to a value, this API will return TRUE, which means that the process is being debugged. This technique can also be used for local process, but simply read PEB much faster. It may be noted that, although some applications like the Peb-> BeingDebugged set to FALSE to deceive anti-debugging programs, but there is no way to DebugPort set to NULL, because the kernel itself does not allow you to debug (and you do not have permission to access the kernel structure ).
With that in mind, let's look at how to implement the whole point of Win32 debugging infrastructure WaitForDebugEvent. This requires prior simpler ContinueDebugEvent / DebugActiveProcessStop displayed, because it introduces advanced internal structure Win32 for packaging DbgUi.

BOOL
WINAPI
WaitForDebugEvent(IN LPDEBUG_EVENT lpDebugEvent,
                  IN DWORD dwMilliseconds)
{
    LARGE_INTEGER WaitTime;
    PLARGE_INTEGER Timeout;
    DBGUI_WAIT_STATE_CHANGE WaitStateChange;
    NTSTATUS Status;

    /* Check if this is an infinite wait */
    if (dwMilliseconds == INFINITE)
    {
        /* Under NT, this means no timer argument */
        Timeout = NULL;
    }
    else
    {
        /* Otherwise, convert the time to NT Format */
        WaitTime.QuadPart = UInt32x32To64(-10000, dwMilliseconds);
        Timeout = &WaitTime;
    }

    /* Loop while we keep getting interrupted */
    do
    {
        /* Call the native API */
        Status = DbgUiWaitStateChange(&WaitStateChange, Timeout);
    } while ((Status == STATUS_ALERTED) || (Status == STATUS_USER_APC));

    /* Check if the wait failed */
    if (!(NT_SUCCESS(Status)) || (Status != DBG_UNABLE_TO_PROVIDE_HANDLE))
    {
        /* Set the error code and quit */
        SetLastErrorByStatus(Status);
        return FALSE;
    }

    /* Check if we timed out */
    if (Status == STATUS_TIMEOUT)
    {
        /* Fail with a timeout error */
        SetLastError(ERROR_SEM_TIMEOUT);
        return FALSE;
    }

    /* Convert the structure */
    Status = DbgUiConvertStateChangeStructure(&WaitStateChange, lpDebugEvent);
    if (!NT_SUCCESS(Status))
    {
        /* Set the error code and quit */
        SetLastErrorByStatus(Status);
        return FALSE;
    }

    /* Check what kind of event this was */
    switch (lpDebugEvent->dwDebugEventCode)
    {
        /* New thread was created */
        case CREATE_THREAD_DEBUG_EVENT:

            /* Setup the thread data */
            SaveThreadHandle(lpDebugEvent->dwProcessId,
                             lpDebugEvent->dwThreadId,
                             lpDebugEvent->u.CreateThread.hThread);
            break;

        /* New process was created */
        case CREATE_PROCESS_DEBUG_EVENT:

            /* Setup the process data */
            SaveProcessHandle(lpDebugEvent->dwProcessId,
                              lpDebugEvent->u.CreateProcessInfo.hProcess);

            /* Setup the thread data */
            SaveThreadHandle(lpDebugEvent->dwProcessId,
                             lpDebugEvent->dwThreadId,
                             lpDebugEvent->u.CreateThread.hThread);
            break;

        /* Process was exited */
        case EXIT_PROCESS_DEBUG_EVENT:

            /* Mark the thread data as such */
            MarkProcessHandle(lpDebugEvent->dwProcessId);
            break;

        /* Thread was exited */
        case EXIT_THREAD_DEBUG_EVENT:

            /* Mark the thread data */
            MarkThreadHandle(lpDebugEvent->dwThreadId);
            break;

        /* Nothing to do for anything else */
        default:
            break;
    }

    /* Return success */
    return TRUE;
}

First, let's take a look now DbgUi api. First, DbgUiWaitStateChange is WaitForDebugEvent Native version, which is responsible for performing the actual wait for debugging object and obtain the structure associated with this event. However, DbgUi use their own internal structure (Later we will show), so that the kernel can understand it, but Win32 using Win9x define a number of different structures. Therefore, it is necessary to convert it to Win32 representation, DbgUiConvertStateChange API to perform this conversion, returns backward compatibility and recording LPDEBUG_EVENT Win32 structures on MSDN.
Next is a switch, create, or delete its interest in a new process or thread. Use four API: SaveProcessHandle and SaveRead handles that hold these respective handle (remember that a new process must have an associated thread, the thread handle is also saved), and MarkProcessHandle and MulthTrHead handle, which is marked the exit of these handles . Let's look at this in detail high-level framework.

VOID
WINAPI
SaveProcessHandle(IN DWORD dwProcessId,
                  IN HANDLE hProcess)
{
    PDBGSS_THREAD_DATA ThreadData;

    /* Allocate a thread structure */
    ThreadData = RtlAllocateHeap(RtlGetProcessHeap(),
                                 0,
                                 sizeof(DBGSS_THREAD_DATA));
    if (!ThreadData) return;

    /* Fill it out */
    ThreadData->ProcessHandle = hProcess;
    ThreadData->ProcessId = dwProcessId;
    ThreadData->ThreadId = 0;
    ThreadData->ThreadHandle = NULL;
    ThreadData->HandleMarked = FALSE;

    /* Link it */
    ThreadData->Next = DbgSsGetThreadData();
    DbgSsSetThreadData(ThreadData);
}

This function allocates a new structure DBGSS_THREAD_DATA, and simply fill it with the handle and process ID sent. Finally, it will link it to the current DBGSS_THREAD_DATA structure, and set itself as the new current structure (to create a circular list DBGSS_THREAD_DATA structure). Let's look at this structure:

typedef struct _DBGSS_THREAD_DATA
{
    struct _DBGSS_THREAD_DATA *Next;
    HANDLE ThreadHandle;
    HANDLE ProcessHandle;
    DWORD ProcessId;
    DWORD ThreadId;
    BOOLEAN HandleMarked;
} DBGSS_THREAD_DATA, *PDBGSS_THREAD_DATA;

Accordingly, this general structure allows storage process / thread handles and id, and we talked about MarkProcess / ThreadHandle logo. We have also seen some DbgSsSet / GetThreadData functions that will show us the location of this loop structure array. Let's take a look at their implementation:

#define DbgSsSetThreadData(d) \
    NtCurrentTeb()->DbgSsReserved[0] = d

#define DbgSsGetThreadData() \
    ((PDBGSS_THREAD_DATA)NtCurrentTeb()->DbgSsReserved[0])


Easy enough, and now we know what the first element of the mysterious DbgSsReserved array in the TEB is. Although you can probably guess the SaveThreadHandle implementation yourself, let's look at it for completeness's sake:

VOID
WINAPI
SaveThreadHandle(IN DWORD dwProcessId,
                 IN DWORD dwThreadId,
                 IN HANDLE hThread)
{
    PDBGSS_THREAD_DATA ThreadData;

    /* Allocate a thread structure */
    ThreadData = RtlAllocateHeap(RtlGetProcessHeap(),
                                 0,
                                 sizeof(DBGSS_THREAD_DATA));
    if (!ThreadData) return;

    /* Fill it out */
    ThreadData->ThreadHandle = hThread;
    ThreadData->ProcessId = dwProcessId;
    ThreadData->ThreadId = dwThreadId;
    ThreadData->ProcessHandle = NULL;
    ThreadData->HandleMarked = FALSE;

    /* Link it */
    ThreadData->Next = DbgSsGetThreadData();
    DbgSsSetThreadData(ThreadData);
}


As expected, nothing new here. The MarkThread/Process functions as just as straight-forward:

VOID
WINAPI
MarkThreadHandle(IN DWORD dwThreadId)
{
    PDBGSS_THREAD_DATA ThreadData;

    /* Loop all thread data events */
    ThreadData = DbgSsGetThreadData();
    while (ThreadData)
    {
        /* Check if this one matches */
        if (ThreadData->ThreadId == dwThreadId)
        {
            /* Mark the structure and break out */
            ThreadData->HandleMarked = TRUE;
            break;
        }

        /* Move to the next one */
        ThreadData = ThreadData->Next;
    }
}

VOID
WINAPI
MarkProcessHandle(IN DWORD dwProcessId)
{
    PDBGSS_THREAD_DATA ThreadData;

    /* Loop all thread data events */
    ThreadData = DbgSsGetThreadData();
    while (ThreadData)
    {
        /* Check if this one matches */
        if (ThreadData->ProcessId == dwProcessId)
        {
            /* Make sure the thread ID is empty */
            if (!ThreadData->ThreadId)
            {
                /* Mark the structure and break out */
                ThreadData->HandleMarked = TRUE;
                break;
            }
        }

        /* Move to the next one */
        ThreadData = ThreadData->Next;
    }
}

Note that the only less important implementation details that need to be analyzed to find an array of matching process and thread ID. Now, we see these structures, let us look at ContinueDebugEvent API associated, it picked up after WaitForDebugEvent API, in order to resume the thread.

BOOL
WINAPI
ContinueDebugEvent(IN DWORD dwProcessId,
                   IN DWORD dwThreadId,
                   IN DWORD dwContinueStatus)
{
    CLIENT_ID ClientId;
    NTSTATUS Status;

    /* Set the Client ID */
    ClientId.UniqueProcess = (HANDLE)dwProcessId;
    ClientId.UniqueThread = (HANDLE)dwThreadId;

    /* Continue debugging */
    Status = DbgUiContinue(&ClientId, dwContinueStatus);
    if (!NT_SUCCESS(Status))
    {
        /* Fail */
        SetLastErrorByStatus(Status);
        return FALSE;
    }

    /* Remove the process/thread handles */
    RemoveHandles(dwProcessId, dwThreadId);

    /* Success */
    return TRUE;
}

Again, we are dealing with a DbgUI API DbgUiContinue, it will do all the work for us. Our only job is to call RemoveHandles, which is part of the high-level structure of the package DbgUi. This function is slightly more complicated than what we see, because we got PID / TID, so we need to do some find:

VOID
WINAPI
RemoveHandles(IN DWORD dwProcessId,
              IN DWORD dwThreadId)
{
    PDBGSS_THREAD_DATA ThreadData;

    /* Loop all thread data events */
    ThreadData = DbgSsGetThreadData();
    while (ThreadData)
    {
        /* Check if this one matches */
        if (ThreadData->ProcessId == dwProcessId)
        {
            /* Make sure the thread ID matches too */
            if (ThreadData->ThreadId == dwThreadId)
            {
                /* Check if we have a thread handle */
                if (ThreadData->ThreadHandle)
                {
                    /* Close it */
                    CloseHandle(ThreadData->ThreadHandle);
                }

                /* Check if we have a process handle */
                if (ThreadData->ProcessHandle)
                {
                    /* Close it */
                    CloseHandle(ThreadData->ProcessHandle);
                }

                /* Unlink the thread data */
                DbgSsSetThreadData(ThreadData->Next);

                /* Free it*/
                RtlFreeHeap(RtlGetProcessHeap(), 0, ThreadData);

                /* Move to the next structure */
                ThreadData = DbgSsGetThreadData();
                continue;
            }
        }

        /* Move to the next one */
        ThreadData = ThreadData->Next;
    }
}

You do not need much explanation. When resolving circular buffer, we try to find the given PID and TID match the structure. Once you find it, we'll check whether the handle associated with the threads and processes. If so, we can now close the handle.
Therefore, the use of this high-level Win32 mechanism is now obvious: We can handle associated with id up and clean-up or when they remain closed. This is because the handle is not opened by the Win32, but by Dbgk open later. After closing the handle, let's cancel the link of this structure by changing TEB pointer to the next structure in the array, and then release its own array. And parse from the next structure (because there are a plurality of such structures may be associated with this PID / TID associated).
Finally, our analysis is missing the last piece of the puzzle Win32, which is a function of XP detach added. Let's see if it's simple to achieve:

BOOL
WINAPI
DebugActiveProcessStop(IN DWORD dwProcessId)
{
    NTSTATUS Status;
    HANDLE Handle;

    /* Get the process handle */
    Handle = ProcessIdToHandle(dwProcessId);
    if (!Handle) return FALSE;

    /* Close all the process handles */
    CloseAllProcessHandles(dwProcessId);

    /* Now stop debgging the process */
    Status = DbgUiStopDebugging(Handle);
    NtClose(Handle);

    /* Check for failure */
    if (!NT_SUCCESS(Status))
    {
        /* Fail */
        SetLastError(ERROR_ACCESS_DENIED);
        return FALSE;
    }

    /* Success */
    return TRUE;
}

It could not be easier. Like additional, we first convert the PID to a handle, and then use DbgUi call (dbguistopdigung) to handle this process in order to separate themselves from the process. There is also a call that CloseAllProcessHandles. This is part of the advanced debugging Win32 on DbgUi, we have just seen. This routine and RemoveHandles very similar, but it deals only with the process ID, so to achieve a more simple:

VOID
WINAPI
CloseAllProcessHandles(IN DWORD dwProcessId)
{
    PDBGSS_THREAD_DATA ThreadData;

    /* Loop all thread data events */
    ThreadData = DbgSsGetThreadData();
    while (ThreadData)
    {
        /* Check if this one matches */
        if (ThreadData->ProcessId == dwProcessId)
        {
            /* Check if we have a thread handle */
            if (ThreadData->ThreadHandle)
            {
                /* Close it */
                CloseHandle(ThreadData->ThreadHandle);
            }

            /* Check if we have a process handle */
            if (ThreadData->ProcessHandle)
            {
                /* Close it */
                CloseHandle(ThreadData->ProcessHandle);
            }

            /* Unlink the thread data */
            DbgSsSetThreadData(ThreadData->Next);

            /* Free it*/
            RtlFreeHeap(RtlGetProcessHeap(), 0, ThreadData);

            /* Move to the next structure */
            ThreadData = DbgSsGetThreadData();
            continue;
        }

        /* Move to the next one */
        ThreadData = ThreadData->Next;
    }
}

This completes our analysis of the win32api! Let's look at what we learned:

  • The actual debugging features appear in the kernel module called Dbgk in.
  • It can DbgUi Native API interface to access via the interface is located in ntdll NT system libraries.
  • Dbgk realize debugging function object named by NT Debug object, the object also provides NtSetInformation API to modify certain signs.
  • You can use the debug target DbgUiGetThreadObject retrieval threads associated with, but we did not show it where it is stored.
  • You can use NtQueryInformationProcess and DebugPort class information to check whether the process is being debugged. No rootkit can not deceive you.
  • Since some Dbgk open during debug event handler, Win32 id associated need for a method to handle and, using a circulating array structure will be referred DBGSS_THREAD_DATA TEB DbgSsReserved stored in the [0] members.

Guess you like

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