用VC编写网络嗅探工具

目前有很多Sniff工具,比如Netxray和Sniffer pro,是Windows环境下最著名的工具。在Windows环境下使用它们分析包是非常方便的。Sniffit,Snoop,Tcpdump,Dsniff等等在UNIX环境下很常见。介绍了一个用C语言实现的简单的网络嗅探器,网络数据包和分析开发工具libpcap和winpcap。

2网络嗅探器程序的实现

在C环境下编程,源代码如下:

/* 2002年6月2日

* Bby团队19毕业资格项目*/

#包括

#包括

//必须添加路径,必须包含头文件packet32.h。

#包含"..\..\包含\packet32.h "

#包含"..\..\Include\ntddndis.h "

#定义最大数量适配器10

//原型

//外包

void print packets(LP packet LP packet);

//设备列表

char Adapter list[Max _ Num _ Adapter][1024];

//主程序启动

int main()

{

//定义一个指向适配器结构设备指针的指针

LP adapter LP adapter = 0;

//定义一个指向包结构的指针包指针。

LPPACKET lpPacket

int I;

DWORD dwErrorCode

DWORD dwVersion

DWORD dwWindowsMajorVersion

//Unicode字符串(WinNT)

WCHAR adapter name[8192];//网络适配器设备列表

WCHAR *temp,* temp 1;

//ASCII字符串(Win9x)

char adapter namea[8192];//网络适配器设备列表

char *tempa,* temp 1a;

int AdapterNum=0,Open

ULONG适配器长度;

char缓冲区[256000];//保存来自驱动器的数据的缓冲区

struct bpf _ stat stat

//获取本地网卡的名称

AdapterLength = 4096

printf("Packet.dll测试应用程序。库版本:%s\n ",packet get version());

printf("安装的适配器:\ n ");

I = 0;

以下代码用于获取不同版本的网络适配器名称:

Win9x和WinNT中的网卡名称是分别用ASCII和UNICODE实现的,所以要先获取本地操作系统的版本号。

dw version = GetVersion();

dwWindowsMajorVersion =(DWORD)(LOBYTE(LOWORD(dw version)));

这里使用的第一个Packet.dll函数是Packet GetAdapternames(PT STR pStr,PULONG BufferSize),它通常是第一个与驱动程序通信并被调用的函数。它将用户本地系统中安装的网络适配器的名称放在缓冲区PSTR中。BufferSize是缓冲区的长度:

如果(!(dwVersion & gt= 0x80000000。& ampdwWindowsMajorVersion & gt= 4))

{//是Windows NT。

//找不到设备列表。

if(PacketGetAdapterNames(AdapterName,& ampAdapterLength)==FALSE){

printf("无法检索适配器列表!\ n ");

return-1;

一个简单的网络嗅探器的实现来自:书签纸网。

}

//找到设备列表

temp = AdapterName

temp 1 = adapter name;

while ((*temp!='\0')||(*(temp-1)!='\0'))

{

if (*temp=='\0 ')

{

memcpy(AdapterList,temp1,(temp-temp 1)* 2);

temp 1 = temp+1;

i++;

}

temp++;

}

//显示适配器列表

adapter num = I;

for(I = 0;i wprintf(L"\n%d- %s\n ",i+1,adapter list);

printf(" \ n ");

}

Else //否则就是windows 9x,获取适配器名的方法和WinNT下一样。

{

if(PacketGetAdapterNames(adaptername,& ampAdapterLength)==FALSE){

printf("无法检索适配器列表!\ n ");

本文中一个简单网络嗅探器的实现来自于

return-1;

}

tempa = AdapterNamea

temp 1a = adapter namea;

而((*tempa!='\0')||(*(tempa-1)!='\0'))

{

if (*tempa=='\0 ')

{

memcpy(AdapterList,temp1a,tempa-temp 1a);

temp 1a = tempa+1;

i++;

}

tempa++;

}

adapter num = I;

for(I = 0;i printf("\n%d- %s\n ",i+1,adapter list);

printf(" \ n ");

}

下面的代码是让用户选择要监听的网络适配器号:

//选择设备

{

printf("选择要打开的适配器的编号:");

scanf("%d ",& amp打开);

如果(打开& gt适配器编号)

printf(" \ n该数字必须小于%d ",adapter num);

} while(打开& gtadapter num);

然后,打开选定的设备,该设备可以设置为“混合”模式或“直接”模式。代码如下:

//打开设备

LP adapter = packet Open adapter(adapter list[Open-1]);

//当设备无法打开时,显示错误信息:

如果(!LP adapter | |(LP adapter-& gt;hFile ==无效句柄值))

{

dw error code = GetLastError();

printf("无法打开适配器,错误代码:%lx\n ",dw Error Code);

return-1;

}

使用以下代码将网卡设置为“混杂”模式:

这里使用了Packet SethwFilter (LPAdapter适配器对象,ULONG过滤器)函数,它对传入的数据包设置硬件过滤器,如果操作成功,则返回TRUE。AdapterObject是筛选器所在的网卡设备的指针;过滤器的常量过滤器在头文件ntddndis.h中定义,包括:

Ndis-Packet-Type-Promiscuos:设置混杂模式,每一个传入的数据包都会被网卡接受;

NDIS-数据包类型-定向:只接受直接发送到主机网卡的数据包;

NDIS-分组-类型-广播:只接受广播分组;

NDIS-分组-类型-组播:只接受主机所在组的组播分组;

NDIS-分组-类型-所有-多播:接受每个多播分组。

//将网络适配器设置为混杂模式

//如果混杂模式设置失败,会提示错误:

if(PacketSetHwFilter(lpAdapter,NDIS _数据包_类型_混杂)==FALSE){

一个简单的网络嗅探器的实现来自:书签纸网。

printf("警告:无法设置混杂模式!\ n ");

}

然后在驱动程序中放一个512K的缓冲区:

这里用到了函数Packet Set Buff(LP Adapter Adapter Object,int dim),用来设置适配器对象所指向的网卡的驱动的缓冲区,如果成功则返回TRUE。Dim是新缓冲区的大小。设置后,旧缓冲区中的数据将被丢弃,存储的数据包将丢失。

注意:驱动缓冲区的大小设置是否合适会影响包拦截过程的性能,设置要保证运行速度快,不丢包。这里设置的是512000字节。

//在驱动中设置一个512K的缓冲区

//无法设置缓冲区时,会提示错误:

if(PacketSetBuff(lpAdapter,512000)==FALSE){

printf("无法设置内核缓冲区!\ n ");

return-1;

}

packet setreadtime out(LP adapter adapter object,int timeout)的作用是设置绑定到适配器对象指定的网卡的读操作的超时值。超时以毫秒为单位,0表示没有超时。当没有数据包时,read不会返回。

//设置1秒读取超时

//设置读操作超时1秒。

if(PacketSetReadTimeout(LP adapter,1000)==FALSE){

printf("警告:无法设置read tiemout!\ n ");

}

接下来,定位设备,代码如下:

这里使用的函数PacketAllocatePacket(Void)会在内存中分配一个包结构,并返回一个指向它的指针,但是这个结构的Buffer字段还没有设置,所以要再次调用PacketInitPacket函数来初始化它。

//分配并初始化一个数据包结构,该结构将用于

//接收数据包。

//定位失败时,会提示错误:

if((LP packet = PacketAllocatePacket())= = NULL){

printf(" \ n错误:无法分配LPPACKET结构。);

return(-1);

}

然后,您可以初始化设备并开始接受网络数据包:

用PACKET init PACKET(LP PACKET LP PACKET,PVOID Buffer,UINT Length)函数初始化包结构。LpPacket是要初始化的指针;Buffer是指向包含包数据的用户分配的缓冲区的指针;长度是缓冲区的长度。

需要注意的是,与数据包结构相关联的缓冲区存储由数据包捕获驱动程序截获的数据包,数据包的数量受缓冲区大小的限制。最大缓冲区大小是应用程序一次可以从驱动程序中读取的数据量。所以设置大缓冲区可以减少系统调用次数,提高拦截效率。这里的设置是256K K。

PacketInitPacket(lpPacket,(char*)buffer,256000);

接下来,它是包拦截的主循环:

//主捕获循环

这里使用了函数packet receive packet (lpadapter适配器对象,LPPACKET lpPacket,BOOLEAN Sync),它将接受(拦截)一组包。这些参数包括指向用于指定数据包拦截的网卡的适配器结构指针、用于包含数据包的数据包结构以及指示是以同步模式还是异步模式运行的标志。当操作同步时,函数锁定程序;当操作异步时,函数不锁程序,必须调用PacketWaitPacket的进程检查是否正确完成。一般采用同步模式。

//直到有键盘可以键入:

而(!kbhit())

{

//捕获数据包捕获数据包。

//当捕获包失败时,会提示错误:

if(PacketReceivePacket(LP adapter,lpPacket,TRUE)==FALSE){

printf(" Error:PacketReceivePacket失败");

一个简单的网络嗅探器的实现来自:书签纸网。

return(-1);

}

//打印包中的数据,调用自定义函数PrintPackets()。

print packets(LP packet);

}

最后打印出统计数据,代码如下:

这里使用的函数packetgetstats(lpadapter adapter对象,struct bpf_star*s)可以得到两个驱动程序的内部变量的值:调用PacketOpenAdapter以来指定网卡接收到的数据包数;以及网卡已经收到但被内核丢弃的数据包数量。这两个值被驱动程序复制到应用程序提供的bpf_stat结构中。

//打印捕获统计数据

//获取统计信息

//无法从内核读取状态时,会提示错误:

if(PacketGetStats(lpAdapter,& ampstat)==FALSE){

printf("警告:无法从内核获取统计数据!\ n ");

}

//打印“XX包拦截;XX数据包被丢弃”:

其他

printf("\n\n已收到%d个数据包。\n%d个数据包丢失”,stat.bs_recv,stat . bs _ drop);

这里,函数packet free packet(lpPacket LP packet)用于释放LP packet指向的结构:

//释放空间

packet free packet(LP packet);

使用函数packet close adapter(lpAdapter LP adapter)释放适配器结构LP adapter并关闭网卡指针:

//关闭适配器并退出

//关闭设备并退出。

packet close adapter(LP adapter);

return(0);

}//主程序结束

这里不详细描述用于打印数据报的自定义函数PrintPackets()的代码。

3结论

通过网络嗅探器的编写,目的是让大家知道网络管理的重要性,时刻关注网络信息的安全,做好信息的加解密工作。