LPC(Local Procedure Call),众所周知,是微软未公开(未文档化)的一种进程间通信方式,不仅可以用在应用层进程之间通信,还可以用在应用层和内核层通信。由于在驱动层并没有一种通用的机制主动发起向应用层的通信(minifilter在建立和应用层的端口通信后,可以主动发起通信),而LPC恰好可以弥补这一不足,所以便一探这陈酒。
关于LPC通信的原理、示例代码及API函数逆向的文章很多(主要来自看雪,搜索LPC),但复制较多、历史久远,原创性的内容又大多不开放源码,或者语焉不详,撸了两天(走了很多弯路),成此水文,Demo代码附后,欢迎拍砖。
主要文件两个:lpc.h
、lpc.cpp
; 主要函数: 使用两个函数LpcServer()
和LpcClient()
分别测试Server和Client。
PORT_MESSAGE
的定义及PORT_VIEW
的定义:网上找来的示例,由于久远,可能只是在32位系统上做的测试,几个变量定义固定成了32位长度:比如HANDLE
定义成了ULONG
,一些长度SIZE_T
也定义成了ULONG
;而我的主机是Win10 x64,一开始测试的是Win32配置的工程,而导出函数地址是64位ntdll.dll
的地址,你懂的(/(ㄒoㄒ)/~~),总是出各种莫名其妙的错误,九牛二虎之力运行正确后(忘了怎么就跑起来了),发现收发数据总是错位——总算意识到结构体定义的问题了……修改定义,测试x64配置PORT_MESSAGE
和MY PORT_MESSAGE
:两者的关系是消息头和整个消息的关系,或者说是报文头部与整个报文的关系,定义时使用了public
方法,也可以采用下面定义,比较直观:1
2
3
4typedef struct _MYPORT_MESSAGE {
PORT_MESSAGE Header;
UCHAR Data[ MAX_DATA_LEN ];
} MYPORT_MESSAGE ,
*
PMYPORT_MESSAGE ;
32位上Msg最大328(包括消息头24),64位上Msg最大648(包括消息头40)
),详细见注释//// 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 ); |
|:----|:----|
m_ServerView
:如果只是客户端通过共享内存向服务端发送消息,可以不使用。TEST_VIEW
开启和关闭共享内存测试- `m_ServerView.Length`必须定义为消息头长度,否则出错
- `Msg.DataLength = MAX_DATA_LEN`:如果定义长度小,可能会收到截断的消息
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 | 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;} |
---|
略,基本和LpcClient()
相同,见附件。
注:连接代码放在了DriverEntry,如果连接过程中出问题,驱动启动会卡死,请手动放到工作线程中。
- 建立连接时:`ZwConnectPort`可以同时发送消息(报文);
- 建立连接后:`ZwRequestWaitReplyPort`可同时发送消息(报文)和共享内存Server端的
ZwCreatePort
功能未封装,自取自用。本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。