欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

调试器设计3

调试器设计3

标 题: 调试器设计3
作 者: Tweek
时 间: 2010-3-1

最后一篇知识性介绍是关于调试API的内部实现的简单分析。
这样可以帮助我们进一步深入认识被调试程序。
调试API的内部实现原理分析:
我们先看看DebugActiveProcess内部结构
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; 
}
简单的初始化,然后调用DbgUiDebugActiveProcess。
关于DbgUiDebugActiveProcess的功能嘛。内核需要和进程之间交流调试相关的信息。Xp后,调试部不再利用LPC,而是直接为调试建立Debug object。所以它至少建立了一个debug object。我们可以用NtQueryInformationXXX获取debug object的信息。
有了一些初步的认识,让我们再看看WaitForDebugEvent。
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; 
}

DbgUiWaitStateChange 检测debug object,看看是否有调试事件返回。
DbgUiConvertStateChange作用是为了版本的兼容,将以前系统的结构转换成现在可以识别的结构。
到了下面的switch了,里面的东西我们似乎可以通过名字看懂。但是,我们看看它的细节不是更好
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); 
}
出现了DBGSS_THREAD_DATA这个结构体。将当前的DBGSS_THREAD_DATA添加到原来的链表(暂且让我说他是链表)里面。然后将新的设置成链表表头。
再看看DBGSS_THREAD_DATA的定义
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;
嗯,里面有进程和线程的句柄和ID。还有一个句柄的标记。应该是MarkProcess/ThreadHandle返回的。判断是否已经退出。
saveProcessHandle里面还有个这个
DbgSsSet/GetThreadData
让我们来看看他的实现
#define DbgSsSetThreadData(d) \ 
NtCurrentTeb()->DbgSsReserved[0] = d 
#define DbgSsGetThreadData() \ 
((PDBGSS_THREAD_DATA)NtCurrentTeb()->DbgSsReserved[0])
原来是将TEB中的DbgSsReserved的第一个元素被赋予了ThreadData.
关于SaveThreadHandle的实现:
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); 
}
正如你所想。没有什么大的变化。
最后我们看看ContinueDebugEvent
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; 
}
噢,DbgUiContinue已经做了所有的事了。

最后,我们还是看看MS都为我们干了些什么:
首先,调试器访问在ntdll里面的接口DbgUi,然后,DbgUi通过dbgk建立了一个debug object(当然我们可以用NtQueryInformationXXX或者NtSetInformationXXX查看和修改它,修改只能针对部分位置。)然后,dbgk通过DBGSS_THREAD_DATA 将调试事件依次保存在一个”链表”里面。然后,teb的DbgSsReserved的第一个元素被赋予了那个链表。而DbgSsReserved的第二个元素则是指向调试事件。所以,我们可以IsDebugergPresent通过检测TEB,判断是否被调试,同时,我们解释了第二篇的那个例子程序里面为什么会在 不断的弹出大量框。而利用NtQueryInformationXXX和DebugPort则必须利用驱动程序内核完成。因为他们是查询的内核的object。
大家一定意识到debug object在调试中处于一个多么重要的地位。
那么,我们就来看看他到底是什么?
 typedef struct _DEBUG_OBJECT 
{ 
KEVENT EventsPresent; 
FAST_MUTEX Mutex; 
LIST_ENTRY EventList; 
union 
{ 
ULONG Flags; 
struct 
{ 
UCHAR DebuggerInactive:1; 
UCHAR KillProcessOnExit:1; 
}; 
}; 
} DEBUG_OBJECT, *PDEBUG_OBJECT;
没有想象的那么复杂吧。
LIST_ENTRY一个调试事件的列表。
Flag和调试会话相联系
其他的看名字就知道什么意思了吧!

到此,我们设计调试器需要的知识储备已经差不多了。一般性的问题,我们都可以给出解释了。
对调试API也有了比较深入的认识。
上面三篇介绍了一些关于调试器紧密相关的信息,都是介绍比较基本的。
如果希望更深入的了解相关信息。
可以:……
进程更加深入的信息 请参考windows核心编程
调试API更深入的细节。大家可以参考dbgk的介绍。
同时,xp 增加了一些调试API,虽然他们内部实现没有本质变化。
但是,却给调试器的设计给与了更多选择:
 DebugActiveProcessStop,
 DebugSetProcessKillOnExit,
 DebugBreakProcess,
 CheckRemoteDebuggerPresent.
具体使用方法请参考msdn
待续……

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功