Use cutter to solve a simple crackme.

Information

  • category : reverse
  • points : 0

Description

Das Blech umdrehen!

1 file: maybe.bin

Writeup

Let's start by loading the binary with cutter and check the binary's information using the Dashboard window.

The binary is a 64 bit ELF. PIE, NX, Stack Canary are enabled and it's stripped.

We can check what functions have the program in the Functions tab.

Interesting… we have two init, and two fini (usually there are only one of them).

init\(x\) with \(x \in (0, 10)\) contains special code that is executed before the main function. While fini\(x\) are executed after the main.

Let's see what init1 does:

In this case I renamed by hand the variable i (right click on the variable -> Retype function local var, or press Y on the var) and I commented some lines (press ;).

The code is trivial, is reversing the string (badly) since the half of the characters will be symmetrically moved, while the other half will be lost:

Before init1:

junior-totally_the_flag_or_maybe_not

After:

ton_ebyam_ro_galf__flag_or_maybe_not

Now let's move into main:

The decompiled with r2ghidra-dec is the following:

undefined8 main(undefined8 argc, char **argv)
{
    int32_t iVar1;
    uint8_t uVar2;
    uint32_t uVar3;
    char **_argv;
    int64_t _argc;
    undefined8 i;
    
    i._0_4_ = 0;
    while ((int32_t)i < 0x24
    // start with i = 0) {
        "junior-totally_the_flag_or_maybe_not"[(int32_t)i + 0x40] = argv[1][(int32_t)i];
        uVar3 = (uint32_t)((int32_t)"junior-totally_the_flag_or_maybe_not"[(int32_t)i] >> 0x1f) >> 0x18;
        iVar1 = (((int32_t)"junior-totally_the_flag_or_maybe_not"[(int32_t)i] + uVar3 & 0xff) - uVar3) + 0x100;
        uVar2 = (uint8_t)(iVar1 >> 0x37);
        "junior-totally_the_flag_or_maybe_not"[(int32_t)i] = ((char)iVar1 + (uVar2 >> 1) & 0x7f) - (uVar2 >> 1);
        "junior-totally_the_flag_or_maybe_not"[(int32_t)i] = "junior-totally_the_flag_or_maybe_not"[(int32_t)i];
        i._0_4_ = (int32_t)i + 1;
    }
    sym.imp.puts(0x9b1); // str.wrong
    return 0;
}

As I showed in the precedent graph, the function will print or correct or wrong, but the decompiler shows that will be printed wrong unconditionally.

Why?

Because the program will check if the address of the string str.junior_totally is equal to the address of the string str.this_should, but there aren't instructions that modifies the first string's address, so this check will always puts("wrong").

Possible Problem

The string str.junior_totally could be modified by our input? (argv[1]).

There are two ways to verify if the code in the main change the bytes in the string.

  1. Understand the code, maybe reproduce the code in a c program and test it.
  2. Use Cutter's debugger (easier).

Let's start with 1, I used the decompiled code:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>

char totally [] = "this_should_totally_be_a_hering_on_a_kuchenblech";
// remember that the string is 'reversed'
char junior [64] = "ton_ebyam_ro_galf__flag_or_maybe_not";

int main(int argc, char **argv)
{
	int index = 0;
	u_int8_t var2;
	u_int32_t var3;
	int32_t var1;
	char c;
	printf("%d\n", (char *)((int64_t)(2 * 2 + 1) + 0x2010a0));
	while(index < 0x24)
	{
		junior[index + 0x40] = argv[1][(int32_t)index];
		var3 = (u_int32_t)((int32_t)junior[(int32_t)index] >> 0x1f) >> 0x18;
		var1 = (((int32_t)junior[(int32_t)index] + var3 & 0xff) - var3) + 0x100;
		var2 = (u_int8_t)(var1 >> 0x37);
		junior[(int32_t)index] = ((char)var1 + (var2 >> 1) & 0x7f) - (var2 >> 1);
		index += 1;
	}
	printf("%s\n", junior);
	return 0;
}
$ gcc test.c -o test

$ ./test aaaaaaaaaaaaaaaaaaaaaaaaaaaa
ton_ebyam_ro_galf__flag_or_maybe_not

$ ./test sknvdfvbusivndsnvdsovbsdovbdf
ton_ebyam_ro_galf__flag_or_maybe_not

$ ./test cdsklnclkdscev2984385205832905903285
ton_ebyam_ro_galf__flag_or_maybe_not

Nothing change, so probably it doesn't change the string in anyway. I personally studied the algorithm and I'm pretty sure that it doesn't change the input since there are a lots of shr which zeroize the various variable var\(x\).

The second option is easier, we can just put a breakpoint (press F2) after the first while, and see if the string changes.

And nothing change, perfect.

Wait ok… but it prints wrong.

As I said before there are other code which is executed after the main. Let's analyze the first part of fini1.

Basically it does:

str.junior_totally
=
str.junior_totally xor argv[1]

While the second part:

It compares the result from the first part with an array (str.002010a0), if they are the same the flag var will be set to 1 and it will print “correct”. To view this array we can use the hexdump (right click on 0x002010a0 -> 0x002010a0 -> new hexdump).

Then we can parse the array:

.

Now what

We can write a simple script to find the right argument to pass to the binary since the xor operation is very easy to invert.

Exploit

#!/usr/bin/env python3

arr = [0,30,0,26,0,0,0,54,0,10,0,16,0,84,0,0,0,1,0,51,0,23,0,28,0,0,0,9,0,
       20,0,30,0,57,0,52,0,42,0,5,0,4,0,4,0,9,0,61,0,3,0,23,0,60,0,5,0,62,
       0,20,0,3,0,3,0,54,0,15,0,78,0,85]

def main():
    totally = "ton_ebyam_ro_galf__flag_or_maybe_not"
    flag = ""
    for j in range(0, len(totally)):
        flag += chr(ord(totally[j]) ^ arr[(j * 2) + 1])
    print(flag)

if __name__ == '__main__':
    main()
./maybe.bin $(./exploit.py)
wrong!
aber es ist nur noch eine sache von sekunden!
correct!

Flag

junior-alles_nur_kuchenblech_mafia!!