65.9K
CodeProject 正在变化。 阅读更多。
Home

NDIS MONITOR .NET 32位 v1.00

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (36投票s)

2007年4月26日

8分钟阅读

viewsIcon

183733

downloadIcon

9980

NDIS Monitor 允许捕获和记录在内核空间中发生的 NDIS 微型端口驱动程序和网络协议模块之间的数据包交换。

Screenshot - screenshot.jpg

引言

在开发、研究或修复 bug 时,我经常使用 SysInternals 的 FileMon 和 RegMon 等工具。我最喜欢这些工具的一点是,当您需要它们时,您可以直接运行一个 .EXE 文件,即可立即获得有关系统磁盘或注册表活动的实时日志,而无需任何预先安装或重启。这简直是无价的,因为当您陷入问题时,您最不想做的就是安装一个复杂的应用程序或重启您正在工作的机器。

我一直需要一个类似于 FileMon 或 RegMon 的工具,但更侧重于网络和网络数据包及活动的实时日志记录。因此,我开发了 NDIS Monitor。NDIS Monitor 由三个组件组成:一个 C# WinForms 应用程序(“NdisMonitor”文件夹)、一个调用内核驱动程序的 C++ 层(“NdisHook”文件夹)以及内核驱动程序本身。NDIS Monitor 不需要对宿主机器进行任何预先安装或重启。只需解压缩文件并运行即可开始记录网络流量。

本文提供的 NDIS Monitor 源代码演示了在实际应用程序中使用的多项高级 .NET 功能:多线程、消息过滤、反射、CodeDom、WinForm 用户控件、PInvoke、非托管空间封送、通过 Windows 消息同步等。源代码已完全注释。

NDIS Monitor 的内核驱动程序(源代码可在此处下载)直接钩取多个 NDIS 微型端口 API 来捕获网络流量。这不是执行此类操作的标准和“记录在案”的方式,但它是最灵活的方式,可以即时拦截网络数据。

这种钩取方法是我很久以前进行广泛研究和逆向工程的结果。然而,我发现许多商业和共享软件产品(如防火墙)实际上使用相同的方法将自身插入到任意 NDIS 微型端口驱动程序与其绑定的 NDIS 协议模块之间的数据包流中,NDIS Monitor 也是如此。有关内核驱动程序实现细节的信息可以在 NDIS Monitor 主页 中找到。

我想强调的是,与许多类似程序不同,NDIS Monitor 没有任何外部依赖项,也不需要任何第三方软件或库即可运行。

如何开始使用 NDIS Monitor

只需解压缩文件并运行 .EXE 文件。

首次启动时,NDIS Monitor 会尝试枚举稍后将提供给用户的所有扩展。这通过查看“bin”子目录并使用反射检查和加载找到的程序集来完成。在应用程序工具栏中,有一个下拉列表显示所有已加载的扩展以及当前显示给用户的扩展,如上方的屏幕截图所示。“扩展”本质上是一个 .NET WinForms 用户控件,它托管在应用程序主窗口的客户端区域。如果您想为 NDIS Monitor 编写自己的自定义扩展,只需创建一个 WinForms 用户控件并实现 INmExtension 接口。完整的说明可以在我网站上的 NDIS Monitor 主页 中找到。

第一步是在上述下拉列表中选择您要使用的扩展。“Standard”扩展(在 NmStandardExtension.dll 文件中实现)是一个用户控件,它(在列表控件中)显示通过指定接口发送和接收的所有数据包。对于每个数据包,扩展会显示方向(发送或接收)、编号、相应的日志时间、MAC 和 IP 地址以及简短描述(如果可用)。双击一个数据包,扩展将打开“Packet Viewer”窗口,显示所选数据包的内容。

Screenshot - screenshot_02.jpg

还提供了一个名为“User Example”(在 NmExampleExtension.dll 文件中实现)的扩展,供想要实现自己扩展的人员参考。

选择扩展后,“Open Adapters”窗口会弹出。此窗口(通过树控件)显示所有 NDIS 协议及其对应的已打开 NDIS 适配器的列表。然后,用户需要勾选所有打算拦截其流量的 NDIS 适配器(请看本页面顶部的第一个截图)。通过所选适配器发送和接收的所有数据包都将被传递给当前的用户控件(即扩展)进行处理。通常,您应该在“TCPIP”协议下选择一个网卡。如果您想拦截从 WAN 适配器(如 ADSL 调制解调器)发送和接收的数据包,则选择“TCPIP_WANARP”协议下的适配器可能会有用。

如何脚本化 NDIS Monitor

NDIS Monitor 允许使用 System.CodeDom.Compiler 命名空间中的对象进行就地脚本化。

Screenshot - screenshot_03.jpg

要打开“C# 代码窗口”,只需从“Extension”菜单中选择“Code Window”。此处提供了一个“Code Wizard”,可以根据常见标准(例如数据包中是否存在特定字节序列)自动生成过滤代码。本质上,通过脚本化 NDIS Monitor,您可以告诉程序在收到来自内核驱动程序的新数据包时该做什么。一旦您完成了脚本的编写,就可以直接从程序的 [用户界面] 中编译并最终修复它。

Screenshot - screenshot_04.jpg

可以打开一个可选的控制台,用于与用户进行交互。您的脚本可以轻松地在该控制台中接收命令和/或记录事件。

Screenshot - screenshot_05.jpg

在“C# 代码窗口”中,您需要实现 ProcessNextPacket 方法,这是 NDIS Monitor 在每次向当前选定的 NDIS 适配器发送或从其接收新数据包时调用的方法。当您完成在“C# 代码窗口”中编写 NDIS 过滤器后,只需按 ALT+RETURN 即可编译您的代码并使其生效。这是 ProcessNextPacket 方法的签名。

