Table of contents



Description

The elves have whispered to Elowen that the key to the Dragon's Heart is never directly visible; 
instead, there are clues in all the sounds and movements hidden in the world.

File Information

File: challenge
Type: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked,
interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 4.4.0, BuildID[sha1]=155
a486c0ae2b9b06e0678261204f2a81b9c7947, stripped

In this challenge, we’ll be reversing a stripped 64-bit ELF executable.

Initial Code Analysis

Since the binary is stripped, there are no symbols available. We first need to identify the main function by examining the __libc_start_main call.

After renaming variables for clarity, we can see that the function operates in two distinct phases:

Phase 1: Dynamically Loading Code into Memory

Code loading loops

The program uses two nested loops to generate code by seemingly randomly selecting characters to build a program in the memory_chunk. This approach prevents effective static analysis since the actual executable code is generated at runtime.

Phase 2: Executing the Generated Code and Checking the Result

Code execution

In the second phase, the program executes the code loaded into memory_chunk and displays a validation message if the return value equals 1.

Runtime Analysis

Program execution

When executing the program, we’re prompted with “What is the flag?” (to which I responded with “conflict”).

This prompt string doesn’t appear in our initial decompilation, indicating it’s part of the dynamically generated code in memory_chunk.

Based on this observation, we can infer that the generated code:

  1. Prompts for user input
  2. Performs some manipulation on the input
  3. Compares it against the actual flag
  4. Returns 1 if the entered flag is correct, 0 otherwise

Dynamic Analysis through Debugging

Since static analysis is limited by the runtime code generation, we need to debug the program to examine the actual code in memory.

Locating the Code in Memory

To view the dynamically generated code, we run the program and interrupt it with CTRL+C when it’s waiting for input. This reveals the current execution address in memory:

Current execution address

Examining the Disassembled Code

Disassembly of generated code

In the disassembly, we observe:

  1. Two values being loaded: 0x67616c6620656874 and 0x2073692074616857, which correspond to the ASCII string “What is the flag?
  2. Code that reads user input and XORs it with the constant 0xbeefcafe
  3. A comparison between the XORed input and a value stored at memory address 0x7ffff7ffa084

Extracting the Flag Bytes

The disassembly indicates the flag is 26 characters long (0x1a in hexadecimal). We extract 26 bytes from the address where the expected flag value is stored:

Extracted bytes

Solution

Understanding that the flag has been XORed with the key 0xbeefcafe, we can reverse this operation by applying the same XOR again to recover the original flag. When implementing the solution, we must be careful about endianness.

Solution Script

#!/usr/bin/python3

# Memory bytes containing the expected flag value (XORed)
memory_bytes = [
    0xb6, 0x9e, 0xad, 0xc5, 0x92, 0xfa, 0xdf, 0xd5,
    0xa1, 0xa8, 0xdc, 0xc7, 0xce, 0xa4, 0x8b, 0xe1,
    0x8a, 0xa2, 0xdc, 0xe1, 0x89, 0xfa, 0x9d, 0xd2,
    0x9a, 0xb7
]

# XOR key (in little-endian byte order)
key = [0xFE, 0xCA, 0xEF, 0xBE]

flag = ""
for i in range(len(memory_bytes)):
    decoded_byte = memory_bytes[i] ^ key[i % 4]
    flag += chr(decoded_byte)

print("Flag:", flag)

Conclusion

As a beginner in reverse engineering, I found this challenge very fun. It provided an opportunity to practice debugging techniques and learn about runtime code generation evasion techniques.

If you’ve identified any errors in my approach or know of a more efficient solution, please feel free to contact me on Discord!

back to top