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.
This commit is contained in:
Odysseus Yang 2022-01-13 10:54:01 -08:00 committed by A Wireshark GitLab Utility
parent 0f5025eae4
commit 36e834b6b7
5 changed files with 761 additions and 21 deletions

View File

@ -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.

View File

@ -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.

View File

@ -292,6 +292,7 @@ if(BUILD_etwdump AND WIN32)
etwdump.c
etl.c
etw_message.c
etw_ndiscap.c
)
set_executable_resources(etwdump "etwdump")

View File

@ -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;

705
extcap/etw_ndiscap.c Normal file
View File

@ -0,0 +1,705 @@
/* etw_ndiscap.c
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* 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 <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <evntrace.h>
#include <evntcons.h>
#include <tdh.h>
#include <strsafe.h>
#include <winsock2.h>
#include <netiodef.h>
// 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:
*/