Back to CTFs

Forensics — Chronos

Introduction

Going by the title of the challenge, I suspect this will be related to data exfil of some sort.

Description
-----------
Would you rather posess the powers of Chronos, the God of Time or posess the powers of a bilingual?

Handout: chronos.pcap

Recon

When I first opened chronos.pcap, there was only SYN packets without much additional info. The overall shape was odd — three hundred packets spread across two and a half minutes. Unusually slow for a network activity. So I started looking for data in the packets.

tshark -r chronos.pcap -q -z io,stat,0
Duration: 155.8 secs
Frames:   312
Bytes:    12480

What's inside the packets?

I checked the protocol details to make sure I wasn't missing plaintext in the payload:

  • IPv4 TCP, source 10.10.10.5:4444192.168.1.20:80
  • TCP flags: SYN only, no payload data
  • TCP window size: 8192 on every packet (constant — not the flag channel)

312 identical-looking SYN packets with nothing to read inside. Dead end for data exfiltration.

Given the unusual time taken for the packets. I corelated with the challenge title and checked the per-packet timing:

tshark -r chronos.pcap -T fields \
  -e frame.number -e frame.time_relative -e frame.time_delta -e frame.len \
  | head -20
1    0.000000000                  40
2    0.750000000    0.750000000    40
3    1.500000000    0.750000000    40
4    1.750000000    0.250000000    40
5    2.000000000    0.250000000    40
...

Few information from this:

  1. Every packet is exactly 40 bytes. Same size, all the way through.
  2. Only two gap values between packets: 0.250 seconds and 0.750 seconds.
  3. The ratio is 1:3 — short gap, long gap. Dot and dash shape, maybe Morse?

Building the bitstream

I tried greedy Morse decode first — that produced gibberish like :XYQZXYQ4;=32)=... I considered the short interval packets as dots and longer interval packets as dashes; no proper decoding so not Morse. If not morse, then it has to be binary

Unique time deltas across the capture:

  • 0.250 s — 155 times → bit 0
  • 0.750 s — 156 times → bit 1

311 measurements total (packet 1 has no previous packet to compare against).

The alignment problem

The flag format boroCTF{...} — so 312/8 = 39 ASCII characters. But inter-packet deltas only give 311 bits (packets 2 through 312).

Decoding 311 bits at various offsets mostly gave garbage. One offset was almost readable but missing the leading b: oroCTF{c0mbobulat3_sp@gh3tti_nep0t1$m}.

Prepending a single 0 bit for packet 1. That gives exactly 312 bits and decodes cleanly.

Solve script

import subprocess
out = subprocess.check_output(
  ["tshark", "-r", "chronos.pcap", "-T", "fields", "-e", "frame.time_delta"],
  text=True,
)
deltas = [float(x) for x in out.splitlines() if x.strip()]
print(deltas)
print("count:", len(deltas)) # 311

bits = "0" + "".join(
  "0" if abs(d - 0.25) < 0.01 else "1"
  for d in deltas
)

flag = "".join(chr(int(bits[i : i + 8], 2)) for i in range(0, len(bits), 8))
print(flag)
#boroCTF{c0mbobulat3_sp@gh3tti_nep0t1$m}

FLAG: boroCTF{c0mbobulat3_sp@gh3tti_nep0t1$m}