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.2 修改WinPcap的内核驱动代码 - 图1

图 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.2 修改WinPcap的内核驱动代码 - 图2

图 13-4 支持内核转储功能的测试结果

通过Wireshark捕获网络上发送的数据包,如图13-5所示。采用Wireshark提供的打开存储文件功能打开内核转储文件test.txt,如图13-6所示。可以看到,两种方式的数据结果一致,所以对内核代码的修改是成功的。

13.2 修改WinPcap的内核驱动代码 - 图3

图 13-5 发送端发送的测试数据包(共100个)

13.2 修改WinPcap的内核驱动代码 - 图4

图 13-6 内核转储文件(test.txt)的数据包分析(共18个)