查看原文
其他

LPC通信撸码笔记

comor 看雪学苑 2022-07-01


本文为看雪论坛优秀文章

看雪论坛作者ID:comor



第一次发帖,写的是没落的Win技术,还有多少人在搞……

LPC(Local Procedure Call),众所周知,是微软未公开(未文档化)的一种进程间通信方式,不仅可以用在应用层进程之间通信,还可以用在应用层和内核层通信。由于在驱动层并没有一种通用的机制主动发起向应用层的通信(minifilter在建立和应用层的端口通信后,可以主动发起通信),而LPC恰好可以弥补这一不足,所以便一探这陈酒。

关于LPC通信的原理、示例代码及API函数逆向的文章很多(主要来自看雪,搜索LPC),但复制较多、历史久远,原创性的内容又大多不开放源码,或者语焉不详,撸了两天(走了很多弯路),成此水文,Demo代码附后,欢迎拍砖。




一、目的


应用层的Demo:分LpcServer和LpcClient,验证报文通信、共享内存通信

驱动Client Demo:作为LpcClient(如果作为Server,相当于应用层主动向驱动通信,还是用Device IO吧),验证报文通信、共享内存通信





二、 主要代码及要点


主要文件两个:lpc.h、lpc.cpp;

主要函数:使用两个函数LpcServer()和LpcClient()分别测试Server和Client。


1、lpc.h


PORT_MESSAGE的定义及PORT_VIEW的定义:网上找来的示例,由于久远,可能只是在32位系统上做的测试,几个变量定义固定成了32位长度:比如HANDLE定义成了ULONG,一些长度SIZE_T也定义成了ULONG;而我的主机是Win10 x64,一开始测试的是Win32配置的工程,而导出函数地址是64位ntdll.dll的地址,你懂的,总是出各种莫名其妙的错误,九牛二虎之力运行正确后,发现收发数据总是错位——总算意识到结构体定义的问题了……修改定义,测试x64配置

PORT_MESSAGE和MY PORT_MESSAGE:两者的关系是消息头和整个消息的关系,或者说是报文头部与整个报文的关系,定义时使用了public方法,也可以采用下面定义,比较直观:

typedef struct _MYPORT_MESSAGE { PORT_MESSAGE Header; UCHAR Data[ MAX_DATA_LEN ];} MYPORT_MESSAGE , * PMYPORT_MESSAGE ;

消息长度:经过测试,消息最大长度和一些参考书或者代码说的不太一致,有的说是256(不知道怎么来的),有的说是消息最长0x148(328,和32位测试一致),数据最长0x104(260),测试结果为(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 );

2、lpc.cpp


服务端的m_ServerView:如果只是客户端通过共享内存向服务端发送消息,可以不使用。

通过宏定义TEST_VIEW开启和关闭共享内存测试。

数据长度的赋值:

  • 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、驱动代码


略,基本和LpcClient()相同,见附件。

注:连接代码放在了DriverEntry,如果连接过程中出问题,驱动启动会卡死,请手动放到工作线程中。





三、结论和疑问


1、结论


1)测试了WinXP、Win7(x86、x64)、Win10(x64),工作正常


2)发送消息的方式:
  • 建立连接时:ZwConnectPort可以同时发送消息(报文);
  • 建立连接后:ZwRequestWaitReplyPort可同时发送消息(报文)和共享内存

3)Server端的hPortClient好像没什么用。

4)并没有哪个字段标识收发消息数据的长度或者共享内存有效数据的长度,需要自定义。

5)32位和64位不通用,即32位Client不能连接64位Server,可以参考老V的回答 https://bbs.pediy.com/thread-181647.htm 解决这一问题(未验证),或者其他“奇淫巧计”。


2、疑问


消息最大长度限制的由来?逆向不太熟,应该是这个函数ZwCreatePort功能未封装,自取自用。


3、其他


  • 看起来,确实挺好用的

  • 关于多个客户端连接同一个服务器和压测,未测试




- End -



看雪ID:comor

https://bbs.pediy.com/user-home-809870.htm

  *本文由看雪论坛 comor 原创,转载请注明来自看雪社区。



推荐文章++++

* 内核APC调用过程学习笔记

* 探究 Process Explorer 进程树选项灰色问题

* Linux Kernel Pwn 学习笔记(栈溢出)

* ARM栈回溯——从理论到实践,开发IDA-arm-unwind-plugin

* CVE-2020-1048、CVE-2020-1337漏洞原理及利用







公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



求分享

求点赞

求在看


“阅读原文”一起来充电吧!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存