您的位置:首页 > 理论基础 > 计算机网络

使用NDIS驱动监測以太网络活动

2017-05-15 19:32 330 查看
转载自: http://blog.csdn.net/ddtpower/article/details/656687
本论文提供了NDIS的主要的理解,应用程序怎样与驱动程序交互。发挥驱动程序最佳性能。本论文也说明了使用样例驱动(PACKET.SYS)监測以太网的应用程序。

本论文不是帮助程序猿开发网络驱动而是帮助他使用这种驱动。


引言
从计算机被发明以来,对大多数编程人员来说。编写设备驱动都是令人着迷的。开发驱动是为了满足特定应用的须要。这个目的导致大量不同种类的驱动被开发出来,比如打印机驱动,文件系统驱动等。

此外。特定的应用与特定种类的驱动相相应。随着Internet的到来,编写网络驱动成为驱动开发的核心。为帮助开发网络设备驱动。微软为Windows
NT操作系统开发了网络设备接口规范(NDIS)库。
1、简单介绍
1、1 什么是设备驱动
设备驱动是操作系统和输入输出设备间的粘合剂。驱动负责将操作系统的请求传输。转化为特定物理设备控制器可以理解的命令。
1、2 什么是网络接口卡
网络接口卡(NIC)是一个物理设备,类似于网关,通过它,网络中的不论什么设备都能够发送和接收数据帧。不同网络中。网络接口卡的名称是不同的。

比如。以太网中称为以太网接口卡,令牌环网中称为令牌环网接口卡,等等。

1、3 什么是网络驱动接口规范(NDIS)
网络驱动接口规范描写叙述了一个接口,通过这个接口。一个或多个NIC驱动能够与一个或多个覆盖在其上的协议驱动和操作系统通信。NDIS为网络驱动开发提供了完整的抽象。

对全部外部功能来说。NIC驱动都依赖于NDIS。这些功能包含与协议驱动的通信。注冊,截获NIC硬件中断。与下层的NIC的通信。
1、4 为什么须要NDIS
NDIS库(NDIS.SYS)为NIC驱动的编写提供了完整的抽象接口。库输出全部能够在NIC驱动开发中使用的NT内核模式函数。这个库负责响应全部下层NIC驱动特定的任务,保持绑定和状态信息。

2、特点与使用
2、1基本特征
l 单NIC驱动
l NDIS库
l 均衡处理器支持
l 多协议驱动支持
l 管理
l 发送类型
l 操作标志
l 全双工操作
l ARCNET和WAN支持
2、2 驱动类型
Windows NT支持三种类型的驱动:
l 网络接口卡驱动(NIC)
l 中间协议驱动
l 上层协议驱动

2、2、1 网络接口卡驱动
NIC驱动管理网络接口卡(NIC)。NIC驱动接口在下边界直接控制硬件(NIC)。在上边界提供上层驱动訪问的接口:
l 发送和接收包
l 重置NIC(Reset)
l 停止NIC
l 查询NIC
l 设置NIC操作特性
NIC驱动的两种类型
l 微port驱动:微port驱动应用于管理NIC硬件特殊操作,包含在NIC上发送和接收数据。微port驱动不能直接呼叫系统例程,仅仅能呼叫NDIS提供的函数。
l 全然NIC驱动:全然NIC驱动不仅管理硬件并且管理NDIS完毕的的操作系统特定任务。

全然NIC驱动必须保持接收数据的绑定信息。
2、2、2 中间协议驱动
中间协议驱动接口位于上层协议驱动和微port驱动之间。

对于上层传输驱动程序来书,中间驱动看起来像是微port驱动。对微port驱动来说,看起来像是协议驱动。使用中间协议驱动的主要是为了传输媒质,存在于对于传输驱动未知和微port管理之间的新的媒质类型。

2、2、3 上层协议驱动
上层协议驱动应用于TDI接口或其他向用户提供服务的特定应用接口。比如驱动调用NDIS分配包,向包中烤贝数据和向底层发送包。

