LPC通信撸码笔记
2020-08-27 02:53:55 Author: bbs.pediy.com(查看原文) 阅读量:392 收藏

第一次发帖,写的是没落的Win技术,还有多少人在搞。。
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方法,也可以采用下面定义,比较直观:
    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、结论

  • 测试了WinXP、Win7(x86、x64)、Win10(x64),工作正常
  • 发送消息的方式:

    • 建立连接时:ZwConnectPort可以同时发送消息(报文);
    • 建立连接后:ZwRequestWaitReplyPort可同时发送消息(报文)和共享内存
  • Server端的hPortClient好像没什么用

  • 并没有哪个字段标识收发消息数据的长度或者共享内存有效数据的长度,需要自定义。
  • 32位和64位不通用,即32位Client不能连接64位Server,可以参考老V的回答( https://bbs.pediy.com/thread-181647.htm )解决这一问题(未验证),或者其他“奇淫巧计”

2、疑问

  • 消息最大长度限制的由来?逆向不太熟,应该是这个函数ZwCreatePort

    功能未封装,自取自用。

3、其他

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

《0day安全 软件漏洞分析技术(第二版)》第三次再版印刷预售开始!

最后于 5小时前 被comor编辑 ,原因:


文章来源: https://bbs.pediy.com/thread-261668.htm
如有侵权请联系:admin#unsafe.sh