庖丁解牛—winpcap源码彻底解密续 (12)

来源:互联网 发布:用户画像 大数据挖掘 编辑:程序博客网 时间:2024/05/16 09:33
 

应用程序如何和内核驱动交互Event;

/*!

  \brief Allocates the read event associated with the capture instance, passes it down to the kernel driver

  and stores it in an _ADAPTER structure.

  \param AdapterObject Handle to the adapter.

  \return If the function succeeds, the return value is nonzero.

 

  This function is used by PacketOpenAdapter() to allocate the read event and pass it to the driver by means of an ioctl

  call and set it in the _ADAPTER structure pointed by AdapterObject.

 

  NOTE: this function is used for NPF adapters, only.

*/

BOOLEAN PacketSetReadEvt(LPADAPTER AdapterObject)

{

     DWORD BytesReturned;

     HANDLE hEvent;

 

     TRACE_ENTER("PacketSetReadEvt");

 

     if (AdapterObject->ReadEvent != NULL)

     {

         SetLastError(ERROR_INVALID_FUNCTION);

         return FALSE;

     }

 

     hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);  //手动设置成无信号

 

     if (hEvent == NULL)

     {

         //SetLastError done by CreateEvent  

         TRACE_EXIT("PacketSetReadEvt");

         return FALSE;

     }

 

     if(DeviceIoControl(AdapterObject->hFile,

              BIOCSETEVENTHANDLE,

              &hEvent,

              sizeof(hEvent),

              NULL,

              0,

              &BytesReturned,

              NULL)==FALSE)

     {

         DWORD dwLastError = GetLastError();

         CloseHandle(hEvent);

         SetLastError(dwLastError);

         TRACE_EXIT("PacketSetReadEvt");

         return FALSE;

     }

 

     AdapterObject->ReadEvent = hEvent;

     AdapterObject->ReadTimeOut=0;

 

     TRACE_EXIT("PacketSetReadEvt");

     return TRUE;

}

 

该函数的调用堆栈如下:

Pcap_open_live  àpcap_create 中设置回调函数pcap_active_win32,然后调用PacketOpenAdapter函数进入packet.c文件,然后调用packetOpenAdapterNPF,然后再该函数里面调用packetSetReadEvt;

     hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

 

 

读数据包时,调用

BOOLEAN PacketReceivePacket(LPADAPTER AdapterObject,LPPACKET lpPacket,BOOLEAN Sync)

{

 

     。。。。。。。。。。。。。。。。。。。。。

          if (AdapterObject->Flags == INFO_FLAG_NDIS_ADAPTER)

     {

         if((int)AdapterObject->ReadTimeOut != -1)

              WaitForSingleObject(AdapterObject->ReadEvent, (AdapterObject->ReadTimeOut==0)?INFINITE:AdapterObject->ReadTimeOut);

    

         res = (BOOLEAN)ReadFile(AdapterObject->hFile, lpPacket->Buffer, lpPacket->Length, &lpPacket->ulBytesReceived,NULL);

     }

     else

     {

         TRACE_PRINT1("Request to read on an unknown device type (%u)", AdapterObject->Flags);

         res = FALSE;

     }

}

 

