Alice and I recently participated in PlaidCTF 2020 and solved the challenge reee. You can download the binary here:

reee.zip

Setup

reee is a reversing challenge / crackme. Run the program with the flag as input, and you're told if you got it right or wrong:

root@4cc0f48f74a2:/ctf/reee# ./reee 'AAAA'
Wrong!

We used Docker on macOS — see here for more on how to effectively use Docker for CTFs.

The flag format is pctf{$FLAG}.

Reverse Engineering

We began by opening the binary in IDA. Our goal was to trace the input to determine how it affects the result. Interestingly, only argc is checked to ensure an argument is supplied — the input buffer isn't touched by main otherwise.

At this point, we ran the program in GDB and located our buffer on the stack, then setting a watchpoint to see which instruction accessed our buffer. This location turns out to be 0x40071a; which, in IDA, appears as a mix of garbage instructions and data. Looking more carefully, we realized that 0x40071a is inside an encrypted function that is decrypted at runtime.

With this context, we can then build a better understanding of main:

int main(int argc, char **argv, char **envp)
{
  int result; // eax
  int byte_offset; // [rsp+18h] [rbp-28h]
  int i_loop; // [rsp+1Ch] [rbp-24h]

  if ( argc <= 1 )
    puts("need a flag!");

  for ( i_loop = 0; i_loop <= 31336; ++i_loop )
  {
    for ( byte_offset = 0; byte_offset <= 551; ++byte_offset )
      *((_BYTE *)&oracle + byte_offset) = decrypt_byte_of_oracle(*((_BYTE *)&call_me + byte_offset));
  }

  if ( ((__int64 (*)(void))oracle)() )
    result = puts("Correct!");
  else
    result = puts("Wrong!");
  return result;
}

Extracting the runtime-decrypted code

We ran the program under GDB and dumped the decrypted function with:

dump binary memory oracle.bin 0x4006E5 0x40090C

oracle was our name for the validation routine. The starting address was determined by looking at where oracle begins. The size of oracle is 551 (decimal). Thus, the end address is start + len.

We then created a new IDA project with oracle.bin. When loading the function, we had to configure the segment offset of 0x4006E5 to preserve the original addresses from reee.bin.

Anti-disassembler techniques

oracle contained anti-disassembler techniques which had to be dealt with.

  1. There were a number of "jump-in-the-middle" instructions, which is a jmp that targeted itself. The jump instruction jumps to the middle of the jmp itself, throwing off linear disassembly. To fix this, nop out the first byte of the jmp