Files
HIP7CTF_Writeups/de/twisted.md
m0rph3us1987 a79656b647 Added writeups
2026-03-08 12:22:39 +01:00

5.2 KiB

Twisted

twisted ist eine Reverse-Engineering-Challenge, bei der wir eine Flagge aus einer bereitgestellten Binärdatei und einem verschlüsselten Ausgabestring wiederherstellen müssen.

Informationsbeschaffung

Wir beginnen mit der Analyse des Dateityps der twisted-Binärdatei:

$ file twisted
twisted: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, ... stripped

Die Binärdatei ist "stripped", was bedeutet, dass ihr Debugging-Symbole wie Funktionsnamen fehlen. Wenn wir uns mit dem Challenge-Server verbinden, erhalten wir die verschlüsselte Flagge:

Here is your twisted flag: 34d133c640536c58ffcebb864a836aaf3bc432c3606b331df2d981a472bd6e80

Reverse Engineering

Wir öffnen die Binärdatei in Ghidra, um ihre Logik zu analysieren. Da die Binärdatei gestrippt ist, suchen wir zuerst den Einsprungpunkt (entry), der __libc_start_main aufruft. Das erste Argument für __libc_start_main ist die Adresse der main-Funktion. Diesem Pfad folgend gelangen wir zur Funktion bei 0x40190a, die wir in main umbenennen.

Main-Funktion (0x40190a)

Der dekompilierte Code für main enthüllt die erwarteten Argumente und eine grundlegende Validierung:

undefined8 main(int param_1,long param_2)

{
  size_t sVar1;
  
  if (param_1 < 2) {
    printf("Usage: %s <flag>\n",*(undefined8 *)(param_2 + 8)); // Usage-String bei 0x49e081
    return 1;
  }
  sVar1 = strlen(*(char **)(param_2 + 8));
  if (sVar1 == 32) {
    FUN_004017b5(*(undefined8 *)(param_2 + 8));
    return 0;
  }
  printf("Error: Flag must be exactly %d characters long.\n",32); // Error-String bei 0x49e098
  return 1;
}

Daraus lernen wir, dass die Eingabeflagge genau 32 Zeichen lang sein muss. Wenn die Länge korrekt ist, ruft sie FUN_004017b5 auf.

Transformationsfunktion (0x4017b5)

Wir analysieren FUN_004017b5, welche die Kernverschlüsselungslogik enthält. Sie führt zwei verschiedene Operationen auf dem Eingabestring aus.

void FUN_004017b5(long param_1)

{
  long lVar1;
  int local_84; // Zähler für Schleife 1
  int local_80; // Zähler für Schleife 2
  int local_7c; // Zähler für Schleife 3
  byte local_70 [32]; // Gemischter Puffer
  byte local_50 [32]; // Finaler XOR-Puffer
  byte local_30 [32]; // Eingabekopie
  
  // ... Setup und Kopieren der Eingabe nach local_30 ...

  // --- SCHRITT 1: Permutation ---
  local_84 = 0;
  while (local_84 < 32) {
    // Lade Byte aus Permutationstabelle bei 0x49e020
    // Verwende es als Index in den Eingabestring
    local_70[local_84] = local_30[(int)(uint)(byte)(&DAT_0049e020)[local_84]];
    local_84 = local_84 + 1;
  }

  // --- SCHRITT 2: XOR-Verschlüsselung ---
  local_80 = 0;
  while (local_80 < 32) {
    // XOR das gemischte Byte mit einem Schlüsselbyte von 0x49e040
    local_50[local_80] = local_70[local_80] ^ (&DAT_0049e040)[local_80];
    local_80 = local_80 + 1;
  }

  // --- Ergebnis drucken ---
  printf("Here is your twisted flag: "); // String bei 0x49e060
  local_7c = 0;
  while (local_7c < 32) {
    printf("%02x",(ulong)local_50[local_7c]);
    local_7c = local_7c + 1;
  }
  // ...
}

Der Algorithmus ist:

  1. Permutation: Verwende das Array bei 0x49e020, um die Eingabezeichen neu anzuordnen. shuffled[i] = input[PERM[i]]
  2. XOR: XOR die neu angeordneten Zeichen mit dem Array bei 0x49e040. encrypted[i] = shuffled[i] ^ KEY[i]

Datenextraktion

Wir untersuchen den Speicher an den identifizierten Adressen, um die Permutationstabelle und den XOR-Schlüssel abzurufen.

Permutationstabelle (0x49e020): Werte: 3, 0, 1, 2, 7, 4, 5, 6, 10, 11, 8, 9, 15, 12, 13, 14, 19, 16, 17, 18, 22, 23, 20, 21, 25, 26, 27, 24, 31, 28, 29, 30

XOR-Schlüssel (0x49e040): Werte (Hex): 55, AA, 55, AA, 12, 34, 56, 78, 9A, BC, DE, F0, 0F, F0, 0F, F0, 55, AA, 55, AA, 12, 34, 56, 78, 9A, BC, DE, F0, 0F, F0, 0F, F0

Lösung

Um die Flagge 34d133c6... zu entschlüsseln, kehren wir die Operationen um:

  1. XOR umkehren: shuffled[i] = encrypted[i] ^ KEY[i]
  2. Permutation umkehren: input[PERM[i]] = shuffled[i]

Solver-Skript

import sys

# Extrahiert von 0x49e020
PERM = [
    3, 0, 1, 2, 7, 4, 5, 6, 
    10, 11, 8, 9, 15, 12, 13, 14, 
    19, 16, 17, 18, 22, 23, 20, 21, 
    25, 26, 27, 24, 31, 28, 29, 30
]

# Extrahiert von 0x49e040
KEY = [
    0x55, 0xAA, 0x55, 0xAA, 0x12, 0x34, 0x56, 0x78,
    0x9A, 0xBC, 0xDE, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0,
    0x55, 0xAA, 0x55, 0xAA, 0x12, 0x34, 0x56, 0x78,
    0x9A, 0xBC, 0xDE, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0
]

def solve(hex_string):
    encrypted_bytes = bytes.fromhex(hex_string)
    
    if len(encrypted_bytes) != 32:
        print("Error: Length mismatch")
        return

    # 1. XOR umkehren
    shuffled = [0] * 32
    for i in range(32):
        shuffled[i] = encrypted_bytes[i] ^ KEY[i]

    # 2. Permutation umkehren
    original = [0] * 32
    for i in range(32):
        target_idx = PERM[i]
        original[target_idx] = shuffled[i]

    print("Flag: " + "".join(chr(b) for b in original))

if __name__ == "__main__":
    solve("34d133c640536c58ffcebb864a836aaf3bc432c3606b331df2d981a472bd6e80")

Das Ausführen des Skripts gibt uns die Flagge: {flag: Reverse_Engineer_The_Map}