From 36e834b6b7996f75097505e88c6de5bafd42248a Mon Sep 17 00:00:00 2001 From: Odysseus Yang Date: Thu, 13 Jan 2022 10:54:01 -0800 Subject: [PATCH] ETW: Extract IP packets from Windows event trace With this change, Wireshark will be enhanced to display IP packets from an event trace logfile or an event trace live session. --- doc/etwdump.adoc | 5 +- docbook/release-notes.adoc | 1 + extcap/CMakeLists.txt | 1 + extcap/etl.c | 70 +++- extcap/etw_ndiscap.c | 705 +++++++++++++++++++++++++++++++++++++ 5 files changed, 761 insertions(+), 21 deletions(-) create mode 100644 extcap/etw_ndiscap.c diff --git a/doc/etwdump.adoc b/doc/etwdump.adoc index 0ceba79e4c..f386c95e61 100644 --- a/doc/etwdump.adoc +++ b/doc/etwdump.adoc @@ -27,8 +27,8 @@ etwdump - Provide an interface to read Event Tracing for Windows (ETW) == DESCRIPTION -*etwdump* is a extcap tool that provides access to a etl file. -It is only used to display event traces on Windows. +*etwdump* is a extcap tool that provides access to a event trace log file or an event trace live session. +It is only used to display event trace on Windows that includes readable text message and different protocols (like MBIM and IP packets). == OPTIONS @@ -134,6 +134,7 @@ To see interface configuration options: To capture: etwdump --extcap-interface etwdump --fifo=/tmp/etw.pcapng --capture --params "--p=Microsoft-Windows-Wmbclass-Opn --p=Microsoft-Windows-wmbclass --k=0xff --l=4" + etwdump --extcap-interface etwdump --fifo=/tmp/etw.pcapng --capture --params "--p=Microsoft-Windows-Wmbclass-Opn --p=Microsoft-Windows-NDIS-PacketCapture" NOTE: To stop capturing CTRL+C/kill/terminate the application. diff --git a/docbook/release-notes.adoc b/docbook/release-notes.adoc index 023f0b8950..d6a56301d3 100644 --- a/docbook/release-notes.adoc +++ b/docbook/release-notes.adoc @@ -114,6 +114,7 @@ They previously shipped with Npcap 1.55. * The interface list on the welcome page sorts active interfaces first and only displays the sparkline for active interfaces. Additionally, the interfaces can now be hidden/unhidden via the context menu in the interface list +* ETW reader now supports to display IP packets from an event trace logfile or an event trace live session === Removed Features and Support * CMake: The options starting with DISABLE_something were renamed ENABLE_something for consistency. diff --git a/extcap/CMakeLists.txt b/extcap/CMakeLists.txt index d70accf2c3..0b8af2b657 100644 --- a/extcap/CMakeLists.txt +++ b/extcap/CMakeLists.txt @@ -292,6 +292,7 @@ if(BUILD_etwdump AND WIN32) etwdump.c etl.c etw_message.c + etw_ndiscap.c ) set_executable_resources(etwdump "etwdump") diff --git a/extcap/etl.c b/extcap/etl.c index 9baaf217cb..c1a36e2477 100644 --- a/extcap/etl.c +++ b/extcap/etl.c @@ -34,7 +34,10 @@ extern int g_include_undecidable_event; +//Microsoft-Windows-Wmbclass-Opn const GUID mbb_provider = { 0xA42FE227, 0xA7BF, 0x4483, {0xA5, 0x02, 0x6B, 0xCD, 0xA4, 0x28, 0xCD, 0x96} }; +// Microsoft-Windows-NDIS-PacketCapture +const GUID ndis_capture_provider = { 0x2ed6006e, 0x4729, 0x4609, 0xb4, 0x23, 0x3e, 0xe7, 0xbc, 0xd6, 0x78, 0xef }; EXTERN_C const GUID DECLSPEC_SELECTANY EventTraceGuid = { 0x68fdd900, 0x4a3e, 0x11d1, {0x84, 0xf4, 0x00, 0x00, 0xf8, 0x04, 0x64, 0xe3} }; EXTERN_C const GUID DECLSPEC_SELECTANY ImageIdGuid = { 0xb3e675d7, 0x2554, 0x4f18, { 0x83, 0xb, 0x27, 0x62, 0x73, 0x25, 0x60, 0xde } }; @@ -69,8 +72,8 @@ typedef struct _PROVIDER_FILTER { UCHAR Level; } PROVIDER_FILTER; -static gchar g_err_info[FILENAME_MAX] = { 0 }; -static int g_err = ERROR_SUCCESS; +char g_err_info[FILENAME_MAX] = { 0 }; +int g_err = ERROR_SUCCESS; static wtap_dumper* g_pdh = NULL; extern ULONGLONG g_num_events; static PROVIDER_FILTER g_provider_filters[32] = { 0 }; @@ -78,9 +81,10 @@ static BOOL g_is_live_session = FALSE; static void WINAPI event_callback(PEVENT_RECORD ev); void etw_dump_write_opn_event(PEVENT_RECORD ev, ULARGE_INTEGER timestamp); +void etw_dump_write_ndiscap_event(PEVENT_RECORD ev, ULARGE_INTEGER timestamp); void etw_dump_write_general_event(PEVENT_RECORD ev, ULARGE_INTEGER timestamp); void etw_dump_write_event_head_only(PEVENT_RECORD ev, ULARGE_INTEGER timestamp); -void wtap_etl_rec_dump(ULARGE_INTEGER timestamp, WTAP_ETL_RECORD* etl_record, ULONG total_packet_length, BOOLEAN is_inbound); +void wtap_etl_rec_dump(char* etl_record, ULONG total_packet_length, ULONG original_packet_length, unsigned int interface_id, BOOLEAN is_inbound, ULARGE_INTEGER timestamp, int pkt_encap, char* comment, unsigned short comment_length); wtap_dumper* etw_dump_open(const char* pcapng_filename, int* err, gchar** err_info); DWORD GetPropertyValue(WCHAR* ProviderId, EVT_PUBLISHER_METADATA_PROPERTY_ID PropertyId, PEVT_VARIANT* Value) @@ -307,7 +311,7 @@ wtap_open_return_val etw_dump(const char* etl_filename, const char* pcapng_filen &super_trace_properties.prop); if (*err != ERROR_SUCCESS) { - *err_info = ws_strdup_printf("StartTrace failed with %u", *err); + *err_info = ws_strdup_printf("StartTrace failed with 0x%x", *err); returnVal = WTAP_OPEN_ERROR; break; } @@ -330,7 +334,7 @@ wtap_open_return_val etw_dump(const char* etl_filename, const char* pcapng_filen NULL); if (*err != ERROR_SUCCESS) { - *err_info = ws_strdup_printf("EnableTraceEx failed with %u", *err); + *err_info = ws_strdup_printf("EnableTraceEx failed with 0x%x", *err); returnVal = WTAP_OPEN_ERROR; break; } @@ -340,7 +344,7 @@ wtap_open_return_val etw_dump(const char* etl_filename, const char* pcapng_filen trace_handle = OpenTrace(&log_file); if (trace_handle == INVALID_PROCESSTRACE_HANDLE) { *err = GetLastError(); - *err_info = ws_strdup_printf("OpenTrace failed with %u", err); + *err_info = ws_strdup_printf("OpenTrace failed with 0x%x", *err); returnVal = WTAP_OPEN_NOT_MINE; break; } @@ -355,7 +359,7 @@ wtap_open_return_val etw_dump(const char* etl_filename, const char* pcapng_filen *err = ProcessTrace(&trace_handle, 1, 0, 0); if (*err != ERROR_SUCCESS) { returnVal = WTAP_OPEN_ERROR; - *err_info = ws_strdup_printf("ProcessTrace failed with %u", err); + *err_info = ws_strdup_printf("ProcessTrace failed with 0x%x", *err); break; } @@ -450,13 +454,10 @@ static void WINAPI event_callback(PEVENT_RECORD ev) { etw_dump_write_opn_event(ev, timestamp); } - /* TODO:: You can write events from other providers that needs specific sub dissector */ -#if 0 - else if (IsEqualGUID(&ev->EventHeader.ProviderId, &ndis_packcapture_provider)) + else if (IsEqualGUID(&ev->EventHeader.ProviderId, &ndis_capture_provider)) { - etw_dump_write_packet_event(ev, timestamp); + etw_dump_write_ndiscap_event(ev, timestamp); } -#endif /* Write any event form other providers other than above */ else { @@ -579,7 +580,33 @@ ULONG wtap_etl_record_buffer_init(WTAP_ETL_RECORD** out_etl_record, PEVENT_RECOR return total_packet_length; } -void wtap_etl_rec_dump(ULARGE_INTEGER timestamp, WTAP_ETL_RECORD* etl_record, ULONG total_packet_length, BOOLEAN is_inbound) +void wtap_etl_add_interface(int pkt_encap, char* interface_name, unsigned short interface_name_length, char* interface_desc, unsigned short interface_desc_length) +{ + wtap_block_t idb_data; + wtapng_if_descr_mandatory_t* descr_mand; + gchar* err_info; + int err; + + idb_data = wtap_block_create(WTAP_BLOCK_IF_ID_AND_INFO); + descr_mand = (wtapng_if_descr_mandatory_t*)wtap_block_get_mandatory_data(idb_data); + descr_mand->wtap_encap = pkt_encap; + descr_mand->tsprecision = WTAP_TSPREC_USEC; + /* Timestamp for each pcapng packet is usec units, so time_units_per_second need be set to 10^6 */ + descr_mand->time_units_per_second = G_USEC_PER_SEC; + if (interface_name_length) { + wtap_block_add_string_option(idb_data, OPT_IDB_NAME, interface_name, interface_name_length); + } + if (interface_desc_length) { + wtap_block_add_string_option(idb_data, OPT_IDB_DESCRIPTION, interface_desc, interface_desc_length); + } + if(!wtap_dump_add_idb(g_pdh, idb_data, &err, &err_info)) { + g_err = err; + sprintf_s(g_err_info, sizeof(g_err_info), "wtap_dump failed, %s", err_info); + g_free(err_info); + } +} + +void wtap_etl_rec_dump(char* etl_record, ULONG total_packet_length, ULONG original_packet_length, unsigned int interface_id, BOOLEAN is_inbound, ULARGE_INTEGER timestamp, int pkt_encap, char* comment, unsigned short comment_length) { gchar* err_info; int err; @@ -587,10 +614,15 @@ void wtap_etl_rec_dump(ULARGE_INTEGER timestamp, WTAP_ETL_RECORD* etl_record, UL wtap_rec_init(&rec); rec.rec_header.packet_header.caplen = total_packet_length; - rec.rec_header.packet_header.len = total_packet_length; - rec.rec_header.packet_header.pkt_encap = WTAP_ENCAP_ETW; + rec.rec_header.packet_header.len = original_packet_length; + rec.rec_header.packet_header.pkt_encap = pkt_encap; + rec.rec_header.packet_header.interface_id = interface_id; + rec.presence_flags = WTAP_HAS_INTERFACE_ID; rec.block = wtap_block_create(WTAP_BLOCK_PACKET); wtap_block_add_uint32_option(rec.block, OPT_PKT_FLAGS, is_inbound ? PACK_FLAGS_DIRECTION_INBOUND : PACK_FLAGS_DIRECTION_OUTBOUND); + if (comment_length) { + wtap_block_add_string_option(rec.block, OPT_COMMENT, comment, comment_length); + } /* Convert usec of the timestamp into nstime_t */ rec.ts.secs = (time_t)(timestamp.QuadPart / G_USEC_PER_SEC); rec.ts.nsecs = (int)(((timestamp.QuadPart % G_USEC_PER_SEC) * G_NSEC_PER_SEC) / G_USEC_PER_SEC); @@ -605,7 +637,7 @@ void wtap_etl_rec_dump(ULARGE_INTEGER timestamp, WTAP_ETL_RECORD* etl_record, UL /* Only flush when live session */ if (g_is_live_session && !wtap_dump_flush(g_pdh, &err)) { g_err = err; - sprintf_s(g_err_info, sizeof(g_err_info), "wtap_dump failed, %d", err); + sprintf_s(g_err_info, sizeof(g_err_info), "wtap_dump failed, 0x%x", err); } wtap_rec_cleanup(&rec); } @@ -618,7 +650,7 @@ void etw_dump_write_opn_event(PEVENT_RECORD ev, ULARGE_INTEGER timestamp) /* 0x80000000 mask the function to host message */ is_inbound = ((*(INT32*)(ev->UserData)) & 0x80000000) ? TRUE : FALSE; total_packet_length = wtap_etl_record_buffer_init(&etl_record, ev, TRUE, NULL, NULL); - wtap_etl_rec_dump(timestamp, etl_record, total_packet_length, is_inbound); + wtap_etl_rec_dump((char*)etl_record, total_packet_length, total_packet_length, 0, is_inbound, timestamp, WTAP_ENCAP_ETW, NULL, 0); g_free(etl_record); } @@ -627,7 +659,7 @@ void etw_dump_write_event_head_only(PEVENT_RECORD ev, ULARGE_INTEGER timestamp) WTAP_ETL_RECORD* etl_record = NULL; ULONG total_packet_length = 0; total_packet_length = wtap_etl_record_buffer_init(&etl_record, ev, FALSE, NULL, NULL); - wtap_etl_rec_dump(timestamp, etl_record, total_packet_length, FALSE); + wtap_etl_rec_dump((char*)etl_record, total_packet_length, total_packet_length, 0, FALSE, timestamp, WTAP_ENCAP_ETW, NULL, 0); g_free(etl_record); } @@ -715,7 +747,7 @@ void etw_dump_write_general_event(PEVENT_RECORD ev, ULARGE_INTEGER timestamp) format_message(formatMessage, prop_arr, dwTopLevelPropertyCount, wszMessageBuffer, sizeof(wszMessageBuffer)); total_packet_length = wtap_etl_record_buffer_init(&etl_record, ev, FALSE, wszMessageBuffer, (WCHAR*)ADD_OFFSET_TO_POINTER(pInfo, pInfo->ProviderNameOffset)); - wtap_etl_rec_dump(timestamp, etl_record, total_packet_length, FALSE); + wtap_etl_rec_dump((char*)etl_record, total_packet_length, total_packet_length, 0, FALSE, timestamp, WTAP_ENCAP_ETW, NULL, 0); g_free(etl_record); is_message_dumped = TRUE; diff --git a/extcap/etw_ndiscap.c b/extcap/etw_ndiscap.c new file mode 100644 index 0000000000..fc43f1ab5d --- /dev/null +++ b/extcap/etw_ndiscap.c @@ -0,0 +1,705 @@ +/* etw_ndiscap.c + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* +* Reads IP packets from an Windows event trace logfile or an Windows event trace live session +* and write out a pcap file with LINKTYPE_ETHERNET, LINKTYPE_RAW or LINKTYPE_IEEE802_11. +* The major code of this file is from https://github.com/microsoft/etl2pcapng with some changes by Odysseus Yang. +* The changes mainly include but not limited +* 1. calling pcapng APIs instead of writing the data in the pcapng binary format by its own implementation in etl2pcapng. +* 2. Optimize the process of adding pcapng interfaces so it doesn't need process the same Windows event trace logfile twice, + that not only impacts the performance, but also breaks Wireshark live capture function. +*/ + +#define WIN32_LEAN_AND_MEAN 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// inet_ipv6.h and netiodef.h define exactly the same stuff, like _IPV6_ROUTING_HEADER and IP6F_OFF_MASK. +// So wiretap/wtap.h cannot be directly included in this file. Defines below three WTAP_ENCAP types with the value in wtap.h for compile +#define WTAP_ENCAP_ETHERNET 1 +#define WTAP_ENCAP_RAW_IP 7 +#define WTAP_ENCAP_IEEE_802_11 20 + +#define MAX_PACKET_SIZE 0xFFFF + +// From the ndiscap manifest +#define KW_MEDIA_WIRELESS_WAN 0x200 +#define KW_MEDIA_NATIVE_802_11 0x10000 +#define KW_PACKET_START 0x40000000 +#define KW_PACKET_END 0x80000000 +#define KW_SEND 0x100000000 +#define KW_RECEIVE 0x200000000 + +#define tidPacketFragment 1001 +#define tidPacketMetadata 1002 +#define tidVMSwitchPacketFragment 1003 + +// From: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/windot11/ns-windot11-dot11_extsta_recv_context +#pragma pack(push,8) +typedef struct _NDIS_OBJECT_HEADER { + unsigned char Type; + unsigned char Revision; + unsigned short Size; +} NDIS_OBJECT_HEADER, * PNDIS_OBJECT_HEADER; + +typedef struct DOT11_EXTSTA_RECV_CONTEXT { + NDIS_OBJECT_HEADER Header; + unsigned long uReceiveFlags; + unsigned long uPhyId; + unsigned long uChCenterFrequency; + unsigned short usNumberOfMPDUsReceived; + long lRSSI; + unsigned char ucDataRate; + unsigned long uSizeMediaSpecificInfo; + void *pvMediaSpecificInfo; + unsigned long long ullTimestamp; +} DOT11_EXTSTA_RECV_CONTEXT, * PDOT11_EXTSTA_RECV_CONTEXT; +#pragma pack(pop) + +// From: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/windot11/ne-windot11-_dot11_phy_type +#define DOT11_PHY_TYPE_NAMES_MAX 10 +static const char* DOT11_PHY_TYPE_NAMES[] = { + "Unknown", // dot11_phy_type_unknown = 0 + "Fhss", // dot11_phy_type_fhss = 1 + "Dsss", // dot11_phy_type_dsss = 2 + "IrBaseband", // dot11_phy_type_irbaseband = 3 + "802.11a", // dot11_phy_type_ofdm = 4 + "802.11b", // dot11_phy_type_hrdsss = 5 + "802.11g", // dot11_phy_type_erp = 6 + "802.11n", // dot11_phy_type_ht = 7 + "802.11ac", // dot11_phy_type_vht = 8 + "802.11ad", // dot11_phy_type_dmg = 9 + "802.11ax" // dot11_phy_type_he = 10 +}; + +unsigned long long NumFramesConverted = 0; +char AuxFragBuf[MAX_PACKET_SIZE] = {0}; +unsigned long AuxFragBufOffset = 0; + +DOT11_EXTSTA_RECV_CONTEXT PacketMetadata; +BOOLEAN AddWlanMetadata = FALSE; + +typedef struct _NDIS_NET_BUFFER_LIST_8021Q_INFO { + union { + struct { + UINT32 UserPriority : 3; // 802.1p priority + UINT32 CanonicalFormatId : 1; // always 0 + UINT32 VlanId : 12; // VLAN Identification + UINT32 Reserved : 16; // set to 0 for ethernet + } TagHeader; + + struct { + UINT32 UserPriority : 3; // 802.1p priority + UINT32 CanonicalFormatId : 1; // always 0 + UINT32 VlanId : 12; // VLAN Identification + UINT32 WMMInfo : 4; + UINT32 Reserved : 12; // set to 0 for Wireless LAN + } WLanTagHeader; + + PVOID Value; + }; +} NDIS_NET_BUFFER_LIST_8021Q_INFO, *PNDIS_NET_BUFFER_LIST_8021Q_INFO; + +// The max OOB data size might increase in the future. If it becomes larger than MaxNetBufferListInfo, +// this tool will print a warning and the value of MaxNetBufferListInfo in the code should be increased. +// From: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/nblinfo/ne-nblinfo-ndis_net_buffer_list_info +#define MaxNetBufferListInfo 200 +#define Ieee8021QNetBufferListInfo 4 +PBYTE OobData[MaxNetBufferListInfo]; + +typedef struct _VMSWITCH_SOURCE_INFO { + unsigned long SourcePortId; + char* SourcePortName; + char* SourceNicName; + char* SourceNicType; +} VMSWITCH_SOURCE_INFO, *PVMSWITCH_SOURCE_INFO; + +typedef struct _VMSWITCH_PACKET_FRAGMENT { + unsigned long SourcePortId; + unsigned long DestinationCount; + short VlanId; +} VMSWITCH_PACKET_FRAGMENT, *PVMSWITCH_PACKET_FRAGMENT; + +BOOLEAN CurrentPacketIsVMSwitchPacketFragment = FALSE; +VMSWITCH_PACKET_FRAGMENT VMSwitchPacketFragment; + +struct INTERFACE { + struct INTERFACE* Next; + unsigned long LowerIfIndex; + unsigned long MiniportIfIndex; + unsigned long PcapNgIfIndex; + int PktEncapType; + short VlanId; + + BOOLEAN IsVMNic; + VMSWITCH_SOURCE_INFO VMNic; +}; + +#define IFACE_HT_SIZE 100 +struct INTERFACE* InterfaceHashTable[IFACE_HT_SIZE] = {0}; +unsigned long NumInterfaces = 0; + +void wtap_etl_rec_dump(char* etl_record, ULONG total_packet_length, ULONG original_packet_length, unsigned int interface_id, BOOLEAN is_inbound, ULARGE_INTEGER timestamp, int pkt_encap, char* comment, unsigned short comment_length); +void wtap_etl_add_interface(int pkt_encap, char* interface_name, unsigned short interface_name_length, char* interface_desc, unsigned short interface_desc_length); + +extern char g_err_info[FILENAME_MAX]; +extern int g_err; + +unsigned long HashInterface(unsigned long LowerIfIndex) +{ + if (CurrentPacketIsVMSwitchPacketFragment) { + return VMSwitchPacketFragment.SourcePortId * (VMSwitchPacketFragment.VlanId + 1); + } else { + return LowerIfIndex; + } +} + +struct INTERFACE* GetInterface(unsigned long LowerIfIndex) +{ + struct INTERFACE* Iface = InterfaceHashTable[HashInterface(LowerIfIndex) % IFACE_HT_SIZE]; + while (Iface != NULL) { + if (CurrentPacketIsVMSwitchPacketFragment) { + if (Iface->IsVMNic && + Iface->LowerIfIndex == LowerIfIndex && + Iface->VlanId == VMSwitchPacketFragment.VlanId && + Iface->VMNic.SourcePortId == VMSwitchPacketFragment.SourcePortId) { + return Iface; + } + } else { + if (!Iface->IsVMNic && Iface->LowerIfIndex == LowerIfIndex && Iface->VlanId == 0) { + return Iface; + } + } + Iface = Iface->Next; + } + return NULL; +} + +struct INTERFACE* AddInterface(PEVENT_RECORD ev, unsigned long LowerIfIndex, unsigned long MiniportIfIndex, int Type) +{ + struct INTERFACE** Iface = &InterfaceHashTable[HashInterface(LowerIfIndex) % IFACE_HT_SIZE]; + struct INTERFACE* NewIface = malloc(sizeof(struct INTERFACE)); + +#define IF_STRING_MAX_SIZE 128 + char IfName[IF_STRING_MAX_SIZE]; + size_t IfNameLength = 0; + char IfDesc[IF_STRING_MAX_SIZE]; + size_t IfDescLength = 0; + //etw pcagng interface will be 0 always, network pcagng interface will start with 1 + static PcapNgIfIndex = 1; + + if (NewIface == NULL) { + g_err = ERROR_OUTOFMEMORY; + sprintf_s(g_err_info, sizeof(g_err_info), "malloc failed to allocate memory for NewIface"); + exit(1); + } + + NewIface->LowerIfIndex = LowerIfIndex; + NewIface->MiniportIfIndex = MiniportIfIndex; + NewIface->PktEncapType = Type; + NewIface->VlanId = 0; + NewIface->IsVMNic = FALSE; + + if (CurrentPacketIsVMSwitchPacketFragment) { + + 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) { + g_err = ERROR_OUTOFMEMORY; + sprintf_s(g_err_info, sizeof(g_err_info), "malloc failed to allocate memory for NewIface->VMNic.SourceNicName"); + exit(1); + } + 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, + NewIface->VMNic.SourceNicName, + ParamNameSize / sizeof(wchar_t) + 1, + NULL, + NULL); + NewIface->VMNic.SourceNicName[wcslen(Buffer)] = '\0'; + + // SourcePortName + Desc.PropertyName = (unsigned long long)(L"SourcePortName"); + Desc.ArrayIndex = ULONG_MAX; + (void)TdhGetPropertySize(ev, 0, NULL, 1, &Desc, &ParamNameSize); + NewIface->VMNic.SourcePortName = malloc((ParamNameSize / sizeof(wchar_t)) + 1); + if (NewIface->VMNic.SourcePortName == NULL) { + g_err = ERROR_OUTOFMEMORY; + sprintf_s(g_err_info, sizeof(g_err_info), "malloc failed to allocate memory for NewIface->VMNic.SourcePortName"); + exit(1); + } + 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, + NewIface->VMNic.SourcePortName, + ParamNameSize / sizeof(wchar_t) + 1, + NULL, + NULL); + NewIface->VMNic.SourcePortName[wcslen(Buffer)] = '\0'; + + // SourceNicType + Desc.PropertyName = (unsigned long long)(L"SourceNicType"); + Desc.ArrayIndex = ULONG_MAX; + (void)TdhGetPropertySize(ev, 0, NULL, 1, &Desc, &ParamNameSize); + NewIface->VMNic.SourceNicType = malloc((ParamNameSize / sizeof(wchar_t)) + 1); + if (NewIface->VMNic.SourceNicType == NULL) { + g_err = ERROR_OUTOFMEMORY; + sprintf_s(g_err_info, sizeof(g_err_info), "malloc failed to allocate memory for NewIface->VMNic.SourceNicType"); + exit(1); + } + 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, + NewIface->VMNic.SourceNicType, + ParamNameSize / sizeof(wchar_t) + 1, + NULL, + NULL); + NewIface->VMNic.SourceNicType[wcslen(Buffer)] = '\0'; + + + NewIface->VMNic.SourcePortId = VMSwitchPacketFragment.SourcePortId; + NewIface->VlanId = VMSwitchPacketFragment.VlanId; + } + + NewIface->Next = *Iface; + + *Iface = NewIface; + NumInterfaces++; + + NewIface->PcapNgIfIndex = PcapNgIfIndex; + PcapNgIfIndex++; + memset(IfName, 0, sizeof(IfName)); + memset(IfDesc, 0, sizeof(IfDesc)); + switch (NewIface->PktEncapType) { + case WTAP_ENCAP_ETHERNET: + if (NewIface->IsVMNic) { + printf("IF: medium=%s\tID=%u\tIfIndex=%u\tVlanID=%i", + NewIface->VMNic.SourceNicType, + NewIface->PcapNgIfIndex, + NewIface->VMNic.SourcePortId, + NewIface->VlanId + ); + StringCchPrintfA( + IfName, + IF_STRING_MAX_SIZE, + "%s:%s:%lu:%i", + NewIface->VMNic.SourcePortName, + NewIface->VMNic.SourceNicType, + NewIface->VMNic.SourcePortId, + NewIface->VlanId + ); + } + else { + printf("IF: medium=eth\tID=%u\tIfIndex=%u\tVlanID=%i", NewIface->PcapNgIfIndex, NewIface->LowerIfIndex, NewIface->VlanId); + StringCchPrintfA(IfName, IF_STRING_MAX_SIZE, "eth:%lu:%i", NewIface->LowerIfIndex, NewIface->VlanId); + } + break; + case WTAP_ENCAP_IEEE_802_11: + printf("IF: medium=wifi ID=%u\tIfIndex=%u", NewIface->PcapNgIfIndex, NewIface->LowerIfIndex); + StringCchPrintfA(IfName, IF_STRING_MAX_SIZE, "wifi:%lu", NewIface->LowerIfIndex); + break; + case WTAP_ENCAP_RAW_IP: + printf("IF: medium=mbb ID=%u\tIfIndex=%u", NewIface->PcapNgIfIndex, NewIface->LowerIfIndex); + StringCchPrintfA(IfName, IF_STRING_MAX_SIZE, "mbb:%lu", NewIface->LowerIfIndex); + break; + } + StringCchLengthA(IfName, IF_STRING_MAX_SIZE, &IfNameLength); + + if (NewIface->LowerIfIndex != NewIface->MiniportIfIndex) { + printf("\t(LWF over IfIndex %u)", NewIface->MiniportIfIndex); + StringCchPrintfA(IfDesc, IF_STRING_MAX_SIZE, "LWF over IfIndex %lu", NewIface->MiniportIfIndex); + StringCchLengthA(IfDesc, IF_STRING_MAX_SIZE, &IfDescLength); + } + + if (NewIface->VlanId != 0) { + StringCchPrintfA(IfDesc + IfDescLength, IF_STRING_MAX_SIZE, " VlanID=%i ", NewIface->VlanId); + StringCchLengthA(IfDesc, IF_STRING_MAX_SIZE, &IfDescLength); + } + + printf("\n"); + + wtap_etl_add_interface(NewIface->PktEncapType, IfName, (unsigned short)IfNameLength, IfDesc, (unsigned short)IfDescLength); + return NewIface; +} + +void ParseVmSwitchPacketFragment(PEVENT_RECORD ev) +{ + // Parse the current VMSwitch 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. + + PROPERTY_DATA_DESCRIPTOR Desc; + int Err; + PNDIS_NET_BUFFER_LIST_8021Q_INFO pNblVlanInfo; + + // Get VLAN from OOB + unsigned long OobLength; + Desc.PropertyName = (unsigned long long)L"OOBDataSize"; + Desc.ArrayIndex = ULONG_MAX; + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(OobLength), (PBYTE)&OobLength); + if (Err != NO_ERROR) { + g_err = Err; + sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty OobLength failed, err is 0x%x", Err); + return; + } + + if (OobLength > sizeof(OobData)) { + g_err = ERROR_INVALID_DATA; + sprintf_s(g_err_info, sizeof(g_err_info), "OOB data of %lu bytes too large to fit in hardcoded buffer of size %lu", OobLength, (unsigned long)sizeof(OobData)); + return; + } + + Desc.PropertyName = (unsigned long long)L"OOBData"; + Desc.ArrayIndex = ULONG_MAX; + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, OobLength, (PBYTE)&OobData); + if (Err != NO_ERROR) { + g_err = Err; + sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty OobData failed, err is 0x%x", Err); + return; + } + + pNblVlanInfo = (PNDIS_NET_BUFFER_LIST_8021Q_INFO)&OobData[Ieee8021QNetBufferListInfo]; + VMSwitchPacketFragment.VlanId = pNblVlanInfo->TagHeader.VlanId; + + // SourcePortId + Desc.PropertyName = (unsigned long long)L"SourcePortId"; + Desc.ArrayIndex = ULONG_MAX; + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(VMSwitchPacketFragment.SourcePortId), (PBYTE)&VMSwitchPacketFragment.SourcePortId); + if (Err != NO_ERROR) { + g_err = Err; + sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty SourcePortId failed, err is 0x%x", Err); + return; + } + + // DestinationCount + Desc.PropertyName = (unsigned long long)L"DestinationCount"; + Desc.ArrayIndex = ULONG_MAX; + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(VMSwitchPacketFragment.DestinationCount), (PBYTE)&VMSwitchPacketFragment.DestinationCount); + if (Err != NO_ERROR) { + g_err = Err; + sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty DestinationCount failed, err is 0x%x", Err); + return; + } +} + +void etw_dump_write_ndiscap_event(PEVENT_RECORD ev, ULARGE_INTEGER timestamp) +{ + int Err; + unsigned long LowerIfIndex; + + struct INTERFACE* Iface; + unsigned long FragLength; + PROPERTY_DATA_DESCRIPTOR Desc; + int Type; + unsigned long TotalFragmentLength; + unsigned long InferredOriginalFragmentLength = 0; + PETHERNET_HEADER EthHdr; + PIPV4_HEADER Ipv4Hdr; + PIPV6_HEADER Ipv6Hdr; + + if ((ev->EventHeader.EventDescriptor.Id != tidPacketFragment && + ev->EventHeader.EventDescriptor.Id != tidPacketMetadata && + ev->EventHeader.EventDescriptor.Id != tidVMSwitchPacketFragment)) { + return; + } + + CurrentPacketIsVMSwitchPacketFragment = (ev->EventHeader.EventDescriptor.Id == tidVMSwitchPacketFragment); + if (CurrentPacketIsVMSwitchPacketFragment) { + 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) { + g_err = Err; + sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty LowerIfIndex failed, err is 0x%x", Err); + return; + } + + Iface = GetInterface(LowerIfIndex); + + if (!!(ev->EventHeader.EventDescriptor.Keyword & KW_MEDIA_NATIVE_802_11)) { + Type = WTAP_ENCAP_IEEE_802_11; + } else if (!!(ev->EventHeader.EventDescriptor.Keyword & KW_MEDIA_WIRELESS_WAN)) { + Type = WTAP_ENCAP_RAW_IP; + } else { + Type = WTAP_ENCAP_ETHERNET; + } + + // 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) { + g_err = Err; + sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty MiniportIfIndex failed, err is 0x%x", Err); + return; + } + Iface = AddInterface( + ev, + LowerIfIndex, + MiniportIfIndex, + Type + ); + } else if (Iface->PktEncapType != Type) { + printf("WARNING: inconsistent media type in packet events!\n"); + } + + if (Iface == NULL) { + // We generated the list of interfaces directly from the + // packet traces themselves, so there must be a bug. + g_err = ERROR_INVALID_DATA; + sprintf_s(g_err_info, sizeof(g_err_info), "Packet with unrecognized IfIndex"); + exit(1); + } + + // Save off Ndis/Wlan metadata to be added to the next packet + if (ev->EventHeader.EventDescriptor.Id == tidPacketMetadata) { + unsigned long MetadataLength = 0; + Desc.PropertyName = (unsigned long long)L"MetadataSize"; + Desc.ArrayIndex = ULONG_MAX; + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(MetadataLength), (PBYTE)&MetadataLength); + if (Err != NO_ERROR) { + g_err = Err; + sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty MetadataSize failed, err is 0x%x", Err); + return; + } + + if (MetadataLength != sizeof(PacketMetadata)) { + g_err = ERROR_INVALID_DATA; + sprintf_s(g_err_info, sizeof(g_err_info), "Unknown Metadata length. Expected %lu, got %lu", (unsigned long)sizeof(DOT11_EXTSTA_RECV_CONTEXT), MetadataLength); + return; + } + + Desc.PropertyName = (unsigned long long)L"Metadata"; + Desc.ArrayIndex = ULONG_MAX; + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, MetadataLength, (PBYTE)&PacketMetadata); + if (Err != NO_ERROR) { + g_err = Err; + sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty Metadata failed, err is 0x%x", Err); + return; + } + + AddWlanMetadata = TRUE; + return; + } + + // N.B.: Here we are querying the FragmentSize property to get the + // total size of the packet, and then reading that many bytes from + // the Fragment property. This is unorthodox (normally you are + // supposed to use TdhGetPropertySize to get the size of a property) + // but required due to the way ndiscap puts packet contents in + // multiple adjacent properties (which happen to be contiguous in + // memory). + + Desc.PropertyName = (unsigned long long)L"FragmentSize"; + Desc.ArrayIndex = ULONG_MAX; + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(FragLength), (PBYTE)&FragLength); + if (Err != NO_ERROR) { + g_err = Err; + sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty FragmentSize failed, err is 0x%x", Err); + return; + } + + if (FragLength > RTL_NUMBER_OF(AuxFragBuf) - AuxFragBufOffset) { + g_err = ERROR_INVALID_DATA; + sprintf_s(g_err_info, sizeof(g_err_info), "Packet too large (size = %u) and skipped", AuxFragBufOffset + FragLength); + return; + } + + Desc.PropertyName = (unsigned long long)L"Fragment"; + Desc.ArrayIndex = ULONG_MAX; + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, FragLength, (PBYTE)(AuxFragBuf + AuxFragBufOffset)); + if (Err != NO_ERROR) { + g_err = Err; + sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty Fragment failed, err is 0x%x", Err); + return; + } + + // The KW_PACKET_START and KW_PACKET_END keywords are used as follows: + // -A single-event packet has both KW_PACKET_START and KW_PACKET_END. + // -A multi-event packet consists of an event with KW_PACKET_START followed + // by an event with KW_PACKET_END, with zero or more events with neither + // keyword in between. + // + // So, we accumulate fragments in AuxFragBuf until KW_PACKET_END is + // encountered, then call PcapNgWriteEnhancedPacket and start over. There's + // no need for us to even look for KW_PACKET_START. + // + // NB: Starting with Windows 8.1, only single-event packets are traced. + // This logic is here to support packet captures from older systems. + + if (!!(ev->EventHeader.EventDescriptor.Keyword & KW_PACKET_END)) { + + if (ev->EventHeader.EventDescriptor.Keyword & KW_MEDIA_NATIVE_802_11 && + AuxFragBuf[1] & 0x40) { + // Clear Protected bit in the case of 802.11 + // Ndis captures will be decrypted in the etl file + + AuxFragBuf[1] = AuxFragBuf[1] & 0xBF; // _1011_1111_ - Clear "Protected Flag" + } + + // COMMENT_MAX_SIZE must be multiple of 4 + #define COMMENT_MAX_SIZE 256 + char Comment[COMMENT_MAX_SIZE] = { 0 }; + size_t CommentLength = 0; + + if (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", + ev->EventHeader.ProcessId, + PacketMetadata.uReceiveFlags, + DOT11_PHY_TYPE_NAMES[PacketMetadata.uPhyId], + PacketMetadata.uChCenterFrequency, + PacketMetadata.usNumberOfMPDUsReceived, + PacketMetadata.lRSSI, + PacketMetadata.ucDataRate); + + AddWlanMetadata = FALSE; + memset(&PacketMetadata, 0, sizeof(DOT11_EXTSTA_RECV_CONTEXT)); + } else if (CurrentPacketIsVMSwitchPacketFragment) { + if (VMSwitchPacketFragment.DestinationCount > 0) { + Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d VlanId=%d SrcPortId=%d SrcNicType=%s SrcNicName=%s SrcPortName=%s DstNicCount=%d", + ev->EventHeader.ProcessId, + Iface->VlanId, + Iface->VMNic.SourcePortId, + Iface->VMNic.SourceNicType, + Iface->VMNic.SourceNicName, + Iface->VMNic.SourcePortName, + VMSwitchPacketFragment.DestinationCount + ); + } else { + Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d VlanId=%d SrcPortId=%d SrcNicType=%s SrcNicName=%s SrcPortName=%s", + ev->EventHeader.ProcessId, + Iface->VlanId, + Iface->VMNic.SourcePortId, + Iface->VMNic.SourceNicType, + Iface->VMNic.SourceNicName, + Iface->VMNic.SourcePortName + ); + } + } else { + Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d", ev->EventHeader.ProcessId); + } + + if (Err != NO_ERROR) { + printf("Failed converting comment to string with error: %u\n", Err); + } else { + Err = StringCchLengthA(Comment, COMMENT_MAX_SIZE, &CommentLength); + + if (Err != NO_ERROR) { + printf("Failed getting length of comment string with error: %u\n", Err); + CommentLength = 0; + memset(Comment, 0, COMMENT_MAX_SIZE); + } + } + + TotalFragmentLength = AuxFragBufOffset + FragLength; + + // Parse the packet to see if it's truncated. If so, try to recover the original length. + if (Type == WTAP_ENCAP_ETHERNET) { + if (TotalFragmentLength >= sizeof(ETHERNET_HEADER)) { + EthHdr = (PETHERNET_HEADER)AuxFragBuf; + if (ntohs(EthHdr->Type) == ETHERNET_TYPE_IPV4 && + TotalFragmentLength >= sizeof(IPV4_HEADER) + sizeof(ETHERNET_HEADER)) { + 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)) { + Ipv6Hdr = (PIPV6_HEADER)(EthHdr + 1); + InferredOriginalFragmentLength = ntohs(Ipv6Hdr->PayloadLength) + sizeof(IPV6_HEADER) + sizeof(ETHERNET_HEADER); + } + } + } else if (Type == WTAP_ENCAP_RAW_IP) { + // Raw frames begins with an IPv4/6 header. + if (TotalFragmentLength >= sizeof(IPV4_HEADER)) { + Ipv4Hdr = (PIPV4_HEADER)AuxFragBuf; + if (Ipv4Hdr->Version == 4) { + InferredOriginalFragmentLength = ntohs(Ipv4Hdr->TotalLength) + sizeof(ETHERNET_HEADER); + } else if (Ipv4Hdr->Version == 6) { + Ipv6Hdr = (PIPV6_HEADER)(AuxFragBuf); + InferredOriginalFragmentLength = ntohs(Ipv6Hdr->PayloadLength) + sizeof(IPV6_HEADER) + sizeof(ETHERNET_HEADER); + } + } + } + + wtap_etl_rec_dump(AuxFragBuf, + TotalFragmentLength, + // For LSO v2 packets, inferred original fragment length is ignored since length field in IP header is not filled. + InferredOriginalFragmentLength <= TotalFragmentLength ? TotalFragmentLength : InferredOriginalFragmentLength, + Iface->PcapNgIfIndex, + !(ev->EventHeader.EventDescriptor.Keyword & KW_SEND), + timestamp, + Type, + Comment, + (unsigned short)CommentLength + ); + + AuxFragBufOffset = 0; + NumFramesConverted++; + } else { + AuxFragBufOffset += FragLength; + } +} + + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */