Files
ctf/2025/glacier/rev/c2/build_packet.py
2025-11-28 12:20:39 +01:00

117 lines
4.2 KiB
Python

import struct
def calculate_c2_checksum(payload_bytes: bytes) -> int:
"""
Calculates the 8-bit checksum for the C2 packet payload based on the
decompiled Rust/C logic (main_packet_building).
The algorithm is a custom XOR reduction over the payload data, primarily
using 4-byte (DWORD) and 1-byte chunks, mimicking the final reduction
of the SSE logic.
The original logic is complex (32-byte blocks with SSE, then 4-byte,
then 1-byte), but the core of the final reduction is based on
accumulating XORs in 32-bit blocks and then folding the result.
For simplicity and focusing on the non-SSE reduction path (which handles
most sizes and remainders), we use a DWORD-based XOR sum and then a
byte-based XOR sum for the remainder.
"""
payload_len = len(payload_bytes)
checksum_v16 = 0
# --- 1. Process data in 4-byte (DWORD) chunks (Handles up to N & 0xFFFC) ---
# This section mimics the logic for sizes < 32 and the 4-byte remainder loop.
# v26 = a5 & 0x3FFC (The largest multiple of 4 less than a5)
processed_4byte_len = payload_len & 0xFFFC
# Iterate through the payload 4 bytes at a time
for i in range(0, processed_4byte_len, 4):
# Read 4 bytes as a little-endian integer (DWORD)
dword_value = struct.unpack('<I', payload_bytes[i:i+4])[0]
# Accumulate the XOR sum of the DWORDs
# In the original code, this happens in an SSE register, but we simplify
# it to an integer XOR sum for the final 8-bit result.
checksum_v16 ^= dword_value
# --- 2. Final Reduction/Folding (Mimics SSE reduction) ---
# The original SSE reduction folds a 128-bit XOR sum down to an 8-bit value.
# We perform a similar reduction on the 32-bit (dword) XOR sum.
# Fold 32-bit result to 16-bit: v28 = _mm_xor_si128(_mm_srli_epi32(v27, 0x10u), v27)
# This means XORing the upper 16 bits with the lower 16 bits.
checksum_v16 = (checksum_v16 & 0xFFFF) ^ (checksum_v16 >> 16)
# Fold 16-bit result to 8-bit: _mm_xor_si128(_mm_srli_epi16(v28, 8u), v28)
# This means XORing the upper 8 bits with the lower 8 bits.
checksum_v16 = (checksum_v16 & 0xFF) ^ (checksum_v16 >> 8)
# The checksum is now a single 8-bit value (0-255)
checksum_v16 &= 0xFF
# --- 3. Process the final 1-3 byte remainder (The byte loop LABEL_21) ---
# The remainder starts at the end of the 4-byte processed section.
# The XOR accumulation continues with the remaining 1-3 bytes.
for i in range(processed_4byte_len, payload_len):
checksum_v16 ^= payload_bytes[i]
# Ensure the final result is 8 bits
return checksum_v16 & 0xFF
def build_packet(command_id: int, payload: bytes) -> bytes:
"""
Constructs the complete C2 packet ready for transmission.
"""
magic_bytes = b'\x43\x42' # 0x4342 Big-Endian
cmd_id = command_id.to_bytes(1, 'big')
reserved_byte = b'\x00'
payload_len = len(payload)
# Payload length is Big-Endian (BYTE1(a5) then a5)
len_bytes = payload_len.to_bytes(2, 'big')
# Calculate the 8-bit checksum over the payload data
checksum_v16 = calculate_c2_checksum(payload)
# Checksum field is 2 bytes (Big-Endian), where MSB is 0x00 and LSB is the 8-bit checksum.
checksum_bytes = b'\x00' + checksum_v16.to_bytes(1, 'big')
# Combine all parts
packet = (
magic_bytes +
cmd_id +
reserved_byte +
len_bytes +
payload +
checksum_bytes
)
return packet
# --- Example Usage ---
# 1. Define inputs
COMMAND_OPCODE = 0x01 # Example command ID
PAYLOAD_DATA = b"cato@unknown" # N = 43 bytes
# 2. Calculate checksum and build the packet
final_packet = build_packet(COMMAND_OPCODE, PAYLOAD_DATA)
final_checksum_byte = final_packet[-1]
print(f"--- Packet Construction ---")
print(f"Payload Length (N): {len(PAYLOAD_DATA)} bytes")
print(f"Calculated Checksum (v16): {hex(final_checksum_byte)}")
print(f"Total Packet Size: {len(final_packet)} bytes (N + 8)")
print(f"Final Packet (Hex): {final_packet.hex()}")
# Verify structure:
# Magic Bytes: 0x4342 ('CB')
# CMD ID: 0x01
# Reserved: 0x00
# Length: 0x002B (43 decimal)
# Payload: 43 bytes of "This is..."
# Checksum: 0x00{v16}