这里有两个地方需要注意,WaitForSingleObject(AdapterObject->ReadEvent,等待事件有信号或超时;

然后才去读底层的数据包;

     res = (BOOLEAN)ReadFile(AdapterObject->hFile, lpPacket->Buffer, lpPacket->Length, &lpPacket->ulBytesReceived,NULL);

  这里面的lpPacket->Length默认是256000个字节;如果用户设置了应用缓冲区的大小,那么lpPacket->Length就是应用缓冲区的大小;即ReadFile每次都是去读应用缓冲区size的数据包;

 

下面跟踪到npf.sys里面去看驱动是怎么将Event设置成有信号的。

在packet.c里面

case BIOCSETEVENTHANDLE:

        TRACE_MESSAGE(PACKET_DEBUG_LOUD, "BIOCSETEVENTHANDLE");

       

#ifdef _AMD64_

        if (IoIs32bitProcess(Irp))

        {

            //

            // validate the input

            //

            if (IrpSp->Parameters.DeviceIoControl.InputBufferLength != sizeof (hUserEvent32Bit))

            {

                SET_FAILURE_INVALID_REQUEST();

                break;

            }

            hUserEvent32Bit = *(VOID*POINTER_32*)Irp->AssociatedIrp.SystemBuffer;

            hUserEvent = hUserEvent32Bit;

        }

        else

#endif //_AMD64_

        {

            //

            // validate the input

            //

            if (IrpSp->Parameters.DeviceIoControl.InputBufferLength != sizeof (hUserEvent))

            {

                SET_FAILURE_INVALID_REQUEST();

                break;

            }

            hUserEvent = *(PHANDLE)Irp->AssociatedIrp.SystemBuffer;

        }

 

        //

        // NT4 doesn't seem to have EVENT_MODIFY_STATE, so on NT4 we request a wider set

        // of privileges for the event handle

        //

#ifdef __NPF_NT4__

        Status = ObReferenceObjectByHandle(hUserEvent,

            OBJECT_TYPE_ALL_ACCESS, *ExEventObjectType, Irp->RequestorMode,

            (PVOID*) &pKernelEvent, NULL);

#else   //__NPF_NT4__

        Status = ObReferenceObjectByHandle(hUserEvent,

            EVENT_MODIFY_STATE, *ExEventObjectType, Irp->RequestorMode,

            (PVOID*) &pKernelEvent, NULL);

#endif  //__NPF_NT4__      

        if (!NT_SUCCESS(Status))

        {

            // Status = ??? already set

            Information = 0;

            break;

        }

 

        //

        // NT4 does not have InterlockedCompareExchangePointer

        // InterlockedCompareExchange on NT4 has the same prototype of InterlockedCompareExchange

        // on NT5x, so we use this one.

        //

#ifdef __NPF_NT4__

        if (InterlockedCompareExchange(&Open->ReadEvent, pKernelEvent, NULL) != NULL)

#else

        if (InterlockedCompareExchangePointer(&Open->ReadEvent, pKernelEvent, NULL) != NULL)

#endif

        {

            //

            // dereference the new pointer

            //

            ObDereferenceObject(pKernelEvent);

            SET_FAILURE_INVALID_REQUEST();

            break;

        }

        KeResetEvent(Open->ReadEvent);

        SET_RESULT_SUCCESS(0);

        break;

 

 


/*ObReferenceObjectByHandle函数的作用是

http://blog.sina.com.cn/s/blog_62a630640100gost.html

调用方传递给驱动程序的句柄不会经过 I/O 管理器,因此 I/O 管理器不对这类句柄执行任何验证检查。决不要假设一个句柄有效;始终确保句柄拥有正确的对象类型、对于所需任务的合适的访问权、正确的访问模式,并且访问模式与请求的访问兼容。

驱动程序应该谨慎使用句柄,特别是那些从用户模式应用程序接收到的句柄。第一,这种句柄特定于进程上下文,因此它们仅在打开句柄的进程中有效。当从不同的进程上下文或工作线程使用时,句柄可以引用不同的对象或者只是变得无效。第二,在驱动程序使用句柄期间,攻击者可以关闭和重新打开句柄来改变其引用的内容。第三,攻击者可以传入这样一个句柄来引诱驱动程序执行对于应用程序非法的操作,例如调用ZwXxx 函数。对于这些函数的内核模式调用方,访问检查被跳过,因此攻击者可以使用这种机制绕过验证。

驱动程序还应该确保用户模式应用程序不能误用驱动程序创建的句柄。为一个句柄设置 OBJ_KERNEL_HANDLE 属性使其成为内核句柄,内核句柄可以在任何进程上下文中使用,但是只能从内核模式进行访问(对于传递给ZwXxx 例程的句柄,这特别重要)。用户模式的进程不能访问、关闭或替换内核句柄。

您应该做什么?

接收到任何句柄之后,立即调用 ObReferenceObjectByHandle 来为对象指针交换用户模式句柄:

始终指定期望的对象类型,从而您可以使用 ObReferenceObjectByHandle 提供的类型检查。

对于用户模式句柄,将 AccessMode 指定为 UserMode(假设期望用户对文件对象的访问权限与您的驱动程序相同)。

始终检查 ObReferenceObjectByHandle 返回的状态码,并且当它为 STATUS_SUCCESS 时进行处理。

当您完成对 ObReferenceObjectByHandle 提供的对象指针的使用时,调用 ObDereferenceObject 来释放指针并避免资源泄漏。

创建在内核模式中使用的句柄之前,调用 InitializeObjectAttributes 来初始化一个 OBJECT_ATTRIBUTES 结构,其中Attributes 值设置为 OBJ_KERNEL_HANDLE。

下列代码片段显示 ObReferenceObjectByHandle 的正确用法,在这个例子中为一个事件的句柄。

NTSTATUS status; PKEVENT userEvent; HANDLE handle; handle = RetrieveHandleFromIrpBuffer(…); status = ObReferenceObjectByHandle(handle, EVENT_MODIFY_STATE, *ExEventObjectType, UserMode, (PVOID*) &userEvent, NULL); if (NT_SUCCESS(status)) { // do something interesting here KeSetEvent(userEvent, IO_NO_INCREMENT, FALSE); ObDereferenceObject(userEvent); }

 

 

InterlockedCompareExchangePointer函数的作用是:

PVOID InterlockedCompareExchange(PLONG plDestination, LONG lExchange, LONG lComparand);

PVOID InterlockedCompareExchangePointer(PVOID* ppvDestination,

                                                                 PVOID pvExchange, PVOID pvComparand);

这两个函数负责执行一个原子测试和设置操作。如果是32位应用程序,那么两个函数都在32位值上运行,但是,如果是64位应用程序,InterlockedCompareExchange函数在32位值上运行,而InterlockedCompareExchangePointer函数则在64位值上运行。

在伪代码中,它的运行情况如下面所示:

LONG InterlockedCompareExchange(PLONG plDestination, LONG lExchange, LONG lComparand)

{

 LONG lRet = *plDestination;

 //Original value

 if(*plDestination == lComparand)

   {

      *plDestination = lExchange;

   }

 return(lRet);

}

该函数对当前值( plDestination 参数指向的值)与lComparand参数中传递的值进行比较。如果两个值相同,那么* plDestination 改为lExchange参数的值。如果* plDestination 中的值与lExchange的值不匹配, * plDestination 保持不变。该函数返回* plDestination 中的原始值。记住,所有这些操作都是作为一个原子执行单位来进行的。

 

在if (InterlockedCompareExchangePointer(&Open->ReadEvent, pKernelEvent, NULL) != NULL)

当Open->ReadEvent=NULL时,将Open->ReadEvent= pKernelEvent,否则保持不变;

 

KeResetEvent(Open->ReadEvent);

将事件置为没信号;它和KeClearEvent的区别如下:
LONG KeResetEvent
(
  IN PRKEVENT Event
  );
复位一个指定事件到没有信号状态并返回以前这个事件的信号状态.
  返回非零说明以前是有信号状态;
  返回 零说明以前是无信号状态;

VOID KeClearEvent
(
  IN PRKEVENT Event
  );
设置一个指定事件到没有信号状态.
说明:
  KeResetEvent 和 KeClearEvent 都是设置一个指定事件到没有信号状态,
  如果不想得到以前这个事件的信号状态的话用 KeClearEvent 更好.

 

 

原创粉丝点击