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 StreamOobEditData( _Inout_ STREAM_EDITOR* streamEditor, _Inout_ NET_BUFFER_LIST* netBufferListChain, size_t totalDataLength, DWORD streamFlags ) /* ++ This function first copies the stream data into a flat inspection buffer; it then parses the buffer looking for the matching pattern. For non-matching sections it re-injects the data back; for a match it skips over and injects an replacement section. If a match can not be determined due to lack of data, it injects the non-matching section back and moves the potential match to the beginning of the inspection buffer. When an EOF is reached, it flushes all processed stream sections back and re-injects the FIN back to end the stream. -- */ { NTSTATUS status = STATUS_SUCCESS; UINT i = 0; BOOLEAN streamModified = FALSE; BOOLEAN potentialMatch = FALSE; BYTE* dataStart; UINT findLength = (UINT) strlen(configStringToFind); status = StreamOobCopyDataToFlatBuffer( streamEditor, netBufferListChain, totalDataLength, streamFlags ); if (!NT_SUCCESS(status)) { goto Exit; } dataStart = (BYTE*)streamEditor->scratchBuffer + streamEditor->dataOffset; for (; i < streamEditor->dataLength; ++i) { if (i + findLength <= streamEditor->dataLength) { if (RtlCompareMemory( dataStart + i, configStringToFind, findLength ) == findLength) { if (i != 0) { status = StreamOobReinjectData( streamEditor, streamFlags, dataStart, i ); if (!NT_SUCCESS(status)) { goto Exit; } streamEditor->dataOffset += i; streamEditor->dataLength -= i; i = 0; } status = StreamOobInjectReplacement( streamEditor, streamFlags, gStringToReplaceMdl, strlen(configStringToReplace) ); if (!NT_SUCCESS(status)) { goto Exit; } streamEditor->dataOffset += findLength; streamEditor->dataLength -= findLength; streamModified = TRUE; if (streamEditor->dataLength > 0) { dataStart = (BYTE*)streamEditor->scratchBuffer + streamEditor->dataOffset; --i; continue; } else { streamEditor->dataOffset = 0; } } } else { if (streamEditor->oobEditInfo.noMoreData) { break; } if (RtlCompareMemory( dataStart + i, configStringToFind, streamEditor->dataLength - i ) == streamEditor->dataLength - i) { potentialMatch = TRUE; // this is a partial find status = StreamOobReinjectData( streamEditor, streamFlags, dataStart, i ); if (!NT_SUCCESS(status)) { goto Exit; } RtlMoveMemory( (BYTE*)streamEditor->scratchBuffer, dataStart + i, streamEditor->dataLength - i ); streamEditor->dataOffset = 0; streamEditor->dataLength = streamEditor->dataLength - i; break; } } } if (streamModified && streamEditor->dataLength > 0) { status = StreamOobReinjectData( streamEditor, streamFlags, dataStart, streamEditor->dataLength ); if (!NT_SUCCESS(status)) { goto Exit; } streamEditor->dataOffset = 0; streamEditor->dataLength = 0; } if (!streamModified && !potentialMatch) { if (totalDataLength > 0) { NT_ASSERT(!(streamFlags & FWPS_STREAM_FLAG_SEND_DISCONNECT) && !(streamFlags & FWPS_STREAM_FLAG_RECEIVE_DISCONNECT)); status = StreamOobQueueUpOutgoingData( streamEditor, netBufferListChain, TRUE, totalDataLength, streamFlags, NULL ); if (!NT_SUCCESS(status)) { goto Exit; } netBufferListChain = NULL; } else if (streamEditor->dataLength > 0) { status = StreamOobReinjectData( streamEditor, streamFlags, dataStart, streamEditor->dataLength ); if (!NT_SUCCESS(status)) { goto Exit; } } streamEditor->dataOffset = 0; streamEditor->dataLength = 0; } if (streamEditor->oobEditInfo.nblEof != NULL) { status = StreamOobFlushOutgoingData(streamEditor); if (!NT_SUCCESS(status)) { goto Exit; } status = FwpsStreamInjectAsync( gInjectionHandle, NULL, 0, streamEditor->oobEditInfo.flowId, streamEditor->oobEditInfo.calloutId, streamEditor->oobEditInfo.layerId, streamFlags | (configInspectionOutbound ? FWPS_STREAM_FLAG_SEND_DISCONNECT : FWPS_STREAM_FLAG_RECEIVE_DISCONNECT), streamEditor->oobEditInfo.nblEof, 0, StreamOobInjectCompletionFn, NULL ); if (!NT_SUCCESS(status)) { goto Exit; } streamEditor->oobEditInfo.nblEof = NULL; streamEditor->oobEditInfo.noMoreData = FALSE; } Exit: if (netBufferListChain != NULL) { FwpsDiscardClonedStreamData( netBufferListChain, 0, FALSE ); } if (streamEditor->oobEditInfo.nblEof != NULL) { FwpsDiscardClonedStreamData( streamEditor->oobEditInfo.nblEof, 0, FALSE ); streamEditor->oobEditInfo.nblEof = NULL; } 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; }