⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄

📁 兼容内核漫谈 适合想将Windows上的程序移植到其它平台上的朋友研究查看
💻
📖 第 1 页 / 共 4 页
字号:
       CRequest = (PEPORT_CONNECT_REQUEST_MESSAGE)&Request->Message;
       memcpy(&Header, &Request->Message, sizeof(LPC_MESSAGE));
       Header.DataSize = CRequest->ConnectDataLength;
       Header.MessageSize = Header.DataSize + sizeof(LPC_MESSAGE);
       Status = MmCopyToCaller(LpcMessage, &Header, sizeof(LPC_MESSAGE));
       if (NT_SUCCESS(Status))
       {
         Status = MmCopyToCaller((PVOID)(LpcMessage + 1),
                         CRequest->ConnectData, CRequest->ConnectDataLength);
       }
   }
   else
   {
       Status = MmCopyToCaller(LpcMessage, &Request->Message,
                                   Request->Message.MessageSize);
   }
   . . . . . .
   if (Request->Message.MessageType == LPC_CONNECTION_REQUEST)
   {
       KeAcquireSpinLock(&Port->Lock, &oldIrql);
       EiEnqueueConnectMessagePort(Port, Request);
       KeReleaseSpinLock(&Port->Lock, oldIrql);
   }
   else
   {
       ExFreePool(Request);
   }
  
   /* Dereference the port */
   ObDereferenceObject(Port);
   return(STATUS_SUCCESS);
}[/code]

    在Port机制的实现中,NtReplyWaitReceivePortEx()是个经常用到的函数。刚才我们看到的是从NtListenPort()逐层调用下来的,所以没有应答报文需要发送,直接就进入等待接收报文的阶段了,但在多数情况下是有应答报文要发送的,服务线程运行的典型情景就是“接收-处理-应答-再接收”,所以把应答和再接收合并在一起。因此,我们也应看一下在有应答报文要发送时的操作,就是代码中if (LpcReply != NULL && !Disconnected)语句里面的操作。
    当然,如果端口已经处于断开状态、即Disconnected为TRUE,那就不发送应答报文了。从代码中可以看出,应答报文的发送是通过EiReplyOrRequestPort()完成的。完成以后还要向对方端口的“信号量”执行一次V操作,即增量为1的KeReleaseSemaphore()操作,如果对方线程正在睡眠等待就把它唤醒。后面这一步属于线程间同步,读者想必已经熟悉了,这里看一下EiReplyOrRequestPort()的代码:

[code][NtReplyWaitReceivePort() > NtReplyWaitReceivePortEx()> EiReplyOrRequestPort()]

NTSTATUS STDCALL
EiReplyOrRequestPort (IN PEPORT  Port,
                     IN PLPC_MESSAGE  LpcReply,
                     IN ULONG  MessageType,
                     IN PEPORT  Sender)
{
   . . . . . .

   MessageReply = ExAllocatePoolWithTag(NonPagedPool, sizeof(QUEUEDMESSAGE),
                                                      TAG_LPC_MESSAGE);
   MessageReply->Sender = Sender;
  
   if (LpcReply != NULL)
   {
      memcpy(&MessageReply->Message, LpcReply, LpcReply->MessageSize);
   }
   MessageReply->Message.ClientId.UniqueProcess = PsGetCurrentProcessId();
   MessageReply->Message.ClientId.UniqueThread = PsGetCurrentThreadId();
   MessageReply->Message.MessageType = MessageType;
   MessageReply->Message.MessageId = InterlockedIncrementUL(&LpcpNextMessageId);
  
   KeAcquireSpinLock(&Port->Lock, &oldIrql);
   EiEnqueueMessagePort(Port, MessageReply);
   KeReleaseSpinLock(&Port->Lock, oldIrql);
  
   return(STATUS_SUCCESS);
}[/code]
    参数LpcReply是从上面传下来的指针,指向一个LPC_MESSAGE数据结构,这就是待发送的报文。下面是LPC_MESSAGE数据结构的格式定义:

