API Hook实现Named Pipe消息嗅探失效,求确认及原因分析
命名管道API Hook未捕获消息的问题排查
我尝试通过API Hook技术对Named Pipe(命名管道)的消息进行嗅探,编写了如下C#代码,但在使用命名管道流进行通信时,未能捕获到任何命名管道消息。我不确定自己的API Hook实现方式是否正确,希望有人能确认该实现的正确性并告知问题原因。
using System; using System.Diagnostics; using System.IO; using System.IO.Pipes; using System.Reflection; using System.Runtime.InteropServices; using System.Text; public class PipeMonitor { private const string KERNEL32_DLL = "kernel32.dll"; private const string NTDLL_DLL = "ntdll.dll"; private delegate bool ConnectNamedPipeDelegate(IntPtr hNamedPipe, IntPtr lpOverlapped); private static ConnectNamedPipeDelegate originalConnectNamedPipe; private static ConnectNamedPipeDelegate hookConnectNamedPipe; [DllImport("kernel32.dll")] private static extern bool GetNamedPipeHandleState(IntPtr hNamedPipe, out int lpState, IntPtr lpCurInstances, IntPtr lpMaxCollectionCount, IntPtr lpCollectDataTimeout, IntPtr lpUserName, uint nMaxUserNameSize); private static class PipeState { public const int PIPE_CONNECTED_STATE = 2; } private static bool HookConnectNamedPipe(IntPtr hNamedPipe, IntPtr lpOverlapped) { bool result = originalConnectNamedPipe(hNamedPipe, lpOverlapped); if (result) { string pipeName = GetPipeNameFromHandle(hNamedPipe); Console.WriteLine($"Message received on pipe '{pipeName}'"); } return result; } private static string GetPipeNameFromHandle(IntPtr hNamedPipe) { StringBuilder pipeName = new StringBuilder(256); IntPtr hWnd = new IntPtr(Convert.ToInt32(pipeName)); int state; if (GetNamedPipeHandleState(hNamedPipe, out state, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0)) { if (state != PipeState.PIPE_CONNECTED_STATE) { return null; } if (GetNamedPipeHandleState(hNamedPipe, out state, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, hWnd, 256)) { return pipeName.ToString(); } } return true ? pipeName.ToString() : null; } private static void HookConnectNamedPipe() { IntPtr kernel32ModuleHandle = NativeMethods.GetModuleHandle(KERNEL32_DLL); IntPtr connectNamedPipeAddress = NativeMethods.GetProcAddress(kernel32ModuleHandle, "ConnectNamedPipe"); originalConnectNamedPipe = Marshal.GetDelegateForFunctionPointer<ConnectNamedPipeDelegate>(connectNamedPipeAddress); hookConnectNamedPipe = HookConnectNamedPipe; uint oldProtect; NativeMethods.VirtualProtect(connectNamedPipeAddress, (UIntPtr)5, 0x40, out oldProtect); IntPtr hookPointer = Marshal.GetFunctionPointerForDelegate(hookConnectNamedPipe); Marshal.WriteByte(connectNamedPipeAddress, 0xE9); Marshal.WriteInt32(connectNamedPipeAddress + 1, (int)(hookPointer.ToInt64() - connectNamedPipeAddress.ToInt64() - 5)); } private static class NativeMethods { [DllImport(KERNEL32_DLL, SetLastError = true)] public static extern IntPtr GetModuleHandle(string lpModuleName); [DllImport(KERNEL32_DLL, SetLastError = true)] public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); [DllImport(KERNEL32_DLL, SetLastError = true)] public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect); [DllImport(KERNEL32_DLL, SetLastError = true)] public static extern bool GetNamedPipeHandleState(IntPtr hNamedPipe, IntPtr lpState, IntPtr lpCurInstances, IntPtr lpMaxCollectionCount, IntPtr lpCollectDataTimeout, StringBuilder lpUserName, int nMaxUserNameSize); } public static void Main(string[] args) { HookConnectNamedPipe(); Console.WriteLine("Press any key to exit..."); Console.ReadKey(); } }
问题分析与修复方案
1. Hook范围仅局限于当前进程
你的代码只修改了当前进程内ConnectNamedPipe函数的内存地址,而命名管道通信通常涉及服务端、客户端两个独立进程。如果监控程序和目标管道进程不是同一个,Hook完全不会生效。要实现跨进程嗅探,必须将Hook逻辑封装为DLL并注入到目标进程中。
2. GetPipeNameFromHandle函数存在多处致命错误
- 错误地将
StringBuilder强制转换为IntPtr,完全不符合GetNamedPipeHandleState的API参数要求,该参数需要传入StringBuilder的内存地址,而非对象本身的数值转换结果。 - 混淆了API用途:
GetNamedPipeHandleState的lpUserName参数是获取连接用户的名称,不是管道名。获取管道名需要调用NtQueryObject或GetFileInformationByHandleEx。 - 最后一行的三元表达式逻辑完全错误,无论结果如何都会返回空字符串。
修复后的管道名获取实现:
private static string GetPipeNameFromHandle(IntPtr hNamedPipe) { int state; if (!GetNamedPipeHandleState(hNamedPipe, out state, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0)) { return null; } if (state != PipeState.PIPE_CONNECTED_STATE) { return null; } return GetPipeNameViaNtQueryObject(hNamedPipe); } [DllImport("ntdll.dll")] private static extern int NtQueryObject(IntPtr ObjectHandle, int ObjectInformationClass, IntPtr ObjectInformation, int ObjectInformationLength, out int ReturnLength); private static string GetPipeNameViaNtQueryObject(IntPtr hPipe) { int bufferSize = 1024; IntPtr buffer = Marshal.AllocHGlobal(bufferSize); try { int returnLength; // ObjectInformationClass=17对应ObjectNameInformation int status = NtQueryObject(hPipe, 17, buffer, bufferSize, out returnLength); if (status != 0) { return null; } // 解析UNICODE_STRING结构,Buffer字段偏移8字节 IntPtr namePtr = buffer + 8; ushort length = Marshal.ReadUInt16(buffer); return Marshal.PtrToStringUni(namePtr, length / 2); } finally { Marshal.FreeHGlobal(buffer); } }
3. 选错了Hook的API
ConnectNamedPipe是服务端等待客户端连接的API,不是消息收发的API。一旦连接建立,后续的消息传输依赖ReadFile/WriteFile或.NET管道流方法,你的Hook根本无法捕获这些消息操作。要嗅探消息,必须HookReadFile、WriteFile或者目标程序使用的管道流相关方法。
4. 手动Hook的稳定性问题
手动修改内存写入跳转指令存在诸多风险:
- 未处理32位/64位进程差异,64位环境下指令长度、内存地址计算逻辑都会失效。
- 未保存原函数的前5字节,直接调用原函数地址会执行错误指令,导致程序崩溃。
总结修复步骤
- 将Hook逻辑封装为DLL,注入到目标命名管道进程(服务端或客户端)中。
- 替换
ConnectNamedPipe的Hook为ReadFile/WriteFile的Hook,才能捕获实际的消息收发操作。 - 使用
NtQueryObject修正管道名获取逻辑,替代错误的GetNamedPipeHandleState调用。 - 优先使用成熟的Hook库(如EasyHook、MinHook),避免手动修改内存带来的稳定性问题。
内容的提问来源于stack exchange,提问作者user2250991
相关产品推荐
相关产品推荐

