Sitemap

HTB Baby Frame Writeup

4 min readJul 7, 2026

--

Category: Satellite
Difficulty: Very Easy

Press enter or click to view image in full size

Challenge Description

A recently recovered experimental spacecraft broadcasting under spacecraft ID 12 has entered visibility range. Ground telemetry suggests that one onboard diagnostic application remains active on APID 42 over virtual channel 3. Mission operators believe the service is waiting for a single correctly formatted CCSDS space packet containing the user payload HEALTHCHECK.

We are given a minimal client.py template with two missing functions:

  • generate_space_packet()
  • generate_tc_frame()

The objective is to implement these functions according to the official CCSDS specifications and successfully communicate with the remote spacecraft simulator.

Initial Analysis

The provided client performs the following operations:

space_packet = generate_space_packet(
apid=42,
packet_count=0,
payload=b"TEST_PAYLOAD"
)
frame = generate_tc_frame(
spacecraft_id=12,
virtual_channel_id=3,
tc_packet_count=0,
payload=space_packet
)
payload = frame + space_packet

From the challenge description, we know the required values:

FieldValueSpacecraft ID12Virtual Channel3APID42PayloadHEALTHCHECK

The two protocol references included with the challenge immediately point us toward the official CCSDS standards:

  • CCSDS Space Packet Protocol
  • CCSDS Telecommand (TC) Space Data Link Protocol

This indicates that the challenge is primarily a protocol implementation exercise rather than a traditional exploitation challenge.

Understanding the CCSDS Space Packet

A CCSDS Space Packet begins with a fixed 6-byte Primary Header.

0                   1                   2                   3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Ver|Type|SHF| APID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Sequence Flags| Packet Sequence Count |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Packet Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

The header consists of three 16-bit words.

First Header Word

The first word contains:

  • Version = 0
  • Packet Type = 1 (Telecommand)
  • Secondary Header Flag = 0
  • APID = 42

This is packed as

Version << 13
PacketType << 12
SecondaryHeader << 11
APID

which evaluates to

0x102A

Second Header Word

The second word stores

  • Sequence Flags = 11 (Unsegmented Packet)
  • Packet Sequence Count = 0

This becomes

0xC000

Packet Length

Unlike most protocols, CCSDS stores

Packet Length = Data Length - 1

The payload is

HEALTHCHECK

which is 11 bytes.

Therefore

Length = 10

or

0x000A

Final Space Packet

The packet layout becomes

+----------------------+
| Primary Header (6B) |
+----------------------+
| HEALTHCHECK |
+----------------------+

Understanding the Telecommand Frame

The Telecommand Transfer Frame acts as the transport layer for the Space Packet.

The primary header contains

  • Spacecraft ID
  • Virtual Channel ID
  • Frame Length
  • Sequence Number

For this challenge:

Spacecraft ID = 12
VCID = 3
Sequence = 0

The interesting observation is that the provided template later performs

payload = frame + space_packet

instead of embedding the packet inside generate_tc_frame().

Therefore generate_tc_frame() only returns the 5-byte TC Primary Header, while the client appends the Space Packet afterwards.

The transmitted data therefore looks like

+--------------------+
| TC Header |
+--------------------+
| Space Packet |
+--------------------+

This matches the expected packet layout for the challenge service.

Packet Construction

The Space Packet is created by

def generate_space_packet(apid, packet_count, payload):
first = (1 << 12) | apid
second = (3 << 14) | packet_count
length = len(payload) - 1
    return struct.pack(">HHH", first, second, length) + payload

The Telecommand header is created by

def generate_tc_frame(scid, vcid, count, payload):
word1 = scid
frame_len = len(payload) + 5 - 1
word2 = ((vcid & 0x3F) << 10) | frame_len
    return struct.pack(">HHB", word1, word2, count)

Finally, the packet sent to the server is

frame + space_packet

where the application payload is

b"HEALTHCHECK"

Communication Flow

The completed packet follows this structure:

TCP Connection


┌──────────────────────────┐
│ TC Transfer Frame Header │
└──────────────────────────┘


┌──────────────────────────┐
│ CCSDS Space Packet │
├──────────────────────────┤
│ Primary Header (6 bytes) │
├──────────────────────────┤
│ HEALTHCHECK │
└──────────────────────────┘


Diagnostic Service (APID 42)

The onboard diagnostic application validates:

  • Spacecraft ID = 12
  • Virtual Channel = 3
  • APID = 42
  • Payload = HEALTHCHECK

If all fields are correctly encoded according to the CCSDS specifications, the spacecraft responds with the challenge flag.

Although classified as a “Very Easy” challenge, it provides an excellent introduction to real-world satellite communication protocols and demonstrates how spacecraft exchange telecommands using CCSDS standards.

--

--