简述:
监听网络上的所有数据,是一个比较有趣的题目。流传比较广的一些监听程序,它们都使用了一个更加著名的开发包Packet32。比如,ntsniff、EthernetSpy、ntpacket等,还有赫赫有名的WinPcap。应用程序通过它可以设置网卡的工作模式,直接在网卡上读写数据,等等。
一般使用的Packet32的实现版本,是微软的Packet32.c和Packet32.h。这个版本写得比较简单。
WinPcap开发包中自带的Packet32,是Politecnico di Torino重写的,增加了许多错误处理,而且注释翔实,值得一读。
在http://winpcap.polito.it/default.htm中,WinPcap开发包被描述为:WinPcap is an architecture for packet capture and network analysis for the Win32 platforms. It includes a kernel-level packet filter, a low-level dynamic link library (packet.dll), and a high-level and system-independent library (wpcap.dll, based on libpcap version 0.6.2).
下面就给出Packet32这个实现版本的说明,仿照Microsft SDK的风格。
内容依次为:
Packet32包的内容
Packet32包中的函数
PacketGetAdapterNames
PacketOpenAdapter等
Packet32包中的数据结构
例子1:用Packet32设置网卡为混杂模式,监听所有的数据包。
Packet32包的内容
Packet驱动:Oemsetup.inf安装信息文件、Packet.sys系统文件,在利用Packet32包开发网络监控程序前,需要用这两个文件安装Packet驱动。Packet32程序开发库:Packet32.lib静态链接库、 Packet32.dll动态链接库,用户可以通过调用库中的函数直接对网卡进行操作。
Packet32包中的函数说明:
No.1. PacketGetAdapterNames(从注册表中读取网卡名)
得到现有的网络适配器的列表和它们的描述。
BOOLEAN PacketGetAdapterNames(
PTSTR pStr,
PULONG BufferSize
);
Parameters:
pStr:
[in , out] 一块用户负责分配的缓冲区,将把适配器的名字填充进去。
BufferSize:
[in] pStr这块缓冲区的大小。
Return Values:
如果查询成功,返回一个非零值。
Usage:
[C/C++]
C/C++ Usage Sample
char AdapterNamea[8192];
ULONG AdapterLength;
PacketGetAdapterNames(AdapterName,&AdapterLength);
Remarks:
通常,这都是与网卡通信时要调用的第一个函数。它返回系统上安装了的网卡的名字。在每个网卡的名字后面,pStr中还有一个与之相应的描述。
由于结果都是通过查询注册表得到的,所以WindowsNTx和Windows9X/Me下得到的字符串编码是不同的。Windows9X下用ASCII编码存储,而WindowsNTx则是Unicode。
如果是在Windows9X下,调用完PacketGetAdapterNames后,得到的pStr将是这样的:
- 一串用"\0"分隔的ASCII字符串,每一个都是一个网卡的名字;
- 两个"\0";
- 一串用"\0"分隔的ASCII字符串,每一个都是一个网卡的描述;顺序是和网卡名字一样的;
- 两个"\0";
如果是在WindowsNTx下,调用完PacketGetAdapterNames后,得到的pStr将是这样的:
- 一串用一个Unicode的"\0"分隔的Unicode字符串,每一个都是一个网卡的名字;
- 两个Unicode的"\0";
- 一串用ASCII的"\0"分隔的ASCII字符串,每一个都是一个网卡的描述;顺序是和网卡名字一样的;
- 两个ASCII的"\0";
这个函数的操作大致为:
网卡的注册表项是:
HKEY_LOCAL_MACHINE\
SYSTEM\
CurrentControlSet\
Control\
Class\
{4D36E972-E325-11CE-BFC1-08002BE10318}
先打开这个键值;
再枚举下面的每一项,依次读取参数:
对子项\Linkage\UpperBind参数,核对是否等于” NdisWan”,如果不是,就跳过去;
如果是” NdisWan”,则读取子项\Linkage\Export,这就是网卡的名字。
如果前面的查询有网卡记录,那么执行下面这个循环:
将调用PacketOpenAdapter打开每个网卡;
其中将调用PacketRequest(adapter,FALSE,OidData)来得到网卡的描述;
具体方法是这样,OidData是一个PACKET_OID_DATA结构,我们事先设置它的Oid成员为OID_GEN_VENDOR_DESCRIPTION,然后调用PacketRequest把这个OID发送给网卡driver,就可以从OidData->Data中拿到网卡的描述了。
最后调用PacketCloseAdapter关闭适配器。
如果前面没有查询到网卡记录,那么执行我们将根据TCP/IP Binding来查找网卡:
先打开这个键值;
HKEY_LOCAL_MACHINE\
SYSTEM\
CurrentControlSet\
Services\
Tcpip\
Linkage
它的Bind参数设置的就是现在系统上绑定的网卡的名字。
得到名字之后,同上调用PacketOpenAdapter和PacketRequest方法向网卡查询它的描述。
(To be continued)
Writen by zhengyun@tomosoft.com
本文档所包含的信息代表了在发布之日,ZhengYun 对所讨论问题的当前看法,Zhengyun 不保证所给信息在发布之日以后的准确性。
本文档仅供参考。对本文档中的信息,Zhengyun 不做任何明示或默示的保证。
参考文献:
1.如何编写网络监视器 作者:邹建平
2.《Windows NT下开发网络监控程序》 作者:北京锐信科学技术培训中心
Packet32包中的函数说明:
No.2.PacketOpenAdapter (打开网卡)
根据传入的设备名,打开它。
LPADAPTER PacketOpenAdapter(
LPTSTR AdapterName
);
Parameters:
AdapterName:
[in] 要打开的设备的名字。
Return Values:
如果打开成功,返回一个指针,它指向一个正确初始化了的ADAPTER Object。
否则,返回NULL。
Usage:
[C/C++]
C/C++ Usage Sample
LPADAPTER adapter;
adapter = PacketOpenAdapter(pStr+rewind);
Remarks:
这个函数尝试加载并启动packet driver,这样,管理driver对于应用程序来说就十分的透明了。
Windows9X版本的NPF driver用的是ASCII编码,而WindowsNTx用的是Unicode编码。所以提请注意这个输入参数AdapterName,在Windows9X下,必须是正确的编码格式!在WindowsNTx下,这个函数能够监测到ASCII编码,并在送给driver 之前先转换为Unicode编码。
这个函数的操作大致为:
首先调用OpenSCManager,以Administrators的身份连接Service Control Manager,权限是SC_MANAGER_ALL_ACCESS。这也说明,使用Packet.dll你必须是本机管理员组成员。
如果可以连接SCM,检查NPF注册表项是否存在。如果存在,说明driver已经安装了,就不需要我们调用PacketInstallDriver了。
NPF注册表项:
HKEY_LOCAL_MACHINE\
SYSTEM\
CurrentControlSet\
Services\
NPF
如果不存在此键,则调用PacketInstallDriver安装当前路径下的driver:npf.sys。
如果这次PacketInstallDriver安装也失败了,就到系统路径下查找并安装这个驱动。
如果以上操作都成功的话,调用OpenService打开NPF服务。如果服务存在的话,就调用QueryServiceStatus查询当前服务状态。这就是我们的driver的状态。
如果这个服务没有启动,就调用StartService启动之。
确认服务启动之后,检查AdapterName是否是ASCII编码,是的话,就转换为Unicode。
由于一般输入参数AdapterNAme类似于这样:
\Device\NPF_{A67CEC3B-C099-47E0-B096-480B01FAF348}
所以,我们会重新组织一个设备名SymbolicLink:
“Packet_”的前缀 + AdapterName[8]
也就是:
\\.\Packet_NPF_{A67CEC3B-C099-47E0-B096-480B01FAF348}
先尝试着CreateFile函数能不能马上用这个SymbolicLink名字打开设备。
如果可以,就调用PacketSetReadEvt函数打开一个open事件等。
否则,调用DefineDosDevice定义一个新的MS-DOS设备:
名字类似于” Packet_NPF_{A67CEC3B-C099-47E0-B096-480B01FAF348}”。
通过这个DOS设备名,我们的应用层程序才能向驱动发出请求。
接着,调用CreateFile函数来建立并打开一个联系设备的文件句柄(GENERIC_WRITE| GENERIC_READ,OPEN_EXISTING)。
之后,调用PacketSetReadEvt函数打开一个open事件等。
(To be continued)
Writen by zhengyun@tomosoft.com
本文档所包含的信息代表了在发布之日,ZhengYun 对所讨论问题的当前看法,Zhengyun 不保证所给信息在发布之日以后的准确性。
本文档仅供参考。对本文档中的信息,Zhengyun 不做任何明示或默示的保证。
Packet32包中的数据结构:
第一个重要的数据结构:_ADAPTER(关于Network Adapter的)
typedef struct _ADAPTER
{
// 一个打开的NPF driver实例的句柄:
HANDLE hFile;
// 当前打开的网卡的名字:
CHAR SymbolicLink[MAX_LINK_NAME_LENGTH];
// 在这块Adapter上,一个数据包被写的次数:
int NumWrites;
// 这块Adapter上的read操作的通知事件。它可以被传递给标准Win32函数(如WaitForSingleObject或者WaitForMultipleObjects),这样可以等待到driver的缓冲区内有数据到来。在同时等待几个事件的GUI程序中,它特别有用。在Windows2000/XP中,函数PacketSetMinToCopy()可以用来设置内核缓冲区中激发本事件的最小数据大小:
HANDLE ReadEvent;
// 设置一个时间,到时候,即使没有捕获任何包,read操作也会被释放,ReadEvent也会被触发:
UINT ReadTimeOut;
} ADAPTER, *LPADAPTER;
第二个重要的数据结构:_PACKET(关于Packet的)
typedef struct _PACKET
{
// 向后兼容用的:
HANDLE hEvent;
// 向后兼容用的:
OVERLAPPED OverLapped;
// 存放Packets的缓冲区:
PVOID Buffer;
// 缓冲区的大小:
UINT Length;
// 当前缓冲区中有效的字节数,如,上一次调用PacketReceivePacket()函数接收到的字节数:
DWORD ulBytesReceived;
// 向后兼容用的:
BOOLEAN bIoComplete
} PACKET, *LPPACKET;
第三个重要的数据结构:_PACKET_OID_DATA (关于OID请求的)
typedef struct _PACKET_OID_DATA
{
// OID的code,有效的OID code的定义参见ntddndis.h;比如:
// OID_GEN_SUPPORTED_LIST,OID_GEN_VENDOR_DESCRIPTION等:
ULONG Oid;
// 成员Data的长度:
ULONG Length;
// 存放发送给网卡或者从网卡接收的数据的缓冲区:
UCHAR Data[1];
}
typedef struct _PACKET_OID_DATA PACKET_OID_DATA, *PPACKET_OID_DATA;
其他数据结构:
npf_if_addr(网卡的地址):
typedef struct npf_if_addr {
struct sockaddr IPAddress; // IP address.
struct sockaddr SubnetMask; // Netmask for that address.
struct sockaddr Broadcast; // Broadcast address.
}npf_if_addr;
bpf_hdr(Packet Header):
struct bpf_hdr {
// 捕获到的packet的timestamp:
struct timeval bh_tstamp;
// 捕获到的packet的长度:
UINT bh_caplen;
// 原始packet的长度:
UINT bh_datalen;
// bpf header的长度(this struct plus alignment padding):
USHORT bh_hdrlen;
};
bpf_insn(一个简单的BPF伪指令):
bpf_insn中包含了一个BPF注册机的简单指令,它被用来发送一个filter程序给driver。
struct bpf_insn {
// 指令的类型和寻址模式:
USHORT code;
// Jump if true:
UCHAR jt;
// Jump if false:
UCHAR jf;
// 通用的一个字段,有多种目的:
int k;
};
bpf_program(一个BPF伪汇编程序):
这段程序将被PacketSetBPF()注射入内核,并被应用到每一个进来的Packet。
struct bpf_program {
// 程序指令数目,如,后面的bpf_insn结构的数目:
UINT bf_len;
// 指向第一个bpf_insn结构的指针:
struct bpf_insn *bf_insns;
};
bpf_stat (本次捕获的统计数据):
这个结构将被Packet.dll用来返回捕获过程中的统计数据。
struct bpf_stat {
// 从开始捕获起,这个driver从网卡上接收的Packet的数量(包括driver丢失的Packet):
UINT bs_recv;
//从开始捕获起,这个driver丢失的Packet的数量,一般地,包丢失,是因为driver的缓冲区满了,这时driver将扔掉这个包:
UINT bs_drop;
UINT ps_ifdrop;
// 通过filter的包的数量:
UINT bs_capt;
};
dump_bpf_hdr(Dump Packet Header):
struct dump_bpf_hdr{
// Packet的timestamp:
struct timeval ts;
// 捕获到的packet的长度:
UINT caplen;
// 原始Packet的长度:
UINT len;
};
NetType (网络类型):
NetType用于PacketGetNetType(),返回当前网卡的类型和速度。
struct NetType{
//当前网卡的MAC:
UINT LinkType;
// 网络的速度(bits/s):
UINT LinkSpeed;
};
(To be continued)
Writen by zhengyun@tomosoft.com
本文档所包含的信息代表了在发布之日,ZhengYun 对所讨论问题的当前看法,Zhengyun 不保证所给信息在发布之日以后的准确性。
本文档仅供参考。对本文档中的信息,Zhengyun 不做任何明示或默示的保证。
Packet32包中的函数说明:
No.3. PacketSetHwFilter (设置过滤器)
设置一个hardware filter。比如,Filter参数传递NDIS_PACKET_TYPE_PROMISCUOUS,就可以设置网卡为混杂模式。
BOOLEAN PacketSetHwFilter(
LPADAPTER AdapterObject,
ULONG Filter
);
Parameters:
AdapterObject:
[in] 指向一个_ADAPTER结构的指针。
Filter:
[in] 过滤器的id。
Return Values:
如果执行成功,返回一个非零值。
Usage:
C/C++ Usage Sample
lpAdapter = PacketOpenAdapter(AdapterList[Open-1]);
PacketSetHwFilter(lpAdapter,NDIS_PACKET_TYPE_PROMISCUOUS);
Remarks:
过滤器定义在ntddndis.h中。下面是一些最常用的:
NDIS_PACKET_TYPE_PROMISCUOUS:设置混杂模式。网卡接收每一个Packet;
NDIS_PACKET_TYPE_DIRECTED;
NDIS_PACKET_TYPE_BROADCAST:只接收broadcast packets;
NDIS_PACKET_TYPE_MULTICAST:只接收multicast packets,而且本机网卡是接收组的一个成员;
NDIS_PACKET_TYPE_ALL_MULTICAST:所有multicast packets都接收;
NDIS_PACKET_TYPE_ALL_LOCAL:所有local packets。