阅读:3327回复:1
详解进程创建中与csrss的通信流程
其实每天都到本站查一前的好帖子,这回也写点心得:
刚才第八个门写了个在内核创建进程,以为结束了,不过细细研究, 发现有个小问题,也就是zhuwg说的 和csrss通信问题。这个问题直接导致他那个创建法有错误, 最后会弹出一个对话框来提示进程初始化失败。 当时没怎么管,休息了几天后细细调试了番整个windows进程启动的最后与csrss通信部分, 发现还是有点收获的, 过程也走了点弯路,所以发了出来以防自己忘记。 先不说最后结果,来说说怎么调试出来,我想这个比结果更有意义吧 。首先用spywin确认发现那个对话框是csrss弹出。 于是启动内核调试器,切换到csrss的进程中观察堆栈,果然发现了点有意思的东西, 原来是csrss的CsrApiRequestThread 弹出。好在这个函数是有代码的,在nt4或者reactOS里能直接看到。 读后发现原来这个线程就是csrss处理外部各种请求的 线程,并通过LPC通信。里面通过NtReplyWaitReceivePort不断获取外界消息。 这里的对话框也是这个线程执行如 LoadedServerDll->HardErrorRoutine(……)的代码弹出的。 这里牵扯到LPC的流程,这里可以简单结合csrss说下: 每当有消息发来,NtReplyWaitReceivePort的ReceiveMsg参数的ReceiveMsg.h.u2.s2.Type字段保存了消息的类型, 也就是指示CsrApiRequestThread下一步要进行什么。如果是LPC_REQUEST,ReceiveMsg.ApiNumber则将起到一个回调序列号的 作用: Copy code ApiNumber = ReceiveMsg.ApiNumber; ServerDllIndex = CSR_APINUMBER_TO_SERVERDLLINDEX( ApiNumber ); LoadedServerDll = CsrLoadedServerDll[ ServerDllIndex ] (*(LoadedServerDll->ApiDispatchTable[ ApiTableIndex ]))( &ReceiveMsg, &ReplyStatus ); 下面来看进程启动时整个csrss的通信流程:父进程创建的最后阶段会通过 CsrClientCallServer将自己的PID,TID,子进程的 句柄,PID发送给CsrApiRequestThread。这时其会通过 CsrLocateThreadByClientId查询父进程的相关信息。然后调用: CSRSRV!CsrCreateProcess来将子进程信息存储到csrss的hash表中。 上面说的CsrLocateThreadByClientId也是在这个hash表中查询, 下面是调用栈: 005afe78 75ab4eea 0000034c 00000148 005aff1c CSRSRV!CsrCreateProcess (FPO: [6,12,0]) 005afed0 75aa4a47 0000034c 005affd8 00000005 basesrv!BaseSrvCreateProcess+0x13f (FPO: [2,11,4]) 005afff4 00000000 00000090 00000000 00000000 CSRSRV!CsrApiRequestThread+0x431 (FPO: [Non-Fpo]) 下面是PCSR_THREAD 代码: Copy code CsrLocateThreadByClientId( OUT PCSR_PROCESS *Process OPTIONAL, IN PCLIENT_ID ClientId ) { ULONG Index; PLIST_ENTRY ListHead, ListNext; PCSR_THREAD Thread; Index = THREAD_ID_TO_HASH(ClientId->UniqueThread); if (ARGUMENT_PRESENT(Process)) { *Process = NULL; } ListHead = &CsrThreadHashTable[Index]; ListNext = ListHead->Flink; while (ListNext != ListHead) { Thread = CONTAINING_RECORD( ListNext, CSR_THREAD, HashLinks ); if ( Thread->ClientId.UniqueThread == ClientId->UniqueThread && Thread->ClientId.UniqueProcess == ClientId->UniqueProcess ) { if (ARGUMENT_PRESENT(Process)) { *Process = Thread->Process; } return Thread; } ListNext = ListNext->Flink; } return NULL; } CsrThreadHashTable既是我们提到的、后面要操作的表。当这一阶段完成后, 再看子进程,这时的子进程在 第一次通过APC执行ntdll的_LdrpInitialize时,由于是win32子系统的进程,所以会通过LdrGetProcedureAddress来查找 BaseProcessInitPostImport,这时通过LdrpRunInitializeRoutines->LdrpCallInitRoutine会调用到kernel32!_BaseDllInitialize, 而这里会调用NtSecureConnectPort来初始化。而这个过程中又会发送一个消息给CSRSS, 而后在 代码: KeReleaseSemaphore( ConnectionPort->MsgQueue.Semaphore, 1, 1, FALSE ); 这里等待。此时CsrApiRequestThread收到名为LPC_CONNECTION_REQUEST的消息后 会通过CsrApiHandleConnectionRequest来创建一个 回复端口,也既NtAcceptConnectPort通知NtSecureConnectPort。 这个过程光看代码可能难以了解详细的过程,我 也是跟了一段时间才知道的。 下面是CsrApiHandleConnectionRequest的 代码: 代码: Copy code NTSTATUS CsrApiHandleConnectionRequest( IN PCSR_API_MSG Message ) { NTSTATUS Status; REMOTE_PORT_VIEW ClientView; BOOLEAN AcceptConnection; HANDLE PortHandle; PCSR_PROCESS Process; PCSR_THREAD Thread; PCSR_API_CONNECTINFO ConnectionInformation; ConnectionInformation = &Message->ConnectionRequest; AcceptConnection = FALSE; AcquireProcessStructureLock(); Thread = CsrLocateThreadByClientId( NULL, &Message->h.ClientId ); if (Thread != NULL && (Process = Thread->Process) != NULL) { Status = NtDuplicateObject( NtCurrentProcess(), CsrObjectDirectory, Process->ProcessHandle, &ConnectionInformation->ObjectDirectory, 0, 0, DUPLICATE_SAME_ACCESS | DUPLICATE_SAME_ATTRIBUTES ); if (NT_SUCCESS( Status )) { Status = CsrSrvAttachSharedSection( Process, ConnectionInformation ); if (NT_SUCCESS( Status )) { #if DBG ConnectionInformation->DebugFlags = CsrDebug; #endif AcceptConnection = TRUE; } } } ReleaseProcessStructureLock(); ClientView.Length = sizeof( ClientView ); ClientView.ViewSize = 0; ClientView.ViewBase = 0; Status = NtAcceptConnectPort( &PortHandle, AcceptConnection ? (PVOID)Process->SequenceNumber : 0, &Message->h, AcceptConnection, NULL, &ClientView ); if (NT_SUCCESS( Status ) && AcceptConnection) { IF_CSR_DEBUG( LPC ) { DbgPrint( "CSRSS: ClientId: %lx.%lx has ClientView: Base=%lx, Size=%lx\n", Message->h.ClientId.UniqueProcess, Message->h.ClientId.UniqueThread, ClientView.ViewBase, ClientView.ViewSize ); } Process->ClientPort = PortHandle; Process->ClientViewBase = (PCH)ClientView.ViewBase; Process->ClientViewBounds = (PCH)ClientView.ViewBase + ClientView.ViewSize; Status = NtCompleteConnectPort( PortHandle ); if (!NT_SUCCESS( Status )) { IF_DEBUG { DbgPrint( "CSRSS: NtCompleteConnectPort - failed. Status == %X\n", Status ); } // FIX, FIX - need to destroy Session } } else { if (!NT_SUCCESS( Status )) { IF_DEBUG { DbgPrint( "CSRSS: NtAcceptConnectPort - failed. Status == %X\n", Status ); } } else { IF_DEBUG { DbgPrint( "CSRSS: Rejecting Connection Request from ClientId: %lx.%lx\n", Message->h.ClientId.UniqueProcess, Message->h.ClientId.UniqueThread ); } } } return Status; } 由此可知,如果第一阶段的通信失败,则csrss无法保存父进程的相关信息。 因此CsrApiHandleConnectionRequest 中的CsrLocateThreadByClientId就会失败。 此时NtAcceptConnectPort的 AcceptConnection参数 必为false。因此在NtSecureConnectPort等待收到的回复时 ClientPort->ConnectedPort必为空。因此 if (ClientPort->ConnectedPort != NULL) 这句既失败,也不会执行到 代码: Status = ObInsertObject( ClientPort, NULL, PORT_ALL_ACCESS, 0, (PVOID *)NULL, &Handle ); 故kernel32中想通过NtSecureConnectPort创建CsrApiPort也会失败。 而这又导致kernel32的dllmain失败。 这样整个进程初始化便失败了。 然后_LdrpInitialize便会调用LdrpInitializationFailure又通过LPC通知Csrss的CsrApiRequestThread弹出对话框: f7f5facc 804fc1e2 nt!KiUnlockDispatcherDatabase+0x77 (FPO: [Uses EBP] [0,0,4]) f7f5fae4 80598997 nt!KeReleaseSemaphore+0x70 (FPO: [4,1,4]) f7f5fb18 80598b21 nt!LpcpRequestWaitReplyPort+0x3ff (FPO: [4,4,0]) f7f5fb30 8060a467 nt!LpcRequestWaitReplyPortEx+0x21 (FPO: [3,0,0]) f7f5fcd4 8060a8c5 nt!ExpRaiseHardError+0x1bd (FPO: [Non-Fpo]) f7f5fd44 8053d808 nt!NtRaiseHardError+0x16b (FPO: [Non-Fpo]) f7f5fd44 7c92eb94 nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ f7f5fd64) 0010fc7c 7c92e273 ntdll!KiFastSystemCallRet (FPO: [0,0,0]) 0010fc80 7c97146b ntdll!NtRaiseHardError+0xc (FPO: [6,0,0]) 0010fca4 7c95f8f3 ntdll!LdrpInitializationFailure+0x2d (FPO: [1,1,0]) 0010fd1c 7c92eac7 ntdll!_LdrpInitialize+0x239 (FPO: [Non-Fpo]) 00000000 f000ff53 ntdll!KiUserApcDispatcher+0x7 这样一来,就回到我们开始的那个对话框了。 调试过程还有个小地方,就是BaseSrvCreateProcess会在TEB->CsrClientThread中取到父进程的PID。这是 我开始想不通的。不明白为什么windows会单独设一个字段保存这东西, 完全可以通过参数压栈。也许是历史原因吧。 下面这几句可以看出CsrApiRequestThread先把父进程的信息保存在Teb->CsrClientThread 然后在BaseSrvCreateProcess中 供NtDuplicateObject使用,然后再还原的过程: 代码: Copy code Teb->CsrClientThread = (PVOID)Thread; ReplyMsg = &ReceiveMsg; ReplyPortHandle = Thread->Process->ClientPort; ReplyStatus = CsrReplyImmediate; ReplyMsg->ReturnValue = (*(LoadedServerDll->ApiDispatchTable[ ApiTableIndex ]))( &ReceiveMsg, &ReplyStatus ); ++CsrpStaticThreadCount; Teb->CsrClientThread = (PVOID)MyThread; 下面来说说要注意的地方,也是我调试中碰到的小问题吧。 首先是弹出的对话框我以为也是通过NtRequestWaitReplyPort, 不过断遍了整个发送LPC的ntxx函数都没断到。 后来才发现原来是NtRaiseHardError->LpcpRequestWaitReplyPort, 这才恍然大悟,NtRaiseHardError不就是专门来弹这种框的么。 甚至在驱动也可以霸气弹框。 然后是觉得这几套逻辑的函数看似简单,不过消息发来发去的,调的我有点烦躁了。 所以调试过程中耐心真是必不可少啊。 下面再说说第八个门中的错误。最后他枚举句柄表来找到CsrApiPort的句柄, 可惜代码写的有点问题。正确的枚举应该是这样的: 代码: Copy code ObjectHeader = (POBJECT_HEADER)(((ULONG_PTR)(HandleTableEntry->Object))&~OBJ_HANDLE_ATTRIBUTES); if ((ObjectHeader->ObjectType) != *LpcPortObjectType) {return FALSE;} pLPCPortObject = (PVOID)(*(DWORD*)&(ObjectHeader->Body)); LPC_PORT_OBJECT_2K *pLPCPortObject2K = NULL; LPC_PORT_OBJECT_XP *pLPCPortObjectXP = NULL; pLPCPortObjectXP = pLPCPortObject; pConnectionPort = pLPCPortObjectXP->ConnectionPort; ObDereferenceObject(pLPCPortObject); pLPCPortObject = NULL; if (!pConnectionPort || gpCsrssPort != pConnectionPort) {return FALSE;} g_CsrPortObj = (PVOID)(*(DWORD*)&(ObjectHeader->Body)); return TRUE; 还有个问题,即使枚举到了句柄,发送消息也不可能成功。因为发送过去的消息保存的是system进程的信息。 这个在csrss的hash表中是没有的。因此应该DIY一下这个hash表,以便让CsrLocateThreadByClientId得以成功,这个 可能是比较麻烦的吧…… 由于时间关系,上面难免有不少错误,还望各位大牛指正。 |
|
沙发#
发布于:2010-05-17 01:45
学习!
|
|