x64驱动基础教程 25

WFP 是微软推出来替代 TDI HOOKNDIS HOOK 等拦截网络通信的方案, WFP 的框架非常庞大, 在 RING3 RING0 各有一套类似的函数, 令人兴奋的是,即使在 R3 使用 WFP, 也可以做到全局拦截访问网络。 由于 WFP 的范围太广,实在难以一言概括, 感兴趣的朋友可以自行到 MSDN 上查看微软对它的官方概述。 本文的目的, 是给大家理顺 WFP 的框架, 并利用 WFP 拦截指定进程访问网络, 或拦截对指定 IP 地址/端口的访问。一个标准的 WFP 程序大体是这样子的: 首先使用 FwpmEngineOpen 开启 WFP 引擎(获得一个 WFP 的使用句柄),然后用 FwpmTransactionBegin 设置对网络通信内容的过滤权限(是只读还是允许修改), 然后用 FwpsCalloutRegisterFwpmCalloutAddFwpmFilterAdd 选择你要过滤的内容, 并添加过滤器对象和回调函数, 最后用 FwpmTransactionCommit 确认刚才的内容 , 让刚才添加的回调函数开始生效。 当你不用 WFP 的时候 , 就要用FwpmFilterDeleteByIdFwpmCalloutDeleteByIdFwpsCalloutUnregisterById 把你刚才添加的过滤器对象和回调函数删除掉,然后用 FwpmEngineClose 关闭 WFP 引擎(类似于关闭句柄)。一个概述已经是这样子了,实现起来就更加麻烦了。 为了方便大家学习, 我已经把微软的 WFP 实例进行了最大简化,并把核心的注册回调功能封装成了一个函数。 下面一步一步进行分析。 在具体分析之前,有一些重要的内容需要先说明, 否则后面的内容肯定会让大家一头雾水。

 一、 WFP 一次性要注册的回调函数不是 1 个,而是 3 个。 但只有 1 个是“事前”回调, 另外 2 个都是“事后” 回调(一般只使用“事前” 回调,不使用“事后”回调)。

 二、WFP 能过滤的内容很多,你必须选择一个感兴趣的内容。 这个“感兴趣的内容” 用官话来说叫做“过滤条件标志”, 这货其实是一个常量, 由于我们是要过滤进程联网, 而且一般都是用 IPV4 协议,所以“过滤条件标志” 为 FWPM_LAYER_ALE_AUTH_CONNECT_V4(在这个页面可以查到所有的“过滤条件标志”)。

三、你必须为这个“过滤条件标志”指定一个 GUID,当然 GUID 的值随便设置就行,只要在系统范围内不重复。代码中的 GUID 就是随意设定的,它的名称为GUID_ALE_AUTH_CONNECT_CALLOUT_V4

第一步:开启 WFP 引擎、 选择过滤权限(监控还是监视)、 注册回调(输入感兴趣的内容、回调函数地址*3、返回过滤器和回调函数的“句柄”)、 确认所有内容(让回调函数开始生效)。

NTSTATUS WallRegisterCallouts() //启用 WFP 防火墙的主函数
{
    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_V4failed!\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;
}
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 的回调函数里提供了丰富的信息, 这是 WFP 最大的优点,不用我们为获得各种相关信息而绞尽脑汁。 比如在 FWPM_LAYER_ALE_AUTH_CONNECT_V4的回调函数里, 我们能获得进程 ID、 进程路径、本地、 远程的 IP 地址/端口号以及协议代码。但最爽的是此回调函数的最后一个参数, 能让我们指定一个值,决定是放行还是拦截。

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_RE
        MOTE_ADDRESS].value.uint32;
    ProtocolName = ProtocolIdToName(inFixedValues->incomingValue[FWPS_FIELD_ALE_AUT
        H_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].val
        ue.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 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;
}

代码执行的效果如下(密密麻麻一大片信息):

无论怎么看, WFP 都是一个非常完美的模型,不过在这里我想说一下 WFP 一个非常坑爹的地方, 就是在 WFP 的回调函数里, IRQL 有时候不等于 0 而是等于 2。 这意味着如果你要实现主动防御, 就不能使用经典方法(KeWaitForSingleObject + 弹框),而是要设计一套异步处理机制(除非遇到 IRQL 不等于 0 的情况直接放行或者拦截)。 实际上, WFP 内就有一套异步处理的模型, 但我一直无法使用成功,大家有兴趣可以看看如何实现




打赏作者

发表评论

电子邮件地址不会被公开。 必填项已用*标注