首页
社区
课程
招聘
[分享]封包拦截阶段实践(WFP)
发表于: 2天前 527

[分享]封包拦截阶段实践(WFP)

2天前
527

前言

之前学习了 Windows Filtering Platform 框架,今天实现一个对系统所有 TCP、UDP 请求的拦截监控,本次实践采用ALE_AUTH(授权阶段)确认请求,STREAM 层 (TCP 专用层)拦截TCP,DATAGRAM 层 (UDP 专用层)拦截UDP。

其中对于ALE_AUTH到STREAM和DATAGRAM的关联笔者使用了自制的Map通过flowHandle实现,并没有使用FwpsFlowAssociateContext进行回调(因为没有搞定),然后也并没有在一个Callout中设置多个Filter,而是直接设置每一层一个Callout

可以发现除了浏览器请求外系统通信也被成功拦截并成功获取封包内容,但目前还没有做封包解析 图片描述开启了浏览器后请求就刷屏了,非常多 图片描述

注册Callout

NTSTATUS RegisterCallout(
    PDEVICE_OBJECT driver,
    IN const GUID *calloutKey, 
    IN FWPS_CALLOUT_CLASSIFY_FN classifyFn, 
    IN FWPS_CALLOUT_NOTIFY_FN notifyFn, 
    IN FWPS_CALLOUT_FLOW_DELETE_NOTIFY_FN flowDeleteFn,
    OUT ULONG32 *calloutId)
{
    NTSTATUS status = STATUS_SUCCESS;

    // 初始化Callout
    FWPS_CALLOUT sCallout = { 0 };
    sCallout.calloutKey = *calloutKey;
    sCallout.classifyFn = classifyFn;
    sCallout.flowDeleteFn = flowDeleteFn;
    sCallout.notifyFn = notifyFn;

    // 注册Callout
    status = FwpsCalloutRegister(driver, &sCallout, calloutId);
    return status;
}

classifyFn

VOID NTAPI classifyFn_CONNECT(
    _In_ const FWPS_INCOMING_VALUES0* inFixedValues,
    _In_ const FWPS_INCOMING_METADATA_VALUES0* inMetaValues,
    _Inout_opt_ void* layerData,
    _In_opt_ const void* classifyContext,
    _In_ const FWPS_FILTER2* filter,
    _In_ UINT64 flowContext,
    _Inout_ FWPS_CLASSIFY_OUT0* classifyOut)

flowDeleteFn

VOID NTAPI flowDeleteFn(
    _In_ UINT16 layerId, 
    _In_ UINT32 calloutId, 
    _In_ UINT64 flowContext)

notifyFn

NTSTATUS NTAPI notifyFn(
    _In_ FWPS_CALLOUT_NOTIFY_TYPE notifyType, 
    _In_ const GUID* filterKey, 
    _Inout_ FWPS_FILTER2* filter)

设置滤点

NTSTATUS SetFilter(
    IN const GUID *layerKey, 
    IN const GUID *calloutKey, 
    OUT ULONG64 *filterId, 
    OUT HANDLE *engine)
{
    HANDLE hEngine = NULL;
    NTSTATUS status = STATUS_SUCCESS;

    FWPM_SESSION session = { 0 };
    FWPM_FILTER mFilter = { 0 };
    FWPM_CALLOUT mCallout = { 0 };

    //创建Session
    session.flags = FWPM_SESSION_FLAG_DYNAMIC;
    status = FwpmEngineOpen(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &hEngine);
    if (!NT_SUCCESS(status))
    {
        return status;
    }

    //开始事务
    status = FwpmTransactionBegin(hEngine, 0);
    if (!NT_SUCCESS(status))
    {
        return status;
    }

    //添加Callout
    FWPM_DISPLAY_DATA mDispData = { 0 };
    mDispData.name = L"Test WFP";
    mDispData.description = L"Testing";
    mCallout.applicableLayer = *layerKey;
    mCallout.calloutKey = *calloutKey;
    mCallout.displayData = mDispData;
    status = FwpmCalloutAdd(hEngine, &mCallout, NULL, NULL);
    if (!NT_SUCCESS(status))
    {
        return status;
    }

    //设置过滤器参数
    mFilter.action.calloutKey = *calloutKey;
    mFilter.action.type = FWP_ACTION_CALLOUT_TERMINATING;
    mFilter.displayData.name = L"MY WFP LyShark";
    mFilter.displayData.description = L"WORLD OF DEMON";
    mFilter.layerKey = *layerKey;
    mFilter.subLayerKey = FWPM_SUBLAYER_UNIVERSAL;
    mFilter.weight.type = FWP_EMPTY;//如果 filter weight 太低可能被系统 filter 覆盖。

    //添加过滤器
    status = FwpmFilterAdd(hEngine, &mFilter, NULL, filterId);
    if (!NT_SUCCESS(status))
    {
        return status;
    }

    //提交事务
    status = FwpmTransactionCommit(hEngine);
    if (!NT_SUCCESS(status))
    {
        return status;
    }

    //回写 engine 卸载用
    *engine = hEngine;
    return status;
}