[code]typedef struct _LPC_MESSAGE {
USHORT  DataSize;
USHORT  MessageSize;
USHORT  MessageType;
USHORT  VirtualRangesOffset;
CLIENT_ID  ClientId;
ULONG  MessageId;
ULONG  SectionSize;
UCHAR  Data[ANYSIZE_ARRAY];
} LPC_MESSAGE, *PLPC_MESSAGE;[/code]

    实际上这只是报文的头部,后面的不定长数组Data[ANYSIZE_ARRAY]才是具体的报文。这里ANYSIZE_ARRAY定义为1,其实定义成0也可以,只是表示从这儿开始才是具体的报文内容。具体报文的数据结构可以由使用者自行定义,接收方根据头部的MessageType就可以知道收到的是什么报文。不过Port机制定义了两种特殊的报文用于建立连接的过程:

[code]typedef struct _EPORT_CONNECT_REQUEST_MESSAGE
{
  LPC_MESSAGE MessageHeader;
  PEPROCESS ConnectingProcess;
  struct _SECTION_OBJECT* SendSectionObject;
  LARGE_INTEGER SendSectionOffset;
  ULONG SendViewSize;
  ULONG ConnectDataLength;
  UCHAR ConnectData[0];
} EPORT_CONNECT_REQUEST_MESSAGE;[/code]

    这就是“连接请求”报文,其第一个成分是一个LPC_MESSAGE数据结构,那就是报文的头部。下面的“连接应答”报文也是一样。前面的参数LpcReply表面上是LPC_MESSAGE指针,但是实际上却可以是具体报文的指针。注意最后的ConnectData[0]表示在这个数据结构的后面还可以有不定长的数据,报文头部的DataSize就反映了这部分数据的大小。所以,报文头部的Data[ANYSIZE_ARRAY]实际上是具体报文的正身,而具体报文如“连接请求”中的ConnectData[0]则是随同具体报文发送的数据。不过这是额外的数据,因为建立连接所必需的信息已经体现在报文正身的各个字段中了。

[code]typedef struct _EPORT_CONNECT_REPLY_MESSAGE
{
  LPC_MESSAGE MessageHeader;
  PVOID SendServerViewBase;
  ULONG ReceiveClientViewSize;
  PVOID ReceiveClientViewBase;
  ULONG MaximumMessageSize;
  ULONG ConnectDataLength;
  UCHAR ConnectData[0];
} EPORT_CONNECT_REPLY_MESSAGE;[/code]

    最后,包括额外数据在内的报文总长度是有限的(256字节),如果是大块数据就要通过共享缓冲区发送。
    再看上面EiReplyOrRequestPort()的代码。它先在内核空间分配一个QUEUEDMESSAGE数据结构作为报文的“容器”,再把需要发送的报文拷贝到这个数据结构中。注意这里的LpcReply有可能是从用户空间传下来的,相应的缓冲区也在用户空间,所以需要把它搬到内核空间的缓冲区中。然后,真正的“发送”操作其实只是把这个数据结构挂入目标端口的接收报文队列中,这是由EiEnqueueMessagePort()完成的。注意这里的LpcpNextMessageId是报文的序号,每次递增。还要说明,拷贝到QUEUEDMESSAGE数据结构中的只是报文本身,而不包括通过共享内存区传递的数据,这正是为什么要使用共享内存区的原因。

    回到NtReplyWaitReceivePortEx()的代码。由于没有应答报文要发送,这里直接就通过KeWaitForSingleObject()进入了睡眠等待。当这个线程接收到了报文而被唤醒(在这里我们忽略超时的可能)时,端口的报文队列里已经有了报文,所以通过EiDequeueMessagePort()从队列中摘下一个报文的数据结构。如果这是个“连接请求”报文,就把它复制到用户提供的缓冲区中,代码中的两次调用MmCopyToCaller()就是分别复制报文的正身及其所附带的数据。其实不是“连接请求”报文也要复制,只不过此时只复制报文的正身。
    然后,对于“连接请求”报文,这里还通过EiEnqueueConnectMessagePort()把它挂入端口的ConnectQueueListHead队列,这是为随后的“接受连接”操作、即系统调用NtAcceptConnectPort()留下一个参考,后面我们就会看到其作用。
    不过,我们这儿讲的只是如果接收到了连接请求就要做些什么操作,但是实际上此刻还没有客户方提出连接请求,所以服务方线程只是睡眠等待。

    至此,服务方已经作好了准备,就等着客户方的连接请求了。如前所述,客户方通过系统调用NtConnectPort()向命名的连接端口请求连接。注意这里连接的目标就是一个连接端口,而并不指定某个具体的进程。哪一个进程在这个连接端口上执行了NtListenPort()并因此而受到阻塞正在等待,这请求就实际上是发给那个进程(线程)的。
    请求连接的一方对于将来要发送多大的数据量应该是心里有数的。如果数据量不大,那就可以作为附加信息随同报文(在同一个缓冲区中)一起发送。但是,要是数据量比较大(报文总长大于256字节),那就要通过共享内存区“发送”,因为否则的话一来传输效率低下,二来报文缓冲区的大小也不好静态地安排确定。所以,在期望的发送数据量比较大时要准备好一个共享内存区(Section)用于数据发送。这是要由请求连接的一方、即客户方做好准备,通过调用参数传给NtConnectPort()的。为此客户方要准备好两个数据结构,就是LPC_SECTION_WRITE和LPC_SECTION_READ,把有关共享内存区的信息填写在前一个数据结构中,再把指向这两个数据结构的指针作为参数传给NtConnectPort()。
    这两个数据结构的定义为:

[code]typedef struct _LPC_SECTION_WRITE {
ULONG  Length;
HANDLE  SectionHandle;
ULONG  SectionOffset;
ULONG  ViewSize;
PVOID  ViewBase;
PVOID  TargetViewBase;
} LPC_SECTION_WRITE;

typedef struct _LPC_SECTION_READ {
ULONG  Length;
ULONG  ViewSize;
PVOID  ViewBase;
} LPC_SECTION_READ;[/code]

    注意客户方只提供(和映射)用于它写入(发送)的共享内存区,而反方向的共享内存区则由服务方提供(和映射)。所以LPC_SECTION_READ数据结构只是用来获取映射后的结果。
    下面看NtConnectPort()的代码。

[code]NTSTATUS STDCALL
NtConnectPort (PHANDLE    UnsafeConnectedPortHandle,
        PUNICODE_STRING   PortName,
        PSECURITY_QUALITY_OF_SERVICE Qos,
        PLPC_SECTION_WRITE  UnsafeWriteMap,
        PLPC_SECTION_READ  UnsafeReadMap,
        PULONG    UnsafeMaximumMessageSize,
        PVOID    UnsafeConnectData,
        PULONG    UnsafeConnectDataLength)
{
  . . . . . .

  /* Copy in write map and partially validate. */
  . . . . . .
  /* Handle connection data. */
  . . . . . .
  /* Reference the named port. */
  Status = ObReferenceObjectByName (PortName, 0, NULL, PORT_ALL_ACCESS,
                       LpcPortObjectType, UserMode, NULL, (PVOID*)&NamedPort);
  . . . . . .
  /* Reference the send section object. */
  if (WriteMap.SectionHandle != INVALID_HANDLE_VALUE)
  {
      Status = ObReferenceObjectByHandle(WriteMap.SectionHandle,
                SECTION_MAP_READ | SECTION_MAP_WRITE,
                MmSectionObjectType, UserMode, (PVOID*)&SectionObject, NULL);
      . . . . . .
  }
  else
  {
      SectionObject = NULL;
  }

  /* Do the connection establishment. */
  Status = EiConnectPort(&ConnectedPort, NamedPort, SectionObject, SectionOffset,
             WriteMap.ViewSize, &WriteMap.ViewBase, &WriteMap.TargetViewBase,
             &ReadMap.ViewSize, &ReadMap.ViewBase, &MaximumMessageSize,
             ConnectData, &ConnectDataLength);
  . . . . . .

  /* Do some initial cleanup. */
  if (SectionObject != NULL)
  {
      ObDereferenceObject(SectionObject);
      SectionObject = NULL;
  }
  ObDereferenceObject(NamedPort);
  NamedPort = NULL;

  /* Copy the data back to the caller. */
  . . . . . .

  Status = ObInsertObject(ConnectedPort, NULL, PORT_ALL_ACCESS,
                                  0, NULL, &ConnectedPortHandle);
  . . . . . .
  Status = MmCopyToCaller(UnsafeConnectedPortHandle,
                        &ConnectedPortHandle, sizeof(HANDLE));
  . . . . . .
  if (UnsafeWriteMap != NULL)
  {
      Status = MmCopyToCaller(UnsafeWriteMap,
                        &WriteMap, sizeof(LPC_SECTION_WRITE));
      . . . . . .
  }
  if (UnsafeReadMap != NULL)
  {
      Status = MmCopyToCaller(UnsafeReadMap,
                        &ReadMap, sizeof(LPC_SECTION_READ));
      . . . . . .
  }
  . . . . . .
  return(STATUS_SUCCESS);

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -