Short Message LPC机理分析

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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值