初始化WFP

//初始化WFP
NTSTATUS InitWfp(PDEVICE_OBJECT pDevObj)
{
    PZY_PRINT("启动WFP");
    NTSTATUS status = STATUS_SUCCESS;

    //初始化 Context Map
    MapInit(&g_Map);

    //CONNECT
    status = InitLayer(pDevObj, &FWPM_LAYER_ALE_AUTH_CONNECT_V4, &GUID_CONNECT,
        classifyFn_CONNECT, notifyFn, flowDeleteFn, &g_keyinfos[CONNECT].CalloutId, &g_keyinfos[CONNECT].FilterId, & g_keyinfos[CONNECT].Engine);
    if (!NT_SUCCESS(status))
    {
        PZY_PRINT("CONNECT回调失败");
        return status;
    }

    //STREAM
    status = InitLayer(pDevObj, &FWPM_LAYER_STREAM_V4, &GUID_STREAM,
        classifyFn_STREAM, notifyFn, flowDeleteFn, &g_keyinfos[STREAM].CalloutId, &g_keyinfos[STREAM].FilterId, &g_keyinfos[STREAM].Engine);
    if (!NT_SUCCESS(status))
    {
        PZY_PRINT("STREAM回调失败");
        return status;
    }

    //DATAGRAM
    status = InitLayer(pDevObj, &FWPM_LAYER_DATAGRAM_DATA_V4, &GUID_DATAGRAM,
        classifyFn_DATAGRAM, notifyFn, flowDeleteFn, &g_keyinfos[STREAM].CalloutId, &g_keyinfos[STREAM].FilterId, &g_keyinfos[STREAM].Engine);
    if (!NT_SUCCESS(status))
    {
        PZY_PRINT("DATAGRAM回调失败");
        return status;
    }

    //开始循环检查Context Map
    LoopThroughContextMap();

    return status;
}

classifyFn_CONNECT

VOID NTAPI classifyFn_CONNECT(
    _In_ const FWPS_INCOMING_VALUES0* inFixedValues,
    _In_ const FWPS_INCOMING_METADATA_VALUES0* inMetaValues,
    _Inout_opt_ void* layerData,
    _In_opt_ const void* classifyContext,
    _In_ const FWPS_FILTER2* filter,
    _In_ UINT64 flowContext,
    _Inout_ FWPS_CLASSIFY_OUT0* classifyOut)
{
    // 数据包的方向,取值 FWP_DIRECTION_INBOUND = 1 或 FWP_DIRECTION_OUTBOUND = 0
    WORD direct = inFixedValues->incomingValue[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_DIRECTION].value.int8;

    // 定义本机地址与本机端口
    ULONG localIp = inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_LOCAL_ADDRESS].value.uint32;
    WORD localPort = inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_LOCAL_PORT].value.uint16;

    // 定义对端地址与对端端口
    ULONG remoteIp = inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_REMOTE_ADDRESS].value.uint32;
    WORD remotePort = inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_REMOTE_PORT].value.uint16;

    // 获取进程ID
    ULONG64 processId = inMetaValues->processId;

    // 获取当前协议类型
    CHAR protocol[256] = { 0 };
    GetProtocolNameByProtocolId(inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_PROTOCOL].value.uint16, protocol);
    
    // 设置默认规则 允许连接
    classifyOut->actionType = FWP_ACTION_PERMIT;

    //匹配key
    UINT64 transportEndpointHandle = inMetaValues->transportEndpointHandle;
    UINT64 flowHandle = inMetaValues->flowHandle;

    //储存
    WFP_CONTEXT* wfpContext = ExAllocatePoolWithTag(NonPagedPool, sizeof(WFP_CONTEXT), 'flow');
    RtlZeroMemory(wfpContext, sizeof(WFP_CONTEXT));
    wfpContext->transportEndpointHandle = transportEndpointHandle;
    wfpContext->flowHandle = flowHandle;
    wfpContext->processId = processId;
    wfpContext->protocol = protocol;
    wfpContext->direct = direct;
    wfpContext->localIp = localIp;
    wfpContext->localPort = localPort;
    wfpContext->remoteIp = remoteIp;
    wfpContext->remotePort = remotePort;
    MapPut(&g_Map, flowHandle, wfpContext);

    //输出
    PZY_PRINT("[CONNECT][%llX][Pid:%d][Way:%s] Local[%u.%u.%u.%u:%d] %s Remote[%u.%u.%u.%u:%d]",
        flowHandle,
        processId,
        protocol,
        (localIp >> 24) & 0xFF,
        (localIp >> 16) & 0xFF,
        (localIp >> 8) & 0xFF,
        (localIp) & 0xFF,
        localPort,
        direct == FWP_DIRECTION_OUTBOUND ? "->" : "<-",
        (remoteIp >> 24) & 0xFF,
        (remoteIp >> 16) & 0xFF,
        (remoteIp >> 8) & 0xFF,
        (remoteIp) & 0xFF,
        remotePort
    );
}

