当前位置: 首页 > article >正文

windows 驱动实例分析系列: NDIS 6.0的Filter 驱动改造(三)

 数据包的发送

NDIS数据包的发送是一件非常麻烦的事情,当然,在应用层,使用socket库感觉不到这一点,但是在内核中,内核主要实现是的七层结构中的下面4层,并且Filter驱动和协议驱动不一样,它完全就是裸数据,需要从Mac数据包首部进行构建。

TCP/IP协议的详细解释自行查阅资料,这里不做详细介绍,本文中的数据包格式可能存在问题,但是这和本文的目的无关~本文的含义是详细阐述网络数据包的收发本质,不是网络协议的普及文,文中仅仅依赖了Mac层的协议知识,从数据包的Mac地址填充来实现数据包的收发。

和发送数据相关的回调例程是下面的回调例程:

// 发送缓冲区的回调例程
FILTER_SEND_NET_BUFFER_LISTS_HANDLER            SendNetBufferListsHandler;

// 发送完成回调
FILTER_SEND_NET_BUFFER_LISTS_COMPLETE_HANDLER   SendNetBufferListsCompleteHandler;

这个例程可能在两个地方使用,一个地方是调用NdisFRegisterFilterDriver函数时候注册回调例程;另一个地方则是FILTER_SET_FILTER_MODULE_OPTIONS_HANDLER,事实上可以在Filter运行时候动态的切换挂接的例程。

这里不会对NET_BUFFER_LIST结构进行解释,可以参考这部分文章: NET_BUFFER_LIST体系

分配缓冲区

分配NET_BUFFER_LIST的代码如下:

// 分配一个NET_BUFFER_LIST
PNET_BUFFER_LIST AllocateNetBuffList(
IN NDIS_HANDLE pParam, 
IN PUCHAR pBuffer, 
IN ULONG ulBufferSize
)
{
    PMS_FILTER              pFilter = (PMS_FILTER)pParam;
    PMDL                       pMDL = NULL;
    PUCHAR              pMDLAddress = NULL;
    PNET_BUFFER_LIST pNetBufferList = NULL;

    if (pFilter->SendNetBufferListPool == NULL)
    {
        return NULL;
    }

    // 1. 分配一块内存
    pMDLAddress = (PUCHAR)NdisAllocateMemoryWithTagPriority(
        pFilter->FilterHandle, 
        ulBufferSize, 
        FILTER_ALLOC_TAG, 
        LowPoolPriority);

    NdisZeroMemory(pMDLAddress, ulBufferSize);

    // 2. 根据分配的虚拟内存创建MDL
    pMDL = NdisAllocateMdl(pFilter->FilterHandle, pMDLAddress, ulBufferSize);
    if (pMDL == NULL)
    {
        return NULL;
    }

    // 3. 将需要复制的数据拷贝到MDL
    NdisMoveMemory(pMDLAddress, pBuffer, ulBufferSize);

    // 4. 根据MDL来分配一个NET_BUFFER_LIST
    pNetBufferList = NdisAllocateNetBufferAndNetBufferList(
        pFilter->SendNetBufferListPool,
        0/*sizeof(FILTER_SEND_NETBUFLIST_RSVD)*/, 
        0, 
        pMDL, 
        0, 
        ulBufferSize);
    if (pNetBufferList == NULL)
    {
        NdisFreeMemoryWithTagPriority(
            pFilter->FilterHandle, 
            pMDLAddress, 
            FILTER_ALLOC_TAG
        );
        NdisFreeMdl(pMDL);
        return NULL;
    }

    // 5. 将分配池的句柄指向Filter;
    pNetBufferList->SourceHandle = pFilter->FilterHandle;
    return pNetBufferList;
}

注意到这个过程非常麻烦,分配内存地址-> 分配MDL->分配NET_BUFFER_LIST结构;最后还将句柄赋予NET_BUFFER_LIST结构的SourceHandle字段,这是因为内存分配遵循"谁分配、谁释放"的原则,接下来会在特定的地方释放它。

克隆数据包

另外一项技术则是拷贝技术,这项技术并未在代码中演示,不过还是介绍一下,在一些网络应用中,驱动将会暂存下上层传递下来的NET_BUFFER_LIST,然后使用自己分配的NET_BUFFER_LIST,有一对函数可以快捷的完成这个操作:

NDIS_EXPORTED_ROUTINE NET_BUFFER_LIST * NdisAllocateCloneNetBufferList(
  [in]           NET_BUFFER_LIST *OriginalNetBufferList,
  [in, optional] NDIS_HANDLE     NetBufferListPoolHandle,
  [in, optional] NDIS_HANDLE     NetBufferPoolHandle,
  [in]           ULONG           AllocateCloneFlags
);

调用 NdisAllocateCloneNetBufferList 以创建克隆 NET_BUFFER_LIST 结构,该结构可用于在单独的数据路径上发送重复数据。

原始 NET_BUFFER_LIST 结构中的每个 NET_BUFFER 结构仅从已用数据空间的开头克隆,而不是从整个数据空间的开头克隆。 若要获取从数据空间开头到已用数据空间开头的偏移量,请使用 NET_BUFFER_DATA_OFFSET 宏。

如果克隆的 NET_BUFFER_LIST 结构应具有与给定池关联的属性,则调用方必须在 NetBufferListPoolHandle 或 NetBufferPoolHandle 参数中指定池句柄。 例如,NET_BUFFER_LIST 结构的 ProtocolType 成员与池相关联。

克隆NET_BUFFER_LIST结构描述的数据与 OriginalNetBufferList 中的 NET_BUFFER_LIST 结构所描述的数据相同。 NDIS 不会将原始 MDL 描述的数据复制到新的数据缓冲区。 相反,克隆的结构引用原始数据缓冲区。 克隆 NET_BUFFER_LIST 结构不包括初始 NET_BUFFER_LIST_CONTEXT 结构。

可以调用 NdisFreeCloneNetBufferList 函数,用于释放 NET_BUFFER_LIST 结构和所有通过调用 NdisAllocateCloneNetBufferList 分配的关联结构和 MDL 链。

	pClone = NdisAllocateCloneNetBufferList(
		pSendData->pNetBufferLists, 
		pFilter->SendNetBufferListPool, 
		pFilter->SendNetBufferPool, 
		NDIS_CLONE_FLAGS_USE_ORIGINAL_MDLS);
	if(NULL == pClone)
	{
		return NULL;
	}

	pClone->SourceHandle        = pFilter->FilterHandle;
	pClone->ParentNetBufferList = pSendData->pNetBufferLists;

发送缓冲区

发送缓冲区非常简单:

NdisFSendNetBufferLists(
pFilter->FilterHandle, 
pNetBufferList, 
NDIS_DEFAULT_PORT_NUMBER, 
NDIS_SEND_FLAGS_DISPATCH_LEVEL| NDIS_SEND_FLAGS_CHECK_FOR_LOOPBACK);

真正麻烦的地方在于,在内核中,需要填充Mac首部、IP首部、TCP/UDP/ICMP首部,下面是这些首部的定义:

// IP数据包的MAC首部
typedef struct _tagMac
{
    UCHAR   mac_des[6];
    UCHAR   mac_src[6];
    USHORT  mac_type;
}MAC_HEADER, * PMAC_HEADER;

// IP数据包的首部
typedef struct _tagIPHeader 
{
    UCHAR     ip_verlen;             // Version and length.
    UCHAR     ip_tos;                // Type of service.
    USHORT    ip_length;             // Total length of datagram.
    USHORT    ip_id;                 // Identification.
    USHORT    ip_offset;             // Flags and fragment offset.
    UCHAR     ip_ttl;                // Time to live.
    UCHAR     ip_protocol;           // Protocol.
    USHORT    ip_xsum;               // IP校验和
    UINT      ip_src;                // Source address.
    UINT      ip_dest;               // Destination address.
}IP_HEADER, * PIP_HEADER;

// UDP数据包首部
typedef struct _tagUDPHeader 
{
    USHORT  src_port;
    USHORT  des_port;
    USHORT  udp_length;
    USHORT  udp_sum;        // UDP校验和
}UDP_HEADER, * PUDP_HEADER;

// ICMP包首部
typedef struct _tagICMPHead
{
    UCHAR   type;       // 类型
    UCHAR   code;       // 操作码
    USHORT  checksum;   // ICMP校验和
    USHORT  id;
    USHORT  seq;        
}ICMPHead, *PICMPHead;

这里解释一下,为什么需要一个单独的结构来传递:

// 发送ICMP结构时候用于构建UDP信息的结构
// 注意,这里没有包含UDP和TCP需要的端口信息,这部分不在本代码的演示范围内
typedef struct _tagICMP_INFO
{
    UCHAR  des_mac[6];        // 目标MAC地址
    UCHAR  src_mac[6];        // 源MAC地址

    ULONG  des_ip;            // 目标IP地址, 
    ULONG  src_ip;            // 源IP地址

    PCHAR pData;             // 数据地址
    USHORT usSize;            // 数据长度
}ICMP_INFO, * PICMP_INFO;

当需要收发数据的时候,必须指定网络适配器,所以传入的这结构中的src_mac将用于从链表中找到需要发送的网络适配器:

PMS_FILTER FilterFindObject(
    _In_reads_bytes_(BufferLength)
    PUCHAR                   Buffer,
    _In_ ULONG                    BufferLength
)
{
    PMS_FILTER              pFilter;
    PLIST_ENTRY             Link;
    BOOLEAN                 False = FALSE;

    FILTER_ACQUIRE_LOCK(&FilterListLock, False);

    Link = FilterModuleList.Flink;

    while (Link != &FilterModuleList)
    {
        // 这个CONTAINING_RECORD用于从结构对象中的结构成员反向解析出结构对象的地址
        pFilter = CONTAINING_RECORD(Link, MS_FILTER, FilterModuleLink);

        if (BufferLength == _MAC_LENGTH)
        {
            if (NdisEqualMemory(Buffer, pFilter->Mac, _MAC_LENGTH))
            {
                FILTER_RELEASE_LOCK(&FilterListLock, False);
                return pFilter;
            }
        }
        Link = Link->Flink;
    }

    FILTER_RELEASE_LOCK(&FilterListLock, False);
    return NULL;
}

这样调用NdisFSendNetBufferLists函数的时候,第一个参数就是对应的网络适配器,驱动会在调用FilterAttach的时候将底层网络适配器的Handle传递进来,这个时候就需要保存下来用于后续NDIS函数调用。

注意: 在此处,将发送的缓冲区取整了,实际上使用了74字节,但是为了对齐使用了80字节,事实证明换回74字节,驱动并不会异常,不过并未修改此处。

释放缓冲区

根据内核内存分配的惯例,需要自行释放在驱动中分配的缓冲区,所以需要添加SendNetBufferListsCompleteHandler对应的例程,下面是案例的代码。

VOID
FilterSendNetBufferListsComplete(
    NDIS_HANDLE         FilterModuleContext,
    PNET_BUFFER_LIST    NetBufferLists,
    ULONG               SendCompleteFlags
    )
/*++

Routine Description:

发送完成处理程序

每当下层完成处理发送的 NET_BUFFER_LIST 时,就会调用此例程。如果 Filter 不需要参与发送路径,则应删除此例程和 FilterSendNetBufferLists
例程。NDIS 将比 Filter 更有效地代表您的 Filter 传递发送数据包。

Arguments:

    FilterModuleContext     - Filter上下文
    NetBufferLists          - 数据包
    SendCompleteFlags       - 发送标志

Return Value:

     NONE

--*/
{
    PMS_FILTER         pFilter = (PMS_FILTER)FilterModuleContext;
    //PNET_BUFFER_LIST   CurrNetBufferList = NULL;
    //PNET_BUFFER_LIST   NextNetBufferList;
    ULONG              NumOfSendCompletes = 0;
    BOOLEAN            DispatchLevel;
    PNET_BUFFER_LIST   CurrNbl;

    DEBUGP(DL_TRACE, "===>SendNBLComplete, NetBufferList: %p.\n", NetBufferLists);

    // 如果您的 Filter 将任何发送数据包注入到要发送的数据路径中,
    // 您必须在此处识别他们的NBL并将其从链中删除。不要尝试将完整的NBL发送到更高层;

    // 如果您的 Filter 在FilterSendNetBufferLists处理程序修改了您的数据库中的任何NBL
    // 或NBs、MDL等),您必须在此处撤消修改。
    // 一般来说,NBL必须以与您之前相同的条件归还,
    // 你收到了它们。例外情况:NBL可以在链接上重新使用列表,划痕字段为“NO”

    // 发送完整的NBL。如果您从链中删除了任何NBL,请确保链不是空的(即NetBufferLists!=NULL)

#ifdef FILTER_TEST
    // NetBufferLists是个链表,但是在 以太网 级别的内核驱动,基本都是单独一个NET_BUFFER;
    // 稳妥的写法还是遍历链表,下面以简便为主;
    //for (CurrNetBufferList = NetBufferLists;
    //    CurrNetBufferList != NULL;
    //    CurrNetBufferList = NextNetBufferList)
    //{
    //    NextNetBufferList = NET_BUFFER_LIST_NEXT_NBL(CurrNetBufferList);

        if (pFilter->FilterHandle == NetBufferLists->SourceHandle)
        {
            // 释放自己分配的数据包
            ReleaseNetBufferList(pFilter->SendNetBufferListPool, NetBufferLists);
        }
        else
        {
            NdisFSendNetBufferListsComplete(pFilter->FilterHandle, NetBufferLists, SendCompleteFlags);
        }
    //}
#else
    NdisFSendNetBufferListsComplete(
        pFilter->FilterHandle, 
        NetBufferLists, 
        SendCompleteFlags
    );
#endif

    DEBUGP(DL_TRACE, "<===SendNBLComplete.\n");
}

当数据包发送到下层驱动之后,这个函数会调用,然后进行处理,filter驱动的数据包来源可能是自己分配的,也可能不是自己分配的,所以需要使用下面的代码来判断:

	if(pFilter->FilterHandle == pNetBufferList->SourceHandle)
	{
		// 是自行分配的数据包
		if(pNetBufferList->ParentNetBufferList == NULL)
        {
            // 自行分配的数据包
        }
        else
        {
            // 自行克隆的数据包

            // 一般除了释放克隆数据包之外,还需要完成之前暂存的数据包
            NdisFSendNetBufferListsComplete(...)
        }
    }

注意到里面调用了 ReleaseNetBufferList,这个函数的代码如下:

void ReleaseNetBufferList(IN NDIS_HANDLE pNdisPoolHandle, IN PNET_BUFFER_LIST NetBufferLists)
{
    PNET_BUFFER_LIST	pCurrNbl;
    PNET_BUFFER			pCurrBuff;
    PMDL				pMdl;
    PUCHAR              pCopyData = NULL;
    ULONG               BufferLength;

    pCurrNbl = NetBufferLists;
    pCurrBuff = NET_BUFFER_LIST_FIRST_NB(pCurrNbl);
    // 获取MDL
    pMdl = NET_BUFFER_FIRST_MDL(pCurrBuff);
    if (pMdl != NULL)
    {
        // 获取MDL指向的内存
        NdisQueryMdl(pMdl, (PVOID*)&pCopyData, &BufferLength, NormalPagePriority);
        if (pCopyData != NULL)
        {
            NdisFreeMdl(pMdl);//Free MDL  
            NdisFreeMemoryWithTagPriority(pNdisPoolHandle, pCopyData, FILTER_ALLOC_TAG);
        }
    }
    NdisFreeNetBufferList(pCurrNbl);

    return ;
}

http://www.kler.cn/a/371113.html

相关文章:

  • ARCGIS PRO SDK 实现图层标注
  • 深入理解Java虚拟机(六)
  • 知识吾爱纯净版小程序系统 leibiao SQL注入漏洞复现(XVE-2024-30663)
  • 实验九 视图的使用
  • Hexo提交部署命令与Git Bash Here控制终端中按下Ctrl+C无法中断hexo s的解决办法
  • 开源一个开发的聊天应用与AI开发框架,集成 ChatGPT,支持私有部署的源码
  • SpringMVC学习(3)
  • Android的SQLiteOpenHelper类 笔记241027
  • 开发了一个成人学位英语助考微信小程序
  • Spark SQL DSL
  • QNAP威联通NAS怎么通过HBS3设置定时备份计划至外部存储
  • Python日志系统详解:Logging模块最佳实践
  • 如何选择适合自己的 Python IDE
  • 「C/C++」C++11 之 std::pair单元存储的途径
  • Rust 基础语法与常用特性
  • 论文略读:Can We Edit Factual Knowledge by In-Context Learning?
  • 154. 寻找旋转排序数组中的最小值 II
  • MATLAB车道检测与跟踪
  • ctfshow(179->182)--SQL注入--更多过滤与like运算符
  • 基于fpga技术的脉冲信号源设计(论文+源码)