它也在它的底层提供协议接口,来接收下层驱动发送来的包。

2、3 应用程序和驱动的交互
在Windows NT下的全部驱动必须具有DriverEntry函数,作为驱动的进入点。

驱动中其他的函数是通过DriverEntry函数声明的。应用程序调用函数如CreateFile。ReadFile等。会由NT I/O管理器生成对应IRP(输入/输出请求包)。
在NT下。差点儿全部I/O操作都是包驱动的。

每一个I/O操作由工作顺序来描写叙述,工作顺序告诉驱动做什么和通过I/O子系统追踪请求的过程。

这些工作顺序通过一个称为I/O请求包(IRP)的数据结构的形式给出。

这个IRP为完毕特定操作按顺序调用驱动中的进入点。

3、在以太网中使用NDIS监測数据包
本节以一个NDIS驱动样例说明监測以太网。
Packet 监測捕获在局域网中传输的的全部包。这是由以太网的广播特性决定的。通过以太网发送的包是向网络中全部计算机广播的。每台计算机上的以太网卡检查每一个包的目的是否是它自己。假设是则接收,不是则拒绝。利用这个主要的功能来捕获网络中传输的全部数据包。以太网卡能够被设置工作于好几种模式,能够设置于期望中的捕获数据包的模式。一个这种能够工作于期望的样例驱动程序PACKET.SYS来自微软Windows
NT 设备开发工具(DDK)。

3、1 以太网接口卡的模式
以太网接口卡能够被设置的模式例如以下:
l 广播
l 多播
l 直接
l 混杂
广播:数据帧能够发向网络中全部计算机。这样的帧的目的地址是0xffffff,也称为帧的广播地址。不论什么设置为广播模式的网卡都接收目的地址为广播地址的数据帧。通常全部的网卡被配置为接收广播帧。

多播:发往一组计算机的帧称为多播帧。使用特定的多播地址作为目的地址。这些计算机的组构成了多播组。这样。多播组里的不论什么一个成员计算机将会接收具有多播目的地址的帧。虽然网卡能够不是一个多播组里成员,但能够配置为多播模式,这样就能够接收全部多播帧。

直接:发往特定计算机的帧具有特定计算机的物理地址(以太网地址)的目的地址。具有特定物理地址的计算机将接收特定的帧,丢弃其他的帧。网卡能够设置为只接收直接帧。
混杂:设置为这个模式的网卡接收全部收到的数据包。这个模式具有的以太网的广播特性是包监測应用程序的关键。

PACKET.SYS样例驱动能够将网卡设置为以上提到的模式。

应用能够利用PACKET.SYS将网卡设置为混杂模式,以便于捕获网络中传输的全部数据包。

3、2 关于样例驱动PACKET.SYS
样例PACKET.SYS来自NT DDK。

这个驱动能够将网卡设置为不论什么期望的模式,而且同意应用程序通过网络发送和接收数据包。除驱动程序的sys文件之外。还提供了一个DLL(PACKET32.DLL),通过此DLL应用程序能够和驱动程序通信。
3、2、1应用程序怎样同PACKET.SYS通信
应用程序调用DLL中的函数。这些函数依次调用PACKET.SYS驱动中的进入点。驱动程序利用NDIS.SYS输出函数与网络接口卡通信。

3、2、2 怎样在应用程序中使用PACKET32.DLL
应用程序使用驱动读取全部到达NIC的数据包。下面演示样例说明了这个过程。
在上述过程中使用的结构体定义例如以下:

[cpp]
view plaincopy

typedef struct _ADAPTER
{
HANDLE hFile; // 保存由CreateFile方法返回的句柄
TCHAR SymbolicLink[MAX_LINK_NAME_LENGTH]; // 保存驱动的符号链接名
} ADAPTER, *LPADAPTER;

