← Back to Writeups
CUSTOMEasyForensics

Suspicious Traffic

XESXOR9/2/20263 min read
#forensics#custom#easy#pcap_traffic_analysis#anomaly_hunting#tshark_field_extraction#morse_code_decoding#linux

Suspicious Traffic

Platform: hackerlab | Category: Forensics | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-04-05 | Status: Solved Techniques: pcap_traffic_analysis, anomaly_hunting, tshark_field_extraction, morse_code_decoding

Summary

Task: analyze a PCAPNG capture with mostly normal QUIC/TLS/DNS traffic and identify the hidden exfiltration channel. Solution: isolate a tiny anomalous TCP stream, map payload bytes 00/20 to Morse dots and dashes, then decode the message to recover the flag.

Suspicious Traffic — HackerLab

Description

Подозрительный трафик

English summary: the archive contains a single task.pcapng capture. Most traffic looks like ordinary browsing from 192.168.138.129, but one very small TCP conversation hides a message that must be decoded to recover the flag.

Analysis

Before continuing, I followed the local knowledge-base workflow requirement: searched for similar covert-channel / PCAP exfiltration writeups and reviewed a related covert-channel PCAP writeup. The exact technique was different here, but the review helped confirm that small anomalous packet patterns are worth isolating early. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Initial reconnaissance showed a 4.5 MB PCAPNG capture with 5429 packets. Protocol statistics and endpoint/conversation summaries showed that most traffic was normal user activity: QUIC, TLS, and DNS generated by 192.168.138.129.

The important anomaly was a tiny one-way TCP conversation:

  • tcp.stream == 6
  • 192.168.1.99:12345 -> 192.168.1.1:80
  • exactly 42 packets
  • all packets had ACK flag only (tcp.flags == 0x0010)
  • all reused seq=1, ack=1, ttl=64, ip.id=0xabcd, window=28944
  • Wireshark/tshark marked them as suspected retransmissions ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

That pattern is not normal retransmission behavior for real application traffic. It strongly suggests a covert channel where header fields stay constant and only the minimal payload changes.

The payload lengths varied from 1 to 6 bytes, but each payload used only two byte values: 0x00 and 0x20. That ruled out normal ASCII and suggested symbolic encoding instead. The key insight was to treat:

  • each 00 byte as .
  • each 20 byte as -
  • each packet as one Morse character

Decoding the 42 payloads in order produced: ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

HEYBROIHAVESECRETINFOCODEBY(REDACTED)

The message uses parentheses because { and } are not standard Morse punctuation in the expected form here. Converting them to braces yields the required flag format.

Solution

1. Inspect the capture

Useful commands:

bash
file task.pcapng
capinfos task.pcapng
tshark -r task.pcapng -q -z io,phs
tshark -r task.pcapng -q -z endpoints,ip -z conv,udp -z conv,tcp

This showed that the capture was mostly routine browsing traffic and that only one tiny TCP stream looked suspicious. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

2. Isolate the abnormal stream

bash
tshark -r task.pcapng -Y "ip.addr == 192.168.1.99" -T fields -e ip.src -e ip.dst -e tcp.stream -e tcp.flags -e tcp.len
tshark -r task.pcapng -Y "tcp.stream == 6" -T fields -e frame.number -e tcp.len -e tcp.payload

The extracted tcp.payload values, in order, were:

text
00000000, 00, 20002020, 20000000, 002000, 202020, 0000, 00000000, 0020, 00000020, 00, 000000, 00, 20002000, 002000, 00, 20, 0000, 2000, 00002000, 202020, 20002000, 202020, 200000, 00, 20000000, 20002020, 2000202000, 20002020, 00, 0000000020, 2020, 2020202020, 002000, 000000, 0000002020, 0000000020, 202000, 0020, 0020202020, 2000, 200020200020

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

3. Decode the covert Morse channel

python
#!/usr/bin/env python3
import subprocess

MORSE = {
    '.-': 'A', '-...': 'B', '-.-.': 'C', '-..': 'D', '.': 'E', '..-.': 'F',
    '--.': 'G', '....': 'H', '..': 'I', '.---': 'J', '-.-': 'K', '.-..': 'L',
    '--': 'M', '-.': 'N', '---': 'O', '.--.': 'P', '--.-': 'Q', '.-.': 'R',
    '...': 'S', '-': 'T', '..-': 'U', '...-': 'V', '.--': 'W', '-..-': 'X',
    '-.--': 'Y', '--..': 'Z', '-----': '0', '.----': '1', '..---': '2',
    '...--': '3', '....-': '4', '.....': '5', '-....': '6', '--...': '7',
    '---..': '8', '----.': '9', '-.--.': '(', '-.--.-': ')'
}
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

def packet_to_morse(hex_payload: str) -> str:
    data = bytes.fromhex(hex_payload)
    return ''.join('.' if b == 0x00 else '-' if b == 0x20 else '?' for b in data)

result = subprocess.run(
    [
        'tshark', '-r', 'task.pcapng', '-Y', 'tcp.stream == 6',
        '-T', 'fields', '-e', 'tcp.payload'
    ],
    capture_output=True, text=True, check=True
)

symbols = [line.strip() for line in result.stdout.splitlines() if line.strip()]
morse_chars = [packet_to_morse(s) for s in symbols]
decoded = ''.join(MORSE[m] for m in morse_chars)
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

print(decoded)
print(decoded.replace('(', '{').replace(')', '}'))

Output:

text
HEYBROIHAVESECRETINFOCODEBY(REDACTED)
HEYBROIHAVESECRETINFOCODEBY{REDACTED}

The human-readable text contains extra lead-in words, but the flag portion is clear. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