void NTAPI StreamOobInjectCompletionFn( _Inout_ void* context, _Inout_ NET_BUFFER_LIST* netBufferList, BOOLEAN dispatchLevel ) /* ++ Injection completion function for injecting an NBL created using FwpsAllocateNetBufferAndNetBufferList. This function frees up resources allocated during StreamOobReinjectData(). -- */ { MDL* mdl = (MDL*)context; UNREFERENCED_PARAMETER(dispatchLevel); FwpsFreeNetBufferList(netBufferList); if (mdl != NULL) { IoFreeMdl(mdl); // // The MDL mapped over a pool alloc which we need to free here. // ExFreePoolWithTag( mdl->MappedSystemVa, STREAM_EDITOR_MDL_DATA_TAG ); } }
void NTAPI StreamInjectCompletionFn( IN void* context, IN OUT NET_BUFFER_LIST* netBufferList, IN BOOLEAN dispatchLevel) { PENDED_PACKET *packet = (PENDED_PACKET*) context; UNREFERENCED_PARAMETER(dispatchLevel); FwpsFreeNetBufferList(netBufferList); FreePendedPacket(packet); }
void NTAPI StreamInjectCompletionFn( _Inout_ void* context, _Inout_ NET_BUFFER_LIST* netBufferList, _In_ BOOLEAN dispatchLevel ) { MDL* mdl = (MDL*)context; UNREFERENCED_PARAMETER(dispatchLevel); if (mdl != NULL) { IoFreeMdl(mdl); } FwpsFreeNetBufferList(netBufferList); }
NTSTATUS StreamOobInjectReplacement( _Inout_ STREAM_EDITOR* streamEditor, UINT32 streamFlags, _In_opt_ MDL* data, size_t length ) /* ++ This function injects a section of replacement data (in place of data removed from the stream) into the data stream. The MDL describes the replacement data is allocated during DriverEntry and does not need to be freed during injection completion. -- */ { NTSTATUS status; NET_BUFFER_LIST* netBufferList = NULL; status = FwpsAllocateNetBufferAndNetBufferList( gNetBufferListPool, 0, 0, data, 0, length, &netBufferList ); if (!NT_SUCCESS(status)) { goto Exit; } NT_ASSERT(!(streamFlags & FWPS_STREAM_FLAG_SEND_DISCONNECT) && !(streamFlags & FWPS_STREAM_FLAG_RECEIVE_DISCONNECT)); status = StreamOobQueueUpOutgoingData( streamEditor, netBufferList, FALSE, length, streamFlags, NULL ); if (!NT_SUCCESS(status)) { FwpsFreeNetBufferList(netBufferList); goto Exit; } netBufferList = NULL; Exit: if (netBufferList != NULL) { FwpsFreeNetBufferList(netBufferList); } return status; }
NTSTATUS StreamOobReinjectData( _Inout_ STREAM_EDITOR* streamEditor, UINT32 streamFlags, const void* data, size_t length ) /* ++ This function injects a section of the original indicated data back to the data stream. An MDL is allocated to describe the data section. -- */ { NTSTATUS status; void* dataCopy = NULL; MDL* mdl = NULL; NET_BUFFER_LIST* netBufferList = NULL; dataCopy = ExAllocatePoolWithTag( NonPagedPool, length, STREAM_EDITOR_MDL_DATA_TAG ); if (dataCopy == NULL) { status = STATUS_NO_MEMORY; goto Exit; } RtlCopyMemory(dataCopy, data, length); mdl = IoAllocateMdl( dataCopy, (ULONG)length, FALSE, FALSE, NULL ); if (mdl == NULL) { status = STATUS_NO_MEMORY; goto Exit; } MmBuildMdlForNonPagedPool(mdl); status = FwpsAllocateNetBufferAndNetBufferList( gNetBufferListPool, 0, 0, mdl, 0, length, &netBufferList ); if (!NT_SUCCESS(status)) { goto Exit; } NT_ASSERT(!(streamFlags & FWPS_STREAM_FLAG_SEND_DISCONNECT) && !(streamFlags & FWPS_STREAM_FLAG_RECEIVE_DISCONNECT)); status = StreamOobQueueUpOutgoingData( streamEditor, netBufferList, FALSE, length, streamFlags, mdl ); if (!NT_SUCCESS(status)) { goto Exit; } dataCopy = NULL; mdl = NULL; netBufferList = NULL; Exit: if (netBufferList != NULL) { FwpsFreeNetBufferList(netBufferList); } if (mdl != NULL) { IoFreeMdl(mdl); } if (dataCopy != NULL) { ExFreePoolWithTag( dataCopy, STREAM_EDITOR_MDL_DATA_TAG ); } return status; }
NTSTATUS StreamOobFlushOutgoingData( _Inout_ STREAM_EDITOR* streamEditor ) { NTSTATUS status = STATUS_SUCCESS; KLOCK_QUEUE_HANDLE editLockHandle; OUTGOING_STREAM_DATA* outgoingStreamData = NULL; for(;;) { KeAcquireInStackQueuedSpinLock( &streamEditor->oobEditInfo.editLock, &editLockHandle ); if (!IsListEmpty(&streamEditor->oobEditInfo.outgoingDataQueue)) { LIST_ENTRY* listEntry = RemoveHeadList(&streamEditor->oobEditInfo.outgoingDataQueue); outgoingStreamData = CONTAINING_RECORD( listEntry, OUTGOING_STREAM_DATA, listEntry ); } KeReleaseInStackQueuedSpinLock(&editLockHandle); if (outgoingStreamData == NULL) { break; } status = FwpsStreamInjectAsync( gInjectionHandle, NULL, 0, streamEditor->oobEditInfo.flowId, streamEditor->oobEditInfo.calloutId, streamEditor->oobEditInfo.layerId, outgoingStreamData->streamFlags, outgoingStreamData->netBufferList, outgoingStreamData->dataLength, outgoingStreamData->isClone ? StreamOobInjectCloneCompletionFn : StreamOobInjectCompletionFn, outgoingStreamData->mdl ); if (!NT_SUCCESS(status)) { goto Exit; } ExFreePoolWithTag( outgoingStreamData, STREAM_EDITOR_OUTGOING_DATA_TAG ); outgoingStreamData = NULL; } Exit: if (outgoingStreamData != NULL) { NT_ASSERT(!NT_SUCCESS(status)); if (outgoingStreamData->isClone) { FwpsDiscardClonedStreamData( outgoingStreamData->netBufferList, 0, FALSE ); } else { FwpsFreeNetBufferList(outgoingStreamData->netBufferList); if (outgoingStreamData->mdl != NULL) { IoFreeMdl(outgoingStreamData->mdl); ExFreePoolWithTag( outgoingStreamData->mdl->MappedSystemVa, STREAM_EDITOR_MDL_DATA_TAG ); } } ExFreePoolWithTag( outgoingStreamData, STREAM_EDITOR_OUTGOING_DATA_TAG ); } return status; }
NTSTATUS InlineEditFlushData( _In_ STREAM_FLOW_CONTEXT *pFlowContext, _In_ ULONG DataLength, _In_ UINT StreamFlags ) /* This function re-injects buffered data back to the data stream. The data was buffered because it was not big enough (size wise) to make an editing decision. */ { NTSTATUS Status = STATUS_SUCCESS; PVOID Buffer = NULL; MDL* mdl = NULL; NET_BUFFER_LIST* NetBufferList = NULL; NT_ASSERT(!(StreamFlags & FWPS_STREAM_FLAG_SEND_DISCONNECT) && !(StreamFlags & FWPS_STREAM_FLAG_RECEIVE_DISCONNECT)); if (DataLength == 0) { DataLength = (ULONG)pFlowContext->ScratchDataLength; } DoTraceLevelMessage(TRACE_LEVEL_INFORMATION, CO_ENTER_EXIT, "--> %!FUNC!: FlowCtx %p, Flushing %u bytes", pFlowContext, DataLength); do { if (DataLength == 0) break; Buffer = ExAllocatePoolWithTag(NonPagedPool, DataLength, STMEDIT_TAG_MDL_DATA); if (Buffer == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; DoTraceLevelMessage(TRACE_LEVEL_ERROR, CO_GENERAL, "Failed to allocate Buffer to flush data!"); break; } // Copy the contents that need to be flushed. RtlMoveMemory(Buffer, pFlowContext->ScratchBuffer, DataLength); mdl = IoAllocateMdl( Buffer, DataLength, FALSE, FALSE, NULL); if (mdl == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; DoTraceLevelMessage(TRACE_LEVEL_ERROR, CO_GENERAL, "Failed to allocate MDL"); break; } MmBuildMdlForNonPagedPool(mdl); Status = FwpsAllocateNetBufferAndNetBufferList( Globals.NetBufferListPool, 0, 0, mdl, 0, DataLength, &NetBufferList); if (!NT_SUCCESS(Status)) { DoTraceLevelMessage(TRACE_LEVEL_ERROR, CO_GENERAL, "FwpsAllocateNetBufferAndNetBufferList Failed with %!STATUS!", Status); break; } Status = FwpsStreamInjectAsync( Globals.InjectionHandle, NULL, 0, pFlowContext->FlowHandle, pFlowContext->CalloutId, pFlowContext->LayerId, StreamFlags, NetBufferList, DataLength, StreamEditInjectCompletionFn, mdl); if (!NT_SUCCESS(Status)) { DoTraceLevelMessage(TRACE_LEVEL_ERROR, CO_GENERAL, "FwpsStreamInjectAsync failed with %!STATUS!", Status); break; } DoTraceLevelMessage(TRACE_LEVEL_INFORMATION, CO_GENERAL, "FlowCtx %p, Flushed %lu bytes via NBL %p, MDL %p", pFlowContext, DataLength, NetBufferList, mdl); // Control transferred to WFP. mdl = NULL; NetBufferList = NULL; Buffer = NULL; } while (FALSE); if (!NT_SUCCESS(Status)) { if (Buffer != NULL) { ExFreePoolWithTag(Buffer, STMEDIT_TAG_MDL_DATA); } if (mdl != NULL) { IoFreeMdl(mdl); } if (NetBufferList != NULL) { FwpsFreeNetBufferList(NetBufferList); } } DoTraceLevelMessage(TRACE_LEVEL_INFORMATION, CO_ENTER_EXIT, "<-- %!FUNC!: FlowCtx %p, %!STATUS!", pFlowContext, Status); return Status; }
NTSTATUS InlineInjectToken( PSTREAM_FLOW_CONTEXT FlowContext, UINT32 StreamFlags ) /* Inject a replacement token into the data stream! */ { NTSTATUS Status; NET_BUFFER_LIST* NetBufferList; do { Status = FwpsAllocateNetBufferAndNetBufferList( Globals.NetBufferListPool, 0, 0, Globals.StringToReplaceMdl, 0, Globals.StringToReplaceLength, &NetBufferList ); if (!NT_SUCCESS(Status)) { DoTraceLevelMessage(TRACE_LEVEL_ERROR, CO_GENERAL, "FlowCtx %p, FwpsAllocateNetBufferAndNetBufferList failed with %#x, dropping connection", FlowContext, Status); break; } Status = FwpsStreamInjectAsync( Globals.InjectionHandle, NULL, 0, FlowContext->FlowHandle, FlowContext->CalloutId, FlowContext->LayerId, StreamFlags, NetBufferList, Globals.StringToReplaceLength, StreamEditInjectCompletionFn, NULL ); if (!NT_SUCCESS(Status)) { FwpsFreeNetBufferList(NetBufferList); DoTraceLevelMessage(TRACE_LEVEL_ERROR, CO_GENERAL, "FlowCtx %p, FwpsStreamInjectAsync failed with %!STATUS!, dropping connection", FlowContext, Status); break; } FlowContext->ScratchDataOffset += Globals.StringXLength; FlowContext->ScratchDataLength -= Globals.StringXLength; if (FlowContext->ScratchDataLength > 0) { FlowContext->InlineEditState = INLINE_EDIT_SCANNING; } else { FlowContext->ScratchDataOffset = 0; FlowContext->InlineEditState = INLINE_EDIT_IDLE; } } while (FALSE); return Status; }
NTSTATUS ReinjectPendedPacket( IN PENDED_PACKET *packet, IN FLOW_DATA *flowData) { NTSTATUS status; UINT32 flags; NET_BUFFER_LIST* netBufferList = NULL; FLOW_DATA *flowCtx; ULONG dataLength; if(packet->dataLength == 0 || packet->data == NULL) { return STATUS_UNSUCCESSFUL; } packet->mdl = IoAllocateMdl( packet->data, packet->dataLength, FALSE, FALSE, NULL); if (packet->mdl == NULL) { status = STATUS_NO_MEMORY; goto Exit; } MmBuildMdlForNonPagedPool(packet->mdl); status = FwpsAllocateNetBufferAndNetBufferList( gNetBufferListPool, 0, 0, packet->mdl, 0, packet->dataLength, &netBufferList); if(!NT_SUCCESS(status)) { goto Exit; } flags = packet->flags; dataLength = packet->dataLength; flowCtx = packet->flowContext; #ifdef DEBUG debugPacket(packet); DbgPrintEx( DPFLTR_IHVNETWORK_ID, DPFLTR_ERROR_LEVEL, "\n localCtr=%d, remoteCtr=%d\n", flowCtx->localCounter, flowCtx->remoteCounter); #endif // Keep correct sequence numbers // (Assume every reinjection is successful, otherwise synchronous injection is // needed for consistent sequence numbers implementation) if(flags & FWPS_STREAM_FLAG_SEND) { flowCtx->localCounter += dataLength; } else if(flags & FWPS_STREAM_FLAG_RECEIVE) { flowCtx->remoteCounter += dataLength; } else { #ifdef DEBUG DbgBreakPoint(); #endif } status = FwpsStreamInjectAsync( gInjectionHandle, NULL, 0, flowData->flowHandle, gStreamCalloutIdV4, FWPS_LAYER_STREAM_V4, flags, netBufferList, packet->dataLength, StreamInjectCompletionFn, packet); if (!NT_SUCCESS(status)) { goto Exit; } // Ownership transferred netBufferList = NULL; packet = NULL; Exit: if (netBufferList != NULL) { FwpsFreeNetBufferList(netBufferList); } if (packet != NULL) { FreePendedPacket(packet); } return status; }
NTSTATUS StreamEditFlushData( _Inout_ STREAM_EDITOR* streamEditor, UINT64 flowId, UINT32 calloutId, UINT16 layerId, UINT32 streamFlags ) /* ++ This function re-injects buffered data back to the data stream upon receiving a FIN. The data was buffered because it was not big enough (size wise) to make an editing decision. -- */ { NTSTATUS status; MDL* mdl = NULL; NET_BUFFER_LIST* netBufferList = NULL; NT_ASSERT(streamEditor->dataOffset == 0); mdl = IoAllocateMdl( streamEditor->scratchBuffer, (ULONG)(streamEditor->dataLength), FALSE, FALSE, NULL ); if (mdl == NULL) { status = STATUS_NO_MEMORY; goto Exit; } MmBuildMdlForNonPagedPool(mdl); status = FwpsAllocateNetBufferAndNetBufferList( gNetBufferListPool, 0, 0, mdl, 0, streamEditor->dataLength, &netBufferList ); if (!NT_SUCCESS(status)) { goto Exit; } streamFlags &= ~(FWPS_STREAM_FLAG_SEND_DISCONNECT | FWPS_STREAM_FLAG_RECEIVE_DISCONNECT); status = FwpsStreamInjectAsync( gInjectionHandle, NULL, 0, flowId, calloutId, layerId, streamFlags, netBufferList, streamEditor->dataLength, StreamInjectCompletionFn, mdl ); if (!NT_SUCCESS(status)) { goto Exit; } mdl = NULL; netBufferList = NULL; Exit: if (mdl != NULL) { IoFreeMdl(mdl); } if (netBufferList != NULL) { FwpsFreeNetBufferList(netBufferList); } return status; }
void StreamInlineEdit( _Inout_ STREAM_EDITOR* streamEditor, _In_ const FWPS_INCOMING_VALUES* inFixedValues, _In_ const FWPS_INCOMING_METADATA_VALUES* inMetaValues, _In_ const FWPS_FILTER* filter, _In_ const FWPS_STREAM_DATA* streamData, _Inout_ FWPS_STREAM_CALLOUT_IO_PACKET* ioPacket, _Inout_ FWPS_CLASSIFY_OUT* classifyOut ) /* ++ This function implements the state machine that scans the content and computes the number of bytes to permit, bytes to block, and performs stream injection to replace the blocked data. -- */ { UINT findLength = (UINT) strlen(configStringToFind); UINT replaceLength = (UINT) strlen(configStringToReplace); if ((streamData->flags & FWPS_STREAM_FLAG_SEND_DISCONNECT) || (streamData->flags & FWPS_STREAM_FLAG_RECEIVE_DISCONNECT)) { if (streamEditor->dataLength > 0) { StreamEditFlushData( streamEditor, inMetaValues->flowHandle, filter->action.calloutId, inFixedValues->layerId, streamData->flags ); streamEditor->dataLength = 0; streamEditor->dataOffset = 0; } NT_ASSERT(streamEditor->inlineEditState == INLINE_EDIT_WAITING_FOR_DATA); ioPacket->streamAction = FWPS_STREAM_ACTION_NONE; classifyOut->actionType = FWP_ACTION_PERMIT; if (filter->flags & FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT) { classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE; } goto Exit; } if (streamData->dataLength == 0) { ioPacket->streamAction = FWPS_STREAM_ACTION_NONE; classifyOut->actionType = FWP_ACTION_PERMIT; if (filter->flags & FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT) { classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE; } goto Exit; } if (streamEditor->inlineEditState != INLINE_EDIT_SKIPPING) { if ((streamData->dataLength < findLength) && !(classifyOut->flags & FWPS_CLASSIFY_OUT_FLAG_NO_MORE_DATA)) { ioPacket->streamAction = FWPS_STREAM_ACTION_NEED_MORE_DATA; ioPacket->countBytesRequired = findLength; classifyOut->actionType = FWP_ACTION_NONE; goto Exit; } } switch (streamEditor->inlineEditState) { case INLINE_EDIT_WAITING_FOR_DATA: { if (StreamCopyDataForInspection( streamEditor, streamData ) == FALSE) { ioPacket->streamAction = FWPS_STREAM_ACTION_DROP_CONNECTION; classifyOut->actionType = FWP_ACTION_NONE; goto Exit; } // // Pass-thru to scanning // } case INLINE_EDIT_SCANNING: { UINT i; BYTE* dataStart = (BYTE*)streamEditor->scratchBuffer + streamEditor->dataOffset; BOOLEAN found = FALSE; for (i = 0; i < streamEditor->dataLength; ++i) { if (i + findLength <= streamEditor->dataLength) { if (RtlCompareMemory( dataStart + i, configStringToFind, findLength ) == findLength) { found = TRUE; streamEditor->inlineEditState = INLINE_EDIT_MODIFYING; if (i != 0) { ioPacket->streamAction = FWPS_STREAM_ACTION_NONE; ioPacket->countBytesEnforced = i; classifyOut->actionType = FWP_ACTION_PERMIT; if (filter->flags & FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT) { classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE; } streamEditor->dataOffset += i; streamEditor->dataLength -= i; break; } else { goto modify_data; } } } else { if (classifyOut->flags & FWPS_CLASSIFY_OUT_FLAG_NO_MORE_DATA) { break; } if (RtlCompareMemory( dataStart + i, configStringToFind, streamEditor->dataLength - i ) == streamEditor->dataLength - i) { found = TRUE; // this is a partial find ioPacket->streamAction = FWPS_STREAM_ACTION_NONE; ioPacket->countBytesEnforced = i; classifyOut->actionType = FWP_ACTION_PERMIT; if (filter->flags & FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT) { classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE; } RtlMoveMemory( streamEditor->scratchBuffer, dataStart + i, streamEditor->dataLength - i ); streamEditor->dataOffset = 0; streamEditor->dataLength = streamEditor->dataLength - i; streamEditor->inlineEditState = INLINE_EDIT_SKIPPING; break; } } } if (!found) { ioPacket->streamAction = FWPS_STREAM_ACTION_NONE; ioPacket->countBytesEnforced = 0; classifyOut->actionType = FWP_ACTION_PERMIT; if (filter->flags & FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT) { classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE; } streamEditor->dataOffset = 0; streamEditor->dataLength = 0; streamEditor->inlineEditState = INLINE_EDIT_WAITING_FOR_DATA; } break; } case INLINE_EDIT_SKIPPING: { ioPacket->streamAction = FWPS_STREAM_ACTION_NONE; ioPacket->countBytesEnforced = 0; classifyOut->actionType = FWP_ACTION_BLOCK; classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE; streamEditor->inlineEditState = INLINE_EDIT_WAITING_FOR_DATA; break; } case INLINE_EDIT_MODIFYING: modify_data: { NTSTATUS status; NET_BUFFER_LIST* netBufferList; status = FwpsAllocateNetBufferAndNetBufferList( gNetBufferListPool, 0, 0, gStringToReplaceMdl, 0, replaceLength, &netBufferList ); if (!NT_SUCCESS(status)) { ioPacket->streamAction = FWPS_STREAM_ACTION_DROP_CONNECTION; classifyOut->actionType = FWP_ACTION_NONE; goto Exit; } status = FwpsStreamInjectAsync( gInjectionHandle, NULL, 0, inMetaValues->flowHandle, filter->action.calloutId, inFixedValues->layerId, streamData->flags, netBufferList, replaceLength, StreamInjectCompletionFn, NULL ); if (!NT_SUCCESS(status)) { FwpsFreeNetBufferList(netBufferList); ioPacket->streamAction = FWPS_STREAM_ACTION_DROP_CONNECTION; classifyOut->actionType = FWP_ACTION_NONE; goto Exit; } ioPacket->streamAction = FWPS_STREAM_ACTION_NONE; ioPacket->countBytesEnforced = findLength; classifyOut->actionType = FWP_ACTION_BLOCK; classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE; streamEditor->dataOffset += findLength; streamEditor->dataLength -= findLength; if (streamEditor->dataLength > 0) { streamEditor->inlineEditState = INLINE_EDIT_SCANNING; } else { streamEditor->dataOffset = 0; streamEditor->inlineEditState = INLINE_EDIT_WAITING_FOR_DATA; } break; } default: NT_ASSERT(FALSE); break; }; Exit: return; }