You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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字节,直接调用原函数地址会执行错误指令,导致程序崩溃。

总结修复步骤

  1. 将Hook逻辑封装为DLL,注入到目标命名管道进程(服务端或客户端)中。
  2. 替换ConnectNamedPipe的Hook为ReadFile/WriteFile的Hook,才能捕获实际的消息收发操作。
  3. 使用NtQueryObject修正管道名获取逻辑,替代错误的GetNamedPipeHandleState调用。
  4. 优先使用成熟的Hook库(如EasyHook、MinHook),避免手动修改内存带来的稳定性问题。

内容的提问来源于stack exchange,提问作者user2250991

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 20:52:57