wfp驱动流量监控之尝试

最近在看wfp驱动,使用wfp驱动实现进程流量监控,原先是参考微软例子,在establish层通过fwpsassociatecontext绑定在传输层。但是当应用先启动,后启动驱动,此时流量监控很不准。于是看了看ale层的一些介绍,选用ale-auth-connect进行fwpsassociate绑定在传输层,此时发现调用fwpsassociate函数的返回值既有0(成功)又有000004(已绑定)如果检测到已绑定的话,是不是要调用fwpsfloeremovecontext的函数进行删除以前绑定的上下文。但是使用establish层 回调函数的flowdeletion函数会自动进行回收删除。但是使用ale-auth-connect就会有些问题,在关联上下文时,返回值有较多的已关联(status_object_name_exists)这种该如何处理呢?
或者说如果使用establish层进行关联上下文能保证应用先启动,驱动后启动也能监控到流量信息呢?

使用ALE层的FWPS_LAYER_ALE_AUTH_CONNECT_V4或FWPS_LAYER_ALE_AUTH_CONNECT_V6层来进行流量监控。由于这两个层是在连接建立的时候就开始处理的,所以能够更准确地监控流量。

在已经绑定上下文的情况下,可以根据需要选择使用FwpsFlowRemoveContext()函数手动删除以前绑定的上下文或保持之前的绑定不变。如果选择手动删除可通过检查函数返回值确定是否成功,若成功则可能需要进行相应的清理操作。

当使用FWP_CALLOUT_FLAG_ALE_AUTH_CONNECT时,在关联上下文时返回"status_object_name_exists"的错误是正常的现象。 这是因为这个标志表示已与身份验证连接相关联。 只有一个流对象可以与身份验证连接相关联,所以当尝试将另一个流对象添加到身份验证连接上下文时会发生此错误。 可以在处理此错误时忽略它。如果您要使用FWP_CALLOUT_FLAG_ESTABLISHED_CONNECTION标志,可以保证应用程序启动后再启动驱动时能正确监视流量信息。

  1. 编写WFP驱动:需要使用WFP的API和内核模式编程技术,编写WFP驱动,以达到对数据包的拦截和处理。

  2. 添加匹配规则:可以在WFP驱动中添加匹配规则,对网络流量进行过滤。

  3. 记录流量信息:当流量匹配规则时,可以将相关信息记录到日志文件中,方便后续分析。

  4. 分析数据:对记录的流量信息进行分析,可以了解网络流量的类型、来源、目的地等信息,以便进行进一步的优化和管理。

需要注意的是,WFP驱动流量监控需要具备一定的编程和内核知识,不建议初学者进行尝试,且在实践过程中需要格外注意系统稳定性和安全性。

首先,关于使用FWPM_LAYER_ALE_AUTH_CONNECT层进行绑定的问题,你可以根据返回值的状态码处理,如果是已经绑定的状态码(status_object_name_exists),则可以不必进行FWPSFlowRemoveContext调用,因为已经存在上下文了。这种情况下,你可以选择不进行任何处理,因为已经有一个绑定的上下文了。

其次,在使用FWPM_LAYER_STREAM_ESTABLISHED层进行绑定时,如果应用程序先启动,驱动后启动,则的确会有一段时间内监测不到流量信息。因为在应用程序启动之前,驱动并没有机会对传输进行监管。要解决这个问题,你可以参考以下方案:

1、在驱动加载时,就立即进行绑定操作,这样就可以提前监测到所有流量。
2、如果不能在驱动加载时就进行绑定,你可以尝试通过在驱动中创建一个线程,等待应用程序启动后再进行绑定。这样应该也能达到监测流量的目的。



#include <ntddk.h>
#pragma warning(push)
#pragma warning(disable:4201)       // unnamed struct/union
#pragma warning(disable:4995)
#include <ndis.h>
#include <fwpsk.h>
#pragma warning(pop)
#include <fwpmk.h>
#include <limits.h>
#include <ws2ipdef.h>
#include <in6addr.h>
#include <ip2string.h>
#include <strsafe.h>
#include <wdm.h>
 
#define INITGUID
#include <guiddef.h>
#define bool BOOLEAN
#define true TRUE 
#define false FALSE
#define DEVICE_NAME L"\\Device\\MyDriver"
#define DEVICE_DOSNAME L"\\DosDevices\\MyDriver"
#define kmalloc(_s) ExAllocatePoolWithTag(NonPagedPool, _s, 'SYSQ')
#define kfree(_p) ExFreePool(_p)
 
DEFINE_GUID // {6812FC83-7D3E-499a-A012-55E0D85F348B}
(
    GUID_ALE_AUTH_CONNECT_CALLOUT_V4, 
    0x6812fc83, 
    0x7d3e, 
    0x499a, 
    0xa0, 0x12, 0x55, 0xe0, 0xd8, 0x5f, 0x34, 0x8b
);
 
PDEVICE_OBJECT  gDevObj;
HANDLE    gEngineHandle = 0;
HANDLE    gInjectHandle = 0;
//CalloutId
UINT32    gAleConnectCalloutId = 0;
//FilterId
UINT64    gAleConnectFilterId = 0;




/*
以下两个回调函数没啥用
*/
NTSTATUS NTAPI WallNotifyFn(
    IN FWPS_CALLOUT_NOTIFY_TYPE  notifyType,
    IN const GUID  *filterKey,
    IN const FWPS_FILTER  *filter
){
    return STATUS_SUCCESS;
}
 
VOID NTAPI WallFlowDeleteFn(
    IN UINT16  layerId,
    IN UINT32  calloutId,
    IN UINT64  flowContext
){
    return;
}
 
//协议代码转为名称
char* ProtocolIdToName(UINT16 id)
{
    char *ProtocolName=kmalloc(16);
    switch(id)    //http://www.ietf.org/rfc/rfc1700.txt
    {
        case 1:
            strcpy_s(ProtocolName,4+1,"ICMP");
            break;
        case 2:
            strcpy_s(ProtocolName,4+1,"IGMP");
            break;
        case 6:
            strcpy_s(ProtocolName,3+1,"TCP");
            break;
        case 17:
            strcpy_s(ProtocolName,3+1,"UDP");
            break;
        case 27:
            strcpy_s(ProtocolName,3+1,"RDP");
            break;
        default:
            strcpy_s(ProtocolName,7+1,"UNKNOWN");
            break;
    }
    return ProtocolName;
}
 
//最重要的过滤函数
//http://msdn.microsoft.com/en-us/library/windows/hardware/ff551238(v=vs.85).aspx
void NTAPI WallALEConnectClassify
(
    IN const FWPS_INCOMING_VALUES0* inFixedValues,
    IN const FWPS_INCOMING_METADATA_VALUES0* inMetaValues,
    IN OUT void* layerData,
    IN const void* classifyContext,
    IN const FWPS_FILTER* filter,
    IN UINT64 flowContext,
    OUT FWPS_CLASSIFY_OUT* classifyOut
){
    char *ProtocolName=NULL;
    DWORD LocalIp,RemoteIP;
    LocalIp=inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_LOCAL_ADDRESS].value.uint32;
    RemoteIP=inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_REMOTE_ADDRESS].value.uint32;
    ProtocolName=ProtocolIdToName(inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_PROTOCOL].value.uint16);
    DbgPrint("[WFP]IRQL=%d;PID=%ld;Path=%S;Local=%u.%u.%u.%u:%d;Remote=%u.%u.%u.%u:%d;Protocol=%s\n",
             (USHORT)KeGetCurrentIrql(),
             (DWORD)(inMetaValues->processId),
             (PWCHAR)inMetaValues->processPath->data,
             (LocalIp>>24)&0xFF,
             (LocalIp>>16)&0xFF,            
             (LocalIp>>8)&0xFF,
             LocalIp&0xFF,
             inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_LOCAL_PORT].value.uint16,
             (RemoteIP>>24)&0xFF,
             (RemoteIP>>16)&0xFF,
             (RemoteIP>>8)&0xFF,
             RemoteIP&0xFF,    
             inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_REMOTE_PORT].value.uint16,
              ProtocolName
     );
    kfree(ProtocolName);
    classifyOut->actionType = FWP_ACTION_PERMIT;//允许连接
     
    //禁止IE联网(设置“行动类型”为FWP_ACTION_BLOCK)
    // if(wcsstr((PWCHAR)inMetaValues->processPath->data,L"iexplore.exe"))
    // {
    // classifyOut->actionType = FWP_ACTION_BLOCK;
    // classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
    // classifyOut->flags |= FWPS_CLASSIFY_OUT_FLAG_ABSORB;
    // }
    return;
}



// WFP 回调
NTSTATUS RegisterCalloutForLayer
(
    IN const GUID* layerKey,
    IN const GUID* calloutKey,
    IN FWPS_CALLOUT_CLASSIFY_FN classifyFn,
    IN FWPS_CALLOUT_NOTIFY_FN notifyFn,
    IN FWPS_CALLOUT_FLOW_DELETE_NOTIFY_FN flowDeleteNotifyFn,
    OUT UINT32* calloutId,
    OUT UINT64* filterId
)
{
    NTSTATUS        status = STATUS_SUCCESS;
    FWPS_CALLOUT    sCallout = {0};
    FWPM_FILTER     mFilter = {0};
    FWPM_FILTER_CONDITION mFilter_condition[1] = {0};
    FWPM_CALLOUT    mCallout = {0};
    FWPM_DISPLAY_DATA mDispData = {0};
    BOOLEAN         bCalloutRegistered = FALSE; 
    sCallout.calloutKey = *calloutKey;
    sCallout.classifyFn = classifyFn;
    sCallout.flowDeleteFn = flowDeleteNotifyFn;
    sCallout.notifyFn = notifyFn;
    //要使用哪个设备对象注册
    status = FwpsCalloutRegister( gDevObj,&sCallout,calloutId );
    if( !NT_SUCCESS(status))
        goto exit;
    bCalloutRegistered = TRUE;
    mDispData.name = L"WFP TEST";
    mDispData.description = L"TESLA.ANGELA's WFP TEST";
    //你感兴趣的内容
    mCallout.applicableLayer = *layerKey;
    //你感兴趣的内容的GUID
    mCallout.calloutKey = *calloutKey;
    mCallout.displayData = mDispData;
    //添加回调函数
    status = FwpmCalloutAdd( gEngineHandle,&mCallout,NULL,NULL);
    if( !NT_SUCCESS(status))
        goto exit;
    mFilter.action.calloutKey = *calloutKey;
    //在callout里决定
    mFilter.action.type = FWP_ACTION_CALLOUT_TERMINATING;
    mFilter.displayData.name = L"WFP TEST";
    mFilter.displayData.description = L"TESLA.ANGELA's WFP TEST";
    mFilter.layerKey = *layerKey;
    mFilter.numFilterConditions = 0;
    mFilter.filterCondition = mFilter_condition;
    mFilter.subLayerKey = FWPM_SUBLAYER_UNIVERSAL;
    mFilter.weight.type = FWP_EMPTY;
    //添加过滤器
    status = FwpmFilterAdd( gEngineHandle,&mFilter,NULL,filterId );
    if( !NT_SUCCESS( status))
        goto exit;
exit:
    if(!NT_SUCCESS(status)){
        if( bCalloutRegistered )
            FwpsCalloutUnregisterById( *calloutId );
    }
    return status;
}

// 注册 WFP
NTSTATUS WallRegisterCallouts(){
    NTSTATUS    status = STATUS_SUCCESS;
    BOOLEAN     bInTransaction = FALSE;
    BOOLEAN     bEngineOpened = FALSE;
    FWPM_SESSION session = {0};
    session.flags = FWPM_SESSION_FLAG_DYNAMIC;
    //开启WFP引擎
    status = FwpmEngineOpen( 
                    NULL,
                    RPC_C_AUTHN_WINNT,
                    NULL,
                    &session,
                    &gEngineHandle
                );
    if( !NT_SUCCESS(status))
        goto exit;
    bEngineOpened = TRUE;
    //确认过滤权限
    status = FwpmTransactionBegin( gEngineHandle,0 );
    if(!NT_SUCCESS(status))
        goto exit;
    bInTransaction = TRUE;
    //注册回调函数
    status = RegisterCalloutForLayer(
                 &FWPM_LAYER_ALE_AUTH_CONNECT_V4,
                 &GUID_ALE_AUTH_CONNECT_CALLOUT_V4,
                 WallALEConnectClassify,
                 WallNotifyFn,
                 WallFlowDeleteFn,
                 &gAleConnectCalloutId,
                 &gAleConnectFilterId);
    if( !NT_SUCCESS(status)){
        DbgPrint("RegisterCalloutForLayer-FWPM_LAYER_ALE_AUTH_CONNECT_V4 failed!\n");
        goto exit;
    }
    //确认所有内容并提交,让回调函数正式发挥作用
    status = FwpmTransactionCommit(gEngineHandle );
    if( !NT_SUCCESS(status))
        goto exit;
    bInTransaction = FALSE;

exit:
    if( !NT_SUCCESS(status)){
        if( bInTransaction){
            FwpmTransactionAbort( gEngineHandle );
        }
        if( bEngineOpened ){
            FwpmEngineClose( gEngineHandle );
            gEngineHandle = 0;
        }
    }
    return status;
}


// 卸载 WFP

NTSTATUS WallUnRegisterCallouts(){
    if( gEngineHandle != 0 ){
        //删除FilterId
        FwpmFilterDeleteById( gEngineHandle,gAleConnectFilterId );
        //删除CalloutId
        FwpmCalloutDeleteById( gEngineHandle,gAleConnectCalloutId );
        //清空FilterId
        gAleConnectFilterId = 0;
        //反注册CalloutId
        FwpsCalloutUnregisterById( gAleConnectCalloutId );
        //清空CalloutId
        gAleConnectCalloutId = 0;
        //关闭引擎
        FwpmEngineClose( gEngineHandle );
        gEngineHandle = 0;
    }
    return STATUS_SUCCESS;
}

以下答案由GPT-4.5Plus大模型(语音)与博主A努力中.......共同编写:

根据你的描述,我有几点理解:

  1. 使用establish层的fwpsAssociateContext绑定在传输层,如果应用先启动然后启动驱动,会导致流量监控不准确。这是因为establish层的回调是在连接建立时调用的,如果应用已经建立连接,驱动启动后将无法监控这些已建立的连接。
  2. 使用ale-auth-connect层的fwpsAssociateContext可以在应用先启动的情况下也实现准确的流量监控。这是因为ale-auth-connect层的回调在应用尝试访问网络资源时被调用,所以可以监控到应用的网络活动。
  3. 使用ale-auth-connect层时,fwpsAssociateContext的返回值会有较多的FWPS_OBJECT_NAME_EXISTS,表示上下文已经存在。这时需要先使用fwpsFlowRemoveContext删除已经存在的上下文,然后再调用fwpsAssociateContext重新添加上下文。否则会导致重复绑定上下文。
  4. 使用establish层时,flow deletion回调函数flowDeletion会自动删除上下文,不需要手动调用fwpsFlowRemoveContext。所以使用establish层时无需担心重复绑定上下文的问题。

综上,如果要实现应用先启动驱动后启动的场景下准确的流量监控,使用ale-auth-connect层是更好的选择。需要注意ale-auth-connect层绑定上下文时可能返回FWPS_OBJECT_NAME_EXISTS,这时需要先删除已存在的上下文然后再重新绑定。

另外,如果应用和驱动的启动顺序不定,establish层也是可行的选择,它可以简化上下文管理的逻辑。

不过WFP驱动涉及的知识较广,我的理解可能不太准确。你可以参考微软的WFP示例和文档进一步验证这些结论。

WFP驱动开发是使用C语言的。这里是一个简单的C语言例子,使用WFP在传输层捕获HTTP连接并打印源和目的IP与端口:

c
#include <fwpmu.h>     // FWPM包
#include <iphlpapi.h>  // IPOD包
#include <ws2def.h>    // 套接字定义 

#define CALLBACK_TYPE CALLOUT_DRIVER_ALE_AUTH_CONNECT_V4

void CALLBACK fpAssociate( 
    const FWPM_NET_EVENT1 *pNetEvent, 
    FWP_MATCH_GENERIC_KEY *pKey 
)
{
    printf("Source IP: %s, Source Port: %d\n", 
           pNetEvent->incomingValueV4.localAddrV4->addr, 
           ntohs(pNetEvent->incomingValueV4.localPort)
    ); 
    printf("Destination IP: %s, Destination Port: %d\n", 
           pNetEvent->incomingValueV4.remoteAddrV4->addr,  
           ntohs(pNetEvent->incomingValueV4.remotePort) 
    ); 
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath) 
{
    NTSTATUS status;
    FWPM_FILTER filter = {0};
    GUID calloutKey;

    FWPM_NET_EVENT_CALLOUT1 callout = {0};
    callout.calloutKey = IPSEC_DRIVER_ALE_AUTH_CONNECT_V4_CALLOUT;
    callout.classifyFn = fpClassify; 
    callout.notifyFn = fpAssociate;

    status = FwpmCalloutRegister(fwpmEngineHandle, &callout, &calloutKey); 
    ...
}

这个驱动在初始化过程中注册了一个callback函数fpAssociate,这个函数会在发生IPV4的HTTP连接事件(由过滤器指定)时被调用。在回调函数中,我们可以访问连接的源和目的IP与端口,并进行打印。

所以如果应用发起HTTP连接,这个驱动将捕获连接事件并打印相关信息,实现基本的流量监控。

使用WFP开发驱动涉及FWPM库的使用,WFP框架的理解,回调函数的注册与实现等知识。比起用户模式开发会复杂一点,但功能也更加强大。