Windows Filtering Platform Calculate bytes sent and bytes received through TCP connection

66 Views Asked by At

I'm wondering how to calculate bytes sent and bytes received through TCP connections in kernel mode. What I came up with was creating a callout in FWPM_LAYER_ALE_ESTABLISHED_FLOW_V4 to intercept every new TCP connection that is occuring right now and then getting in ClassifyFn to LayerData, then lower to streamData which has a member called dataLength (size_t which is 8 bytes). But I don't understand how to differentiate between bytes sent and bytes received? There is only ONE member called dataLength for the perticular connection. Do you have any suggestions?

Right now my code looks like this:

static NTSTATUS InitializeCallout(PDEVICE_OBJECT deviceObject)
{
    FWPM_SUBLAYER subLayer = {};
    subLayer.displayData.name = const_cast<wchar_t*>(L"TcpInterception Sub-Layer");
    subLayer.subLayerKey = TCP_INTERCEPTION_SUBLAYER;

    NTSTATUS status = FwpmSubLayerAdd(g_engineHandle, &subLayer, nullptr);
    if (!NT_SUCCESS(status))
    {
        return status;
    }

    FWPS_CALLOUT0 sCallout =
    {
        TCP_INTERCEPTION_TRANSPORT_V4_CALLOUT,
        0,
        CalloutConnectClassifyFn,
        CalloutNotifyFn,
        nullptr
    };

    status = FwpsCalloutRegister0(deviceObject, &sCallout, &g_id);
    if (!NT_SUCCESS(status))
    {
        return status;
    }

    FWPM_CALLOUT mCallout = {};
    mCallout.calloutKey = TCP_INTERCEPTION_TRANSPORT_V4_CALLOUT;
    mCallout.applicableLayer = FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4;
    mCallout.displayData.name = const_cast<wchar_t*>(g_displayName);

    status = FwpmCalloutAdd(g_engineHandle, &mCallout, nullptr, nullptr);
    if (!NT_SUCCESS(status))
    {
        return status;
    }

    FWPM_FILTER_CONDITION filterCondition = {};
    filterCondition.fieldKey = FWPM_CONDITION_IP_PROTOCOL;
    filterCondition.matchType = FWP_MATCH_EQUAL;
    filterCondition.conditionValue.type = FWP_UINT8;
    filterCondition.conditionValue.uint16 = IPPROTO_TCP;

    FWPM_FILTER filter = {};
    filter.layerKey = FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4;
    filter.displayData.name = const_cast<wchar_t*>(g_displayName);;
    filter.displayData.description = filter.displayData.name;

    filter.action.type = FWP_ACTION_CALLOUT_UNKNOWN;
    filter.action.calloutKey = TCP_INTERCEPTION_TRANSPORT_V4_CALLOUT;
    filter.filterCondition = &filterCondition;
    filter.numFilterConditions = 1;
    filter.subLayerKey = FWPM_SUBLAYER_UNIVERSAL;
    filter.weight.type = FWP_EMPTY;

    status = FwpmFilterAdd(g_engineHandle, &filter, NULL, NULL);
    if (!NT_SUCCESS(status))
    {
        return status;
    }

    return status;
}
static void CalloutConnectClassifyFn(
    const FWPS_INCOMING_VALUES0* inFixedValues,
    const FWPS_INCOMING_METADATA_VALUES0* inMetaValues,
    void* layerData,
    const FWPS_FILTER0* filter,
    UINT64 /*flowContext*/,
    FWPS_CLASSIFY_OUT0* classifyOut)
{
    // Allowing the traffic for another filter to make a final decision.
    if (FlagOn(classifyOut->rights, FWPS_RIGHT_ACTION_WRITE))
    {
        classifyOut->actionType = FWP_ACTION_CONTINUE;
    }

    ProcessTransportData(inFixedValues, inMetaValues, layerData, classifyOut);

    // Callout function should clear the FWPS_RIGHT_ACTION_WRITE flag when it returns FWP_ACTION_BLOCK for the suggested action
    // and if FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT flag is set
    if (FWP_ACTION_BLOCK == classifyOut->actionType || FlagOn(filter->flags, FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT))
    {
        ClearFlag(classifyOut->rights, FWPS_RIGHT_ACTION_WRITE);
    }
}

And in ProcessTransportData I'm printing some info to the debugger about certain connections:

static void ProcessTransportData(const FWPS_INCOMING_VALUES0* inFixedValues,
    const FWPS_INCOMING_METADATA_VALUES0* inMetaValues,
    void* layerData,
    FWPS_CLASSIFY_OUT0* classifyOut)
{
    ULONGLONG processId = (ULONGLONG)inMetaValues->processId;

    UINT32 localAddress = inFixedValues->incomingValue[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_LOCAL_ADDRESS].value.uint32;
    UINT32 remoteAddress = inFixedValues->incomingValue[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_REMOTE_ADDRESS].value.uint32;
    UINT16 localPort = inFixedValues->incomingValue[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_LOCAL_PORT].value.uint16;
    UINT16 remotePort = inFixedValues->incomingValue[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_REMOTE_PORT].value.uint16;

    if (inFixedValues->incomingValue[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_DIRECTION].value.uint32 == FWP_DIRECTION_INBOUND)
    {
        KdPrint(("Inbound connection\n"));
    }
    else if (inFixedValues->incomingValue[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_DIRECTION].value.uint32 == FWP_DIRECTION_OUTBOUND)
    {
        KdPrint(("Outbound connection\n"));
    }

    KdPrint(("Process Id: %I64u\n", processId));
    KdPrint(("Local IP: %d.%d.%d.%d\n", localAddress & 0xFF, (localAddress >> 8) & 0xFF, (localAddress >> 16) & 0xFF, (localAddress >> 24) & 0xFF));
    KdPrint(("Remote IP: %d.%d.%d.%d\n", remoteAddress & 0xFF, (remoteAddress >> 8) & 0xFF, (remoteAddress >> 16) & 0xFF, (remoteAddress >> 24) & 0xFF));
    KdPrint(("Local Port: %d\n", localPort));
    KdPrint(("Remote Port: %d\n", remotePort));
}

And there I assume that I can reach to this dataLength member through LayerData, but still I don't understand how to retrieve BYTES SENT and BYTES RECEIVED. I was thinking about Stream V4 Layer, but I had another difficulty with it which was adding a filter for TCPPROTO_IP but this layer does not have the field "IP_PROTOCOL". Any solutions? Thanks.

0

There are 0 best solutions below