13.2 修改WinPcap的内核驱动代码
前面曾提到,在WinPcap的官方文档中描述了内核转储的特性,但是还没被激活,因此无法使用该特性。经过笔者的努力,在对WpcapSrc_4_1_2.zip版本的底层驱动程序NPF源代码进行修改与调试后,最终使其可以支持内核转储模式(MODE_DUMP)与内核统计转储模式(MODE_STAT_DUMP)。
下面将详细介绍相关的修改与实验内容。
13.2.1 支持内核转储功能
在WinPcap最新版本(WpcapSrc_4_1_2.zip)上运行测试程序(详见12.2.2节)的结果如图13-3所示。
图 13-3 不支持内核转储功能的测试结果
从结果可见,WinPcap并不能真正支持内核转储的功能。
下面对WinPcap的内核源代码进行修改,使WinPcap真正支持内核转储功能,具体的修改步骤如下:
步骤一:修改winpcap\packetNtx\driver\Packet.c文件的源代码,主要是激活与内核转储相关的命令码对应的操作,具体包括:设置内核转储模式(BIOCSMODE)、设置转储文件名(BIOCSETDUMPFILENAME)、设置转储限制(BIOCSETDUMPLIMITS),以及获得转储过程是否结束的状态(BIOCISDUMPENDED)。
把Packet.c文件原1052~1221行的代码修改为如下代码:
NTSTATUS NPF_IoControl(IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
{
……
case BIOCSMODE://设置模式
if(IrpSp->Parameters.DeviceIoControl.
InputBufferLength<sizeof(ULONG)){
SET_FAILURE_BUFFER_SMALL();
break;
}
mode=*((PULONG)Irp->AssociatedIrp.SystemBuffer);
if(mode==MODE_DUMP)//内核转储
{
Open->mode=MODE_DUMP;
SET_RESULT_SUCCESS(4);
break;
}
else if(mode==MODE_CAPT)
{
Open->mode=MODE_CAPT;
SET_RESULT_SUCCESS(1);
break;
}
else
{
if(mode&MODE_STAT)
{
Open->mode=MODE_STAT;
NdisAcquireSpinLock(&Open->CountersLock);
Open->Nbytes.QuadPart=0;
Open->Npackets.QuadPart=0;
NdisReleaseSpinLock(&Open->CountersLock);
if(Open->TimeOut.QuadPart==0)
Open->TimeOut.QuadPart=-10000000;
}
if(mode&MODE_DUMP)
{
Open->mode|=MODE_DUMP;
}
SET_RESULT_SUCCESS(3);
break;
}
SET_FAILURE_INVALID_REQUEST();
break;
case BIOCSETDUMPFILENAME://设置转储文件名
if(Open->mode&MODE_DUMP)
{
//关闭当前转储文件
if(Open->DumpFileHandle!=NULL)
{
NPF_CloseDumpFile(Open);
Open->DumpFileHandle=NULL;
}
if(IrpSp->Parameters.DeviceIoControl.
InputBufferLength==0)
{
EXIT_FAILURE(0);
}
//分配存储文件名的缓冲区
DumpNameBuff=ExAllocatePoolWithTag(
NonPagedPool,IrpSp->Parameters.DeviceIoControl.
InputBufferLength,
'5PWA');
if(DumpNameBuff==NULL||
Open->DumpFileName.Buffer!=NULL)
{
IF_LOUD(DbgPrint("NPF:unable to allocate the dump
flename:not enough memory or name
already set\n");)
EXIT_FAILURE(0);
}
//复制文件名
RtlCopyBytes((PVOID)DumpNameBuff,
Irp->AssociatedIrp.SystemBuffer,
IrpSp->Parameters.DeviceIoControl.
InputBufferLength);
//在文件名后强制添加“\0”
((PSHORT)DumpNameBuff)[IrpSp->Parameters.
DeviceIoControl.InputBufferLength/2-1]=0;
//给Open->DumpFileName赋unicode字符串格式的文件名
RtlInitUnicodeString(&Open->DumpFileName,
DumpNameBuff);
//创建文件
if(NT_SUCCESS(NPF_StartDump(Open)))
{
DbgPrint("NPF_IoControl—>Open Dump fle OK,Begine Thread\n");
EXIT_SUCCESS(0);
}
}
EXIT_FAILURE(0);
break;
case BIOCSETDUMPLIMITS://设置转储限制
if(IrpSp->Parameters.DeviceIoControl.
InputBufferLength<2*sizeof(ULONG))
{
EXIT_FAILURE(0);
}
//最大字节数
Open->MaxDumpBytes=*(PULONG)Irp->AssociatedIrp.SystemBuffer;
//最大数据包数
Open->MaxDumpPacks=*((PULONG)Irp->AssociatedIrp.SystemBuffer+1);
IF_LOUD(DbgPrint("NPF:Set dump limits to%u bytes,%u packs\n",
Open->MaxDumpBytes,Open->MaxDumpPacks);)
EXIT_SUCCESS(0);
break;
case BIOCISDUMPENDED://获得转储过程是否结束
if(IrpSp->Parameters.DeviceIoControl.OutputBufferLength
<sizeof(UINT))
{
EXIT_FAILURE(0);
}
if((Open->DumpLimitReached)&&(Open->DumpFileHandle==NULL))
{((UINT)Irp->UserBuffer)=1;}
else
{((UINT)Irp->UserBuffer)=0;}
EXIT_SUCCESS(4);
break;
……
}
步骤二:修改NPF_SaveCurrentBuffer函数原型。把winpcap\packetNtx\driver\目录下的Packet.h文件中的第840行代码修改为如下代码:
//NTSTATUS NPF_SaveCurrentBuffer(POPEN_INSTANCE Open);
int NPF_SaveCurrentBuffer(POPEN_INSTANCE Open);
步骤三:修改NPF_Read函数。把winpcap\packetNtx\driver\目录下的Read.c文件中的代码修改为如下代码:
NTSTATUS NPF_Read(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
{
……
//把原来121~137行的源代码修改为如下代码
if(!(Open->mode&MODE_DUMP))//121行
{
if(Occupation<=Open->MinToCopy*g_NCpu)
{
if(Open->ReadEvent!=NULL)
{//等待,直到一些数据包到达或超时
if(Open->TimeOut.QuadPart!=(LONGLONG)IMMEDIATE)
{
KeWaitForSingleObject(
Open->ReadEvent,
UserRequest,
KernelMode,
TRUE,
(Open->TimeOut.QuadPart==(LONGLONG)0)?NULL:&(Open->TimeOut)
);
}
KeClearEvent(Open->ReadEvent);
}
}
}
if(Open->mode&MODE_STAT)//136行
{//处于统计模式
……
//把原来的184行、185行源代码修改为如下代码
header->bh_hdrlen=sizeof(struct bpf_hdr);
Irp->IoStatus.Information=24+sizeof(struct bpf_hdr);
……
//去除原来306行的“}”
……
}
……
}
步骤四:修改NPF_tap函数。把winpcap\packetNtx\driver\目录下的Read.c文件中的代码修改为如下代码:
//屏蔽第592行代码
//Open->DumpLimitReached=TRUE;//This stops the thread
步骤五:修改NPF_Cleanup函数。把winpcap\packetNtx\driver\目录下的Openclose.c文件中的原789~819行的代码修改为如下代码:
if(Open->mode&MODE_DUMP)
NdisSetEvent(&Open->DumpEvent);
else
KeSetEvent(Open->ReadEvent,0,FALSE);
//如果处于转储模式,则完成转储并关闭转储文件
if((Open->mode&MODE_DUMP)&&Open->DumpFileHandle!=NULL)
{
NTSTATUS wres;
ThreadDelay.QuadPart=-50000000;
//等待转储线程的结束
wres=KeWaitForSingleObject(
Open->DumpThreadObject,
UserRequest,
KernelMode,
TRUE,
&ThreadDelay);
ObDereferenceObject(Open->DumpThreadObject);
//把缓冲区的数据写入转储文件并关闭该文件
NPF_CloseDumpFile(Open);
}
//}
步骤六:修改NPF_UnbindAdapter函数。把winpcap\packetNtx\driver\目录下的Openclose.c文件中的原932~946行的代码修改为如下代码:
if(Open->mode&MODE_DUMP)
NdisSetEvent(&Open->DumpEvent);
else
if(Open->ReadEvent!=NULL)
KeSetEvent(Open->ReadEvent,0,FALSE);
//
//The following code has been disabled bcause
//the kernel dump feature has been disabled.
//
////
////If this instance is in dump mode,
//complete the dump and close the fle
////TODO needs to be checked again.
////
if((Open->mode&MODE_DUMP)&&Open->DumpFileHandle!=NULL)
NPF_CloseDumpFile(Open);
步骤七:修改Dump.c文件的代码(此为最重要的修改)。对winpcap\packetNtx\driver\目录下Dump.c文件中的各行代码做如下修改。
修改原104行、105行的代码如下:
ntStatus=ZwCreateFile(&Open->DumpFileHandle,
FILE_WRITE_DATA|FILE_APPEND_DATA,
修改原146~148行的代码如下:
//fsdDevice=IoGetRelatedDeviceObject(Open->DumpFileObject);
//IF_LOUD(DbgPrint("NPF:Dump:write fle created succesfully,
status=%d\n",ntStatus);)
修改原155~514行的代码如下:
/保存转储文件头信息到文件/
NTSTATUS NPF_WriteDumpHead(POPEN_INSTANCE Open)
{
NTSTATUS ntStatus;
struct packet_fle_header hdr;
IO_STATUS_BLOCK IoStatus;
NDIS_REQUEST pRequest;
ULONG MediaType;
OBJECT_ATTRIBUTES ObjectAttributes;
IF_LOUD(DbgPrint("NPF:StartDump.\n");)
//初始化文件头信息
hdr.magic=TCPDUMP_MAGIC;
hdr.version_major=PCAP_VERSION_MAJOR;
hdr.version_minor=PCAP_VERSION_MINOR;
hdr.thiszone=0;/Currently not set/
hdr.snaplen=1514;
hdr.sigfgs=0;
//检测介质类型
switch(Open->Medium){
case NdisMediumWan:
hdr.linktype=DLT_EN10MB;
break;
case NdisMedium802_3:
hdr.linktype=DLT_EN10MB;
break;
case NdisMediumFddi:
hdr.linktype=DLT_FDDI;
break;
case NdisMedium802_5:
hdr.linktype=DLT_IEEE802;
break;
case NdisMediumArcnet878_2:
hdr.linktype=DLT_ARCNET;
break;
case NdisMediumAtm:
hdr.linktype=DLT_ATM_RFC1483;
break;
default:
hdr.linktype=DLT_EN10MB;
}
//把文件头信息存入文件中
ntStatus=ZwWriteFile(
Open->DumpFileHandle,
NULL,
NULL,
NULL,
&IoStatus,
&hdr,
sizeof(hdr),
&Open->DumpOffset,
NULL
);
if(!NT_SUCCESS(ntStatus))
{
IF_LOUD(DbgPrint("11NPF:Error dumping fle%x\n",
ntStatus);)
ZwClose(Open->DumpFileHandle);
Open->DumpFileHandle=NULL;
ntStatus=STATUS_NO_SUCH_FILE;
return ntStatus;
}
//更新文件偏移计数
Open->DumpOffset.QuadPart=sizeof(hdr);
return ntStatus;
}
/创建转储线程/
NTSTATUS NPF_StartDump(POPEN_INSTANCE Open)
{
NTSTATUS ntStatus;
ntStatus=PsCreateSystemThread(
&Open->DumpThreadHandle,
THREAD_ALL_ACCESS,
(ACCESS_MASK)0L,
0,
0,
NPF_DumpThread,
Open);
if(!NT_SUCCESS(ntStatus))
{
IF_LOUD(DbgPrint("NPF:Error creating dump thread,
status=%x\n",ntStatus);)
ZwClose(Open->DumpFileHandle);
Open->DumpFileHandle=NULL;
Open->DumpThreadHandle=NULL;
return ntStatus;
}
ntStatus=ObReferenceObjectByHandle(
Open->DumpThreadHandle,
THREAD_ALL_ACCESS,
*PsThreadType,
KernelMode,
&Open->DumpThreadObject,
0);
if(!NT_SUCCESS(ntStatus))
{
IF_LOUD(DbgPrint("NPF:Error creating dump thread,
status=%x\n",ntStatus);)
ObDereferenceObject(Open->DumpFileObject);
ZwClose(Open->DumpFileHandle);
Open->DumpFileHandle=NULL;
return ntStatus;
}
return ntStatus;
}
/把缓冲区的内容写入文件/
UINT NPF_SaveDumpBuffer(POPEN_INSTANCE Open,
PUCHAR CurrBuff,UINT SizeToDump)
{
NTSTATUS ntStatus;
IO_STATUS_BLOCK IoStatus;
if(Open->DumpFileHandle)
{
ntStatus=ZwWriteFile(
Open->DumpFileHandle,
NULL,
NULL,
NULL,
&IoStatus,
CurrBuff,
SizeToDump,
&Open->DumpOffset,
NULL);
Open->DumpOffset.QuadPart+=SizeToDump;
DbgPrint("Open->DumpOffset=%d\n",
Open->DumpOffset.QuadPart);
if(!NT_SUCCESS(ntStatus))
{
IF_LOUD(DbgPrint("2NPF:
Error NPF_SaveDumpBuffer%x\n",ntStatus);)
ntStatus=STATUS_NO_SUCH_FILE;
return ntStatus;
}
}
else
{
IF_LOUD(DbgPrint("Error:
NPF_SaveDumpBuffer DumpFileHandle is NULL.\n");)
}
return 1;//true
}
/向转储文件中写入数据(包括给每个数据包添加的头信息与数据包)/
int NPF_SaveCurrentBuffer(POPEN_INSTANCE Open)
{
ULONG writed_num=0;
ULONG count,current_cpu,plen,increment,ToCopy;
CpuPrivateData*LocalData;
if(Open->MaxDumpBytes.QuadPart
&&(UINT)Open->DumpOffset.QuadPart>Open->MaxDumpBytes.QuadPart)
{
IF_LOUD(DbgPrint("NPF:dump reach max bytes\n");)
Open->DumpLimitReached=TRUE;
return writed_num;
}
DbgPrint("—>NPF_SaveCurrentBuffer—>Save\n");
count=0;
current_cpu=0;
//对每个CPU进行循环,如果count=NCpu,则表示没有要复制的数据包了
while(count<g_NCpu)
{
LocalData=&Open->CpuData[current_cpu];
if(LocalData->Free<Open->Size)
{//所选中的缓冲区中有一些数据包需要复制
struct PacketHeaderHeader=(struct PacketHeader)
(LocalData->Buffer+LocalData->C);
//检查是否为下一个要复制的数据包
if(Header->SN==Open->ReaderSN)
{
plen=Header->header.bh_caplen;
//转储数据包的头信息
NPF_SaveDumpBuffer(
Open,
(PUCHAR)&(Header->header.bh_tstamp.tv_sec),
sizeof(Header->header.bh_tstamp.tv_sec));
NPF_SaveDumpBuffer(
Open,
(PUCHAR)&(Header->header.bh_tstamp.tv_usec),
sizeof(Header->header.bh_tstamp.tv_usec));
NPF_SaveDumpBuffer(
Open,
(PUCHAR)&(Header->header.bh_caplen),
sizeof(Header->header.bh_caplen));
NPF_SaveDumpBuffer(
Open,
(PUCHAR)&(Header->header.bh_datalen),
sizeof(Header->header.bh_datalen));
LocalData->C+=sizeof(struct PacketHeader);
if(LocalData->C==Open->Size)
LocalData->C=0;
if(Open->Size-LocalData->C<plen)
{
//该数据包被分割成了两块数据,因缓冲区的回卷导致
ToCopy=Open->Size-LocalData->C;
NPF_SaveDumpBuffer(
Open,
LocalData->Buffer+LocalData->C,
ToCopy);
NPF_SaveDumpBuffer(
Open,
LocalData->Buffer,
plen-ToCopy);
LocalData->C=plen-ToCopy;
}
else
{
//该数据包没有被分割
NPF_SaveDumpBuffer(
Open,
LocalData->Buffer+LocalData->C,
plen);
LocalData->C+=plen;
}
writed_num++;
Open->ReaderSN++;
increment=plen+sizeof(struct PacketHeader);
if(Open->Size-LocalData->C
<sizeof(struct PacketHeader))
{
//为了不分割头信息
//生产者并未把头信息存储在缓冲区的回卷处
//而是直接从缓冲区的开始处存储下一个数据包
//消费者也就直接从开始处读取数据包头信息了
increment+=Open->Size-LocalData->C;
LocalData->C=0;
}
InterlockedExchangeAdd(
&Open->CpuData[current_cpu].Free,increment);
count=0;
if(Open->MaxDumpBytes.QuadPart
&&(UINT)Open->DumpOffset.QuadPart>
Open->MaxDumpBytes.QuadPart)
{
IF_LOUD(DbgPrint("NPF:
dump reach max bytes\n");)
Open->DumpLimitReached=TRUE;
return writed_num;
}
}
else
{
current_cpu=(current_cpu+1)%g_NCpu;
count++;
}
}
else
{current_cpu=(current_cpu+1)%g_NCpu;
count++;
}
}
//返回写入数据包的个数
return writed_num;
}
/转储线程的执行函数/
VOID NPF_DumpThread(POPEN_INSTANCE Open)
{
ULONG FrozenNic;
NTSTATUS ntStatus;
IO_STATUS_BLOCK IoStatus;
UINT packetNumer=0;
ntStatus=NPF_OpenDumpFile(
Open,&Open->DumpFileName,TRUE);
if(!NT_SUCCESS(ntStatus))
{
Open->DumpFileHandle=NULL;
return;
}
ntStatus=NPF_WriteDumpHead(Open);
if(!NT_SUCCESS(ntStatus))
{
ZwClose(Open->DumpFileHandle);
Open->DumpFileHandle=NULL;
return;
}
while(TRUE)
{
//等待转储事件或超时
NdisWaitEvent(&Open->DumpEvent,5000);
//把数据写入转储文件中
packetNumer+=NPF_SaveCurrentBuffer(Open);
if(packetNumer>Open->MaxDumpPacks)
{
Open->DumpLimitReached=1;
}
//检查转储是否结束
if((Open->DumpLimitReached)||(Open->Size==0))
{
NPF_CloseDumpFile(Open);
PsTerminateSystemThread(STATUS_SUCCESS);
ObDereferenceObject(Open->DumpThreadObject);
return;
}
NdisResetEvent(&Open->DumpEvent);
}
}
/关闭转储文件/
NTSTATUS NPF_CloseDumpFile(POPEN_INSTANCE Open)
{
NTSTATUS ntStatus;
IO_STATUS_BLOCK IoStatus;
PMDL WriteMdl;
PUCHAR VMBuff;
UINT VMBufLen;
IF_LOUD(DbgPrint("NPF:NPF_CloseDumpFile.\n");)
if(Open->DumpFileHandle==NULL)
{
return STATUS_UNSUCCESSFUL;
}
if(Open->ReadEvent!=NULL)
KeSetEvent(Open->ReadEvent,0,FALSE);
//关闭转储文件
ZwClose(Open->DumpFileHandle);
Open->DumpFileHandle=NULL;
ObDereferenceObject(Open->DumpFileObject);
return STATUS_SUCCESS;
}
在修改了内核驱动源代码后,重新编译NPF,然后把生成的npf.sys文件复制到C:\WINDOWS\system32\drivers目录下,重启操作系统,这样就可验证修改的效果了。测试实例见[ch13/kdump工程],运行结果如图13-4所示。
图 13-4 支持内核转储功能的测试结果
通过Wireshark捕获网络上发送的数据包,如图13-5所示。采用Wireshark提供的打开存储文件功能打开内核转储文件test.txt,如图13-6所示。可以看到,两种方式的数据结果一致,所以对内核代码的修改是成功的。
图 13-5 发送端发送的测试数据包(共100个)
图 13-6 内核转储文件(test.txt)的数据包分析(共18个)