riftCTF - Rev 4 Writeup

The challenge

Value: 300 points

File Name for the challenge: chall4.elf

Message:

  1. find the correct password for the crackme to display the “Correct Password” message.
  2. your goal is not to make the app display “Correct Password” but to find the correct password which does that for you.
  3. brute-forcing won’t help but you can do whatever you want.
  4. don’t expose this challenge to a real work environment.
  5. flag format ritsCTF{<---flag-here--->}. Good Luck!

author - X3eRo0

Having a look at the decompiled binary, we see a lot of complexity has been introduced:


undefined8 realMain(void)

{
  byte bVar1;
  byte bVar2;
  int iVar3;
  size_t preDecodedFlag;
  long in_FS_OFFSET;
  uint local_ac;
  char password [64];
  byte encodedFlagData [88];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  puts("--=[ Very Hard Crackme ]=--");
  printf("passwd > ");
  fgets(password,0x40,stdin);
  encodedFlagData[0] = 0x6e;
  encodedFlagData[1] = 0xf0;
  encodedFlagData[2] = 0xec;
  encodedFlagData[3] = 2;
  encodedFlagData[4] = 0x6b;
  encodedFlagData[5] = 0x24;
  encodedFlagData[6] = 0xb1;
  encodedFlagData[7] = 0x31;
  encodedFlagData[8] = 6;
  encodedFlagData[9] = 0xeb;
  encodedFlagData[10] = 0x57;
  encodedFlagData[11] = 0xf6;
  encodedFlagData[12] = 0x5f;
  encodedFlagData[13] = 0x90;
  encodedFlagData[14] = 0x15;
  encodedFlagData[15] = 0xe6;
  encodedFlagData[16] = 0x1a;
  encodedFlagData[17] = 0x15;
  encodedFlagData[18] = 0x93;
  encodedFlagData[19] = 0x1f;
  encodedFlagData[20] = 0xba;
  encodedFlagData[21] = 0x43;
  encodedFlagData[22] = 0xd7;
  encodedFlagData[23] = 0x3a;
  encodedFlagData[24] = 0xd2;
  encodedFlagData[25] = 0x80;
  encodedFlagData[26] = 0x7a;
  encodedFlagData[27] = 0xc;
  encodedFlagData[28] = 0xab;
  encodedFlagData[29] = 0xcc;
  encodedFlagData[30] = 10;
  encodedFlagData[31] = 0xfa;
  encodedFlagData[32] = 0x93;
  encodedFlagData[33] = 0xdd;
  encodedFlagData[34] = 0xe8;
  encodedFlagData[35] = 0x96;
  encodedFlagData[36] = 0xa7;
  encodedFlagData[37] = 0x75;
  encodedFlagData[38] = 0xe9;
  encodedFlagData[39] = 0x1d;
  encodedFlagData[40] = 0xfc;
  encodedFlagData[41] = 0x68;
  encodedFlagData[42] = 0x7d;
  encodedFlagData[43] = 1;
  encodedFlagData[44] = 0x54;
  encodedFlagData[45] = 0x96;
  encodedFlagData[46] = 0x5a;
  encodedFlagData[47] = 0x85;
  encodedFlagData[48] = 0xcf;
  encodedFlagData[49] = 0xd2;
  encodedFlagData[50] = 200;
  encodedFlagData[51] = 0x9f;
  encodedFlagData[52] = 0xed;
  encodedFlagData[53] = 0x1c;
  encodedFlagData[54] = 0x6a;
  encodedFlagData[55] = 0x29;
  encodedFlagData[56] = 0xda;
  encodedFlagData[57] = 0xd1;
  encodedFlagData[58] = 0x30;
  encodedFlagData[59] = 0x82;
  encodedFlagData[60] = 0x7d;
  encodedFlagData[61] = 0x82;
  encodedFlagData[62] = 0xf1;
  encodedFlagData[63] = 0xeb;
  encodedFlagData[64] = 0xe0;
  encodedFlagData[65] = 0x24;
  encodedFlagData[66] = 0x15;
  encodedFlagData[67] = 0x6d;
  encodedFlagData[68] = 0x32;
  encodedFlagData[69] = 0x30;
  encodedFlagData[70] = 0x87;
  encodedFlagData[71] = 0x28;
  encodedFlagData[72] = 0x5c;
  encodedFlagData[73] = 0x85;
  local_ac = 0;
  while (local_ac < 0x4a) {
    bVar2 = (byte)local_ac;
    bVar1 = ~-~((encodedFlagData[local_ac] << 5 | encodedFlagData[local_ac] >> 3) + 99 ^ bVar2);
    bVar1 = (~((-bVar2 - ((-~(bVar1 << 7 | bVar1 >> 1) ^ 0x39) - 0x40 ^ 0x48) ^ bVar2) + 0x98 ^ 0xf3
              ) ^ bVar2) - bVar2 ^ bVar2;
    bVar1 = ~(~((bVar1 << 6 | bVar1 >> 2) - bVar2) ^ 0x42);
    bVar1 = ~-((((bVar1 * '\x02' | bVar1 >> 7) - 0x12 ^ 0x26) - bVar2 ^ bVar2) + 0x1f);
    bVar1 = (~(((-bVar2 - ((-((bVar1 << 7 | bVar1 >> 1) ^ 0x99) ^ bVar2) + bVar2 ^ bVar2) ^ 0x83) +
                bVar2 ^ 0x2b) + bVar2) ^ 0x62) - 0x25;
    encodedFlagData[local_ac] = bVar1 * -0x80 | bVar1 >> 1;
    local_ac = local_ac + 1;
  }
  preDecodedFlag = strlen((char *)encodedFlagData);
  iVar3 = decrypter(password,encodedFlagData,preDecodedFlag & 0xffffffff,encodedFlagData);
  if (iVar3 == 0) {
    puts("Correct Password...");
  }
  else {
    puts("Wrong Password...");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

And here the updated decrpyter function:

ulong decrypter(long password,long encodedFlag,int size)

{
  uint i;
  int j;
  
  i = 0;
  j = 0;
  while (((j < size && (*(char *)(password + j) != '\0')) && (*(char *)(encodedFlag + j) != '\0')))
  {
    i = i | (int)(char)(*(byte *)(encodedFlag + j) ^ *(byte *)(password + j));
    j = j + 1;
  }
  return (ulong)i;
}

We can see that the key has been omitted here and the expression only evaluates to 0, if the flag is xor-ed with itself. That’s an interesting fact. According to the code, the flag is stored in clear text.

I then decided to have a look at the value of rsi during the xor instruction. So I copied the relative address from BinaryNinja. 2 Next, I fired up gdb-peda, set a relative breakpoint at the xor instruction, let the program run, entered a dummy password and analysed the rsi register once the breakpoint was hit:

starti
brva 0x11b4
c
<some pass>
x/s $rsi

1

Flag: riftCTF{---=[Did_Y0U_kN0W_YoU_CoU1D_ActuallY_FinD_ThE_FlaG_1n_m3M0rY]=--}

About the author

All your $rip belong to us.