LPC (Local procedure calls) (一)数据结构
什么是LPC
LPC(Local-Process-communicationandnotlocalprocedure-Calls)是一种在NT内核中实现的基于消息的高速通信机制。LPC可用于两个用户模式进程之间、用户模式进程与内核模式驱动程序之间或两个内核模式驱动程序之间的通信。一个例子是通过LPC通信的两个用户模式进程。像CSRSS.exe与SMSS.exe通信,在创建登录会话或任何与LSASS.exe。出于安全原因,通过lsa认证端口。用户模式进程与内核模式驱动程序通信的另一个例子是 KSecDD.sys司与LSASS.exe通信用于在读/写加密文件期间对EFS密钥进行加密和解密。
LPC使用两种不同的机制在客户端和服务器进程之间传递数据。它使用LPC消息缓冲区(对于小于304字节的数据),或者使用映射到客户机和服务器地址空间的共享内存部分(对于大于304字节的数据)。
除了用作在同一系统上运行的进程之间选择远程过程调用的协议外,LPC还用于整个系统,例如用于Win32应用程序与CSRSS.exe文件,安全参考监视器与LSASS的通信,WinLogon与LSASS的通信等。
LPC强制客户机进程和服务器进程之间的同步通信模型。Vista反对使用一种称为异步本地进程间通信(ALPC)的新机制来使用LPC。与LPC相比,ALPC有一个固有的优势,即从客户端到服务器的所有调用都是异步的,即客户端不需要阻塞/等待服务器响应消息。在Vista中,对lpcapis的旧应用程序调用会自动重定向到更新的alpcapi。
LPC APIs
LPC api是本机api,即它们在用户模式下通过NTDLL.dll在内核模式下NTOSKRNL.exe文件. lpcapis没有在Win32级别公开,因此Win32应用程序不能直接使用LPC工具。然而,在使用RPC时,Win32应用程序可以通过协议序列“ncalrpc”将LPC指定为其底层传输,从而间接地使用LPC。所有lpcapis都以单词“Port”结尾,这意味着LPC通信端点。
API |
Description |
NtCreatePort |
Used by server to create a connection port |
NtConnectPort |
Used by client to connect to a connection port |
NtListenPort |
Used by server to listen for connection requests on the connection port. |
NtAcceptConnectPort |
Used by server to accept connection requests on the connection port |
NtCompleteConnectPort |
Used by server to complete the acceptance of a connection request |
NtRequestPort |
Used to send a datagram message that does not have a reply |
NtRequestWaitReplyPort |
Used to send a message and wait for a reply |
NtReplyPort |
Used to send a reply to a particular message |
NtReplyWaitReplyPort |
Used to send a reply to a particular message and wait for a reply to a previous message |
NtReplyWaitReceivePort |
Used by server to send a reply to the client and wait to receive a message from the client |
NtImpersonateClientOfPort |
Used by server thread to temporarily borrow the security context of a client thread |
下图说明了LPC服务器进程侦听来自潜在客户端的连接请求所采取的步骤,以及客户端连接到侦听服务器所采取的步骤。
LPC客户机-服务器连接建立顺序
注意:许多服务器进程使用NtReplyWaitReceivePort()API而不是NtListenPort()。NtListenPort()除去连接请求之外的所有LPC消息。因此NtListenPort()只能用于第一个连接。对于以后的连接请求,使用NtReplyWaitReceivePort()。
下图说明了LPC客户机向已建立连接的LPC服务器发送请求所采取的步骤,以及服务器响应消息所采取的步骤。
客户端-服务器数据传输序列
LPC Data Structures
LPC Port Data Structure
LPC Port被称为端口。LPC实现使用相同的端口结构来表示各种类型的端口。LPC使用的端口是服务器连接端口,这些端口是由服务器进程创建的用于接受来自客户端的传入连接的命名端口。客户机通信端口由客户机进程创建以连接到服务器进程和服务器进程创建的服务器通信端口。
LPC端口类型及其关系
LPCP_PORT_OBJECT是LPC用来表示LPC端口的内部数据结构。LPCP_PORT_对象是从带有标记“PORT”的分页池中分配的。
kd> dt nt!_LPCP_PORT_OBJECT+0x000ConnectionPort : Ptr32 _LPCP_PORT_OBJECT+0x004ConnectedPort : Ptr32 _LPCP_PORT_OBJECT+0x008MsgQueue : _LPCP_PORT_QUEUE+0x018Creator : _CLIENT_ID+0x020ClientSectionBase : Ptr32 Void+0x024ServerSectionBase : Ptr32 Void+0x028PortContext : Ptr32 Void+0x02cClientThread : Ptr32 _ETHREAD+0x030SecurityQos : _SECURITY_QUALITY_OF_SERVICE+0x03cStaticSecurity : _SECURITY_CLIENT_CONTEXT+0x078LpcReplyChainHead : _LIST_ENTRY+0x080LpcDataInfoChainHead : _LIST_ENTRY+0x088ServerProcess : Ptr32 _EPROCESS+0x088MappingProcess : Ptr32 _EPROCESS+0x08cMaxMessageLength : Uint2B+0x08eMaxConnectionInfoLength : Uint2B+0x090Flags : Uint4B+0x094 WaitEvent : _KEVENT
LPC (Local procedure calls)(二)内核调试扩展
我们将讨论调试器扩展,这些扩展允许您查看与LPC相关的问题。
LPC Kernel Debugger Extensions
Command |
Description |
!lpc |
Display the list and description of all the !lpc commands |
!lpc message [MessageId] |
Display the message with a given ID and all related information by attempting to match the given Message ID to the EHTREAD->LpcReceivedMessageId and to the ETHREAD->LpcReplyMessageId of all threads in the system. If the MessageId parameter is omitted then it attempts to display all the outstanding messages in the system by searching for the tag ‘LpcM’ in the pools. |
!lpc port [PortAddress] |
Displays port information. If a server connection port address is specified then only information about that port is displayed. If either a client or server communication port is specified it prints information about the specified communication port, the peer communication port and the server connection port. If the PortAddress parameter is omitted then it attempts to walk the list of all objects of type “Port” and “WaitablePort” and display them. Note that for this feature to work the GFlags option “+otl” i.e. “Maintain a list of objects for each type” must be enabled. |
!lpc scan PortAddress |
Displays port information. It attempts to walk the list of all objects of type “Port” and “WaitablePort” and display the one matching the specified port address. Note that for this feature to work the GFlags option “+otl” i.e. “Maintain a list of objects for each type” must be enabled. |
!lpc thread [ThreadAddr] |
If ThreadAddr is specified it walks the list of threads in the ETHREAD-> LpcReplyChain to locate the list head i.e. a “Port” or “WaitablePort” object on which the thread is waiting for a reply. If the ThreadAddr parameter is omitted then it attempts to find all LPC server threads by looking for threads with a non-NULL EHTREAD->LpcReceivedMessageId and all client threads by looking for threads with a non-NULL ETHREAD->LpcReplyMessageId and displays them. |
!lpc PoolSearch |
Toggles a setting that controls whether the “lpc message” command will search for LPC message tag (‘LpcM’) in the kernel pools or not. |
LPC Kernel Debugger Extension Usage
来自调用堆栈的LPC连接端口信息
在LPC数据传输或LPC连接上阻塞的任何客户端或服务器线程的调用堆栈上,将有一个包含函数NtRequestWaitReplyPort()或NtReplyWaitReceivePortEx()之一的帧。其中任何一个函数的第一个参数是它们被阻塞的端口的句柄。
kd> !thread 810de2a8
THREAD 810de2a8 Cid 01dc.01f4 Teb: 7ffde000 Win32Thread:00000000 WAIT: (WrLpcReceive) UserMode Non-Alertable81131188 Semaphore Limit 0x7fffffff810de398 NotificationTimer
Not impersonating
DeviceMap e196c460
Owning Process 810ddda0 Image: rpclpcs.exe
Wait Start TickCount64666666 Ticks: 402 (0:00:00:04.025)
Context Switch Count2UserTime00:00:00.000KernelTime00:00:00.000Win32 Start Address0x77e76bf0Start Address0x7c810856Stack Init f8e28000 Current f8e27c4c Base f8e28000 Limit f8e25000 Call0Priority8 BasePriority 8 PriorityDecrement 0 DecrementCount 0ChildEBP RetAddr Args to Child
f8e27c64 804dc6a6 810de318 810de2a8 804dc6f2 nt!KiSwapContext+0x2e (FPO: [Uses EBP] [0,0,4])
f8e27c70 804dc6f2 e1084108 8055a540 e1084108 nt!KiSwapThread+0x46 (FPO: [0,0,0])
f8e27c98 8056a50a00000001 00000010 00000001 nt!KeWaitForSingleObject+0x1c2 (FPO: [Non-Fpo])
f8e27d48 804df06b 000007c4 002bff7000000000 nt!NtReplyWaitReceivePortEx+0x3dc (FPO: [Non-Fpo])
f8e27d48 7c90eb94 000007c4 002bff7000000000 nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ f8e27d64)
002bff8000000000 00000000 00000000 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
kd> !handle 7c4 3810ddda0
processor number0, process 810ddda0
PROCESS 810ddda0 SessionId:0Cid: 01dc Peb: 7ffd9000 ParentCid: 01b4
DirBase: 058cd000 ObjectTable: e1a13278 HandleCount:18.
Image: rpclpcs.exe
Handle table at e107d000 with18 Entries inuse
07c4: Object: e1084108 GrantedAccess: 001f0001 Entry: e107df88
Object: e1084108 Type: (812b5c80) Port
ObjectHeader: e10840f0 (old version)
HandleCount:1 PointerCount: 4Directory Object: e14c72c8 Name: rpclpc
kd> !lpc port e1084108
Server connection port e1084108 Name: rpclpc
Handles:1 References: 4Server process : 810ddda0 (rpclpcs.exe)
Queue semaphore :81131188Semaphore state0 (0x0)
The message queue is empty
The LpcDataInfoChainHead queue is empty
LPC Messages that are waiting to be picked up by the server thread
MBR反汇编
关于MBR的反汇编,我想花点时间向您展示一下快速而肮脏的方法。
首先,使用低级扇区编辑器(如Microsoft资源工具包实用程序DiskProbe)将MBR转储到文件中。保存文件后,启动一个程序(如记事本)并使用Windbg(Windows调试器)附加到它。
接下来,找到一个有效但未占用的内存范围(默认堆是一个很好的候选者)。内存范围至少需要512(0x200)字节。例如,我在这里找到了我的:
000120a0 00000000 00000000 00000000 00000000000120b000000000 00000000 00000000 00000000000120c000000000 00000000 00000000 00000000000120d000000000 00000000 00000000 00000000000120e000000000 00000000 00000000 00000000000120f000000000 00000000 00000000 00000000 00012100 00000000 00000000 00000000 00000000 00012110 00000000 00000000 00000000 00000000 00012120 00000000 00000000 00000000 00000000...
非分页池的消耗
我最近在处理一个问题,服务器在几天内耗尽了NonPagedPool。通常,我们只需要使用像PoolMon这样的工具来识别有问题的pool标记,然后使用本文中的方法找到使用该pool标记的驱动程序。然而,让这个案例有趣的是pool标记,而且我们无法使用常规方法识别驱动程序。你一会儿就会明白我的意思了。支持给我提供了服务器处于状态时的内核转储,这就是我发现的。
让我们先看看虚拟内存的使用情况:
2: kd> !vm*** Virtual Memory Usage ***Physical Memory:851420 ( 3405680Kb)
Page File: \??\C:\pagefile.sys
Current:3584000 Kb Free Space: 3568552Kb
Minimum:3584000 Kb Maximum: 3584000Kb
Available Pages:573277 ( 2293108Kb)
ResAvail Pages:800628 ( 3202512Kb)
Locked IO Pages:1067 ( 4268Kb)
Free System PTEs:25102 ( 100408Kb)
Free NP PTEs:335 ( 1340Kb)
Free Special NP:0 ( 0Kb)
Modified Pages:22 ( 88Kb)
Modified PF Pages:22 ( 88Kb)
NonPagedPool Usage:31369 ( 125476Kb) ß Very high
NonPagedPool Max:31986 ( 127944Kb)********** Excessive NonPaged Pool Usage *****PagedPool0 Usage: 19071 ( 76284Kb)
PagedPool1 Usage: 735 ( 2940Kb)
PagedPool2 Usage: 747 ( 2988Kb)
PagedPool3 Usage: 720 ( 2880Kb)
PagedPool4 Usage: 746 ( 2984Kb)
PagedPool Usage:22019 ( 88076Kb)
PagedPool Maximum:38912 ( 155648Kb)********** 3 pool allocations have failed **********
Kernel Stack Overflows
今天我想谈谈一个常见的错误,我们在日常工作钟许多案例中都看到了这一点。它涉及到驱动程序占用内核堆栈上的过多空间,从而导致内核堆栈溢出,然后将通过以下错误检查之一使系统崩溃:
1. STOP 0x7F: UNEXPECTED_KERNEL_MODE_TRAP当参数1设置为EXCEPTION_DOUBLE_FAULT时,这是由于覆盖内核堆栈的末尾而导致的。
2. STOP 0x1E: KMODE_EXCEPTION_NOT_HANDLED, 0x7E: SYSTEM_THREAD_EXCEPTION_NOT_HANDLED, or 0x8E: KERNEL_MODE_EXCEPTION_NOT_HANDLED, 异常代码为STATUS_ACCESS_VIOLATION,表示内存访问冲突。
3. STOP 0x2B: PANIC_STACK_SWITCH, 这通常发生在内核模式驱动程序使用太多堆栈空间时。
内核堆栈概述
系统中的每个线程都分配有一个内核模式堆栈。运行在任何内核模式线程(无论是系统线程还是驱动程序创建的线程)上的代码都使用该线程的内核模式堆栈,除非该代码是DPC,在这种情况下,它在某些平台上使用处理器的DPC堆栈。堆栈负增长。这意味着堆栈的开始(底部)的地址高于堆栈的结束(顶部)。例如,让我们保持堆栈的开头是0x80f1000,这是堆栈指针(ESP)指向的位置。如果将一个DWORD值推送到堆栈上,它的地址将是0x80f0ffc。下一个DWORD值将存储在0x80f0ff8,以此类推,直到分配的堆栈的限制(顶部)。堆栈顶部以保护页为边界,以检测溢出。
内核模式堆栈的大小因不同的硬件平台而异。例如:
- 在基于x86的平台上,内核模式堆栈是12K。
- 在基于x64的平台上,内核模式堆栈为24K(基于x64的平台包括使用AMD64体系结构的处理器和使用Intel EM64T体系结构的处理器的系统)。
- 在基于安腾的平台上,内核模式堆栈是32K,有一个32K后备存储。(如果处理器的寄存器文件中的寄存器用完,它将使用后备存储器来保存寄存器的内容,直到分配函数返回为止。这不会直接影响堆栈分配,但操作系统在基于安腾的平台上使用的寄存器比在其他平台上使用的寄存器多,这使得驱动程序可以使用的堆栈相对更多。)