Photo by Robert Taylor / Unsplash

Writeup: Locked Temple - upCTF (Reverse)

Feri Harjulianto

Challenge

Step on the pressure plates with the perfect 8 plate sequence. If you prove worthy, the door will open and the ultimate golden prize will be revealed to you.

Pressure plate order follows this representation:

Flag format: upCTF{PlateOrder_SECRETDIGIT}

Solution

Initial Analysis

$ file locked_temple
locked_temple: ELF 64-bit LSB pie executable, x86-64, dynamically linked, stripped

The binary is a stripped ELF that links against libSDL2-2.0.so.0. It creates a window titled "Locked Temple" with a 10x8 grid of cells, 4 pressure plates, and a player controlled with WASD keys.

Key Data Structures

From the .data section:

AddressContentPurpose
0x402071 22 90 11 63 74 81 52XOR-encoded validation key
0x404003 00 00 00 09 00 00 00Door grid position (col=3, row=9)
0x40604 pairs of (x, y) intsPlate coordinates: (2,1), (7,1), (2,6), (7,6)

State Structure

The game state (passed via rdi) is a 16-byte struct:

struct state {
    int count;       // offset 0x00 - number of plates pressed (0-8)
    char seq[8];     // offset 0x04 - recorded plate sequence
    int done;        // offset 0x0C - set to 1 when correct sequence entered
};

Init Function (0x1bc0) - XOR Decoding

The init function decodes three 8-byte blocks in .data using XOR loops:

Loop 1 (0x4020): key starts at 0, increments by 5
Loop 2 (0x4018): key starts at 0, increments by 8
Loop 3 (0x4010): key starts at 0, increments by 10

Decoded data at 0x4020:

data_orig = [0x71, 0x22, 0x90, 0x11, 0x63, 0x74, 0x81, 0x52]
key = 0
for i in range(8):
    data_orig[i] ^= (key & 0xFF)
    key += 5
# Result: [0x71, 0x27, 0x9A, 0x1E, 0x77, 0x6D, 0x9F, 0x71]

Plate Press Handler (0x1c40)

When the player steps on a plate, this function records the plate index (0-3) into seq[] and increments count. Once count == 8, it falls through to the validation logic.

Sequence Validation (0x1c68)

The validation computes expected plate values using a chained XOR + conditional rotate scheme:

data = [0x71, 0x27, 0x9A, 0x1E, 0x77, 0x6D, 0x9F, 0x71]

eax = data[0] ^ 0x55  # = 0x24
esi = 0x0B
expected = [eax & 3]  # First plate: 0x24 & 3 = 0

for i in range(1, 8):
    prev = expected[-1]
    eax = (esi ^ 0x55 ^ data[i]) & 0xFF
    if prev & 1:          # If previous plate was odd
        eax = ((eax << 4) | (eax >> 4)) & 0xFF  # rol 4
    esi += 0x0B
    expected.append(eax & 3)

Result: [0, 1, 1, 2, 2, 3, 0, 1] → plate order 01122301

Secret Digit - Golden Prize (0x1b40)

When the correct sequence is entered, function 0x1b40 draws three golden lines (SDL_SetRenderDrawColor(0xFF, 0xD7, 0x00, 0xFF)):

Line 1: (210, 142) → (270, 142)   horizontal top
Line 2: (270, 142) → (270, 172)   vertical right (upper)
Line 3: (270, 172) → (270, 202)   vertical right (lower)

Visualized:

──────┐
      │
      │
      │

This is the digit 7.

Flag

upCTF{01122301_7}
CTFReverseWriteup