第一次发帖,写的是没落的Win技术,还有多少人在搞。。
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
方法,也可以采用下面定义,比较直观:typedef 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
:如果定义长度小,可能会收到截断的消息
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端的hPortClien
t好像没什么用
消息最大长度限制的由来?逆向不太熟,应该是这个函数ZwCreatePort
功能未封装,自取自用。
《0day安全 软件漏洞分析技术(第二版)》第三次再版印刷预售开始!
最后于 5小时前 被comor编辑 ,原因: