← Back to Writeups
CUSTOMMediumGamePwn

Car Theft

XESXOR9/2/20264 min read
#gamepwn#custom#medium#class_selection_navigation#custom_protocol_client#position_sync_sweeping#raknet_stringcompressor_decodi#samp_query_fingerprinting

Car Theft

Platform: hackerlab | Category: GamePwn | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-04-05 | Status: Solved Techniques: class_selection_navigation, custom_protocol_client, position_sync_sweeping, raknet_stringcompressor_decoding, samp_query_fingerprinting

Summary

Task: an IP:port led to a SA-MP UDP server instead of a web service, with hints about Bone County and aliens. Solution: fingerprint the server, adapt a custom SA-MP client, sweep Bone County for Infernus spawns, then decode a RakNet StringCompressor 3D label near Lil Probe Inn to recover the flag.

Car Theft — Hackerlab

Description

Подсказка в задании указывала на Bone County и инопланетян.

English summary: the target was a single IP:port. Recon showed it was not a web service but a SA-MP game server. The goal was to connect as a client, find the correct vehicle hinted by the challenge, and extract the flag.

Analysis

The first useful observation was transport-level: the service did not behave like HTTP or a regular TCP daemon. A SA-MP query packet (SAMP + IP bytes + little-endian port + opcode) identified the server as CODEBY GAMES, gamemode codeby.games, version 0.3.7-R2. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

A stock client implementation did not join successfully, so the Python SA-MP client was adjusted to match the server handshake: version 4057, mod byte 1, client version 0.3.7, and duplicated challenge response handling.

After joining, class-selection textdraws rotated through Los Santos → San Fierro → Las Venturas. Because the clue mentioned Bone County and aliens, Las Venturas was the correct branch. Sweeping Bone County with fake on-foot position sync packets revealed several vehicles of model 411 (Infernus). ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The strongest candidate was the Infernus near Lil Probe Inn at about (-109.726, 1370.466, 9.808), which matched the alien hint. It also had the expected visual cues: orange-like color and wheel mod in vehicle mod slot 81.

The final detail was protocol-specific: the flag was not sent as a plain string. SA-MP RPC 36 (ScrCreate3DTextLabel) carried a 3D label attached to vehicle 1791, and its text field was encoded with RakNet StringCompressor. Decoding that payload yielded the flag, with one harmless trailing padding artifact after the closing brace. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

  1. Probe the target with a SA-MP UDP query and confirm it is a SA-MP server.
  2. Patch the custom Python client so it can complete the join sequence accepted by this server.
  3. Join class selection and watch the textdraws until the city sequence shows Las Venturas as the relevant destination.
  4. Select Las Venturas, spawn, and sweep Bone County by sending fake on-foot sync positions.
  5. Filter streamed vehicles by model 411 (Infernus) and inspect the one near Lil Probe Inn.
  6. Parse RPC 36 (ScrCreate3DTextLabel) and decode its StringCompressor payload.
  7. Truncate the decoded text at the closing brace and recover the valid flag. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍
python
#!/usr/bin/env python3
import os
import sys
import time
import threading

BASE = os.path.join("tasks", "hackerlab", "Автокража", "samp-client-python")
sys.path.insert(0, BASE)

from client import SAMPClient
from packet_ids import RPC
from inspect_lil_probe_inn import decode_samp_encoded_string4096

TARGET = (-109.726, 1370.466, 10.0)
KEY_FIRE = 4
KEY_RIGHT = 128
INFERNUS_MODEL = 411


def pulse_spectator_key(client, lr=0, keys=0, repeats=4, delay=0.12):
    for _ in range(repeats):
        client.send_spectator_sync(lr, 0, keys)
        time.sleep(delay)
    client.send_spectator_sync(0, 0, 0)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍


def move_to(client, x, y, z, repeats=8, delay=0.08):
    client.position = [float(x), float(y), float(z)]
    for _ in range(repeats):
        client.send_onfoot_sync()
        time.sleep(delay)


def read_vehicle(bs):
    vehicle_id = bs.read_u16()
    model_id = bs.read_u32()
    x, y, z = bs.read_float(), bs.read_float(), bs.read_float()
    bs.read_float()  # angle
    color1, color2 = bs.read_u8(), bs.read_u8()
    bs.read_float()  # health
    bs.read_u8()     # interior
    bs.read_u32(); bs.read_u32(); bs.read_u8(); bs.read_u8(); bs.read_u8()
    mods = [bs.read_u8() for _ in range(14)]
    return {
        "vehicle_id": vehicle_id,
        "model_id": model_id,
        "x": round(x, 3),
        "y": round(y, 3),
        "z": round(z, 3),
        "color1": color1,
        "color2": color2,
        "mods": mods,
    }
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍


def read_3d_label(bs):
    bs.read_u16()  # label_id
    bs.read_u32()  # color
    x, y, z = bs.read_float(), bs.read_float(), bs.read_float()
    bs.read_float()  # draw distance
    bs.read_u8()     # test_los
    bs.read_u16()    # attached_player
    attached_vehicle = bs.read_u16()
    text = decode_samp_encoded_string4096(bytes(bs._data), bs._read_bit_offset)
    return attached_vehicle, (x, y, z), text


client = SAMPClient("62.173.140.174", 10350, "ProbeInn1")
target_vehicle_id = None


def scenario():
    time.sleep(3)
    for _ in range(3):
        pulse_spectator_key(client, lr=KEY_RIGHT)
        time.sleep(0.6)
    pulse_spectator_key(client, keys=KEY_FIRE)  # select Las Venturas
    time.sleep(1.5)
    client.send_request_spawn()
    time.sleep(0.7)
    client.send_spawn()
    time.sleep(2)
    move_to(client, *TARGET)
    time.sleep(3)
    client.disconnect()
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍


def on_connected():
    threading.Thread(target=scenario, daemon=True).start()


def on_rpc(rpc_id, bs):
    global target_vehicle_id
    if rpc_id == RPC.WorldVehicleAdd:
        vehicle = read_vehicle(bs)
        if vehicle["model_id"] == INFERNUS_MODEL and abs(vehicle["x"] - TARGET[0]) < 30 and abs(vehicle["y"] - TARGET[1]) < 30:
            target_vehicle_id = vehicle["vehicle_id"]
            print("candidate:", vehicle)
    elif rpc_id == RPC.ScrCreate3DTextLabel:
        attached_vehicle, pos, text = read_3d_label(bs)
        if attached_vehicle == target_vehicle_id or attached_vehicle == 1791:
            flag = text[: text.find("}") + 1]
            print("label:", pos, text)
            print("flag:", flag)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍


client.on_connected = on_connected
client.on_rpc = on_rpc
client.connect()
client.wait()

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