_We didn't manage to solve this task during the CTF. I was close enough, so I decided to finish it and share the solution._
We are given pcap traffic of some OpenArena(it is an open-source implementation of quake3 engine) game session. Youtube clip reveals that during this session player draws the flag on the wall.
## OpenArena network protocolWe can get some idea of how this protocol works by looking at the [game sources](https://github.com/OpenArena/legacy/tree/master/engine/openarena-engine-source-0.8.8).Basically it is some kind of reliable udp with adaptive huffman coding and delta compression, but we don't need to know all the specifics to solve this task.Also, I found this cool python wrapper on engine's `msg.c` code to help with huffman decompression -- [q3huff](https://github.com/jkent/q3huff).All we need to know is that all packets start with a sequence number, and contain a sequence of messages.
## SolutionMy first idea was to replay client packets against our server and watch this session. I patched server to match `challenge` and `serverId` values from this session. However, when I was replaying client packets I realized that we actually need to replay server packets in order to see what the other player was doing. I got an idea to write server packets in a demo file, but I didn't manage to do that in time.
Next day, I decided to give it a try.Demo recordings file format is pretty straightforward: it is a sequence of server packets:```[packet.seq|len(packet.data)|packet.data]```The only problem is that we can't just write network packets as is, we need to decode them first(some kind of spoofing protection):```pythondef msg_decode(data, header_size, string, key):    decoded = []    index = 0    for i in range(header_size, len(data)):        # modify the key with the last sent and acknowledged command        if string[index] == 0:            index = 0        if string[index] > 127 or string[index] == ord('%'):            key ^= (ord('.') << (i & 1)) & 0xFF        else:            key ^= (string[index] << (i & 1)) & 0xFF
        index += 1        # decode the data with this key        decoded.append(data[i] ^ key)    return data[:header_size] + bytes(decoded)```As you can see, we need to maintain the list of server and client commands in order to decode all packets.
Thankfully, in this game session, all `svc_serverCommand` and `clc_clientCommand` messages are placed at the start of the packets, so we don't need to write parsers for every message type and just skip irrelevant ones.Server part(client part looks the same):```pythondef svc_parse(seq, msg):    msg.oob = False  # enables Huffman decompression    rAck = msg.read_long()    while True:        cmd = msg.read_byte()        if cmd != svc_serverCommand:            break        index = msg.read_long() & (64 - 1)        s = msg.read_string()        serverCommands[index] = s.encode('utf8') + b'\x00'        print(f'stored svc_serverCommand "{s}" at {index}')```
Now we can decode server packets:```pythonreliableAcknowledge = msg.read_long() & (64 - 1)key = (CHALLENGE ^ seq) & 0xFFdata = msg_decode(data, 4, clientCommands[reliableAcknowledge], key)```
And client packets:```pythonserverId = msg.read_long()messageAcknowledge = msg.read_long()reliableAcknowledge = msg.read_long() & (64 - 1)key = (CHALLENGE ^ serverId ^ messageAcknowledge) & 0xFFdata = msg_decode(data, 12, serverCommands[reliableAcknowledge], key)```
That's it. We can write these packets to the demo file and play it in the game client.```pythondemo_file.write(struct.pack('<II', seq, len(data)))demo_file.write(data)```In the recording we can see that the player writes the flag on the wall: `PCTF{PEWPEWPEWWX}`
Full solution code:```pythonfrom scapy.all import *from collections import defaultdictimport q3huffimport struct
CHALLENGE = -1338626257 & 0xFFFFFFFF
FRAGMENT_SIZE = 0x514
svc_serverCommand = 0x5clc_clientCommand = 0x4
serverCommands = defaultdict(lambda: b'\x00\x00')clientCommands = defaultdict(lambda: b'\x00\x00')
def msg_decode(data, header_size, string, key):    decoded = []    index = 0    for i in range(header_size, len(data)):        # modify the key with the last sent and acknowledged command        if string[index] == 0:            index = 0        if string[index] > 127 or string[index] == ord('%'):            key ^= (ord('.') << (i & 1)) & 0xFF        else:            key ^= (string[index] << (i & 1)) & 0xFF
        index += 1        # decode the data with this key        decoded.append(data[i] ^ key)    return data[:header_size] + bytes(decoded)
def svc_parse(seq, msg):    msg.oob = False  # enables Huffman decompression    rAck = msg.read_long()    while True:        cmd = msg.read_byte()        if cmd != svc_serverCommand:            break        index = msg.read_long() & (64 - 1)        s = msg.read_string()        serverCommands[index] = s.encode('utf8') + b'\x00'        print(f'stored svc_serverCommand "{s}" at {index}')
def clc_parse(seq, msg):    msg.oob = False  # enables Huffman decompression    serverId = msg.read_long()    mAck= msg.read_long()    rAck = msg.read_long()    while True:        cmd = msg.read_byte()        if cmd != clc_clientCommand:            break        index = msg.read_long() & (64 - 1)        s = msg.read_string()        clientCommands[index] = s.encode('utf8') + b'\x00'        print(f'stored clc_clientCommand "{s}" at {index}')
sv_fragment_buffer = b''cl_fragment_buffer = b''
def read_fragment(msg, raw_data):    frag_offset = msg.read_short() & 0xFFFF    frag_size = msg.read_short() & 0xFFFF    return frag_size != FRAGMENT_SIZE, raw_data[4:4+frag_size]
demo_file = open('flag.dm_71', 'wb')for packet in rdpcap('graffiti.pcap'):    if not (packet.haslayer(UDP) and packet[UDP].sport == packet[UDP].dport == 27961):        continue
    is_server = str(packet[IP].src) == '192.168.151.139'    packet_payload = bytes(packet[UDP].payload)
    msg = q3huff.Reader(packet_payload)    msg.oob = True  # disables Huffman decompression
    seq = msg.read_long() & 0xFFFFFFFF    if seq == 0xFFFFFFFF:        # skip connectionless packets        continue
    if is_server:        data = packet_payload[4:]        if seq & (1 << 31):            seq ^= (1 << 31)            is_last, frag = read_fragment(msg, data)            sv_fragment_buffer += frag            if is_last:                data = sv_fragment_buffer                sv_fragment_buffer = b''            else:                continue
        msg = q3huff.Reader(data)
        reliableAcknowledge = msg.read_long() & (64 - 1)        key = (CHALLENGE ^ seq) & 0xFF        data = msg_decode(data, 4, clientCommands[reliableAcknowledge], key)
        demo_file.write(struct.pack('