diff --git a/README.md b/README.md index a1f270f..1525235 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,8 @@ The binary will be at `build/Release/etl2pcapng.exe` # History +1.9.0 - Adding support of Microsoft-Windows-Ras-NdisWanPacketCapture (VPN/RemoteAccess) events decoding + adding ThreadID (TID) to packet comments + 1.8.0 - Adding RSS Hash value to packet comments for VMSwitch packets. 1.7.0 - Include VMSwitch packet info in packet comments. diff --git a/src/main.c b/src/main.c index b765b8b..315eb5b 100644 --- a/src/main.c +++ b/src/main.c @@ -38,10 +38,16 @@ in Windows that produces packet capture events) to pcapng format "Converts a packet capture from etl to pcapng format.\n" // Increment when adding features -#define VERSION "1.8.0" +#define VERSION "1.9.0" #define MAX_PACKET_SIZE 65535 +// 2 characters for each byte in the GUID structure{ ulong, ushort, ushort, uchar[8] } +// 4 hypen characters '-' +// Open / Close curly braces +// Zero terminator. +#define GUID_STR_SIZE ((sizeof(unsigned long) + sizeof(unsigned short) + sizeof(unsigned short) + 8) * 2 + 4 + 2 + 1) + // From the ndiscap manifest #define KW_MEDIA_WIRELESS_WAN 0x200 #define KW_MEDIA_NATIVE_802_11 0x10000 @@ -53,6 +59,8 @@ in Windows that produces packet capture events) to pcapng format #define tidPacketFragment 1001 #define tidPacketMetadata 1002 #define tidVMSwitchPacketFragment 1003 +#define tidRRasNdisWanSendPckts 5001 +#define tidRRasNdisWanRcvPckts 5002 // From: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/windot11/ns-windot11-dot11_extsta_recv_context #pragma pack(push,8) @@ -144,12 +152,29 @@ typedef struct _VMSWITCH_PACKET_FRAGMENT { unsigned long RssHashValue; } VMSWITCH_PACKET_FRAGMENT, *PVMSWITCH_PACKET_FRAGMENT; -BOOLEAN CurrentPacketIsVMSwitchPacketFragment = FALSE; +typedef struct _RAS_NDIS_WAN_PACKET_FRAGMENT { + char RoutingDomainID[GUID_STR_SIZE]; + char Username[256]; + unsigned long InterfacePreHashValue; +} RAS_NDIS_WAN_PACKET_FRAGMENT, *PRAS_NDIS_WAN_PACKET_FRAGMENT; + +typedef struct _RAS_INTERFACE_INFO { + char RoutingDomainID[GUID_STR_SIZE]; + char Username[256]; +} RAS_INTERFACE_INFO, *PRAS_INTERFACE_INFO; + +BOOLEAN CurrentPacketIsVMSwitch = FALSE; VMSWITCH_PACKET_FRAGMENT VMSwitchPacketFragment; const GUID NdisCapId = { // Microsoft-Windows-NDIS-PacketCapture {2ED6006E-4729-4609-B423-3EE7BCD678EF} 0x2ed6006e, 0x4729, 0x4609, 0xb4, 0x23, 0x3e, 0xe7, 0xbc, 0xd6, 0x78, 0xef}; +BOOLEAN CurrentPacketIsRas = FALSE; +RAS_NDIS_WAN_PACKET_FRAGMENT RasNdisWanPacketFragment; + +const GUID RasNdisWanCapId = { // Microsoft-Windows-Ras-NdisWanPacketCapture {D84521F7-2235-4237-A7C0-14E3A9676286} + 0xd84521f7, 0x2235, 0x4237, 0xa7, 0xc0, 0x14, 0xe3, 0xa9, 0x67, 0x62, 0x86}; + struct INTERFACE { struct INTERFACE* Next; unsigned long LowerIfIndex; @@ -160,6 +185,9 @@ struct INTERFACE { BOOLEAN IsVMNic; VMSWITCH_SOURCE_INFO VMNic; + + BOOLEAN IsRas; + RAS_INTERFACE_INFO Ras; }; #define IFACE_HT_SIZE 100 @@ -168,26 +196,41 @@ unsigned long NumInterfaces = 0; unsigned long HashInterface(unsigned long LowerIfIndex) { - if (CurrentPacketIsVMSwitchPacketFragment) { - return VMSwitchPacketFragment.SourcePortId * (VMSwitchPacketFragment.VlanId + 1); + unsigned long PreHash = 0; + + if (CurrentPacketIsVMSwitch) { + PreHash = VMSwitchPacketFragment.SourcePortId * (VMSwitchPacketFragment.VlanId + 1); + } else if (CurrentPacketIsRas) { + PreHash = RasNdisWanPacketFragment.InterfacePreHashValue; + for (unsigned int i = 0; i < strlen(RasNdisWanPacketFragment.Username); i++) { + PreHash += RasNdisWanPacketFragment.Username[i] << i; + } } else { - return LowerIfIndex; + PreHash = LowerIfIndex; } + return PreHash % IFACE_HT_SIZE; } struct INTERFACE* GetInterface(unsigned long LowerIfIndex) { - struct INTERFACE* Iface = InterfaceHashTable[HashInterface(LowerIfIndex) % IFACE_HT_SIZE]; + struct INTERFACE* Iface = InterfaceHashTable[HashInterface(LowerIfIndex)]; while (Iface != NULL) { - if (CurrentPacketIsVMSwitchPacketFragment) { + if (CurrentPacketIsVMSwitch) { if (Iface->IsVMNic && Iface->LowerIfIndex == LowerIfIndex && Iface->VlanId == VMSwitchPacketFragment.VlanId && Iface->VMNic.SourcePortId == VMSwitchPacketFragment.SourcePortId) { return Iface; } + } else if (CurrentPacketIsRas) { + if (Iface->IsRas && + Iface->LowerIfIndex == LowerIfIndex && + strcmp(Iface->Ras.RoutingDomainID, RasNdisWanPacketFragment.RoutingDomainID) == 0 && + strcmp(Iface->Ras.Username, RasNdisWanPacketFragment.Username) == 0) { + return Iface; + } } else { - if (!Iface->IsVMNic && Iface->LowerIfIndex == LowerIfIndex && Iface->VlanId == 0) { + if (!Iface->IsVMNic && !Iface->IsRas && Iface->LowerIfIndex == LowerIfIndex) { return Iface; } } @@ -198,7 +241,6 @@ struct INTERFACE* GetInterface(unsigned long LowerIfIndex) void AddInterface(PEVENT_RECORD ev, unsigned long LowerIfIndex, unsigned long MiniportIfIndex, short Type) { - struct INTERFACE** Iface = &InterfaceHashTable[HashInterface(LowerIfIndex) % IFACE_HT_SIZE]; struct INTERFACE* NewIface = malloc(sizeof(struct INTERFACE)); if (NewIface == NULL) { printf("out of memory\n"); @@ -210,19 +252,23 @@ void AddInterface(PEVENT_RECORD ev, unsigned long LowerIfIndex, unsigned long Mi NewIface->Type = Type; NewIface->VlanId = 0; NewIface->IsVMNic = FALSE; + NewIface->IsRas = FALSE; - if (CurrentPacketIsVMSwitchPacketFragment) { + wchar_t Buffer[8192]; + PROPERTY_DATA_DESCRIPTOR Desc; + int Err; + ULONG ParamNameSize = 0; + if (CurrentPacketIsRas) { + NewIface->IsRas = TRUE; + memcpy(NewIface->Ras.RoutingDomainID, RasNdisWanPacketFragment.RoutingDomainID, GUID_STR_SIZE); + memcpy(NewIface->Ras.Username, RasNdisWanPacketFragment.Username, sizeof(RasNdisWanPacketFragment.Username)); + } else if (CurrentPacketIsVMSwitch) { NewIface->IsVMNic = TRUE; - wchar_t Buffer[8192]; - PROPERTY_DATA_DESCRIPTOR Desc; - int Err; - // SourceNicName Desc.PropertyName = (unsigned long long)(L"SourceNicName"); Desc.ArrayIndex = ULONG_MAX; - ULONG ParamNameSize = 0; (void)TdhGetPropertySize(ev, 0, NULL, 1, &Desc, &ParamNameSize); NewIface->VMNic.SourceNicName = malloc((ParamNameSize / sizeof(wchar_t)) + 1); if (NewIface->VMNic.SourceNicName == NULL) { @@ -297,8 +343,8 @@ void AddInterface(PEVENT_RECORD ev, unsigned long LowerIfIndex, unsigned long Mi NewIface->VlanId = VMSwitchPacketFragment.VlanId; } + struct INTERFACE** Iface = &InterfaceHashTable[HashInterface(LowerIfIndex)]; NewIface->Next = *Iface; - *Iface = NewIface; NumInterfaces++; } @@ -391,6 +437,21 @@ void WriteInterfaces() Interface->LowerIfIndex, Interface->VlanId ); + } else if (Interface->IsRas) { + printf("IF: medium=Rras-tunnel\t\tID=%u\tIfIndex=%u\tRoutingDomainId=%s\tUsername=%s", + Interface->PcapNgIfIndex, + Interface->LowerIfIndex, + Interface->Ras.RoutingDomainID, + Interface->Ras.Username + ); + StringCchPrintfA( + IfName, + IF_STRING_MAX_SIZE, + "Rras:%u:%s:%s", + Interface->LowerIfIndex, + Interface->Ras.RoutingDomainID, + Interface->Ras.Username + ); } else { printf("IF: medium=eth\t\t\tID=%u\tIfIndex=%u\tVlanID=%i", Interface->PcapNgIfIndex, Interface->LowerIfIndex, Interface->VlanId); StringCchPrintfA(IfName, IF_STRING_MAX_SIZE, "eth:%lu:%i", Interface->LowerIfIndex, Interface->VlanId); @@ -433,6 +494,64 @@ void WriteInterfaces() free(InterfaceArray); } +void ParseRasNdisWanPacketFragment(PEVENT_RECORD ev) +{ + // Parse the current RasNdisWan packet event for use elsewhere. + // NB: Here we only do per-packet parsing. For any event fields that only need to be + // parsed once and written into an INTERFACE, we do the parsing in AddInterface. + ULONG Err; + wchar_t Buffer[256]; + PROPERTY_DATA_DESCRIPTOR Desc; + ULONG ParamNameSize = 0; + // RoutingDomainID + Desc.PropertyName = (unsigned long long)(L"RoutingDomainID"); + Desc.ArrayIndex = ULONG_MAX; + (void)TdhGetPropertySize(ev, 0, NULL, 1, &Desc, &ParamNameSize); + if ((ParamNameSize / sizeof(wchar_t)) != GUID_STR_SIZE) { + printf("error decoding Microsoft-Windows-Ras-NdisWanPacketCapture: TdhGetPropertySize returns an unexpected RoutingDomainID size\n"); + return; + } + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(Buffer), (PBYTE)Buffer); + if (Err != NO_ERROR) { + Buffer[0] = L'\0'; + } + Buffer[ParamNameSize / sizeof(wchar_t) + 1] = L'\0'; + WideCharToMultiByte(CP_ACP, + 0, + Buffer, + -1, + RasNdisWanPacketFragment.RoutingDomainID, + ParamNameSize / sizeof(wchar_t) + 1, + NULL, + NULL); + RasNdisWanPacketFragment.RoutingDomainID[wcslen(Buffer)] = '\0'; + + // Username + Desc.PropertyName = (unsigned long long)(L"RRASUserName"); + Desc.ArrayIndex = ULONG_MAX; + (void)TdhGetPropertySize(ev, 0, NULL, 1, &Desc, &ParamNameSize); + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(Buffer), (PBYTE)Buffer); + if (Err != NO_ERROR) { + Buffer[0] = L'\0'; + } + Buffer[ParamNameSize / sizeof(wchar_t) + 1] = L'\0'; + WideCharToMultiByte(CP_ACP, + 0, + Buffer, + -1, + RasNdisWanPacketFragment.Username, + ParamNameSize / sizeof(wchar_t) + 1, + NULL, + NULL); + RasNdisWanPacketFragment.Username[wcslen(Buffer)] = '\0'; + + // Compute InterfacePreHashValue using the last part of RoutingDomainID. + char LastPartOfGuid[9]; + memcpy(&LastPartOfGuid, &RasNdisWanPacketFragment.RoutingDomainID[29], sizeof(LastPartOfGuid)); + LastPartOfGuid[8] = '\0'; + RasNdisWanPacketFragment.InterfacePreHashValue = strtoul(LastPartOfGuid, NULL, 16); +} + void ParseVmSwitchPacketFragment(PEVENT_RECORD ev) { // Parse the current VMSwitch packet event for use elsewhere. @@ -493,7 +612,7 @@ void ParseVmSwitchPacketFragment(PEVENT_RECORD ev) void WINAPI EventCallback(PEVENT_RECORD ev) { int Err; - unsigned long LowerIfIndex; + unsigned long LowerIfIndex = NET_IFINDEX_UNSPECIFIED; struct INTERFACE* Iface; unsigned long FragLength; @@ -506,47 +625,68 @@ void WINAPI EventCallback(PEVENT_RECORD ev) PIPV4_HEADER Ipv4Hdr; PIPV6_HEADER Ipv6Hdr; - if (!IsEqualGUID(&ev->EventHeader.ProviderId, &NdisCapId) || - (ev->EventHeader.EventDescriptor.Id != tidPacketFragment && - ev->EventHeader.EventDescriptor.Id != tidPacketMetadata && - ev->EventHeader.EventDescriptor.Id != tidVMSwitchPacketFragment)) { - return; - } + BOOLEAN IsNdisCapEvent = IsEqualGUID(&ev->EventHeader.ProviderId, &NdisCapId) && + (ev->EventHeader.EventDescriptor.Id == tidPacketFragment || + ev->EventHeader.EventDescriptor.Id == tidPacketMetadata || + ev->EventHeader.EventDescriptor.Id == tidVMSwitchPacketFragment); - CurrentPacketIsVMSwitchPacketFragment = (ev->EventHeader.EventDescriptor.Id == tidVMSwitchPacketFragment); - if (CurrentPacketIsVMSwitchPacketFragment) { - ParseVmSwitchPacketFragment(ev); - } + BOOLEAN IsRasEvent = IsEqualGUID(&ev->EventHeader.ProviderId, &RasNdisWanCapId) && + (ev->EventHeader.EventDescriptor.Id == tidRRasNdisWanSendPckts || + ev->EventHeader.EventDescriptor.Id == tidRRasNdisWanRcvPckts); - Desc.PropertyName = (unsigned long long)L"LowerIfIndex"; - Desc.ArrayIndex = ULONG_MAX; - Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(LowerIfIndex), (PBYTE)&LowerIfIndex); - if (Err != NO_ERROR) { - printf("TdhGetProperty LowerIfIndex failed with %u\n", Err); + if (!IsNdisCapEvent && !IsRasEvent) { return; } - Iface = GetInterface(LowerIfIndex); + CurrentPacketIsVMSwitch = IsNdisCapEvent && (ev->EventHeader.EventDescriptor.Id == tidVMSwitchPacketFragment); + CurrentPacketIsRas = IsRasEvent; - if (!!(ev->EventHeader.EventDescriptor.Keyword & KW_MEDIA_NATIVE_802_11)) { - Type = PCAPNG_LINKTYPE_IEEE802_11; - } else if (!!(ev->EventHeader.EventDescriptor.Keyword & KW_MEDIA_WIRELESS_WAN)) { - Type = PCAPNG_LINKTYPE_RAW; - } else { + // NB: LowerIfIndex and MiniportIfIndex are not applicable for Ras captures, + // so for Ras packets we use NET_IFINDEX_UNSPECIFIED. + + if (CurrentPacketIsRas) { Type = PCAPNG_LINKTYPE_ETHERNET; + ParseRasNdisWanPacketFragment(ev); + } else { + + if (!!(ev->EventHeader.EventDescriptor.Keyword & KW_MEDIA_NATIVE_802_11)) { + Type = PCAPNG_LINKTYPE_IEEE802_11; + } else if (!!(ev->EventHeader.EventDescriptor.Keyword & KW_MEDIA_WIRELESS_WAN)) { + Type = PCAPNG_LINKTYPE_RAW; + } else { + Type = PCAPNG_LINKTYPE_ETHERNET; + } + + if (CurrentPacketIsVMSwitch) { + ParseVmSwitchPacketFragment(ev); + } + + Desc.PropertyName = (unsigned long long)L"LowerIfIndex"; + Desc.ArrayIndex = ULONG_MAX; + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(LowerIfIndex), (PBYTE)&LowerIfIndex); + if (Err != NO_ERROR) { + printf("TdhGetProperty LowerIfIndex failed with %u\n", Err); + return; + } } + Iface = GetInterface(LowerIfIndex); + if (!Pass2) { // Record the IfIndex if it's a new one. if (Iface == NULL) { - unsigned long MiniportIfIndex; - Desc.PropertyName = (unsigned long long)L"MiniportIfIndex"; - Desc.ArrayIndex = ULONG_MAX; - Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(MiniportIfIndex), (PBYTE)&MiniportIfIndex); - if (Err != NO_ERROR) { - printf("TdhGetProperty MiniportIfIndex failed with %u\n", Err); - return; + unsigned long MiniportIfIndex = NET_IFINDEX_UNSPECIFIED; + + if (!CurrentPacketIsRas) { + Desc.PropertyName = (unsigned long long)L"MiniportIfIndex"; + Desc.ArrayIndex = ULONG_MAX; + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(MiniportIfIndex), (PBYTE)&MiniportIfIndex); + if (Err != NO_ERROR) { + printf("TdhGetProperty MiniportIfIndex failed with %u\n", Err); + return; + } } + AddInterface( ev, LowerIfIndex, @@ -567,7 +707,7 @@ void WINAPI EventCallback(PEVENT_RECORD ev) } // Save off Ndis/Wlan metadata to be added to the next packet - if (ev->EventHeader.EventDescriptor.Id == tidPacketMetadata) { + if (IsNdisCapEvent && ev->EventHeader.EventDescriptor.Id == tidPacketMetadata) { unsigned long MetadataLength = 0; Desc.PropertyName = (unsigned long long)L"MetadataSize"; Desc.ArrayIndex = ULONG_MAX; @@ -640,9 +780,11 @@ void WINAPI EventCallback(PEVENT_RECORD ev) // // NB: Starting with Windows 8.1, only single-event packets are traced. // This logic is here to support packet captures from older systems. + // + // NB: This logic does not apply to events generated by Microsoft-Windows-Ras-NdisWanPacketCapture + // which don't use KW_PACKET_START and KW_PACKET_END keywords - if (!!(ev->EventHeader.EventDescriptor.Keyword & KW_PACKET_END)) { - + if (!!(ev->EventHeader.EventDescriptor.Keyword & KW_PACKET_END) || CurrentPacketIsRas) { if (ev->EventHeader.EventDescriptor.Keyword & KW_MEDIA_NATIVE_802_11 && AuxFragBuf[1] & 0x40) { // Clear Protected bit in the case of 802.11 @@ -656,13 +798,14 @@ void WINAPI EventCallback(PEVENT_RECORD ev) char Comment[COMMENT_MAX_SIZE] = { 0 }; size_t CommentLength = 0; - if (AddWlanMetadata) { + if (IsNdisCapEvent && AddWlanMetadata) { if (PacketMetadata.uPhyId > DOT11_PHY_TYPE_NAMES_MAX) { PacketMetadata.uPhyId = 0; // Set to unknown if outside known bounds. } - Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d Packet Metadata: ReceiveFlags:0x%x, PhyType:%s, CenterCh:%u, NumMPDUsReceived:%u, RSSI:%d, DataRate:%u", + Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d TID=%d Packet Metadata: ReceiveFlags:0x%x, PhyType:%s, CenterCh:%u, NumMPDUsReceived:%u, RSSI:%d, DataRate:%u", ev->EventHeader.ProcessId, + ev->EventHeader.ThreadId, PacketMetadata.uReceiveFlags, DOT11_PHY_TYPE_NAMES[PacketMetadata.uPhyId], PacketMetadata.uChCenterFrequency, @@ -672,10 +815,11 @@ void WINAPI EventCallback(PEVENT_RECORD ev) AddWlanMetadata = FALSE; memset(&PacketMetadata, 0, sizeof(DOT11_EXTSTA_RECV_CONTEXT)); - } else if (CurrentPacketIsVMSwitchPacketFragment) { + } else if (CurrentPacketIsVMSwitch) { if (VMSwitchPacketFragment.DestinationCount > 0) { - Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d VlanId=%d SrcPortId=%d SrcNicType=%s SrcNicName=%s SrcPortName=%s DstNicCount=%d HashValue=%08lx", + Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d TID=%d VlanId=%d SrcPortId=%d SrcNicType=%s SrcNicName=%s SrcPortName=%s DstNicCount=%d HashValue=%08lx", ev->EventHeader.ProcessId, + ev->EventHeader.ThreadId, Iface->VlanId, Iface->VMNic.SourcePortId, Iface->VMNic.SourceNicType, @@ -685,8 +829,9 @@ void WINAPI EventCallback(PEVENT_RECORD ev) VMSwitchPacketFragment.RssHashValue ); } else { - Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d VlanId=%d SrcPortId=%d SrcNicType=%s SrcNicName=%s SrcPortName=%s HashValue=%08lx", + Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d TID=%d VlanId=%d SrcPortId=%d SrcNicType=%s SrcNicName=%s SrcPortName=%s HashValue=%08lx", ev->EventHeader.ProcessId, + ev->EventHeader.ThreadId, Iface->VlanId, Iface->VMNic.SourcePortId, Iface->VMNic.SourceNicType, @@ -695,8 +840,10 @@ void WINAPI EventCallback(PEVENT_RECORD ev) VMSwitchPacketFragment.RssHashValue ); } + } else if (CurrentPacketIsRas) { + Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d TID=%d RoutingDomainId=%s Username=%s", ev->EventHeader.ProcessId, ev->EventHeader.ThreadId, Iface->Ras.RoutingDomainID, Iface->Ras.Username); } else { - Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d", ev->EventHeader.ProcessId); + Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d TID=%d", ev->EventHeader.ProcessId, ev->EventHeader.ThreadId); } if (Err != NO_ERROR) { @@ -722,7 +869,7 @@ void WINAPI EventCallback(PEVENT_RECORD ev) Ipv4Hdr = (PIPV4_HEADER)(EthHdr + 1); InferredOriginalFragmentLength = ntohs(Ipv4Hdr->TotalLength) + sizeof(ETHERNET_HEADER); } else if (ntohs(EthHdr->Type) == ETHERNET_TYPE_IPV6 && - TotalFragmentLength >= sizeof(IPV6_HEADER) + sizeof(ETHERNET_HEADER)) { + TotalFragmentLength >= sizeof(IPV6_HEADER) + sizeof(ETHERNET_HEADER)) { Ipv6Hdr = (PIPV6_HEADER)(EthHdr + 1); InferredOriginalFragmentLength = ntohs(Ipv6Hdr->PayloadLength) + sizeof(IPV6_HEADER) + sizeof(ETHERNET_HEADER); }