classifyFn_STREAM

VOID NTAPI classifyFn_STREAM(
    _In_ const FWPS_INCOMING_VALUES0* inFixedValues,
    _In_ const FWPS_INCOMING_METADATA_VALUES0* inMetaValues,
    _Inout_opt_ void* layerData,
    _In_opt_ const void* classifyContext,
    _In_ const FWPS_FILTER2* filter,
    _In_ UINT64 flowContext,
    _Inout_ FWPS_CLASSIFY_OUT0* classifyOut)
{
    //layerData为空直接返回
    if (layerData == NULL) return;

    //放行数据包, 如果你不设置可能会导致数据包被默认处理
    //FWP_ACTION_PERMIT					允许
    //FWP_ACTION_BLOCK					丢弃
    //FWP_ACTION_CALLOUT_TERMINATING	终止并由 callout 决定
    classifyOut->actionType = FWP_ACTION_PERMIT;

    //匹配key
    UINT64 transportEndpointHandle = inMetaValues->transportEndpointHandle;
    UINT64 flowHandle = inMetaValues->flowHandle;

    WFP_CONTEXT* wfpContext = MapGet(&g_Map, flowHandle);
    if (!wfpContext)
    {
        //匹配失败跳过
        return;
    }

    //获取方向
    WORD direct = wfpContext->direct;

    // 获取packet
    FWPS_STREAM_CALLOUT_IO_PACKET0* ioPacket = (FWPS_STREAM_CALLOUT_IO_PACKET0*)layerData;
    //获取stream
    FWPS_STREAM_DATA0* streamData = ioPacket->streamData;
    if (!streamData)
    {
        PZY_PRINT("streamData为空,返回");
        return;
    }
    // 数据长度
    UINT32 length = streamData->dataLength;
    if (length == 0)
    {
        PZY_PRINT("数据长度为0,返回");
        return;
    }

    // 为 payload 分配缓冲区
    PUCHAR buffer = ExAllocatePoolWithTag(
        NonPagedPool,
        length,
        STREAM_CALLOUT_TAG
    );
    if (!buffer)
    {
        PZY_PRINT("分配缓冲区失败,返回");
        return;
    }


    // 将 STREAM 数据复制到 buffer
    UINT32 copied = 0;
    FwpsCopyStreamDataToBuffer(
        streamData,
        buffer,
        length,
        &copied
    );

    //打印
    if (copied > 0)
    {
        // 最多打印 64 字节
        UINT32 printLen = copied > 64 ? 64 : copied;
        // 每个字节 "XX " = 3 字符,再预留一些头部空间
        SIZE_T outSize = printLen * 3 + 64;
        //分配内存
        CHAR* outBuf = ExAllocatePoolWithTag(
            NonPagedPool,
            outSize,
            OUT_BUF_TAG
        );
        if (!outBuf) return;
        //初始化内存
        RtlZeroMemory(outBuf, outSize);

        // 当前写入位置
        CHAR* cur = outBuf;     
        // 剩余空间
        SIZE_T remain = outSize; 
        //状态
        NTSTATUS status;
        // 写入头部
        status = RtlStringCbPrintfExA(
            cur,
            remain,
            &cur,        // 返回新的写入位置
            &remain,     // 返回剩余空间
            0,
            "TCP Payload (%u bytes): ",
            copied
        );
        //失败退出
        if (!NT_SUCCESS(status)) goto EXIT;

        // 追加 HEX 数据
        for (UINT32 i = 0; i < printLen; i++)
        {
            status = RtlStringCbPrintfExA(
                cur,
                remain,
                &cur,
                &remain,
                0,
                "%02X ",
                buffer[i]
            );

            if (!NT_SUCCESS(status)) break;
        }
        // 一次性输出
        PZY_PRINT("[STREAM][%llX][%s] %s", flowHandle, 
            direct == FWP_DIRECTION_OUTBOUND ? "发送数据" : "接收数据", outBuf);
    EXIT:
        ExFreePoolWithTag(outBuf, OUT_BUF_TAG);
    }
    
    //释放内存
    ExFreePoolWithTag(buffer, STREAM_CALLOUT_TAG);

    //处理后删除map条目
    MapRemove(&g_Map, flowHandle);
}

classifyFn_DATAGRAM

VOID NTAPI classifyFn_DATAGRAM(
    _In_ const FWPS_INCOMING_VALUES0* inFixedValues,
    _In_ const FWPS_INCOMING_METADATA_VALUES0* inMetaValues,
    _Inout_opt_ void* layerData,
    _In_opt_ const void* classifyContext,
    _In_ const FWPS_FILTER2* filter,
    _In_ UINT64 flowContext,
    _Inout_ FWPS_CLASSIFY_OUT0* classifyOut)
{
    //layerData为空直接返回
    if (layerData == NULL) return;

    //放行数据包, 如果你不设置可能会导致数据包被默认处理
    //FWP_ACTION_PERMIT					允许
    //FWP_ACTION_BLOCK					丢弃
    //FWP_ACTION_CALLOUT_TERMINATING	终止并由 callout 决定
    classifyOut->actionType = FWP_ACTION_PERMIT;

    //匹配key
    UINT64 flowHandle = inMetaValues->flowHandle;

    WFP_CONTEXT* wfpContext = MapGet(&g_Map, flowHandle);
    if (!wfpContext)
    {
        //匹配失败跳过
        return;
    }

    //获取方向
    WORD direct = wfpContext->direct;

    // DATAGRAM 层 layerData 是 NET_BUFFER_LIST
    NET_BUFFER_LIST* nbl = (NET_BUFFER_LIST*)layerData;
    NET_BUFFER* nb = NET_BUFFER_LIST_FIRST_NB(nbl);
    if (!nb) return;

    ULONG length = NET_BUFFER_DATA_LENGTH(nb);
    if (length == 0)
    {
        PZY_PRINT("数据长度为0,返回");
        return;
    }

    // 尝试直接获取数据指针
    PUCHAR buffer = NdisGetDataBuffer(
        nb,
        length,
        NULL,
        1,
        0
    );

    PUCHAR localBuf = NULL;

    // 如果返回 NULL 说明数据跨 MDL
    if (buffer == NULL)
    {
        localBuf = ExAllocatePoolWithTag(
            NonPagedPool,
            length,
            DATAGRAM_CALLOUT_TAG
        );

        if (!localBuf)
            return;

        buffer = NdisGetDataBuffer(
            nb,
            length,
            localBuf,
            1,
            0
        );
    }

    if (buffer)
    {
        // 最多打印 64 字节
        ULONG printLen = length > 64 ? 64 : length;
        // 每个字节 "XX " = 3 字符,再预留一些头部空间
        SIZE_T outSize = printLen * 3 + 64;
        //分配内存
        CHAR* outBuf = ExAllocatePoolWithTag(
            NonPagedPool,
            outSize,
            OUT_BUF_TAG
        );
        if (!outBuf) return;
        //初始化内存
        RtlZeroMemory(outBuf, outSize);

        // 当前写入位置
        CHAR* cur = outBuf;
        // 剩余空间
        SIZE_T remain = outSize;
        //状态
        NTSTATUS status;
        // 写入头部
        status = RtlStringCbPrintfExA(
            cur,
            remain,
            &cur,        // 返回新的写入位置
            &remain,     // 返回剩余空间
            0,
            "UDP Packet (%lu bytes): ",
            length
        );
        //失败退出
        if (!NT_SUCCESS(status)) goto EXIT;

        // 追加 HEX 数据
        for (UINT32 i = 0; i < printLen; i++)
        {
            status = RtlStringCbPrintfExA(
                cur,
                remain,
                &cur,
                &remain,
                0,
                "%02X ",
                buffer[i]
            );

            if (!NT_SUCCESS(status)) break;
        }
        // 一次性输出
        PZY_PRINT("[DATAGRAM][%llX][%s] %s", flowHandle, 
            direct == FWP_DIRECTION_OUTBOUND ? "发送数据":"接收数据", outBuf);
    EXIT:
        ExFreePoolWithTag(outBuf, OUT_BUF_TAG);
    }

    //释放内存
    if (localBuf) ExFreePoolWithTag(localBuf, DATAGRAM_CALLOUT_TAG);

    //处理后删除map条目
    MapRemove(&g_Map, flowHandle);
}

总结

本贴为记录笔者实践过程,后续会继续完善封包解析功能...


[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!

最后于 2天前 被mb_binusgki编辑 ,原因: 补充内容
收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回