loading...

IOLI Crackmes - 0x03 to 0x05

aibhstin profile image Aibhstin ・4 min read

0x03

Analysing IOLI 0x03 with r2ghidra-dec in radare2 we see this as the main function:

undefined4 main(void)
{
    int32_t var_ch;
    undefined4 var_8h;
    int32_t var_4h;

    sym.imp.printf("IOLI Crackme Level 0x03\n");
    sym.imp.printf("Password: ");
    sym.imp.scanf(0x8048634, &var_4h);
    sym.test(var_4h, 0x52b24);
    return 0;
}

We see the function 'sym.test'. This is given as input the password supplied by the user, as well as the hardcoded constant '0x52b24'. We can evaluate this in radare2 and get the decimal equivalent:

[0x08048498]> ? 0x52b24
int32   338724
uint32  338724
hex     0x52b24
octal   01225444
unit    330.8K
segment 5000:0b24
string  "$+\x05"
fvalue: 338724.0
float:  0.000000f
double: 0.000000
binary  0b000001010010101100100100
trits   0t122012122100

Trying 338724 as the password:

IOLI Crackme Level 0x03
Password: 338724
Password OK!!! :)

0x04

Once again, analyzing crackme0x04 with r2ghidra in radare2 gives us:

undefined4 main(void)
{
    int32_t var_78h;

    sym.imp.printf("IOLI Crackme Level 0x04\n");
    sym.imp.printf("Password: ");
    sym.imp.scanf(0x8048682, &var_78h);
    sym.check((char *)&var_78h);
    return 0;
}

A single variable is given to a function called 'sym.check'. Analyzing this function with r2ghidra:

void sym.check(char *s)
{
    uint32_t uVar1;
    char var_dh;
    uint32_t var_ch;
    uint32_t var_8h;
    int32_t var_4h;

    var_8h = 0;
    var_ch = 0;
    while( true ) {
        uVar1 = sym.imp.strlen(s);
        if (uVar1 <= var_ch) break;
        var_dh = s[var_ch];
        sym.imp.sscanf(&var_dh, 0x8048638, &var_4h);
        var_8h = var_8h + var_4h;
        if (var_8h == 0xf) {
            sym.imp.printf("Password OK!\n");
            sym.imp.exit(0);
        }
        var_ch = var_ch + 1;
    }
    sym.imp.printf("Password Incorrect!\n");
    return;
}

This function is a bit more involved, so it's best to go through it line by line.

while( true ) {

The loop is infinite by default, so it relies on other conditions in order to terminate.

uVar1 = sym.imp.strlen(s);

uVar1 is set equal to the length of the string given as input to the function.

if (uVar1 <= var_ch) break;

If the length of the string is less than or equal to var_ch, we break out of the loop. var_ch is set equal to 0 prior to the loop and is incremented by one on each time around.

var_dh = s[var_ch];

var_dh is set equal to a character that's at the index equal to var_ch.

sym.imp.sscanf(&var_dh, 0x8048638, &var_4h);

We see here the function sscanf being used, likely to read in the character as an integer, storing the result in var_4h.

var_8h = var_8h + var_4h;

var_8h is then incremented by the value of var_4h.

if (var_8h == 0xf)

if var_8h is equal to 0xf (15 in decimal), we print out the victory message and exit. Otherwise:

var_ch = var_ch + 1;

var_ch is incremented.

Using this logic, we can determine that we must supply a numerical password where the digits sum to 15.

IOLI Crackme Level 0x04
Password: 96
Password OK!

Interestingly, because the logic ignores any other digits after it has summed to 15, there is an infinite number of acceptable passwords**. Such as:

IOLI Crackme Level 0x04
Password: 9653654356456456
Password OK!

** there actually isn't an infinite number of acceptable passwords, as the integer type used is fixed in size.

0x05

Again we are analyzing the main function with r2ghidra:

undefined4 main(void)
{
    int32_t var_78h;

    sym.imp.printf("IOLI Crackme Level 0x05\n");
    sym.imp.printf("Password: ");
    sym.imp.scanf(0x80486b2, &var_78h);
    sym.check((char *)&var_78h);
    return 0;
}

Very similar to the last crackme, we need to analyze the check function.

void sym.check(char *s)
{
    uint32_t uVar1;
    char var_dh;
    uint32_t var_ch;
    uint32_t var_8h;
    int32_t var_4h;

    var_8h = 0;
    var_ch = 0;
    while( true ) {
        uVar1 = sym.imp.strlen(s);
        if (uVar1 <= var_ch) break;
        var_dh = s[var_ch];
        sym.imp.sscanf(&var_dh, 0x8048668, &var_4h);
        var_8h = var_8h + var_4h;
        if (var_8h == 0x10) {
            sym.parell(s);
        }
        var_ch = var_ch + 1;
    }
    sym.imp.printf("Password Incorrect!\n");
    return;
}

This is almost identical to the last example, except once a sum has been reached (16 in this case), the string is passed to a function called 'sym.parell'.

Analyzing this with r2ghidra:

void sym.parell(char *s)
{
    int32_t var_4h;

    sym.imp.sscanf(s, 0x8048668, &var_4h);
    if ((var_4h & 1U) == 0) {
        sym.imp.printf("Password OK!\n");
        sym.imp.exit(0);
    }
    return;
}

The string s is read as an integer. This integer is then bitwise AND'd with 1U (A literal unsigned integer with a value of 1). The bitwise AND must produce a 0 in order to produce the victory message. We know we need the sum to be 16, so we can calculate a valid passcode. First trying 97:

[0x08048484]> ? 97 & 1U
int32   1
uint32  1
hex     0x1
octal   01
unit    1
segment 0000:0001
string  "\x01"
fvalue: 1.0
float:  0.000000f
double: 0.000000
binary  0b00000001
trits   0t1

97 is no good. Let's try 88:

[0x08048484]> ? 88 & 1U
int32   0
uint32  0
hex     0x0
octal   00
unit    0
segment 0000:0000
string  "\0"
fvalue: 1.0
float:  0.000000f
double: 0.000000
binary  0b00000000
trits   0t0

It looks like 88 is a valid passcode.

IOLI Crackme Level 0x05
Password: 88
Password OK!

Conclusion: The r2ghidra-dec plugin for radare2 makes reverse engineering a lot easier. Reading assembly is a necessary skill but figuring out a program is a lot more painless when using a good decompiler like the ghidra one used in this post.

Posted on by:

aibhstin profile

Aibhstin

@aibhstin

I'm an Ethical Hacking & Cybersecurity student and a Haskell programmer.

Discussion

markdown guide