前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >LPC通信撸码笔记

LPC通信撸码笔记

作者头像
franket
发布2021-08-10 11:49:24
7940
发布2021-08-10 11:49:24
举报
文章被收录于专栏:技术杂记技术杂记

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

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

一、目的

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

二、 主要代码及要点

主要文件两个:lpc.hlpc.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的地址,你懂的(/(ㄒoㄒ)/~~),总是出各种莫名其妙的错误,九牛二虎之力运行正确后(忘了怎么就跑起来了),发现收发数据总是错位——总算意识到结构体定义的问题了……修改定义,测试x64配置
  • PORT_MESSAGEMY PORT_MESSAGE:两者的关系是消息头和整个消息的关系,或者说是报文头部与整个报文的关系,定义时使用了public方法,也可以采用下面定义,比较直观:1 2 3 4typedef   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开启和关闭共享内存测试
  • 数据长度的赋值:
代码语言:txt
复制
- `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;}

3、驱动代码

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

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

三、结论和疑问

1、结论

  • 测试了WinXP、Win7(x86、x64)、Win10(x64),工作正常
  • 发送消息的方式:
代码语言:txt
复制
- 建立连接时:`ZwConnectPort`可以同时发送消息(报文);
- 建立连接后:`ZwRequestWaitReplyPort`可同时发送消息(报文)和共享内存Server端的

2、疑问

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

3、其他

  • 看起来,确实挺好用的
  • 关于多个客户端连接同一个服务器和压测,未测试

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
作者已关闭评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、目的
  • 二、 主要代码及要点
    • 1、lpc.h
      • 2、lpc.cpp
        • 3、驱动代码
        • 三、结论和疑问
          • 1、结论
            • 2、疑问
              • 3、其他
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档