Short Message LPC機理分析處理序間通訊(IPC)是在多任務作業系統或連網的電腦之間啟動並執行程式和進程所用的通訊技術。有兩種類型的處理序間通訊。本地程序呼叫(LPC)和遠端程序呼叫(RPC)。出於未知的原因,微軟沒有公開 LPC 的介面。微軟為機器間的客戶機伺服器通訊提供了一套 RPC。Windows NT最佳化了RPC,將其轉為了LPC,只不過是客戶機伺服器全在同一台機器上罷了。
本文只討論Short-Message LPC通訊過程,引用一張流程圖可以全部包括,如下:
為了節省篇幅,就不一一列出各函數的反組譯碼代碼了,我們直接看流程:
伺服器端調用NtCreatePort() 函數建立一個連接埠。它返回一個連接埠控制代碼,伺服器就使用此控制代碼調用 NtListenPort() 函數來等待和接受請求。任何用戶端都可以通過此連接埠發送串連請求並得到一個用於通訊的連接埠控制代碼。
NtCreatePort函數原型:
NTSTATUS
NtCreatePort (
OUT PHANDLE PortHandle,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN ULONG MaxConnectionInfoLength,
IN ULONG MaxMessageLength,
IN ULONG MaxPoolUsage
)
實際上,NtCreatePort將調用LpcpCreatePort函數,丟ntoskrnl.exe到IDA,將看到:
__stdcall NtCreatePort(x, x, x, x, x) proc near
PAGE:004C04E6 004 push 0
PAGE:004C04E8 008 push [ebp+Reserved]
PAGE:004C04EB 00C push [ebp+MaxMessageSize]
PAGE:004C04EE 010 push [ebp+MaxDataSize]
PAGE:004C04F1 014 push [ebp+ObjectAttributes]
PAGE:004C04F4 018 push [ebp+PortHandle]
PAGE:004C04F7 01C call LpcpCreatePort(x,x,x,x,x,x)
__stdcall NtCreatePort(x, x, x, x, x) endp
LpcpCreatePort函數原型如下:
NTSTATUS
LpcpCreatePort (
OUT PHANDLE PortHandle,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN ULONG MaxConnectionInfoLength,
IN ULONG MaxMessageLength,
IN ULONG MaxPoolUsage,
IN BOOLEAN Waitable
)
而NtListenPort函數如下:
NTSTATUS
NtListenPort (
IN HANDLE PortHandle,
OUT PPORT_MESSAGE ConnectionRequest
)
這裡值得說明的是NtListenPort函數的實現裡實際調用的是NtReplyWaitReceivePort(PortHandle, NULL, NULL, ConnectionRequest),所以做為實際的編程也可以直接調用NtReplyWaitReceivePort。
NTSTATUS
NtReplyWaitReceivePort (
IN HANDLE PortHandle,
OUT PVOID *PortContext OPTIONAL,
IN PPORT_MESSAGE ReplyMessage OPTIONAL,
OUT PPORT_MESSAGE ReceiveMessage
)
在正式的編程前,有必要介紹下LPCMESSAGE結構,定義如下:
typedef struct LpcMessage {
WORD ActualMessageLength;
WORD TotalMessageLength;
DWORD MessageType;
DWORD ClientProcessId;
DWORD ClientThreadId;
DWORD MessageId;
DWORD SharedSectionSize;
BYTE MessageData[MAX_MESSAGE_DATA];
} LPCMESSAGE, *PLPC_MESSAGE;
M$官方給出的結構是:
typedef struct _TLPC_PORTMSG {
PORT_MESSAGE h;
ULONG Data[ TLPC_MAX_MSG_DATA_LENGTH ];
} TLPC_PORTMSG, *PTLPC_PORTMSG;
其中PORT_MESSAGE結構定義了LPCMESSAGE中的前幾項。
在我們的編程中,將PPORT_MESSAGE替換成PLPC_MESSAGE。
值得關注的是MessageType,M$是這麼定義的:
char * LpcMsgTypes[] = {
"** INVALID **",
"LPC_REQUEST",
"LPC_REPLY",
"LPC_DATAGRAM",
"LPC_LOST_REPLY",
"LPC_PORT_CLOSED",
"LPC_CLIENT_DIED",
"LPC_EXCEPTION",
"LPC_DEBUG_EVENT",
"LPC_ERROR_EVENT",
"LPC_CONNECTION_REQUEST",
NULL
};
那麼,服務端相關的代碼如下:
#define PORTNAME L"\\MyPort"
HANDLE PortHandle, AcceptPortHandle;
LPCMESSAGE LpcMessage;
LSA_OBJECT_ATTRIBUTES ObjectAttr;
UNICODE_STRING uString;
int rc;
RtlInitUnicodeString(&uString, PORTNAME);
memset(&ObjectAttr, 0, sizeof(ObjectAttr));
ObjectAttr.Length = sizeof(ObjectAttr);
ObjectAttr.ObjectName = &uString;
rc = NtCreatePort(&PortHandle, &ObjectAttr,0x100, 0x0, 0x00000);
if (rc != 0) {//... }
memset(&LpcMessage, 0, sizeof(LpcMessage));
while (1) {
rc = NtReplyWaitReceivePort(PortHandle,
NULL, NULL, &LpcMessage);
if (rc != 0) {//... }
if(LPC_CONNECTION_REQUEST == LpcMessage.MessageType) //0x000A
{
rc = NtAcceptConnectPort(
&AcceptPortHandle,
NULL,
&LpcMessage,
TRUE,
NULL,
NULL);
if (rc != 0) {//... }
rc = NtCompleteConnectPort(AcceptPortHandle);
if (rc != 0) {
CloseHandle(AcceptPortHandle);
//...
}
}
}
從上面的代碼我們反過來看,當客戶程式NtConnectPort()向正處於等待中的伺服器發送一個串連請求, 伺服器使用 NtAcceptConnectPort()函數接受請求,當接受到LPC_CONNECTION_REQUEST訊息時,為客戶所請求的串連返回一個新的連接埠控制代碼,並使用 NtCompleteConnectPort() 函數完成連線協定。
當然你也可以處理其他的訊息,比如LPC_REQUEST,調用NtReplyPort();而對LPC_PORT_CLOSED 訊息,釋放掉各個客戶所佔用的資源。下文將會提到這些。
這兩個函數的原型分別為:
NTSTATUS
NtAcceptConnectPort (
OUT PHANDLE PortHandle,
IN PVOID PortContext OPTIONAL,
IN PPORT_MESSAGE ConnectionRequest,
IN BOOLEAN AcceptConnection,
IN OUT PPORT_VIEW ServerView OPTIONAL,
OUT PREMOTE_PORT_VIEW ClientView OPTIONAL
)
NTSTATUS
NtCompleteConnectPort (
IN HANDLE PortHandle
)
由於用戶端相對比較簡單,我們只看NtConnectPort()的原型:
NTSYSAPI
NTSTATUS
NTAPI
NtConnectPort (
OUT PHANDLE PortHandle,
IN PUNICODE_STRING PortName,
IN PSECURITY_QUALITY_OF_SERVICE SecurityQos, //-> winnt.h
IN OUT PPORT_VIEW ClientView OPTIONAL,
IN OUT PREMOTE_PORT_VIEW ServerView OPTIONAL,
OUT PULONG MaxMessageLength OPTIONAL,
IN OUT PVOID ConnectionInformation OPTIONAL,
IN OUT PULONG ConnectionInformationLength OPTIONAL
)
簡單的實現代碼可以這樣寫:
rc = ZwConnectPort(&PortHandle, &uString,
(PSECURITY_QUALITY_OF_SERVICE)&SecurityQos, //static int
0, 0, 0, ConnectDataBuffer,(unsigned long*)&Size);
/*
rc=NtRequestPort(PortHandle, &LpcMessage);
rc = NtRequestWaitReplyPort(PortHandle,
&LpcMessage,
&LpcMessage);
*/ //something else...
在完成握手後,就可以通過這個連接埠開始通訊了。 客戶使用NtRequestPort()函數向子系統發送請求。如果客戶希望請求能得到回複,可以使用 NtRequestWaitReplyPort()函數發送請求並等待回複。伺服器使用 NtReplyWaitReceive()函數接收請求的訊息並使用NtReplyPort()函數發送回複訊息,當然也可以用一個聯合的函數NtReplyWaitReceivePort()將兩步都完成。這裡都是在NtReplyWaitReceivePort函數接受訊息後處理,如:
rc = NtReplyWaitReceivePort(PortHandle,
NULL,
NULL,
&LpcMessage);
if (rc != 0) {//... }
switch (LpcMessage.MessageType) {
case LPC_CONNECTION_REQUEST:
rc = NtAcceptConnectPort(//...)
//...
break;
case LPC_REQUEST:
rc = NtReplyPort(//...)
//...
break;
default:
break;
相關的函式宣告如下:
NTSTATUS
NtRequestPort (
IN HANDLE PortHandle,
IN PPORT_MESSAGE RequestMessage
)
NTSTATUS
NtRequestWaitReplyPort (
IN HANDLE PortHandle,
IN PPORT_MESSAGE RequestMessage,
OUT PPORT_MESSAGE ReplyMessage
)
NTSTATUS
NtReplyWaitReceivePort (
IN HANDLE PortHandle,
OUT PVOID *PortContext OPTIONAL,
IN PPORT_MESSAGE ReplyMessage OPTIONAL,
OUT PPORT_MESSAGE ReceiveMessage
)
對於Shared-Section LPC通訊,有幾點不同的地方:
1.客戶通過CreateFileMapping函數建立共用section,指定section 的大小。 伺服器在串連時得到此section的大小。
2.在調用NtConnectPort、NtAcceptConnectPort等函數的時候,需要填充相應的PPORT_VIEW ClientView,PREMOTE_PORT_VIEW ServerView,對於用戶端的NtRequestWaitReplyPort函數,在發送訊息時還要進行LpcMessage結構的填充。
參考資料:
1.<Undocumented Windows NT> --Prasad Dabak / 董岩
2.<windows 2000 kernel exploit 的一點研究> --ey4s
3.ntoskrnl.exe, ntdll.dll