WiresharkC

Wireshark: Eine Dissektion

Da ich aktuell arbeitsbedingt mich tiefgreifend mit Wireshark auseinandersetzen musste, möchte ich hier näher auf das Netzwerkanalyse Tool eingehen. Neben der Entwicklungsgeschichte bezieht sich der Artikel in einem zweiten Abschnitt auf interne Datenstrukturen und der EPAN Engine.

Wireshark ist ein beliebtes und leistungsfähiges Werkzeug zur Analyse von Netzwerkverkehr, das sowohl von Netzwerkadministratoren als auch von Sicherheitsexperten eingesetzt wird. Doch wie hat sich Wireshark im Laufe der Zeit entwickelt und wie funktioniert es intern? In diesem Blogartikel werden wir einen Blick auf die historische Entwicklung von Wireshark werfen, einen Überblick der Funktionsblöcke schaffen, dann uns genauer die EPAN ansehen und am Ende ein paar Programmierbeispiele zur Anwendung von Wiretap und EPAN aufzeigen.


Zur Entwicklung

Wireshark, ursprünglich bekannt als "Ethereal", ist ein leistungsstarkes Open-Source-Netzwerkanalyse-Tool, das seinen Ursprung im Jahr 1998 hat. Gerald Combs, damals ein Student an der University of Missouri-Columbia, entwickelte Ethereal als Software zur Netzwerkanalyse. Die Veröffentlichung von Ethereal als Open-Source-Projekt führte zur Entstehung einer engagierten Entwicklergemeinschaft.

Im Jahr 2006 wurde Ethereal in Wireshark umbenannt, da es aufgrund von Markenrechtsproblemen mit einem anderen Unternehmen zu Schwierigkeiten kam. Wireshark setzte seine Entwicklung als Open-Source-Projekt fort und erfuhr eine kontinuierliche Weiterentwicklung und Verbesserung. Die aktive Community von Wireshark trug zur Erweiterung der Protokollunterstützung und Entwicklung neuer Funktionen bei.

In den Jahren 2010 bis 2015 wurden weitere Fortschritte erzielt. Wireshark erhielt regelmäßige Aktualisierungen mit neuen Funktionen und Protokollunterstützung. Die Benutzeroberfläche wurde verbessert, um eine intuitivere Bedienung zu ermöglichen. Wireshark wurde auf verschiedenen Betriebssystemen wie Windows, macOS und Linux unterstützt und etablierte sich als eines der am häufigsten verwendeten Netzwerkanalysetools weltweit.

Seit 2015 hat Wireshark seine Funktionalität weiter verbessert und seine Protokollunterstützung erweitert, um den Anforderungen moderner Netzwerke gerecht zu werden. Die Software bietet leistungsstarke Filter- und Suchfunktionen, um den Analyseprozess zu erleichtern. Mit seiner Unterstützung für eine breite Palette von Netzwerkprotokollen und seinen umfangreichen Analysefunktionen ist Wireshark zu einem grundlegenden Werkzeug für Netzwerkadministratoren, Entwickler und Sicherheitsexperten geworden.


Übersicht

Wireshark besteht aus mehreren Funktionsblöcken, wie GUI, Core, Epan, Wiretap, Capture und Dumpcap. Die GUI ist die grafische Benutzeroberfläche, die es dem Benutzer ermöglicht, die Capture-Dateien zu öffnen, zu filtern und zu analysieren. Der Core ist der Hauptteil von Wireshark, der für die Verwaltung der Capture-Sitzungen, die Kommunikation mit den anderen Komponenten und die Bereitstellung von Diensten wie Menüs, Werkzeugleisten und Einstellungen zuständig ist.

image
Epan steht für Enhanced Packet ANalyzer und ist die Engine zur Analyse von Paketen. Es bietet verschiedene APIs, wie Protocol Tree, Dissectors, Dissector Plugins und Display Filters. Der Protocol Tree ist die Datenstruktur, die die hierarchische Darstellung der Paketinformationen enthält. Die Dissectors sind Funktionen, die für die Zerlegung der Pakete in ihre einzelnen Felder verantwortlich sind. Die Dissector Plugins sind Erweiterungen, die es ermöglichen, neue Protokolle oder Funktionen zu Wireshark hinzuzufügen. Die Display Filters sind Ausdrücke, die es dem Benutzer ermöglichen, nur die Pakete anzuzeigen, die bestimmten Kriterien entsprechen. DFilter ist die Anzeigefilter-Engine, die Teil von Epan ist. Sie ermöglicht es Benutzern, Pakete nach bestimmten Kriterien zu filtern. Sie hat ihre eigene Syntax und Funktionen, die in der Wireshark-Dokumentation beschrieben sind. Einige Beispiele für Anzeigefilter sind:

  • ip.addr == 192.168.1.1: zeigt nur Pakete an, die die IP-Adresse 192.168.1.1 enthalten
  • tcp.port == 80: zeigt nur Pakete an, die den TCP-Port 80 verwenden
  • http.request.method == GET: zeigt nur HTTP-Anfragen an, die die GET-Methode verwenden