public bool ProcessNextPacket( ProcessNextPacketFn impl, LogFn log, 
    RawPacket rp, NdisHookStubs.NEXT_PACKET np, int ord, DateTime tm )
{
    return impl( rp, np, ord, tm );
}

如上代码所示,ProcessNextPacket 的默认实现仅调用 impl 委托,该委托指向当前选定的扩展(即前面段落中描述的 WinForms 用户控件)的相应 ProcessNextPacket 方法。如果下拉列表中选择了“Standard”扩展,这仅意味着数据包被添加到列表控件中。相反,如果您在此处返回 false,则数据包将被丢弃,不显示给用户。

其他参数如下:log 委托可用于将字符串发送到日志控制台(请参见上面的屏幕截图)。通常,如果您想显示有关拦截流量的统计信息,并且 rpnp 对象描述了刚刚发送或接收的单个数据包的特征。类型为 RawPacket 的参数 rp 以一种强面向对象的方式表示网络数据包,从而允许,除其他外,使用 C# 的 is 运算符轻松测试实例的类型。这是一个可以在拦截的数据包上进行的测试示例。

public bool ProcessNextPacket( ProcessNextPacketFn impl, LogFn log, 
    RawPacket rp, NdisHookStubs.NEXT_PACKET np, int ord, DateTime tm )
{
    if ( rp is EthernetPacket && ((
        EthernetPacket)rp)._tranHeader is TranProt_TCP )
    {
        TranProt_TCP            tcp = (TranProt_TCP) (
            (EthernetPacket)rp)._tranHeader;
        if ( tcp._srcPort == 80 && np._bDirection == 0 /*RECV*/ )
            return impl( rp, np, ord, tm );
    }
    return false;
}

如果您使用此代码对 NDIS Monitor 进行脚本化,程序将仅在列表控件中显示 TCP 类型且端口为 80 的数据包。RawPacket 类层次结构的完整描述可以在 NDIS Monitor 主页 中找到。

NDIS Monitor 的更复杂脚本示例

为了演示 NDIS Monitor 脚本功能的潜力,我准备了一个小例子。以下代码的作用是拦截所有指向端口 80 的入站 TCP 数据包,然后提取其中的请求 URL 和主机(如果存在)。然后,输出被记录在控制台窗口(菜单“View” -> “Output Window”)中,作为通过该系统上端口 80 从任何 Internet 或 LAN Web 服务器下载的所有资源的 URL 列表。

要尝试此代码,您应该打开“Code Window”(菜单“Extension” -> “Code Window”),用下面的代码替换 ProcessNextPacket 方法的当前实现,重新编译脚本并关闭代码窗口(关闭代码窗口很重要,因为它是模态窗口,NDIS Monitor 仅在用户关闭它时才接受新脚本)。

public bool ProcessNextPacket(ProcessNextPacketFn impl, LogFn log, 
    RawPacket rp, NdisHookStubs.NEXT_PACKET np, int ord, DateTime tm)
{
    // the packet must be a TCP packet.
    if (rp is EthernetPacket && (
        (EthernetPacket)rp)._tranHeader is TranProt_TCP)
    {
        // the packet must be received from the port 80.
        TranProt_TCP tcp = (TranProt_TCP)((EthernetPacket)rp)._tranHeader;
        if (tcp._dstPort == 80 && np._bDirection != 0 /*SENT*/ )
        {
            try
            {
                // must be an HTTP GET request. Skip the IP and TCP headers.
                if (rp._data[54] == 'G' && rp._data[55] == 'E' && 
                    rp._data[56] == 'T')
                {
                    int i;
                    System.Text.ASCIIEncoding ascii = 
                        new System.Text.ASCIIEncoding();

                    // get the host from the HTTP GET request.
                    string host = "";
                    for (i = 58; i < rp._data.Length - 4; i++)
                        if (ascii.GetString(
                            rp._data, i, 5).ToLower() == "host:")
                        {
                            // skip the white spaces.
                            i += 5;
                            while (rp._data[i++] == ' ') ;
                            int hostStart = i - 1;

                            // find the end of the host string.
                            while (rp._data[i++] != 0xD) ;

                            // save the host.
                            host = ascii.GetString(rp._data, hostStart, 
                                i - hostStart - 1);
                            break;
                        }

                    // find the end of the URL string.
                    i = 58;
                    while (rp._data[i++] != ' ') ;

                    // save the url.
                    string url = ascii.GetString(rp._data, 58, i - 58 - 1);

                    // log in the console and show the packet in the list 
                    // control.
                    log("http://" + host + url);
                    return impl(rp, np, ord, tm);
                }
            }
            catch
            { }
        }
    }
    return false;
}

然后打开控制台窗口,尝试导航到 Microsoft 主页。应该会发生这种情况。

Screenshot - screenshot_06.jpg

这只是 NDIS Monitor 功能的一个小例子。您可以使用 .NET FCL 的所有类编写非常复杂的脚本代码,而无需退出程序。这包括,例如,用于读取和写入磁盘文件的类。此外,请注意,UserHook 类实例(即您可以在代码窗口中编辑的类,其中包含 ProcessNextPacket 方法的定义)在编译后会在整个拦截过程中保持持久。这意味着您可以在脚本代码中声明实例数据,以便在不同的 ProcessNextPacket 调用之间存储任何类型的信息。

有关 NDIS Monitor 的更多信息

有关 NDIS Monitor 的更多信息(包括对内核驱动程序工作原理的解释),请访问我网站上的 NDIS Monitor 主页

© . All rights reserved.