其他
LPC通信撸码笔记
本文为看雪论坛优秀文章
看雪论坛作者ID:comor
LPC(Local Procedure Call),众所周知,是微软未公开(未文档化)的一种进程间通信方式,不仅可以用在应用层进程之间通信,还可以用在应用层和内核层通信。由于在驱动层并没有一种通用的机制主动发起向应用层的通信(minifilter在建立和应用层的端口通信后,可以主动发起通信),而LPC恰好可以弥补这一不足,所以便一探这陈酒。
关于LPC通信的原理、示例代码及API函数逆向的文章很多(主要来自看雪,搜索LPC),但复制较多、历史久远,原创性的内容又大多不开放源码,或者语焉不详,撸了两天(走了很多弯路),成此水文,Demo代码附后,欢迎拍砖。
一、目的
二、 主要代码及要点
1、lpc.h
typedef struct _MYPORT_MESSAGE {
PORT_MESSAGE Header;
UCHAR Data[ MAX_DATA_LEN ];
} MYPORT_MESSAGE , * PMYPORT_MESSAGE ;
//
// Valid return values for the PORT_MESSAGE Type file
//
#define LPC_REQUEST 1
#define LPC_REPLY 2
#define LPC_DATAGRAM 3
#define LPC_LOST_REPLY 4
#define LPC_PORT_CLOSED 5
#define LPC_CLIENT_DIED 6
#define LPC_EXCEPTION 7
#define LPC_DEBUG_EVENT 8
#define LPC_ERROR_EVENT 9
#define LPC_CONNECTION_REQUEST 10
// 定义消息数据长度.
//32位上,超过304,ZwCreatePort会报c00000f2(WinXP),c000000d(Win7、Win10);64位上,超过608会报c000000d
//即,32位上Msg最大328(包括消息头24),64位上Msg最大648(包括消息头40)
#ifdef _WIN64
#define MAX_MSG_LEN 648 //0x288
#define MAX_DATA_LEN 608 //0x260
#else
#define MAX_MSG_LEN 328 //0x148
#define MAX_DATA_LEN 304 //0x130
#endif
#define LARGE_MESSAGE_SIZE 0x1000
typedef struct _CLIENT_ID
{
HANDLE UniqueProcess; //32 vs 64
HANDLE UniqueThread; //32 vs 64
} CLIENT_ID , * PCLIENT_ID ;
//
// 为port消息定义头
// 注意:32位和64位系统,消息头大小不同,一个为24,一个为40
//
typedef struct _PORT_MESSAGE
{
USHORT DataLength; // Length of data following header (bytes)
USHORT TotalLength; // Length of data + sizeof(PORT_MESSAGE)
USHORT Type; // Type of the message (LPC_TYPE)
USHORT VirtualRangesOffset; // Offset of array of virtual address ranges
CLIENT_ID ClientId; // Client identifier of the message sender
ULONG MessageId; // Identifier of the particular message instance
union {
SIZE_T ClientViewSize; // Only valid on LPC_CONNECTION_REQUEST message
ULONG CallbackId; // Only valid on LPC_REQUEST message
};
} PORT_MESSAGE , * PPORT_MESSAGE ;
typedef struct _MYPORT_MESSAGE : public PORT_MESSAGE {
UCHAR Data[ MAX_DATA_LEN ];
} MYPORT_MESSAGE , * PMYPORT_MESSAGE ;
typedef struct _PORT_VIEW {
ULONG Length;
HANDLE SectionHandle; //32 vs 64
ULONG SectionOffset;
SIZE_T ViewSize; //32 vs 64
PVOID ViewBase;
PVOID ViewRemoteBase;
} PORT_VIEW , * PPORT_VIEW ;
typedef struct _REMOTE_PORT_VIEW {
ULONG Length;
SIZE_T ViewSize; //32 vs 64
PVOID ViewBase;
} REMOTE_PORT_VIEW , * PREMOTE_PORT_VIEW ;
BOOL LpcInit();
VOID LpcUinit();
DWORD LpcServer( LPCWSTR pwszPortName );
DWORD LpcClient( LPCWSTR pwszPortName );
2、lpc.cpp
数据长度的赋值:
m_ServerView.Length必须定义为消息头长度,否则出错 Msg.DataLength = MAX_DATA_LEN:如果定义长度小,可能会收到截断的消息
DWORD LpcServer( LPCWSTR pwszPortName )
{
NTSTATUS status = STATUS_UNSUCCESSFUL ;
#ifdef TEST_VIEW
HANDLE m_SectionHandle; // 共享内存句柄
PORT_VIEW m_ServerView; // 服务端共享内存映射
REMOTE_PORT_VIEW m_ClientView = { 0 }; // 客户端共享内存映射
LARGE_INTEGER m_SectionSize = { LARGE_MESSAGE_SIZE };
status = NtCreateSection(&m_SectionHandle,
SECTION_ALL_ACCESS ,
NULL ,
&m_SectionSize,
PAGE_READWRITE ,
SEC_COMMIT ,
NULL );
if (! NT_SUCCESS (status))
{
printf( "ZwCreateSection failed, st=%x\n" , status);
return status;
}
// 初始化用于服务端写入的PORT_VIEW
m_ServerView.Length = sizeof ( PORT_VIEW ); //必须是此值
m_ServerView.SectionHandle = m_SectionHandle;
m_ServerView.SectionOffset = 0;
m_ServerView.ViewSize = ( ULONG ) LARGE_MESSAGE_SIZE ;
// 初始化用于读取客户端REMOTE_PORT_VIEW
m_ClientView.Length = sizeof ( REMOTE_PORT_VIEW );
#endif
DWORD nError;
HANDLE hPortServer = INVALID_HANDLE_VALUE ;
HANDLE hPortClient = INVALID_HANDLE_VALUE ;
UNICODE_STRING ustrPortName;
OBJECT_ATTRIBUTES ObjectAttr = { 0 };
// 初始化对象属性结构
RtlInitUnicodeString(&ustrPortName, pwszPortName );
InitializeObjectAttributes (&ObjectAttr, &ustrPortName, 0, NULL , NULL );
// 创建命名端口.
status = ZwCreatePort(&hPortServer, &ObjectAttr, sizeof ( PORT_MESSAGE ), sizeof ( MYPORT_MESSAGE ), 0);
if (status != 0) {
printf( "ZwCreatePort failed: 0x%08x\n" , status);
nError = GetLastError();
return nError;
}
MYPORT_MESSAGE RecvPortMsg;
//MYPORT_MESSAGE ReplyPortMsg;
//memset(&ReplyPortMsg, 0, sizeof(ReplyPortMsg));
printf( "MYPORT_MESSAGE size:%zu %zu\n" , sizeof ( MYPORT_MESSAGE ), sizeof ( PORT_MESSAGE ));
short msg_type = 0;
while (1)
{
printf( "\n-----------------------------------------\n" );
memset(&RecvPortMsg, 0, sizeof (RecvPortMsg));
status = ZwReplyWaitReceivePort(hPortServer, NULL /*(PVOID*)&Ctxt*/ , NULL , &RecvPortMsg);
//status = ZwListenPort(hPortServer, &RecvPortMsg);
if (status != 0) {
printf( "LpcReplyWaitReceivePort failed: 0x%08x\n" , status);
break ;
}
printf( "ZwReplyWaitReceivePort ok\n" );
msg_type = RecvPortMsg.Type;
printf( "msg_type: %d \n" , msg_type);
printf( "RecvPortMsg.DataLength %d\n" , RecvPortMsg.DataLength);
printf( "RecvPortMsg.TotalLength:%d\n" , RecvPortMsg.TotalLength);
printf( "RecvPortMsg.UniqueProcess:%zu\n" , ( SIZE_T )RecvPortMsg.ClientId.UniqueProcess);
printf( "RecvPortMsg.UniqueThread:%zu\n" , ( SIZE_T )RecvPortMsg.ClientId.UniqueThread);
switch (msg_type)
{
case LPC_CONNECTION_REQUEST :
printf( "recv Msg: %s \n" , ( LPSTR )RecvPortMsg.Data);
// 填写发送数据.
lstrcpyA(( LPSTR )RecvPortMsg.Data, "reply" );
// 获得连接请求.
#ifdef TEST_VIEW
status = ZwAcceptConnectPort(
&hPortClient,
NULL ,
&RecvPortMsg,
TRUE , // 接受
NULL /*&m_ServerView*/ ,
&m_ClientView);
#else
status = ZwAcceptConnectPort(
&hPortClient,
NULL,
&RecvPortMsg,
TRUE, // 接受
NULL /*&m_ServerView*/ ,
NULL /*&m_ClientView*/ );
#endif
if (status != 0) {
printf( "LpcAcceptConnectPort failed, status=%x\n" , status);
break ;
}
printf( "LpcAcceptConnectPort ok\n" );
//printf("m_ClientView.ViewSize: %d\n", m_ClientView.ViewSize);
//printf("m_ClientView.Length: %d\n", m_ClientView.Length);
//printf("m_ClientView.ViewBase: %p\n", m_ClientView.ViewBase);
status = ZwCompleteConnectPort(hPortClient);
if (status != 0) {
CloseHandle(hPortClient);
printf( "LpcCompleteConnectPort failed, status=%x\n" , status);
break ;
}
printf( "LpcCompleteConnectPort ok\n" );
break ;
case LPC_REQUEST :
{
#ifdef TEST_VIEW
printf( "m_ClientView.ViewSize: %zu\n" , m_ClientView.ViewSize);
printf( "m_ClientView.Length: %d\n" , m_ClientView.Length);
printf( "m_ClientView.ViewBase: %p\n" , m_ClientView.ViewBase);
printf( "m_ClientView.ViewBase: %s\n" , ( LPSTR )m_ClientView.ViewBase);
lstrcpyA(( LPSTR )m_ClientView.ViewBase, "mapview" );
//m_ClientView.Length = sizeof("mapview");
#endif
printf( "recv Msg: %s \n" , ( LPSTR )RecvPortMsg.Data);
// 填写发送数据.
//lstrcpyA((LPSTR)&RecvPortMsg + dataOffset, "111111");
memset(RecvPortMsg.Data, 0x33, MAX_DATA_LEN - 1);
status = ZwReplyPort(hPortServer, &RecvPortMsg);
if (status != 0) {
printf( "ZwReplyPort failed, status=%x\n" , status);
break ;
}
printf( "ZwReplyPort ok\n" );
}
break ;
case LPC_PORT_CLOSED :
if (hPortClient != INVALID_HANDLE_VALUE )
{
CloseHandle(hPortClient);
hPortClient = INVALID_HANDLE_VALUE ;
}
break ;
default :
printf( "othre type: %d\n" , msg_type);
break ;
}
}
CloseHandle(hPortServer);
//Once the handle pointed to by SectionHandle is no longer in use, the driver must call ZwClose to close it.
ZwClose(m_SectionHandle);
nError = GetLastError();
return nError;
}
DWORD LpcClient( LPCWSTR pwszPortName )
{
NTSTATUS status;
#ifdef TEST_VIEW
HANDLE m_SectionHandle; // 共享内存句柄
PORT_VIEW m_ClientView = { 0 }; // 服务端共享内存映射
REMOTE_PORT_VIEW m_ServerView = { 0 }; // 客户端共享内存映射
LARGE_INTEGER m_SectionSize = { LARGE_MESSAGE_SIZE };
//If the call to this function occurs in user mode, you should
//use the name "NtCreateSection" instead of "ZwCreateSection".
status = NtCreateSection(&m_SectionHandle,
SECTION_ALL_ACCESS ,
NULL ,
&m_SectionSize,
PAGE_READWRITE ,
SEC_COMMIT ,
NULL );
if (! NT_SUCCESS (status))
{
printf( "ZwCreateSection failed, st=%x\n" , status);
return status;
}
// 初始化用于客户端写入的PORT_VIEW
m_ClientView.Length = sizeof ( PORT_VIEW ); //必须是此值
m_ClientView.SectionHandle = m_SectionHandle;
m_ClientView.SectionOffset = 0;
m_ClientView.ViewSize = ( ULONG ) LARGE_MESSAGE_SIZE ;
// 初始化用于读取服务REMOTE_PORT_VIEW
m_ServerView.Length = sizeof ( REMOTE_PORT_VIEW );
#endif
DWORD nError;
HANDLE hClientPort;
UNICODE_STRING ustrPortName;
// 初始化对象属性结构.
RtlInitUnicodeString(&ustrPortName, pwszPortName );
SECURITY_QUALITY_OF_SERVICE sqos;
sqos.Length = sizeof ( SECURITY_QUALITY_OF_SERVICE );
sqos.ImpersonationLevel = SecurityImpersonation ;
sqos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING ;
sqos.EffectiveOnly = FALSE ;
//ULONG len = FIELD_OFFSET(LPC_MESSAGE, Data) + MAX_DATA_LEN;
char ConnectDataBuffer[ MAX_DATA_LEN ];
strcpy_s(ConnectDataBuffer, MAX_DATA_LEN , "123" );
ULONG Size = sizeof (ConnectDataBuffer);
ULONG max_msglen = 0;
//m_ClientView.Length = sizeof("send");
#ifdef TEST_VIEW
status = ZwConnectPort(&hClientPort,
&ustrPortName,
&sqos,
&m_ClientView,
NULL /*&m_ServerView*/ ,
&max_msglen,
ConnectDataBuffer,
&Size);
#else
status = ZwConnectPort(&hClientPort,
&ustrPortName,
&sqos,
NULL /*&m_ClientView*/ ,
NULL /*&m_ServerView*/ ,
&max_msglen,
ConnectDataBuffer,
&Size);
#endif
if (status != 0) {
printf( "Connect failed, status=%x\n" , status);
nError = GetLastError();
return nError;
}
printf( "Connect success.\n" );
printf( "ConnectDataBuffer: %s\n" , ConnectDataBuffer);
MYPORT_MESSAGE Msg;
MYPORT_MESSAGE Out;
memset(&Msg, 0, sizeof (Msg));
memset(&Out, 0, sizeof (Out));
Msg.DataLength = MAX_DATA_LEN ; //最大值为sizeof(MYPORT_MESSAGE) - sizeof(PORT_MESSAGE)
Msg.TotalLength = ( short ) sizeof ( MYPORT_MESSAGE );
printf( "Msg.DataLength %d, Msg.TotalLength:%d\n" , Msg.DataLength, Msg.TotalLength);
memset(Msg.Data, 0x32, MAX_DATA_LEN - 1);
#ifdef TEST_VIEW
//m_ClientView.Length = sizeof("send");
lstrcpyA(( LPSTR )m_ClientView.ViewBase, "send" );
#endif
status = ZwRequestWaitReplyPort(hClientPort, &Msg, &Out);
if (status != 0) {
printf( "ZwRequestWaitReplyPort failed, status=%x\n" , status);
}
else
{
printf( "ZwRequestWaitReplyPort ok\n" );
printf( "recv Msg: %s \n" , ( LPSTR )Out.Data);
#ifdef TEST_VIEW
//printf("m_ServerView.ViewSize: %d\n", m_ServerView.ViewSize);
//printf("m_ServerView.Length: %d\n", m_ServerView.Length);
//printf("m_ServerView.ViewBase: %s\n", m_ServerView.ViewBase);
printf( "m_ClientView.ViewSize: %zu\n" , m_ClientView.ViewSize);
printf( "m_ClientView.Length: %d\n" , m_ClientView.Length);
printf( "m_ClientView.ViewBase: %p\n" , m_ClientView.ViewBase);
printf( "m_ClientView.ViewBase: %s\n" , ( LPSTR )m_ClientView.ViewBase);
printf( "m_ClientView.ViewRemoteBase: %p\n" , m_ClientView.ViewRemoteBase);
#endif
}
CloseHandle(hClientPort);
//Once the handle pointed to by SectionHandle is no longer in use, the driver must call ZwClose to close it.
ZwClose(m_SectionHandle);
nError = GetLastError();
return nError;
}
3、驱动代码
三、结论和疑问
1、结论
1)测试了WinXP、Win7(x86、x64)、Win10(x64),工作正常
建立连接时:ZwConnectPort可以同时发送消息(报文); 建立连接后:ZwRequestWaitReplyPort可同时发送消息(报文)和共享内存
2、疑问
3、其他
看起来,确实挺好用的
关于多个客户端连接同一个服务器和压测,未测试
看雪ID:comor
https://bbs.pediy.com/user-home-809870.htm
*本文由看雪论坛 comor 原创,转载请注明来自看雪社区。
推荐文章++++
* 探究 Process Explorer 进程树选项灰色问题
求分享
求点赞
求在看