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

4.8 KiB

Twisted

twisted is a reverse engineering challenge where we must recover a flag from a provided binary and an encrypted output string.

Information Gathering

We start by analyzing the file type of the twisted binary:

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

The binary is stripped, meaning it lacks debugging symbols like function names. Connecting to the challenge server gives us the encrypted flag:

Here is your twisted flag: 34d133c640536c58ffcebb864a836aaf3bc432c3606b331df2d981a472bd6e80

Reverse Engineering

We open the binary in Ghidra to analyze its logic. Since the binary is stripped, we first locate the entry point (entry), which calls __libc_start_main. The first argument to __libc_start_main is the address of the main function. Following this path leads us to the function at 0x40190a, which we rename to main.

Main Function (0x40190a)

The decompiled code for main reveals the expected arguments and basic validation:

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 at 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 at 0x49e098
  return 1;
}

From this, we learn that the input flag must be exactly 32 characters long. If the length is correct, it calls FUN_004017b5.

Transformation Function (0x4017b5)

We analyze FUN_004017b5, which contains the core encryption logic. It performs two distinct operations on the input string.

void FUN_004017b5(long param_1)

{
  long lVar1;
  int local_84; // Counter for Loop 1
  int local_80; // Counter for Loop 2
  int local_7c; // Counter for Loop 3
  byte local_70 [32]; // Shuffled Buffer
  byte local_50 [32]; // Final XOR Buffer
  byte local_30 [32]; // Input Copy
  
  // ... Setup and copying input to local_30 ...

  // --- STEP 1: Permutation ---
  local_84 = 0;
  while (local_84 < 32) {
    // Load byte from Permutation Table at 0x49e020
    // Use it as an index into the input string
    local_70[local_84] = local_30[(int)(uint)(byte)(&DAT_0049e020)[local_84]];
    local_84 = local_84 + 1;
  }

  // --- STEP 2: XOR Encryption ---
  local_80 = 0;
  while (local_80 < 32) {
    // XOR the shuffled byte with a Key byte from 0x49e040
    local_50[local_80] = local_70[local_80] ^ (&DAT_0049e040)[local_80];
    local_80 = local_80 + 1;
  }

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

The algorithm is:

  1. Permutation: Use the array at 0x49e020 to reorder the input characters. shuffled[i] = input[PERM[i]]
  2. XOR: XOR the reordered characters with the array at 0x49e040. encrypted[i] = shuffled[i] ^ KEY[i]

Data Extraction

We inspect the memory at the identified addresses to retrieve the Permutation Table and XOR Key.

Permutation Table (0x49e020): Values: 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 Key (0x49e040): Values (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

Solution

To decrypt the flag 34d133c6..., we reverse the operations:

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

Solver Script

import sys

# Extracted from 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
]

# Extracted from 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. Reverse XOR
    shuffled = [0] * 32
    for i in range(32):
        shuffled[i] = encrypted_bytes[i] ^ KEY[i]

    # 2. Reverse Permutation
    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")

Running the script gives us the flag: {flag: Reverse_Engineer_The_Map}