[cpp]
view plaincopy

typedef struct _PACKET
{
HANDLE hEvent; // 保存和适配器对象对应的事件句柄
OVERLAPPED OverLapped;// 包括异步输入输出信息的OVERLAPPED结构
PVOID Buffer; // 包括发送和接收数据的缓冲区
UINT Length; // 缓冲区长度
} PACKET, *LPPACKET;

[cpp]
view plaincopy

typedef struct _CONTROL_BLOCK
{
LPADAPTER hFile; // 指向适配器对象的指针
HANDLE hEvent; // 保存事件句柄
// Name of the driver as registered in the registry.
TCHAR AdapterName[64]; // 注冊表中注冊的驱动名
HANDLE hMem; // 保存接收数据包的缓冲区
LPBYTE lpMem;
HGLOBAL hMem2; // 保存发送数据包的缓冲区
LPBYTE lpMem2;
ULONG PacketLength; // 包长度
ULONG LastReadSize; // 最后读取的包大小
UINT BufferSize; // 缓冲区长度
} CONTROL_BLOCK, *PCONTROL_BLOCK;

应用程序開始

[cpp]
view plaincopy

CONTROL_BLOCK cbAdapter;
ULONG NameLength=64;

// 得到驱动在注冊表中注冊的名字
PacketGetAdapterNames(CbAdapter.AdapterName,&NameLength);

CbAdapter.BufferSize=1514; // 保留1514字节,最大帧长度

// 分配并锁定内存缓冲区用于发送和接收数据包
CbAdapter.hMem=GlobalAlloc(GPTR, 1514);
CbAdapter.lpMem=(LPBYTE)GlobalLock(CbAdapter.hMem);
CbAdapter.hMem2=GlobalAlloc(GPTR,1514);
CbAdapter.lpMem2=(LPBYTE)GlobalLock(CbAdapter.hMem2);

// 打开优先的适配器用于接收数据,
// 函数依次调用CreateFile方法。调用驱动程序中对应的进入点。

为随后的读写操作打开适配器
CbAdapter.hFile=(ADAPTER*)PacketOpenAdapter(CbAdapter.AdapterName);

// 打开适配器域
if (CbAdapter.hFile = = NULL)
{
AfxMessageBox("Open Adapter failed");
}

// 此Packet对象用于从网络上接收全部数据的包
PVOID Packet;
// 设置过滤条件为混杂(非选择)模式
// 此函数依次呼叫DeviceIoControl方法。用来设置网卡工作于期望的模式。
Filter = NDIS_PACKET_TYPE_PROMISCUOUS;

// 设置网卡为混杂模式
PacketSetFilter(CbAdapter.hFile, Filter);

// 分配缓冲区用于接收数据包
Packet=PacketAllocatePacket(CbAdapter.hFile);

// 初始化接收数据包缓冲区
if(Packet != NULL)
{
PacketInitPacket((PACKET *)Packet,(char *)pdData[nCurrentWriteLocation].pData,1514);

// 从驱动中读取数据包
// 此函数依次调用ReadFile方法来读取通过EIC从网络上收到的数据
PacketReceivePacket(CbAdapter.hFile,(PACKET *)Packet,TRUE,&pdData[nCurrentWriteLocation].nPacketLength );

上述插入代码描写叙述了应用程序怎样使用PACKET.SYS驱动设置以太网接口卡为混杂模式,用来捕获全部网络上的数据包。

以上代码清晰的描写叙述了应用程序利用样例驱动PACKET.SYS将EIC设置为期望的模式,发送和接收数据包。

3、2、3 应用程序中使用的在Packet32.dll中定义的函数例如以下:
以下的函数PacketGetAdapterNames返回注冊表中注冊的驱动名

[cpp]
view plaincopy

ULONG
PacketGetAdapterNames(
PTSTR pStr,
PULONG BufferSize
)
{
HKEY SystemKey;
HKEY ControlSetKey;
HKEY ServicesKey;
HKEY NdisPerfKey;
HKEY LinkageKey;
LONG Status;
DWORD RegType;
// Open the Key HKEY_LOCAL_MACHINE,打开HKEY_LOCAL_MACHINE键值
Status=RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
TEXT("SYSTEM"),
0,
KEY_READ,
&SystemKey
);
if (Status == ERROR_SUCCESS) {
// Open the key currentcontrolset 打开currentcontrolset键值
Status=RegOpenKeyEx(
SystemKey,
TEXT("CurrentControlSet"),
0,
KEY_READ,
&ControlSetKey
);
if (Status == ERROR_SUCCESS) {
// Open the key Services打开Services键值
Status=RegOpenKeyEx(
ControlSetKey,
TEXT("Services"),
0,
KEY_READ,
&ServicesKey
);
if (Status == ERROR_SUCCESS) {
// Open the key Packet. 打开Packet键值
Status=RegOpenKeyEx(
ServicesKey,
TEXT("Packet"),
0,
KEY_READ,
&NdisPerfKey
);
if (Status == ERROR_SUCCESS) {
// Open the key Linkage.打开Linkage键值
Status=RegOpenKeyEx(
NdisPerfKey,
TEXT("Linkage"),
0,
KEY_READ,
&LinkageKey
);
if (Status == ERROR_SUCCESS) {
// Open the key Export.
Status=RegQueryValueEx(
LinkageKey,
TEXT("Export"),
NULL,
&RegType,
(LPBYTE)pStr,
BufferSize
);
// Close all the keys that have been opened so far.关闭已打开的全部键值
RegCloseKey(LinkageKey);
}
RegCloseKey(NdisPerfKey);
}
RegCloseKey(ServicesKey);
}
RegCloseKey(ControlSetKey);
}
RegCloseKey(SystemKey);
}
return Status;
}

[cpp]
view plaincopy

PVOID PacketOpenAdapter(LPTSTR AdapterName)
{
LPADAPTER lpAdapter;
BOOLEAN Result;

ODS("Packet32: PacketOpenAdapter/n");

// 为适配器对象分配全局内存
lpAdapter=(LPADAPTER)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_ZEROINIT,sizeof(ADAPTER));
if (lpAdapter==NULL)
{
ODS("Packet32: PacketOpenAdapter GlobalAlloc Failed/n");
return NULL;
}

// 将名字复制到符号链接名
wsprintf(lpAdapter->SymbolicLink, TEXT("////.//%s%s"), DOSNAMEPREFIX, &AdapterName[8] );

// Defines an MS-DOS name for the device.
Result=DefineDosDevice(DDD_RAW_TARGET_PATH,&lpAdapter->SymbolicLink[4], AdapterName);

if (Result)
{
// Creates and returns a file handle for the specified device. 为特定设备创建并返回文件句柄
lpAdapter->hFile=CreateFile(lpAdapter->SymbolicLink,GENERIC_WRITE | GENERIC_READ,0,NULL,CREATE_ALWAYS,FILE_FLAG_OVERLAPPED,0);

if (lpAdapter->hFile != INVALID_HANDLE_VALUE)
{
return lpAdapter;
}
}
ODS("Packet32: PacketOpenAdapter Could not open adapter /n");

GlobalFreePtr(lpAdapter );
return NULL;
}

函数PacketOpenAdapter为设备定义了一个新的DOS设备名。调用CreaetFile方法来创建并打开通信设备,得到指向设备的句柄。应用程序预先调用此函数来发送和接收数据包。CreateFile方法调用驱动中指定为IRP_MJ_CREATE的进入点。此进入点调用NDIS库。输出NdisOpenAdapter函数打开适配器。

下面的函数PacketAllocatePacket为packet对象分配内存,调用CreateEvent函数来建立特定文件句柄的事件。

[cpp]
view plaincopy

PVOID PacketAllocatePacket(LPADAPTER AdapterObject)
{
LPPACKET lpPacket;
// 为Packet对象分配内存
lpPacket=(LPPACKET)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_ZEROINIT,sizeof(PACKET));
if (lpPacket==NULL)
{
ODS("Packet32: PacketAllocateSendPacket: GlobalAlloc Failed/n");
return NULL;
}
// 操作结束时建立事件对象
lpPacket->OverLapped.hEvent=CreateEvent( NULL,FALSE,FALSE, NULL);
if (lpPacket->OverLapped.hEvent==NULL)
{
ODS("Packet32: PacketAllocateSendPacket: CreateEvent Failed/n");
GlobalFreePtr(lpPacket);
return NULL;
}
return lpPacket;
}

PacketInitPacket函数设置packet对象缓冲区

[cpp]
view plaincopy

VOID PacketInitPacket(LPPACKET lpPacket,PVOID Buffer,UINT Length)
{
// 设置packet对象缓冲区到缓冲区
lpPacket->Buffer=Buffer;
// 设置packet对象缓冲区长度到缓冲区长度
lpPacket->Length=Length;
}

PacketReceivePacket函数调用驱动中适当的进入点来读取网络中获得的数据包,放入声明的缓冲区。ReadFile方法使用在CreateFile方法中获得的句柄来完毕此操作。

[cpp]
view plaincopy

BOOLEAN PacketReceivePacket(LPADAPTER AdapterObject,LPPACKET lpPacket,BOOLEAN Sync,PULONG BytesReceived)
{
BOOLEAN Result;
// 设置偏移量为0
lpPacket->OverLapped.Offset=0;
lpPacket->OverLapped.OffsetHigh=0;
if (!ResetEvent(lpPacket->OverLapped.hEvent))
{
return FALSE;
}
// 调用ReadFile来读取数据包
Result=ReadFile(AdapterObject->hFile,lpPacket->Buffer, lpPacket->Length, BytesReceived, &lpPacket->OverLapped);
if (Sync)
{
// They want to wait
Result=GetOverlappedResult(AdapterObject->hFile,&lpPacket->OverLapped,BytesReceived, TRUE );
}
else
{
// They don't want to wait, they will call PacketWaitPacket to get
// The real result
//不等待,调用PacketWaitPacket得到真实值
Result = TRUE;
}
return Result;
}

使用驱动PACKET.SYS监測有很多例程是实用的。注意,没有提供很多其它的应用细节。
本节只说明了使用NDIS样例驱动PACKET.SYS监控全部网络动作的应用程序怎样编写。

4、怎样识别HTTP请求
上节描写叙述了如何从以太网中捕获全部的数据包。我们的意图是不只捕获全部数据包。而且监控网络上的Internet活动。这意味着我们应该识别携带HTTP请求的数据包。这须要以太网帧结构和IP。本节描写叙述了TCP/IP包的标识,HTTP请求的标识,帧中的URL信息的获取。

4、1 网络数据流
为了在分层的网络中数据传输,从应用程序数据传输到协议栈中中对应的协议。之后,此协议处理完数据之后,将数据传向栈中的下一个协议。

在数据穿越每一层协议的同一时候,协议栈上对应协议为了栈中下一层协议,将数据封装起来。

因此。封装就是一个将数据存储成协议栈中更低层协议要求的格式的过程。
因此,我们能够看出。应用程序模块封装从用户应用消息传来的数据;TCP模块封装应用数据,添加TCP头而且发往下一层;当数据传向网络栈中IP模块时。IP模块将TCP段格式化为IP报文或称为包。以太网驱动将IP模块传来的数据格式化并将数据放入以太网帧中。

这就解释了帧中怎样封装IP报文,IP包怎样封装TCP/UDP数据。为了识别HTTP请求。首先,我们应该识别包为TCP/IP包,然后检查此包是否是TCP包,最后识别此包是否HTTP请求。

4、2 识别TCP/IP包
为识别一个数据包是TCP/IP包。我们应该首先看看以太网的帧结构。
以太网的帧数据包括了14字节头。
以太网帧头的域是:
l 目的地址(6字节)
l 源地址(6字节)
l 帧类型(2字节)
就像名字的含义。目的地址域说明了以太网帧的目的。类似的,源地址说明了帧的源。帧的类型域是我们关心的。这个域标识了帧的协议。

假设包是有效的IP包。则帧类型域(第13和第14字节)将会是080016。

4、3 识别TCP包
识别了TCP/IP包之后,下一个任务是识别出包是否是TCP包或其它包。由于HTTP请求只通过TCP请求来传输,所以我们能够忽略其它包。为确定包是否是一个TCP包,我们必须分析IP头。IP头例如以下所看到的:
IP头的重要域是:
l 头部长度(4位)和版本(4位)
l 包总长度(2字节)
l 数据在传输层上使用的协议类型。下表说明了IP包的通用TCP/IP协议类型域

Protocol
Value(Decimal)
TCP
6
UDP
17
ICMP
1
IGMP
2
l 头部校验和(2字节)
l 源IP(4字节)
l 目的IP(4字节)
IP头中协议域的值为06说明了数据包是TCP包。

4、4 识别HTTP请求
识别出数据包是TCP包后,我们必须识别包是否是HTTP请求包。为找出包中是否包括HTTP请求。我们必须检查TCP头。TCP头例如以下所看到的:
TCP头中重要的域是:
l 源port(2字节)
l 目的port(2字节)
l 序列号(4字节)
l 标识号(4字节)
l Hlen。保留和代码位(2字节)
l 窗体(2字节)
l 校验和(2字节)
l 紧急点(2字节)
如上所述,TCP头中,我们最关心的域是源和目的port域。这些域说明了连接建立的port。不同的TCP服务比方HTTP,FTP等等使用特定的port号来提供他们的服务。
对HTTP服务来说,port号是008010或005016。假设源port域包括值是80(HTTPport号)。则包就是HTTP响应包。假设目的port域是80,则包是HTTP请求包。

4、5 获取URL
如今我们已经发现包是一个HTTP请求包了,找到包中包括的URL地址就相对简单了。

在浏览器中键入的请求被以GET或POST请求的形式被发HTTPserver。浏览器加入其它的与浏览器相关的信息,发往对应的HTTPserver。完整的信息包括了封装在数据包中的请求页的URL。

因此,依靠分析数据包就能够得到URL。URL出项在数据包中的通用格式为:
GET/HTTP/1.0
以上的URL是我们键入浏览器的不论什么网站默认页。假设我们请求网站的其它页或点击页面中提供的链接。然后请求页也被放入GET请求中。比如,当我们请求的页面为sample.html,则URL将会是:
GET/sample.html HTTP/1.0
因此,通过在数据包中查找GET串,就能够获取URL请求。

5、 驱动性能和操作系统兼容性
在混杂模式下。驱动捕获到达网卡的全部数据包。假设网络流量非常大,就有可能不能捕获而丢包。

这必须考虑在内。驱动中使用的是NDIS版本号是3.0。

驱动在Windows NT下工作良好,但不能在Windows95下工作。由于Windows95只支持的是NDIS是2.1版本号和NDIS 3.1版本号。

在WINDOWS CE中使用驱动的相关问题
Windows CE中NDIS应用是Windows NT下NDIS4.0的一个子集。为Windows NT写的驱动代码能够在Windows CE下工作,但须要考虑到一些问题。才Windows CE下,驱动会被编译为DLL,而不是Windows NT下的.SYS文件。

并且,CE不支持内建的DMA和分配连续的内存块。

更进一步,为CE编写NDIS驱动。程序猿必须考虑到电源管理的问题。

这就是说。必须提供附加的电源管理代码。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: