LPC通信撸码笔记

  • A+
所属分类:安全开发

LPC通信撸码笔记

本文为看雪论坛优秀文章

看雪论坛作者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=%xn" , 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%08xn" , status);        nError = GetLastError();         return  nError;    }      MYPORT_MESSAGE     RecvPortMsg;     //MYPORT_MESSAGE    ReplyPortMsg;     //memset(&ReplyPortMsg, 0, sizeof(ReplyPortMsg));    printf( "MYPORT_MESSAGE size:%zu %zun" ,  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%08xn" , status);             break ;        }        printf( "ZwReplyWaitReceivePort okn" );        msg_type = RecvPortMsg.Type;        printf( "msg_type: %d n" , msg_type);        printf( "RecvPortMsg.DataLength %dn" , RecvPortMsg.DataLength);        printf( "RecvPortMsg.TotalLength:%dn" , RecvPortMsg.TotalLength);        printf( "RecvPortMsg.UniqueProcess:%zun" , ( SIZE_T )RecvPortMsg.ClientId.UniqueProcess);        printf( "RecvPortMsg.UniqueThread:%zun" , ( 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=%xn" , status);                 break ;            }            printf( "LpcAcceptConnectPort okn" );              //printf("m_ClientView.ViewSize: %dn", m_ClientView.ViewSize);             //printf("m_ClientView.Length: %dn", m_ClientView.Length);             //printf("m_ClientView.ViewBase: %pn", m_ClientView.ViewBase);             status = ZwCompleteConnectPort(hPortClient);             if  (status != 0) {                CloseHandle(hPortClient);                printf( "LpcCompleteConnectPort failed, status=%xn" , status);                 break ;            }            printf( "LpcCompleteConnectPort okn" );             break ;         case   LPC_REQUEST :        {#ifdef   TEST_VIEW            printf( "m_ClientView.ViewSize: %zun" , m_ClientView.ViewSize);            printf( "m_ClientView.Length: %dn" , m_ClientView.Length);            printf( "m_ClientView.ViewBase: %pn" , m_ClientView.ViewBase);            printf( "m_ClientView.ViewBase: %sn" , ( 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=%xn" , status);                 break ;            }            printf( "ZwReplyPort okn" );        }             break ;         case   LPC_PORT_CLOSED :             if  (hPortClient !=  INVALID_HANDLE_VALUE )            {                CloseHandle(hPortClient);                hPortClient =  INVALID_HANDLE_VALUE ;            }             break ;         default :            printf( "othre type: %dn" , 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=%xn" , 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=%xn" , status);        nError = GetLastError();         return  nError;    }     printf( "Connect success.n" );    printf( "ConnectDataBuffer: %sn" , 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:%dn" , 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=%xn" , status);    }     else    {        printf( "ZwRequestWaitReplyPort okn" );        printf( "recv Msg: %s n" , ( LPSTR )Out.Data); #ifdef   TEST_VIEW         //printf("m_ServerView.ViewSize: %dn", m_ServerView.ViewSize);         //printf("m_ServerView.Length: %dn", m_ServerView.Length);         //printf("m_ServerView.ViewBase: %sn", m_ServerView.ViewBase);        printf( "m_ClientView.ViewSize: %zun" , m_ClientView.ViewSize);        printf( "m_ClientView.Length: %dn" , m_ClientView.Length);        printf( "m_ClientView.ViewBase: %pn" , m_ClientView.ViewBase);        printf( "m_ClientView.ViewBase: %sn" , ( LPSTR )m_ClientView.ViewBase);        printf( "m_ClientView.ViewRemoteBase: %pn" , 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、其他


  • 看起来,确实挺好用的

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



LPC通信撸码笔记
- End -


LPC通信撸码笔记


看雪ID:comor

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

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



推荐文章++++

LPC通信撸码笔记

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

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

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

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

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







LPC通信撸码笔记
公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]



求分享

求点赞

LPC通信撸码笔记

求在看


LPC通信撸码笔记
“阅读原文一起来充电吧!

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: