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

📄

📁 兼容内核漫谈 适合想将Windows上的程序移植到其它平台上的朋友研究查看
💻
📖 第 1 页 / 共 4 页
字号:
      ExFreePool(CReply);
      ObDereferenceObject(NamedPort);
      return (STATUS_SUCCESS);
  }

  /* Prepare the connection. */
  if (WriteMap != NULL)
  {
      PSECTION_OBJECT SectionObject;
      LARGE_INTEGER SectionOffset;

      Status = ObReferenceObjectByHandle(WriteMap->SectionHandle,
                 SECTION_MAP_READ | SECTION_MAP_WRITE,
                 MmSectionObjectType, UserMode, (PVOID*)&SectionObject, NULL);
      . . . . . .

      SectionOffset.QuadPart = WriteMap->SectionOffset;
      WriteMap->TargetViewBase = 0;
      CReply->ReceiveClientViewSize = WriteMap->ViewSize;
      Status = MmMapViewOfSection(SectionObject, CRequest->ConnectingProcess,
                  &WriteMap->TargetViewBase, 0, CReply->ReceiveClientViewSize,
                  &SectionOffset, &CReply->ReceiveClientViewSize,
                  ViewUnmap, 0, PAGE_READWRITE);
      . . . . . .

      WriteMap->ViewBase = 0;
      Status = MmMapViewOfSection(SectionObject, PsGetCurrentProcess(),
                  &WriteMap->ViewBase, 0, WriteMap->ViewSize,
                  &SectionOffset, &WriteMap->ViewSize,
                  ViewUnmap, 0, PAGE_READWRITE);
      . . . . . .
     
      ObDereferenceObject(SectionObject);
  }
  if (ReadMap != NULL && CRequest->SendSectionObject != NULL)
  {
      LARGE_INTEGER SectionOffset;

      SectionOffset = CRequest->SendSectionOffset;
      ReadMap->ViewSize = CRequest->SendViewSize;
      ReadMap->ViewBase = 0;
      Status = MmMapViewOfSection(
                  CRequest->SendSectionObject, PsGetCurrentProcess(),
                  &ReadMap->ViewBase, 0, CRequest->SendViewSize,
                  &SectionOffset, &CRequest->SendViewSize,
                  ViewUnmap, 0, PAGE_READWRITE);
      . . . . . .
  }

  /* Finish the reply. */
  if (ReadMap != NULL)
  {
      CReply->SendServerViewBase = ReadMap->ViewBase;
  }
  else
  {
      CReply->SendServerViewBase = 0;
  }
  if (WriteMap != NULL)
  {
      CReply->ReceiveClientViewBase = WriteMap->TargetViewBase;
  }
  CReply->MaximumMessageSize = PORT_MAX_MESSAGE_LENGTH;

  /* Connect the two ports */
  OurPort->OtherPort = ConnectionRequest->Sender;
  OurPort->OtherPort->OtherPort = OurPort;
  EiReplyOrRequestPort(ConnectionRequest->Sender,
                        (PLPC_MESSAGE)CReply, LPC_REPLY, OurPort);
  ExFreePool(ConnectionRequest);
  ExFreePool(CReply);
  
  ObDereferenceObject(OurPort);
  ObDereferenceObject(NamedPort);

  return (STATUS_SUCCESS);
}[/code]

    如果接受连接请求,那么服务方也要创建一个通信端口,因为原来的连接端口是专门用来接收连接请求的。第一个参数ServerPortHandle就是用来返回新建通信端口的Handle。而NamedPortHandle当然就是连接端口的Handle,这是本次操作的目标对象。
    参数AcceptIt表示是否接受连接请求。
    参数WriteMap和ReadMap与NtConnectPort()中所用者相同。同样,如果预期需要发送的数据量较大的话,服务方也要为此提供一个共享内存区。
    先看不接受连接请求时的情况,因为这比较简单。这就是条件语句if (!AcceptIt)里面的操作:先将一个“拒绝连接”报文、即类型为LPC_CONNECTION_REFUSED的报文、通过EiReplyOrRequestPort()挂入对方端口的报文队列,然后在对方端口的“信号量”上执行一次V操作,以唤醒正在等待的对方线程。这样就行了。
    接受连接请求时的情况就比较复杂一点:
1. 先创建一个通信端口,就是类型为EPORT_TYPE_SERVER_COMM_PORT的端口。
2. 然后为应答报文LpcMessage准备好一个内核版本、就是类型为EPORT_CONNECT_REPLY_MESSAGE的数据结构Creply。
3. 处理共享内存区的映射。注意这里做了三次映射:
? l 把由服务方提供的共享内存区映射到客户进程的用户空间,这是客户方的接收区。“连接请求”报文中的ConnectingProcess提供了指向客户进程的EPROCESS数据结构的指针。
? l 把由服务方提供的共享内存区映射到服务方自己的用户空间,这是服务方进程的写入区。
? l 把由客户方提供的共享内存区映射到服务方的用户空间,这是服务方进程的读出区。
    注意这里在调用MmMapViewOfSection()时所给定的地址都是0,表示听从分配。所分配的地址要通过WriteMap和ReadMap返回到用户空间,特别是替客户方进程代为映射的地址要通过应答报文发送给对方。
4. 使服务方通信端口和客户方通信端口的指针OtherPort互相指向对方,即建立连接。
5. 通过EiReplyOrRequestPort()将应答报文挂入客户方端口的报文队列,但是并不唤醒客户方线程。

    在完成了NtAcceptConnectPort()以后,服务方线程还需要对新创建的通信端口执行一下另一个系统调用NtCompleteConnectPort()。目的在于唤醒客户方线程。注意此时的操作对象已经是新创建的通信端口,而不再是连接端口。

[code]NTSTATUS STDCALL
NtCompleteConnectPort (HANDLE hServerSideCommPort)
{
  NTSTATUS Status;
  PEPORT ReplyPort;

  . . . . . .
  /* Ask Ob to translate the port handle to EPORT */
  Status = ObReferenceObjectByHandle (hServerSideCommPort, PORT_ALL_ACCESS,
                        LpcPortObjectType, UserMode, (PVOID*)&ReplyPort, NULL);
  . . . . . .
  /* Verify EPORT type is a server-side reply port; otherwise tell the caller
    the port handle is not valid. */
  if (ReplyPort->Type != EPORT_TYPE_SERVER_COMM_PORT)
    {
       ObDereferenceObject (ReplyPort);
       return STATUS_INVALID_PORT_HANDLE;
    }

  ReplyPort->State = EPORT_CONNECTED_SERVER;
  /* Wake up the client thread that issued NtConnectPort. */
  KeReleaseSemaphore(&ReplyPort->OtherPort->Semaphore,
                         IO_NO_INCREMENT, 1, FALSE);
  /* Tell Ob we are no more interested in ReplyPort */  
  ObDereferenceObject (ReplyPort);
  return (STATUS_SUCCESS);
}[/code]

    前面,在NtAcceptConnectPort()的代码中,虽然已经将应答报文挂入了客户方端口的接收队列,却并未唤醒客户方线程。现在就通过对其信号量的V操作将其唤醒。
    这样,就建立起了客户方与服务方的一对通信端口的连接。以后就可以通过这个连接通信了。一般总是服务方线程先通过NtReplyWaitReceivePort()或NtReplyWaitReceivePortEx()等待对方发来报文,由于Port机制实际上只用于LPC,客户方发往服务方的一般都是服务请求报文,而服务方则根据具体的请求提供服务,然后发回应答报文、一般是返回结果。不过,也并没有规定必须是服务方等待客户方的报文,反过来也并无不可。
    不管是那一方,需要向对方发送一个报文时可以通过系统调用NtRequestPort()发送。

[code]NTSTATUS STDCALL
NtRequestPort (IN HANDLE PortHandle, IN PLPC_MESSAGE LpcMessage)
{
   . . . . . .
  
   Status = ObReferenceObjectByHandle(PortHandle, PORT_ALL_ACCESS,
                    LpcPortObjectType, UserMode, (PVOID*)&Port, NULL);
   . . . . . .
   Status = LpcRequestPort(Port->OtherPort, LpcMessage);
   ObDereferenceObject(Port);
   return(Status);
}[/code]

    显然,具体的操作是由LpcRequestPort()完成的。区别在于LpcRequestPort()要求使用指向EPORT数据结构的指针,而传给NtRequestPort()的是Handle,需要加以转换。Handle本质上是数组下标,所以从Handle到结构指针的转换开销并不大。

[code][NtRequestPort() > LpcRequestPort()]

NTSTATUS STDCALL LpcRequestPort (IN PEPORT  Port,
                                   IN PLPC_MESSAGE  LpcMessage)
{
   NTSTATUS Status;
  
   DPRINT("LpcRequestPort(PortHandle %08x, LpcMessage %08x)\n", Port, LpcMessage);

#ifdef __USE_NT_LPC__
   /* Check the message's type */
   if (LPC_NEW_MESSAGE == LpcMessage->MessageType)
   {
      LpcMessage->MessageType = LPC_DATAGRAM;
   }
   else if (LPC_DATAGRAM == LpcMessage->MessageType)
   {
      return STATUS_INVALID_PARAMETER;
   }
   else if (LpcMessage->MessageType > LPC_CLIENT_DIED)
   {
      return STATUS_INVALID_PARAMETER;
   }
   /* Check the range offset */
   if (0 != LpcMessage->VirtualRangesOffset)
   {
      return STATUS_INVALID_PARAMETER;
   }
#endif

   Status = EiReplyOrRequestPort(Port, LpcMessage, LPC_DATAGRAM, Port);
   KeReleaseSemaphore(&Port->Semaphore, IO_NO_INCREMENT, 1, FALSE );
   return(Status);
}[/code]

    可见,NtRequestPort()只是发送,而并不等待对方的回应。如果需要等待回应的话可以采用另一个系统调用NtRequestWaitReplyPort()。
    需要向对方发送应答报文时可以用NtReplyPort()。

[code]NTSTATUS STDCALL
NtReplyPort (IN HANDLE PortHandle, IN PLPC_MESSAGE LpcReply)
{
   NTSTATUS Status;
   PEPORT Port;
  
   DPRINT("NtReplyPort(PortHandle %x, LpcReply %x)\n", PortHandle, LpcReply);
  
   Status = ObReferenceObjectByHandle(PortHandle, PORT_ALL_ACCESS,
                        LpcPortObjectType, UserMode, (PVOID*)&Port, NULL);
   . . . . . .
   Status = EiReplyOrRequestPort(Port->OtherPort, LpcReply, LPC_REPLY, Port);
   KeReleaseSemaphore(&Port->OtherPort->Semaphore, IO_NO_INCREMENT, 1, FALSE);
  
   ObDereferenceObject(Port);
  
   return(Status);
}[/code]

    当然,这是纯粹的发送应答报文,如果是发送应答报文并且等待下一个请求,那就要用NtReplyWaitReceivePort(),这读者已经在前面看到了。

    可见,Port是一种功能相当强、相当齐全、结构又相当完整的综合性的进程间通信机制,这样的机制理应提供给应用软件的开发者,或者在Win32 API上提供相应的库函数,或是把有关的系统调用公诸于世。但是微软却并不这么干,倒是一方面讳莫如深,一方面供自己的软件内部使用。这样,如果都来开发应用软件,那别的公司如何能与微软公平竞争呢?正因为如此,美国一直有人在呼吁甚至提起诉讼,要把操作系统和应用软件的开发分拆开来,不能让同一家公司既做操作系统又做应用软件。另一方面,这也可以解释为什么总是有这许多人热衷于探究Windows和相关产品的“Undocumented…”、“…Internals”、“Inside…”。

    最后还要提一下,有些资料中还提到Windows有一种“快捷LPC(QuickLPC)”机制。这就是建立在上一篇漫谈中讲到的“事件对”基础上的LPC。早期Windows上的csrss通信太频繁了,需要有一种非常轻快的进程间通信手段,所以才有了QuickLPC。现在,一方面是csrss的功能大都移到了内核中,一方面是处理器的速度也有了量级的提高,QuickLPC就变得不那么重要了。

⌨️ 快捷键说明

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