A Wireshark-inspired packet analyzer written from scratch in Python. Point it at a hex dump and it walks the bytes down the stack — Ethernet frame, then IPv4 header, then TCP segment, then HTTP payload — rendering each layer as readable fields, drawing a conversation flow graph, and exporting the whole analysis as a PDF.
Every protocol decoder is hand-written: no scapy, no dpkt. The point of the project was to read RFCs and slice bytes by hand, so the parsers work off raw offsets and bit masks.
You need Python 3.11+ with tkinter. Nothing else — the two runtime dependencies install themselves.
git clone https://github.com/Tinshea/WireOwl.git
cd WireOwlLinux / macOS
chmod +x run.sh
./run.shWindows (PowerShell)
.\run.ps1Or, if you have make:
make setup && make runAll three create a .venv, install the dependencies and launch the app. Press Start, pick any file from sample/, and you are in.
WireOwl reads hex dump text, not
.pcap. It needs no root privileges and never touches a network interface — it only ever parses a file you hand it. To analyse your own traffic, export it from Wireshark with File → Export Packet Dissections → As Plain Text, with "Hex dump" enabled.
Load a capture and every frame is summarised on one line: source and destination, ports, TCP flags, sequence and acknowledgement numbers, window size. Frames belonging to the same conversation share a colour, HTTP frames are picked out in magenta, and clicking one decodes it in the panel below — here the TCP layer of a SYN.
The five icons down the left switch which layer the detail panel shows: the raw frame, Ethernet, IPv4, TCP, or the reassembled HTTP payload.
Type an expression and the frame list narrows to matching frames. The field turns teal when the expression parses and red when it does not, so a typo is obvious. Filters are evaluated by loading the parsed frames into an in-memory SQLite schema and running the expression as a WHERE clause — below, 477 TCP frames pulled out of a 2.3 MB capture.
| Field | Matches on | Example |
|---|---|---|
protocol |
IP protocol number (see table below) | protocol == 6 |
ip.src / ip.addr |
IPv4 source / destination | ip.src == 192.168.1.102 |
tcp.src / tcp.addr |
TCP source / destination port | tcp.addr == 80 |
eth.src / eth.addr |
MAC source / destination | eth.src == f0189859ae32 |
http.version |
HTTP version string | http.version == 1.1 |
Operators are == and !=, combined with and / && / or / ||:
protocol == 6 and ip.src == 192.168.31.69
ip.src == 192.168.1.102 or ip.addr == 192.168.1.117
Two things to know about the syntax:
protocolholds the numeric IANA value, not a name —protocol == 6, notprotocol == TCP. The latter parses fine and simply matches nothing.- Compound expressions must stay within one field family. Each family lives in its own SQLite table (
protocol/ip.*together,tcp.*,eth.*,http.version), and the generated query targets the table named by the first term.protocol == 6 and ip.src == …works;protocol == 6 and tcp.addr == 80is rejected.
The flow graph lays hosts out as columns and frames as rows, drawing each exchange as an arrow annotated with its ports — the shape of a conversation at a glance.
One click writes a full report into PDF/: a linked table of contents, the flow graph, then one page per frame with every decoded field.
traitement() reads the dump, treating a line that starts at offset 0000 as the start of a new frame, and strips the offset column and ASCII gutter to leave one hex string per frame. Those strings then flow through four parsers, each consuming the header it understands and handing the remainder to the next:
hex text ──▶ trame.tramecalcul MAC addresses, EtherType
──▶ paquetip.paquetipv4 IPv4 header: TTL, flags, protocol, addresses
──▶ tcp.tcpaquet ports, sequence numbers, flags, window
──▶ httpmodule.req_or_rep request/response, method, status, headers
Each stage appends its human-readable output to Trames/TrameN.txt, and populates module-level dictionaries keyed by frame number (Source_Address, Port_Source, flagdic, …). Those dictionaries are what the GUI, the filter engine and the PDF exporter all read from — the parse happens once, then everything else is a lookup.
| Layer | What is decoded |
|---|---|
| Ethernet | Source and destination MAC, EtherType |
| IPv4 | Header length, TOS, total length, identification, DF/MF, fragment offset, TTL, protocol, header checksum, addresses |
| TCP | Ports, sequence and acknowledgement numbers, data offset, all six flags, window, checksum, urgent pointer, options |
| HTTP | Request vs response, method, version, status code, headers |
IP protocol numbers are resolved through dico_protocol: 1 ICMP, 2 IGMP, 6 TCP, 8 EGP, 9 IGP, 17 UDP, 36 XTP, 46 RSVP.
Only IPv4 is parsed. Frames carrying anything else — IPv6, ARP — are listed with their type and flagged n'est pas pris en charge par le programme rather than decoded. Likewise, only TCP gets a transport-layer decode; UDP frames are counted and filterable but not broken down.
sample/ doubles as the test suite — several files exist specifically to exercise error paths.
| File | Contents |
|---|---|
Trace.txt, TCP_1..3.txt |
Clean TCP handshakes — the best starting point |
HTTP_2.txt, HTTP_TCP.txt |
HTTP request/response over TCP |
ARP_UDP_ICMP_IGMP.txt |
63 mixed frames; good for exercising filters |
test-3f.txt |
2.3 MB, ~500 frames — the stress case |
HTTP_1.txt |
A single IPv6 frame, i.e. the unsupported-protocol path |
Corrupted.txt, Empty.txt, Text.txt |
Malformed input; should warn, not crash |
WireOwl/
├── run.sh / run.ps1 # set up the venv and launch
├── Makefile # setup / run / clean
├── core/
│ ├── main.py # entry point
│ ├── source.py # tkinter GUI, dump parsing, all screens
│ ├── trame.py # Ethernet layer
│ ├── paquetip.py # IPv4 layer
│ ├── tcp.py # TCP layer
│ ├── httpmodule.py # HTTP layer
│ ├── filtrage.py # SQLite-backed filter engine
│ └── pdf.py # report + flow graph export
├── affichage/ # icons, backgrounds, logo
├── sample/ # hex dump captures (see above)
├── Tools/requirements.txt
├── Trames/ # per-frame decode output (generated)
└── PDF/ # generated reports
main.py must run with the repository root as the working directory: source.py loads its images through relative paths like ./affichage/bg.png. The launch scripts handle this for you.
tkmacosxis optional. It exists only to give macOS a button widget that honoursbg;source.pyfalls back totkinter.Buttoneverywhere else, so it is installed on macOS only.- Linux needs tkinter installed separately on many distributions —
sudo apt install python3-tkon Debian/Ubuntu.run.shchecks and tells you. Trames/is wiped on every load. It holds the per-frame decode dumps and the filter database, all rebuilt from scratch each time you open a capture.- The UI and the decode output are in French, matching the coursework it was written for.
Sorbonne Université — Networks course.
No license specified.