Wiretap ist die Bibliothek, die zum Lesen und Schreiben von Capture-Dateien in verschiedenen Formaten verwendet wird. Sie ist auch für die Handhabung von Capture-Dateistrukturen wie capture_file verantwortlich. Eine capture_file enthält Informationen wie den Dateinamen, den Dateityp, den Dateizeiger, den Schnappschusslängenwert und den Link-Layer-Typ. Wiretap unterstützt viele gängige Capture-Formate wie pcap, pcapng, erf, snoop und netmon.

Capture und Dumpcap sind zwei separate Prozesse, die für das Erfassen von Netzwerkverkehr verantwortlich sind. Capture ist der Prozess, der vom Core gestartet wird und mit dem Betriebssystem interagiert, um auf das Netzwerkinterface zuzugreifen. Dumpcap ist der Prozess, der vom Capture gestartet wird und den Netzwerkverkehr in eine temporäre Datei schreibt. Diese Datei wird dann vom Wiretap gelesen und an den Core weitergeleitet.


EPAN

Die EPAN Bibliothek ist für die Analyse des Netzwerkverkehrs zuständig. Sie ermöglicht es, den Netzwerkverkehr in einzelne Pakete zu zerlegen und diese nach verschiedenen Kriterien zu klassifizieren. EPAN verwendet sogenannte Dissektoren, die jeweils für ein bestimmtes Protokoll oder eine bestimmte Schicht zuständig sind. Es gibt über 2000 Dissektoren in Wireshark, die ständig aktualisiert werden. EPAN bietet auch verschiedene Funktionen zur Filterung und Suche von Paketen sowie zur Berechnung von Statistiken.

Sie aus mehreren Komponenten, die zusammenarbeiten, um Netzwerkverkehr zu analysieren. Die wichtigsten Komponenten sind:

  • Der Capture-Engine: Dies ist die Komponente, die für die Erfassung von Datenpaketen von verschiedenen Quellen verantwortlich ist, wie z.B. Netzwerkkarten, Dateien oder Pipes. Die Capture-Engine verwendet Libpcap oder WinPcap als Schnittstelle zu den Erfassungsgeräten und bietet eine einheitliche API für Wireshark, um auf die erfassten Pakete zuzugreifen.
  • Der Dissector-Engine: Dies ist die Komponente, die für die Zerlegung der erfassten Pakete in ihre einzelnen Protokollschichten verantwortlich ist. Der Dissector-Engine verwendet eine Reihe von Modulen, die als Dissectors bezeichnet werden und die das Format und die Bedeutung jedes Protokolls kennen. Wireshark verfügt über Hunderte von Dissectors, die eine Vielzahl von Protokollen abdecken, von Ethernet bis HTTP. Die Dissectors können auch benutzerdefinierte Felder hinzufügen, um zusätzliche Informationen über die Pakete anzuzeigen.
  • Der Display-Engine: Dies ist die Komponente, die für die Anzeige der analysierten Pakete in einer grafischen Benutzeroberfläche oder einer Textkonsole verantwortlich ist. Der Display-Engine verwendet eine Reihe von Modulen, die als Taps bezeichnet werden und die bestimmte Aspekte des Netzwerkverkehrs überwachen oder aggregieren können. Zum Beispiel kann ein Tap die Anzahl der Pakete pro Protokoll anzeigen oder eine Statistik über die Round-Trip-Zeiten berechnen. Die Taps können auch benutzerdefinierte Spalten hinzufügen, um zusätzliche Informationen über die Pakete anzuzeigen.

Die öffentlichen APIs von EPAN ermöglichen es Entwicklern, auf diese Komponenten zuzugreifen und sie zu erweitern oder anzupassen. Zum Beispiel können Entwickler:

  • Eigene Dissectors schreiben, um neue oder proprietäre Protokolle zu unterstützen oder bestehende Dissectors zu modifizieren.
  • Eigene Taps schreiben, um neue oder spezifische Statistiken oder Visualisierungen des Netzwerkverkehrs zu erstellen oder bestehende Taps zu modifizieren.
  • Eigene Capture-Quellen hinzufügen, um Datenpakete aus anderen Quellen als Netzwerkkarten oder Dateien zu erfassen.
  • Eigene Display-Formate definieren, um die analysierten Pakete in einer anderen Form als der Standard-GUI oder der Textkonsole anzuzeigen.

Die öffentlichen APIs von EPAN sind in C geschrieben und folgen dem GNU Coding Standard. Sie sind in der Datei epan.h definiert, die sich im Verzeichnis epan des Wireshark-Quellcodes befindet. Die Dokumentation der APIs ist in der Datei doc/README.developer enthalten, die ebenfalls im Verzeichnis epan liegt. Die APIs sind in verschiedene Kategorien unterteilt, wie z.B. Capture-Funktionen, Dissector-Funktionen, Tap-Funktionen usw.

