Field notes

Cyber Jawara Quals 2025 - Dior [836]

Reverse engineering a D language binary that encrypts a Dior challenge flag.

Challenge Overview

In this challenge, the description gave us a link to the D programming language download page and a zip file. After extracting the zip, there were two files: dior and flag.txt.dior.

Extracted Dior challenge files containing the dior binary and flag.txt.dior.

Opening flag.txt.dior directly only showed unreadable bytes.

Encrypted flag.txt.dior file opened as text, showing unreadable bytes.

My first assumption was that dior was the program used to encrypt flag.txt. Running file confirmed that it was a 64-bit ELF executable, built from D and stripped.

The file command showing dior is a 64-bit ELF executable built from D.

Investigation and Discovery

I opened the binary in Ghidra and started from the entry point, then worked my way to the main function. The main routine showed a short pipeline: read the input, compress it, shuffle the bytes, XOR the result, create the output filename, and write the encrypted file.

Ghidra decompiler view of the Dior main function.

Function breakdown:

  • FUN_00126450
    Reads the input file into a 16-byte text/slice-like structure. It also checks the text encoding. If the input looks like UTF-16 or UTF-32, the program throws an exception instead of continuing.

  • FUN_0014feb0
    A wrapper around the compression step. From the D references and the surrounding calls, this lines up with zlib compression.

  • FUN_001266c0
    Shuffles the byte order inside the buffer using a PRNG-driven swap loop.

    Ghidra snippet showing the shuffle loop and byte swap operation.
  • FUN_00126020
    Encrypts the shuffled buffer with byte-by-byte XOR.

    Ghidra snippet showing a byte written as the XOR of two values.
  • FUN_0014fec0
    Copies data into a buffer and returns the slice used by the later pipeline.

  • FUN_00126820
    Builds the output filename.

  • FUN_00126750
    Writes the encrypted result to disk.

After reading the decompiler output, the encryption path became:

read file -> compress with zlib -> shuffle bytes -> XOR bytes -> write .dior file

So the decryptor needs to do the inverse:

read .dior file -> undo XOR -> undo shuffle -> decompress zlib -> recover plaintext

Decrypting the Message

Based on that pipeline, I wrote a solver that recreated the XOR stream, reversed the shuffle permutation, and then decompressed the result with zlib.

The important part is the order. Because the binary encrypts after shuffling, decryption must undo the XOR first. Only after the XOR layer is removed can the shuffled buffer be put back into its original order and decompressed.

# high-level solver flow
data = read_file("flag.txt.dior")
data = undo_xor(data)
data = undo_shuffle(data)
flag = zlib.decompress(data)
print(flag.decode())

The Flag

After running the solver, the flag from the notes was:

CJ{christian-dior-dior_1M-UP-IN-ALL-THE-5TORE5_rip-pop-smokeldc2 dior.d }

Resources