Pharaohs Curse

0xFun CTFby smothy

Pharaoh's Curse - Rev

Points: 750 | Flag: 0xfun{ph4r40h_vm_1nc3pt10n} | Solved by: Smothy @ 0xN1umb

pharaoh vibes

what we got

two files: tomb_guardian (ELF binary) and sacred_chamber.7z (password-protected archive). challenge description says some stuff about pharaohs and "speaking the old tongue". classic rev, lets go

the solve

part 1 - tomb_guardian (VM #1)

threw it into IDA. stripped binary, PIE, full protections, and has ptrace anti-debug. cute

the main function is a bytecode VM dispatcher:

c
// dispatch loop - classic switch-case VM
for (i = 0; i < program_length; i = PC) {
    handler_table[bytecode[i]](vm_state);
    if (halt_flag) break;
}

reversed all 19 opcode handlers:

opcodeoperation
0x00NOP
0x01PUSH_IMM
0x02POP_REG
0x10ADD
0x12XOR (with anti-debug key)
0x20CMP_EQ
0x31JZ
0x40GETC (stdin)
0x41PUTC (stdout)
0xFEHALT_OK
0xFFHALT_ERR

the anti-debug is sneaky - ptrace(PTRACE_TRACEME) sets a XOR key to 0xFF if debugger detected, which corrupts the XOR operation. if no debugger, key stays 0x00 so XOR is clean. nice try lol

the bytecode has two phases:

phase 1 - input validation:

GETC ; read char PUSH_IMM XX ; push constant XOR ; xor input with constant PUSH_IMM YY ; push expected CMP_EQ ; compare JZ fail ; jump to "No." if wrong

so input ^ XX == YY means input = XX ^ YY. extracted all 11 pairs:

python
pairs = [(0xAA,0x9A), (0xBB,0xCB), (0xCC,0xFF), (0xDD,0xB3),
         (0xEE,0xB1), (0x11,0x62), (0x22,0x11), (0x33,0x40),
         (0x44,0x70), (0x55,0x38), (0x66,0x55)]
password = ''.join(chr(a^b) for a,b in pairs)
# => "0p3n_s3s4m3"

phase 2 - output message (ADD pairs into PUTC):

The sacred chamber awaits: Kh3ops_Pyr4m1d

"open sesame" in l33t and a cheops pyramid reference. the password Kh3ops_Pyr4m1d unlocks the 7z

$ 7z x -pKh3ops_Pyr4m1d sacred_chamber.7z

out comes hiero_vm and challenge.hiero. oh no. VM inception

part 2 - hiero_vm + challenge.hiero (VM #2)

big brain time

the .hiero file is written in actual hieroglyphic unicode characters. ngl this is kinda beautiful:

š“‹“ 𓁹 š’€€ š“ š“‹“ 𓁹 𒀁 š“ ... 𓁹 𒀆 š“‘€ 𓁹 𒀇 š“‘€ š“ƒ­ 𓁹 š’ƒ˜ š“ˆ– 𓉐 š’‚„ 𒀁

instead of reversing the 351KB Rust binary (lmao no thanks), i just pattern-matched the hieroglyphic program structure:

instruction set (hieroglyphic edition):

  • š“‹“ = READ input char
  • 𓁹 X = LOAD value/memory[X]
  • š“ = STORE
  • š“‘€ = PUSH to stack
  • š“ƒ­ = ADD (the lion does math apparently)
  • š“ˆ– = COMPARE
  • 𓉐 X Y = conditional JUMP
  • š“Œ³ = SUCCESS
  • š“Æ = END

cuneiform characters (U+12000 base) encode both memory indices and literal values

the program:

  1. reads 27 chars into mem[0..26]
  2. checks 24 pairwise ADD constraints: mem[i] + mem[j] == expected

since flag format is 0xfun{...}, we know mem[0..5] = 0xfun{ and mem[26] = }. that pins the first 6 chars and gives us enough info to solve the constraint chain

extracted all 24 constraints:

mem[6] + mem[7] = 0xD8 mem[6] + mem[10] = 0xA4 (cross) mem[7] + mem[8] = 0x9C mem[8] + mem[15] = 0xA1 (cross) mem[8] + mem[9] = 0xA6 mem[12] + mem[20] = 0x9B (cross) mem[9] + mem[10] = 0xA6 mem[15] + mem[23] = 0x9E (cross) mem[10] + mem[11] = 0x64 mem[7] + mem[18] = 0xD6 (cross) mem[11] + mem[12] = 0x98 mem[12] + mem[13] = 0xC7 ...

the cross-constraints + sequential chain = system of equations. used the cross constraint mem[6]+mem[10]=0xA4 combined with the chain mem[6]+mem[7]=0xD8, mem[9]+mem[10]=0xA6:

python
# 0xD8 + 0x9C - 2*mem[7] ≔ 0xA4 (mod 256)
# => mem[7] = 0x68 = 'h'
# then propagate through the whole chain

solved in like 5 lines of python. all 24 constraints satisfied:

p h 4 r 4 0 h _ v m _ 1 n c 3 p t 1 0 n $ echo -n "0xfun{ph4r40h_vm_1nc3pt10n}" | ./hiero_vm challenge.hiero The ancient seals accept your offering

flag

0xfun{ph4r40h_vm_1nc3pt10n}

VM inside a VM. pharaoh themed. hieroglyphic programming language. "inception" in the flag. SwitchCaseAdvocate went hard on this one fr

lets go


smothy out āœŒļø