UPX2000 - Root-Me CTF


The Root-Me CTF took place on october 21st-23th. At this occasion I released a challenge named UPX2000. Here is its writeup.

Reverse Engineering of the binary

This write up will be divided in two steps. First the reverse engineering of the binary and then the solving of the challenge.

First let's open it in IDA. IDA free will be just fine since it is a 64 bits binary. Here is the main function:

__int64 __fastcall main(int a1, char **a2, char **a3)
  signed int v4; // [rsp+4h] [rbp-3Ch]
  char s[8]; // [rsp+10h] [rbp-30h] BYREF
  __int64 v6; // [rsp+18h] [rbp-28h]
  __int64 v7; // [rsp+20h] [rbp-20h]
  __int64 v8; // [rsp+28h] [rbp-18h]
  __int64 v9; // [rsp+30h] [rbp-10h]
  unsigned __int64 v10; // [rsp+38h] [rbp-8h]

  v10 = __readfsqword(0x28u);
  *(_QWORD *)s = 0LL;
  v6 = 0LL;
  v7 = 0LL;
  v8 = 0LL;
  v9 = 0LL;
  puts("Enter your password:");
  fgets(s, 40, stdin);
  v4 = (unsigned int)mmap((void *)0x690000, 0x50uLL, 7, 34, 0, 0LL);
  if ( v4 == -1 )
    perror("Could not mmap");
    return 0xFFFFFFFFLL;
    ((void (*)(void))v4)();
    return 0LL;

We quickly understand a password is asked. Then there is a mmap call mapping a 0x50 bytes area in memory at adress 0x690000. It looks that this part is executable.
Then if the mapping is succesful, a function takes the password as parameter and then the code located on the area previously mapped is run using a call.

Here is the code of sub_80A.

__int64 __fastcall sub_80A(const char *a1)
  int i; // [rsp+14h] [rbp-1Ch]

  for ( i = 0; i <= 79; ++i )
    *(_BYTE *)(i + 690000h) = a1[i % (strlen(a1) - 1)] ^ byte_A40[i];
  return 0LL;

We quickly understand the parameter is a key and some data located at byte_A40 is xored with our key. Then the result is put in our previously mapped memory zone.

The initial statement asks us to recover the password that will display the correct string.


Let's extract the data using IDA. We can use that Shift+E shortcut to do so.

Since the code is called using a call we can assume the code is a function. Hence we will try to perform our plaintext attack on prologues and epilogues of x86_64 functions.
As a reminder here is what a prologue and an epilogue looks like in x86_64

push rbp
mov rbp, rsp 
add rsp, 0x30
sub rsp, 0x30
pop rbp

Let's do a little python script to xor our compiled prologue with our shellcode.

from pwn import * 

shellcode = bytes.fromhex('3029FA9C38E28F2B2DA6B37970086323DE261C16144129040729FA6138E2A3632DA6B0587A616323EC793B488F2952AB2DA6B3787061632354A63BBEB66163026529B4BB7A61636B6A643BFAB4213EA8')

print(xor("\x55\x48\x89\xE5\x48\x83\xC5\x30", shellcode))

That gives us b'easypaJ\x1bx\xee:\x9c8\x8b\xa6\x13\x8bn\x95\xf3\\\xc2\xec4Ras\x84pafSx\xee9\xbd2\xe2\xa6\x13\xb91\xb2\xad\xc7\xaa\x97\x9bx\xee:\x9d8\xe2\xa6\x13\x01\xee\xb2[\xfe\xe2\xa620a=^2\xe2\xa6[?,\xb2\x1f\xfc\xa2\xfb\x98'

And let's do likewise for the epilogue. (reversed so the \xc3 matches with the last character of the shellcode.)

print(xor("\x48\x83\xEC\x30\x5D\xC3"[::-1], shellcode[::-1]))

That gives us b"kc\x11Xys\xa77[\x8f\xe22x\xe9\x19\x89\x81+\xa2\xeb\x8e\xd7%\x1c\xe0>Q\x9c\xfb\xfbep\x9b\xbe\xaa\xc7\x8bfI\x00\xa0+\xa2'h\\%e\xa0\xfe\xd2\xd4\xe2\xb2\xeaZ4\xc5\xc2\\\xd5A\x162\xa0+\xcb-I_%e\xe8\xd2\xd2\xd4\x1f\xb2\xeam"
Don't forget to reverse it. We have ck at the end of the password !
Hence the password is easypack. We can verify it by running the binary and providing it the password:

It's the right password !
Flag: RM{easypack}

Show Comments