Um die APIs von EPAN zu verwenden, müssen Entwickler einige Schritte befolgen:

  • Den Wireshark-Quellcode herunterladen und kompilieren.
  • Eine Entwicklungsumgebung einrichten, die einen C-Compiler, einen Debugger und einen Editor enthält.
  • Eine Plugin-Schnittstelle erstellen, um das eigene Modul mit Wireshark zu verbinden. Dies kann entweder durch das Schreiben einer Makefile oder durch das Verwenden des Tools make-plugin-reg.py erfolgen, das sich im Verzeichnis tools des Wireshark-Quellcodes befindet.
  • Das eigene Modul schreiben, indem man die entsprechenden APIs von EPAN aufruft oder implementiert.
  • Das eigene Modul testen und debuggen, indem man Wireshark mit dem Plugin startet und den Netzwerkverkehr analysiert.

Einer der interessantesten Aspekte von Wireshark ist die Möglichkeit, eigene Dissektoren oder Erweiterungen zu schreiben, um den Netzwerkverkehr nach eigenen Bedürfnissen zu analysieren. Dazu bietet Wireshark zwei Möglichkeiten an: eine native und eine skriptbasierte. Die native Möglichkeit erfordert das Schreiben von C-Code, der mit dem Wireshark-Quellcode kompiliert werden muss. Die skriptbasierte Möglichkeit erlaubt das Schreiben von Lua-Code, der zur Laufzeit interpretiert wird. Beide Möglichkeiten erlauben es, auf die Wiretap- und EPAN-Bibliotheken zuzugreifen und eigene Dissektoren oder Erweiterungen zu registrieren.

Um ein Beispiel für die Anwendung von Wiretap und EPAN zu geben, werden wir einen einfachen Dissektor in C schreiben, der ein benutzerdefiniertes Protokoll analysiert, das aus einem 4-Byte-Header und einem variablen Payload besteht. Der Header besteht aus einem 2-Byte-Typfeld und einem 2-Byte-Längenfeld. Der Payload besteht aus einer beliebigen Anzahl von Bytes. Unser Dissektor soll den Header und den Payload jedes Pakets anzeigen und den Typ des Pakets interpretieren. Der Dissektor soll wie folgt aussehen:

// Include the necessary headers
#include <config.h>
#include <epan/packet.h>

// Define the protocol
#define PROTO_TAG_CUSTOM "CUSTOM"

// Define the type values
#define TYPE_HELLO 0
#define TYPE_GOODBYE 1
#define TYPE_DATA 2

// Define the type names
static const value_string type_names[] = { { TYPE_HELLO, "Hello" },
	                                       { TYPE_GOODBYE, "Goodbye" },
	                                       { TYPE_DATA, "Data" },
	                                       { 0, NULL } };

// Declare the protocol handle
static int proto_custom = -1;

// Declare the field handles
static int hf_custom_type = -1;
static int hf_custom_length = -1;
static int hf_custom_payload = -1;

// Declare the subtree handle
static gint ett_custom = -1;

// Register the protocol
void proto_register_custom(void) {
	// Define the fields
	static hf_register_info hf[] = {
		{ &hf_custom_type, { "Type", "custom.type", FT_UINT16, BASE_DEC, VALS(type_names), 0x0, NULL, HFILL } },
		{ &hf_custom_length, { "Length", "custom.length", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL } },
		{ &hf_custom_payload, { "Payload", "custom.payload", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }
	};

	// Define the subtree
	static gint *ett[] = { &ett_custom };

	// Register the protocol name and description
	proto_custom = proto_register_protocol("Custom Protocol", "Custom", "custom");

	// Register the fields and subtree
	proto_register_field_array(proto_custom, hf, array_length(hf));
	proto_register_subtree_array(ett, array_length(ett));
}

// Define the dissector function
static int dissect_custom(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, void *data _U_) {
	// Check the buffer length
	if (tvb_captured_length(tvb) < 4)
		return 0;

	// Set the protocol name
	col_set_str(pinfo->cinfo, COL_PROTOCOL, PROTO_TAG_CUSTOM);

	// Create a subtree for the protocol
	proto_item *ti = proto_tree_add_item(tree, proto_custom, tvb, 0, -1, ENC_NA);
	proto_tree *custom_tree = proto_item_add_subtree(ti, ett_custom);

	// Get the type and length fields
	guint16 type = tvb_get_ntohs(tvb, 0);
	guint16 length = tvb_get_ntohs(tvb, 2);

	// Add the type and length fields to the subtree
	proto_tree_add_item(custom_tree, hf_custom_type, tvb, 0, 2, ENC_BIG_ENDIAN);
	proto_tree_add_item(custom_tree, hf_custom_length, tvb, 2, 2, ENC_BIG_ENDIAN);

	// Check the payload length
	if (tvb_captured_length(tvb) < 4 + length)
		return 0;

	// Get the payload field
	tvbuff_t *payload_tvb = tvb_new_subset_length(tvb, 4, length);

	// Add the payload field to the subtree
	proto_tree_add_item(custom_tree, hf_custom_payload, payload_tvb, 0, length, ENC_NA);

	return tvb_captured_length(tvb);
}

// Register the dissector for a specific port (1234)
void proto_reg_handoff_custom(void) {
	static dissector_handle_t custom_handle;

	custom_handle = create_dissector_handle(dissect_custom, proto_custom);
	dissector_add_uint("tcp.port", 1234, custom_handle);
}