本文只讨论Short-Message LPC通信过程,引用一张流程图可以全部包括,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/3514b4140dd972b4f80308b7720756f4.jpeg)
为了节省篇幅,就不一一列出各函数的反汇编代码了,我们直接看流程:
服务器端调用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