Wireshark: Packet Capture Analysis for Beginners

Wireshark is the standard tool for capturing and inspecting network traffic at the packet level. In this guide we'll cover the things that trip up beginners most often: the very different syntaxes of capture filters versus display filters, walking through a TCP three-way handshake, reading ARP and DNS exchanges, and a handful of defensive use cases. Everything below works on Wireshark 4.x.

Capture safely. Packet captures often contain credentials, session cookies, personal data, and the contents of plaintext requests. Treat .pcap/.pcapng files as sensitive. Only capture on networks you own or are explicitly authorised to monitor.

Setup and First Capture

Install Wireshark from wireshark.org (Windows/macOS) or from your package manager (e.g. sudo apt-get install wireshark on Debian/Ubuntu). During installation on Linux, accept the prompt to let non-root members of the wireshark group capture; otherwise you'll need sudo every time.

Linux: let your user capture without sudo

# Add yourself to the wireshark group (you must log out / back in to pick this up).
sudo usermod -aG wireshark "$USER"

# Verify your interface list is populated as your normal user.
dumpcap -D

Start Wireshark, double-click an interface (usually eth0, wlan0, or Wi-Fi), and you'll be staring at live traffic. Stop the capture with the red square in the toolbar.

Capture Filters vs Display Filters — Different Languages

This is the #1 thing that confuses people. Wireshark has two filter syntaxes that do similar-looking things, but they are not interchangeable.

  • Capture filters use BPF (Berkeley Packet Filter) syntax. They decide which packets are captured to disk in the first place. They are evaluated by libpcap before Wireshark sees the packet.
  • Display filters use Wireshark's own expression language. They decide which already-captured packets are shown in the list pane. They can use any decoded field name (e.g. http.host, tls.handshake.type) which BPF cannot.

Quick reference:

                     Capture filter (BPF)            Display filter (Wireshark)
                     -------------------             --------------------------
Host                 host 10.0.0.5                   ip.addr == 10.0.0.5
Either direction     src or dst host 10.0.0.5        ip.src == 10.0.0.5 || ip.dst == 10.0.0.5
TCP port             tcp port 443                    tcp.port == 443
UDP only             udp                             udp
Not ARP              not arp                         !arp
HTTP requests        (n/a — no field knowledge)      http.request
DNS                  port 53                         dns
Specific subnet      net 10.0.0.0/24                 ip.addr == 10.0.0.0/24
Combine              tcp and port 443                tcp.port == 443 && ip.src == 10.0.0.5

Rule of thumb: use capture filters when you are capturing on a busy interface and don't want to drown in unrelated packets; use display filters for everything else. Once a packet is captured, you can change display filters as many times as you like without re-capturing.

Examples worth remembering

# Capture filter examples (set in Capture > Options > Capture filter for selected interface)
host 192.168.1.50
tcp port 80 or tcp port 443
not (broadcast or multicast)
ether host aa:bb:cc:dd:ee:ff

# Display filter examples (type into the green bar above the packet list)
ip.addr == 192.168.1.50
tcp.flags.syn == 1 and tcp.flags.ack == 0       # SYN-only packets
http.request.method == "POST"
dns.flags.response == 0                          # DNS queries (not responses)
tls.handshake.type == 1                          # ClientHello
tcp.analysis.retransmission                      # Wireshark's own anomaly detector

Reading the TCP Three-Way Handshake

TCP opens a connection with a three-packet exchange. Filter for one interesting connection (e.g. tcp.port == 443 && ip.host == example.com) and look at the first three packets. The handshake is the single most important thing to be able to read in Wireshark.

Client (C)                                              Server (S)

  ── SYN          seq=X         ───────────────────▶
                  win=64240,
                  options: MSS, SACK, WS, ...

  ◀────────────── SYN, ACK      seq=Y, ack=X+1     ──
                                win=65535,
                                options: MSS, SACK, WS

  ── ACK          seq=X+1, ack=Y+1 ──────────────────▶

  (connection is now ESTABLISHED, data can flow)

What to notice in Wireshark's packet detail pane:

  • In the first packet, expand "Transmission Control Protocol" → "Flags" — only the SYN bit is set, and the Acknowledgment number is 0 / undefined.
  • In the second packet, both SYN and ACK are set. The server's initial sequence is Y; the ack number is X + 1 (acknowledging the client's SYN consumes one sequence number, even though no real payload byte was sent).
  • By default Wireshark shows relative sequence numbers (so the first SYN shows seq=0). To see the real absolute numbers, untick Edit → Preferences → Protocols → TCP → Relative sequence numbers.
  • The third packet is the client's ACK. Wireshark labels it as a "TCP ACK" — at this point the connection is fully established.

If you ever see SYN, then SYN, then SYN again with no SYN+ACK in between, the destination port is closed or filtered — that's the same pattern a stealth scan produces.

ARP — Who Has What MAC?

ARP resolves an IPv4 address to a MAC address on the local segment. The exchange is two packets: broadcast request and unicast reply.

Display filter to isolate ARP

arp
Request  (Broadcast)  : "Who has 192.168.1.1? Tell 192.168.1.50"
                        eth.dst = ff:ff:ff:ff:ff:ff   eth.src = (sender)
                        arp.opcode = 1 (request)

Reply    (Unicast)    : "192.168.1.1 is at aa:bb:cc:dd:ee:01"
                        eth.dst = (original requester)
                        arp.opcode = 2 (reply)

Defensive note: if Wireshark reports "duplicate use of IP address detected" in the Expert Info pane, that means two different MACs are claiming the same IP. That is the signature of ARP spoofing (a man-in-the-middle attempt) or simply two devices misconfigured with the same static IP.

DNS — Query and Response

DNS over UDP/53 is one of the easiest protocols to read in Wireshark.

Display filter for a domain you care about

dns and dns.qry.name contains "example.com"

You'll see two packets per lookup (one query, one response). Expand the Domain Name System tree in the detail pane:

  • Transaction ID matches request to response.
  • Flags: bit 0 of the second byte (QR) is 0 for query, 1 for response.
  • Questions section shows the QNAME and QTYPE (A, AAAA, MX, TXT, ...).
  • Answers section appears in the response with the resolved records and their TTLs.

Defensive note: long, high-entropy, random-looking subdomain queries (x7p9d2.tunnel.evil.example) that occur repeatedly are a hallmark of DNS tunneling — exfiltration over DNS. Sort by dns.qry.name length, or filter on it, when hunting.

HTTP and Follow TCP Stream

Plain HTTP is rare on the public internet now but still very common on internal services, IoT devices, and management ports.

Common HTTP filters

http                                       # all HTTP traffic
http.request                               # only requests
http.request.method == "POST"              # just POSTs
http.response.code >= 400                  # errors
http.host == "internal.example.com"        # by Host header

Right-click any HTTP packet → Follow → HTTP Stream to see the full request/response as plaintext. For TCP-level reconstruction, right-click → Follow → TCP Stream; for TLS, Follow → TLS Stream shows the cleartext if you have the keys (next section).

A Note on TLS

Wireshark can decrypt TLS in two situations:

  • Pre-master keys (the modern, recommended approach): set SSLKEYLOGFILE in the environment of the client process (e.g. Chrome, Firefox, curl) before it starts. Then point Wireshark at the resulting file in Edit → Preferences → Protocols → TLS → (Pre)-Master-Secret log filename. This works for TLS 1.3.
  • Private key (legacy): only works for TLS 1.2 with an RSA key exchange (not ECDHE) — which is rare today. Most modern handshakes use ephemeral key exchange and cannot be decrypted from the server key alone, which is the entire point of forward secrecy.

Statistics: Conversations, Endpoints, IO Graph

The Statistics menu is the most under-used part of Wireshark.

  • Statistics → Conversations: every pair of hosts that exchanged packets, sortable by byte/packet count. Spot "top talkers" instantly.
  • Statistics → Endpoints: individual hosts (IPv4, IPv6, TCP, UDP tabs). Right-click a row → Apply as Filter to focus on it.
  • Statistics → I/O Graphs: traffic over time. Add multiple display-filter-based graphs (e.g. one for tcp.analysis.retransmission, one for dns) to correlate visually.
  • Statistics → Protocol Hierarchy: what percentage of your capture is each protocol. Quick way to spot something unusual on the wire.

Exporting Objects

File → Export Objects → HTTP lists every HTTP-served file in the capture and lets you save them. Similar entries exist for SMB, FTP-DATA, and others. Useful in incident response when you want to recover a payload an attacker dropped over a plaintext channel.

Coloring Rules

View → Coloring Rules. The default ruleset already highlights TCP retransmissions, errors, and bad checksums. You can add your own — for example, paint any packet matching dns.qry.name matches "[a-z0-9]{20,}" bright red to catch suspicious DNS at a glance.

tshark — Wireshark in the Terminal

tshark is Wireshark's CLI. Use it when you need to capture on a remote/headless box, or when you want to script analysis.

Common tshark recipes

# List interfaces
tshark -D

# Capture on eth0 for 30s, write to file
sudo tshark -i eth0 -a duration:30 -w /tmp/capture.pcapng

# Capture only DNS, print summary lines
sudo tshark -i eth0 -f "port 53" -Y "dns"

# Read a file with a display filter and print specific fields
tshark -r /tmp/capture.pcapng \
       -Y "http.request" \
       -T fields -e ip.src -e http.host -e http.request.uri

# Top 10 DNS query names by frequency
tshark -r /tmp/capture.pcapng -Y "dns.flags.response == 0" \
       -T fields -e dns.qry.name | sort | uniq -c | sort -rn | head -10

Note the -f flag is a capture filter (BPF) and -Y is a display filter — same distinction as the GUI.

Defensive Use Cases

To finish, a handful of small investigations you can run against any capture:

  • Suspect ARP spoofing? Use the Expert Info pane (Analyze → Expert Information) and look for "duplicate IP address detected" entries. Compare ARP replies to your DHCP lease records.
  • Suspect data exfil over DNS? Filter dns.qry.name and look at the length distribution. Healthy traffic is a long tail of short names; tunneling produces a flat distribution of long random-looking labels.
  • Suspect port-scan? Filter tcp.flags.syn == 1 and tcp.flags.ack == 0 and group by destination port via Statistics → Conversations. A single source talking to many destination ports in seconds is a giveaway.
  • Suspect slow application? tcp.analysis.retransmission || tcp.analysis.duplicate_ack || tcp.analysis.zero_window isolates almost every "the network feels slow" cause.
  • Suspect plaintext credentials? Wireshark's Statistics → Protocol Hierarchy + Follow → TCP Stream on any FTP, Telnet, HTTP basic-auth, or non-TLS SMTP/IMAP flow will show them in seconds. Disable those protocols where you find them.

Next Steps

Once you're comfortable with the above, look into:

  • Display filter macros (Edit → Preferences → Macros) for filters you use constantly.
  • Lua dissectors for custom or undocumented protocols you encounter on internal networks.
  • Zeek (formerly Bro) for protocol-level logging at scale — Wireshark is great for single captures; Zeek is what you reach for when you need to process a continuous network feed.

And remember: the most powerful thing Wireshark gives you is ground truth. When two systems disagree about who said what to whom, the wire doesn't lie.