NULL, &Handle );
SeDeleteAccessState( &AccessState ); if ( NT_SUCCESS(Status) ) { try {
*ProcessHandle = Handle;
} except (EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode (); } }
return Status; }
if ( ClientIdPresent ) { Thread = NULL;
if (CapturedCid.UniqueThread) {
Status = PsLookupProcessThreadByCid( &CapturedCid, &Process, &Thread );
if (!NT_SUCCESS(Status)) {
SeDeleteAccessState( &AccessState ); return Status; } } else {
Status = PsLookupProcessByProcessId( CapturedCid.UniqueProcess, &Process );
if ( !NT_SUCCESS(Status) ) {
SeDeleteAccessState( &AccessState ); return Status; } }
Status = ObOpenObjectByPointer( Process, Attributes, &AccessState, 0,
*PsProcessType, PreviousMode, &Handle
); //得到进程句柄
30
SeDeleteAccessState( &AccessState ); if (Thread) {
ObDereferenceObject(Thread); }
ObDereferenceObject(Process); if (NT_SUCCESS (Status)) { try {
*ProcessHandle = Handle;
} except (EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode (); } }
return Status; }
return STATUS_INVALID_PARAMETER_MIX; } }
5.6 移除EPROCESS->DebugPort端口
现在一般游戏的保护都会对游戏进程的EPROCESS->DebugPort调试端口清0,这样OD调试器就会收不到调试事件信息。有种方法是把Windows系统内核下所有操作EPROCESS->DebugProt的内核函数,改写成操作EPROCESS结构中不重要的地方,比如操作EPROCESS结构的CreateTime成员,这样就算它对ERPCESS->DebugPort调试端口每时每刻清0,但是并不会影响我们调试的。
由于我们用的所有调试模块都是我们自己实现的,我们可以不用这么做,当OD调用NtDebugActiveProcess()时,进入我们的函数,之后会调用DbgkpPostFakeProcessCreateMessages()函数把这个要调试的游戏进程的所有模块,线程调试事件插入在DebugObject的事件链表中。之后会调用DbgkpSetProcessDebugObject()这个函数设置被调试进程EPROCESS的Debugport成员为先前建立的调试对象,下次有调试事件了就会插入在这个调试对象的事件链表中。
设置代码如下:
if (Process->DebugPort != NULL) {
Status = STATUS_PORT_ALREADY_SET; break; } //
// Assign the debug port to the process to pick up any new threads
//
Process->DebugPort = DebugObject;
我们其实可以自己定义一个结构体来对应要调试的进程的EPROCESS和DEBUGPORT
31
typedef struct __EPROCESSDEBUGPORT {
PEPROCESS Process;
PDEBUG_OBJECT DebugPort;
} EPROCESSDEBUGPORT,*LP EPROCESSDEBUGPORT;
5.7 HOOK Windows 内核下会发送调试事件的内核函数
上面已经提到怎么定义一个结构体,但是怎么改变Windows内核下调试的处理机制,也就是怎么会把调试事件发送到我们自己定义的这个结构体中的DebugPort,而不是原来的EPROCESS的DebugPort成员。这就需要改变Windows内核下一切的这些内核函数。比如常见的DbgkCreateThread,DbgkExitThread,DbgkMapViewOfSection,DbgkUnMapViewOfSection,DbgkExitProcess等等这些函数。
比如用户层建立一个线程,会调用CreateThread(),CreateRemoteThread()。这两个函数。这两个函数最终都会通过ntdll.dll的sysenter进入Ring 0,然后调用系统调用NtCreateThread()。一层一层的调用PspCreateThread()。最后这个线程被Windows系统调度程序调度运行时,首先会调用PspUserThreadStartup()这个函数,它里面会判断调用DbgkCreateThread()函数通知调试器接受建立线程的调试事件信息。部分代码如下:
// If the create worked then notify the debugger. //
if ((Thread->CrossThreadFlags&
(PS_CROSS_THREAD_FLAGS_DEADTHREAD|PS_CROSS_THREAD_FLAGS_HIDEFROMDBG)) == 0) {
DbgkCreateThread (Thread, StartContext);
}
最终DbgkCreateThread会调用DbgkpSendApiMessage()-> DbgkpQueueMessage(),这个函数就会向Process->DebugPort发送调试事件信息。
用户层线程加载DLL时会调用LoadLibrary,LoadLibraryEx,函数,它们同样会通过sysenter进入Ring 0调用系统调用NtMapViewOfSection(),它里面同样会调用DbgkMapViewOfSection()向Process->DebugPort发送调试事件信息。
所以我们做的是HOOK调用这个函数的地方,向它进入我们自己的函数里面处理,然后我们自己再把调试事件信息发向我们想要的地方去。
具体HOOK代码如下:
typedef struct __hook_find {
DWORD* dwFindSrc; //查找的源地址 DWORD* dwFindDst; //查找的函数地址 DWORD dwCallDst; //重定向
32
DWORD dwFindPos; //位置找旱后旱位置 }__hook_find;
__hook_find HookData[]={
{&Addr_PspUserThreadStartup,&Addr_DbgkCreateThread,(DWORD)HxDbgkCreateThread,0},
{&Addr_PspExitThread,&Addr_DbgkExitProcess,(DWORD)HxDbgkExitProcess,0},
{&Addr_PspExitThread,&Addr_DbgkExitThread,(DWORD)HxDbgkExitThread,0},
{&Addr_NtMapViewOfSection,&Addr_DbgkMapViewOfSection,(DWORD)HxDbgkMapViewOfSection,0},
{&Addr_MiUnmapViewOfSection,&Addr_DbgkUnMapViewOfSection,(DWORD)HxDbgkUnMapViewOfSection,0},
{&Addr_CommonDispatchException,&Addr_KiDispatchException,(DWORD)HxKiDispatchException,0},
{&Addr_KiRaiseException,&Addr_KiRaiseException,(DWORD)HxKiDispatchException,0}
};
PUCHAR Search_CallFunction(PUCHAR pbCode,PUCHAR pbCallfunc,IN OUT UINT* pnPos)
{
//e8baf1feff call nt!ObOpenObjectByPointer (80933d4c)
ULONG nSearchAddr=(ULONG)pbCallfunc; ULONG nLen=0,nTmpLen=0;
PUCHAR pPos=pbCode+(*pnPos),pFind=NULL; UCHAR uTemp[20]={0};
UCHAR btCode[5]={0xE8,0,0,0,0}; while(*pPos!=0xCC){
nTmpLen = getNextInstruction(pPos,1,uTemp,20); nLen+=nTmpLen; pPos+=nTmpLen;
if(*pPos==0xE8){
*((ULONG*)(btCode+1)) = ((ULONG)nSearchAddr-(ULONG)pPos-5);
//
DbgPrint(\%2X%2X%2X%2X%2X==%2X%2X%2X%2X%2X\],pPos[3],pPos[4],btCode[0],btCode[1],btCode[2],btCode[3],btCode[4]);
33
if(pPos[0]==btCode[0] && pPos[1]==btCode[1] && pPos[2]==btCode[2] && pPos[3]==btCode[3] && pPos[4]==btCode[4] ) {
pFind=pPos; *pnPos = nLen; break; } } }
return pFind; }
VOID StartHook() {
UINT ln=sizeof(HookData)/sizeof(__hook_find); UINT i,pos; PUCHAR pAddr; DWORD dwOldAddr; KIRQL oldirql; for (i=0;i pos=0; pAddr=Search_CallFunction((PUCHAR)(*HookData[i].dwFindSrc), (PUCHAR)(*HookData[i].dwFindDst), &pos); if(pos) { HookData[i].dwFindPos=(DWORD)pAddr; dwOldAddr = *((ULONG*)(pAddr+1)); CLR_WP(); oldirql=KeAcquireQueuedSpinLockRaiseToSynch(LockQueueDispatcherLock); *((ULONG*)(pAddr+1)) = (HookData[i].dwCallDst-HookData[i].dwFindPos-5); KeReleaseQueuedSpinLock(LockQueueDispatcherLock,oldirql); SET_WP(); HookData[i].dwCallDst=dwOldAddr; } 34