Posts 36C3 Maybe
Post
Cancel

36C3 Maybe

0x0. Introduction

This Challenge was part of the 36C3 Junior CTF. It was a reverse engeneering challenge with a difficulty rating of medium. You were only given a binary.

0x1. Something is weird here

The first thing I always do for reverse engeneering challenges is to look at the binary with “file”

1
2
file chal1-a27148a64d65f6d6fd062a09468c4003 
chal1-a27148a64d65f6d6fd062a09468c4003: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=752335805e2a2991272d32fad8b76fcfb155f8ce, stripped

As you can see it is a 64 bit binary, dynamically linked and stripped. That means we can not just look for a “main” function because there are no symbols in this binary.
But atleast its dynamically linked and not static, this way we can still identify libc functions like “puts”. To find the main function I used a neat little trick that I got from @liveoverflow, it goes like this:
We can always find the “entry” function

1
2
3
4
5
6
7
8
9
10
11
12
void entry(undefined8 param_1,undefined8 param_2,undefined8 param_3)

{
  undefined8 in_stack_00000000;
  undefined auStack8 [8];
  
  __libc_start_main(FUN_001007c0,in_stack_00000000,&stack0x00000008,&LAB_001008c0,&DAT_00100930,
                    param_3,auStack8);
  do {
                    /* WARNING: Do nothing block with infinite loop */
  } while( true );
}

There we see “__libc_start_main” and the first argument of it is another function. That function, “FUN_001007c0”, is our main function

Then we look at the main function of the program in ghidra and we see the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
undefined8 FUN_001007c0(undefined8 param_1,long param_2)

{
  int iVar1;
  byte bVar2;
  uint uVar3;
  int local_10;
  
  local_10 = 0;
  while (local_10 < 0x24) {
    s_junior-totally_the_flag_or_maybe_00301020[local_10 + 0x40] =
         *(char *)((long)local_10 + *(long *)(param_2 + 8));
    uVar3 = (uint)((int)s_junior-totally_the_flag_or_maybe_00301020[local_10] >> 0x1f) >> 0x18;
    iVar1 = (((int)s_junior-totally_the_flag_or_maybe_00301020[local_10] + uVar3 & 0xff) - uVar3) +
            0x100;
    bVar2 = (byte)(iVar1 >> 0x37);
    s_junior-totally_the_flag_or_maybe_00301020[local_10] =
         ((char)iVar1 + (bVar2 >> 1) & 0x7f) - (bVar2 >> 1);
    s_junior-totally_the_flag_or_maybe_00301020[local_10] =
         s_junior-totally_the_flag_or_maybe_00301020[local_10];
    local_10 = local_10 + 1;
  }
  puts("wrong!");
  return 0;
}

And no other function gets called here, so the program should really only ever output “wrong!”.
If we run the program tho, things look a bit different:

1
2
3
./chal1-a27148a64d65f6d6fd062a09468c4003 AAAA
wrong!
aber es ist nur noch eine sache von sekunden!

So where the hell does that second string come from?

0x2 Init and Fini

To find the missing parts of the program we had to look into Init_1 and Fini_1.
These Functions are usually generated by the compiler, but for this challenge they were manipulated.
Usually there is also only one Init and one Fini function, in this case there were 2 of each.

1
2
3
4
5
6
7
8
9
10
11
12
13
void _INIT_1(void)

{
  int local_c;
  
  local_c = 0;
  while (local_c < 0x24) {
    s_junior-totally_the_flag_or_maybe_00301020[local_c] =
         s_junior-totally_the_flag_or_maybe_00301020[0x23 - local_c];
    local_c = local_c + 1;
  }
  return;
}

Here, in _INIT_1, it changes the first part of “junior-totally_the_flag_or_maybe”.
After setting a breakpoint in gdb and single-steping through the main function I could confirm this because I found the altered string

1
$rax   : 0x0000555555755020  →  "ton_ebyam_ro_galf__flag_or_maybe_not"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void _FINI_1(void)

{
  bool bVar1;
  int local_14;
  int local_c;
  
  local_14 = 0;
  while (local_14 < 0x24) {
    s_junior-totally_the_flag_or_maybe_00301020[local_14] =
         s_junior-totally_the_flag_or_maybe_00301020[local_14] ^ s__00301060[local_14];
    local_14 = local_14 + 1;
  }
  bVar1 = true;
  local_c = 0;
  while (local_c < 0x24) {
    if (s_junior-totally_the_flag_or_maybe_00301020[local_c] != (&DAT_003010a0)[local_c * 2 + 1]) {
      bVar1 = false;
    }
    local_c = local_c + 1;
  }
  sleep(10);
  puts("aber es ist nur noch eine sache von sekunden!");
  if (bVar1) {
    puts("correct!");
  }
  return;
}

And there in Fini_1 we found the mising output “aber es ist nur noch eine sache von Sekunden”.

0x3 Exploit

I renamed some variables to make _FINI_1 more readable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void _FINI_1(void)

{
  bool needs_to_be_true;
  int other_counter;
  int counter;
  
  other_counter = 0;
  while (other_counter < 0x24) {
    s_junior-totally_the_flag_or_maybe_00301020[other_counter] =
         s_junior-totally_the_flag_or_maybe_00301020[other_counter] ^ s__00301060[other_counter];
    other_counter = other_counter + 1;
  }
  needs_to_be_true = true;
  counter = 0;
  while (counter < 0x24) {
    if (s_junior-totally_the_flag_or_maybe_00301020[counter] != (&global_array)[counter * 2 + 1]) {
      needs_to_be_true = false;
    }
    counter = counter + 1;
  }
  sleep(10);
  puts("aber es ist nur noch eine sache von sekunden!");
  if (needs_to_be_true) {
    puts("correct!");
  }
  return;
}

We obviously want the “correct!” output to happen, the only information we are still missing is this global array.
If we look at it in Ghidra, we see the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
                             global_array                                    XREF[1]:     _FINI_1:00100775(*)  
        003010a0 00              ??         00h
                             DAT_003010a1                                    XREF[1]:     _FINI_1:0010077c(R)  
        003010a1 1e              ??         1Eh
        003010a2 00              ??         00h
        003010a3 1a              ??         1Ah
        003010a4 00              ??         00h
        003010a5 00              ??         00h
        003010a6 00              ??         00h
        003010a7 36              ??         36h    6
        003010a8 00              ??         00h
        003010a9 0a              ??         0Ah
        003010aa 00              ??         00h
        003010ab 10              ??         10h
        003010ac 00              ??         00h
        003010ad 54              ??         54h    T
        003010ae 00              ??         00h
        003010af 00              ??         00h
        003010b0 00              ??         00h
        003010b1 01              ??         01h
        003010b2 00              ??         00h
        003010b3 33              ??         33h    3
        003010b4 00              ??         00h
        003010b5 17              ??         17h
        003010b6 00              ??         00h
        003010b7 1c              ??         1Ch
        003010b8 00              ??         00h
        003010b9 00              ??         00h
        003010ba 00              ??         00h
        003010bb 09              ??         09h
        003010bc 00              ??         00h
        003010bd 14              ??         14h
        003010be 00              ??         00h
        003010bf 1e              ??         1Eh
        003010c0 00              ??         00h
        003010c1 39              ??         39h    9
        003010c2 00              ??         00h
        003010c3 34              ??         34h    4
        003010c4 00              ??         00h
        003010c5 2a              ??         2Ah    *
        003010c6 00              ??         00h
        003010c7 05              ??         05h
        003010c8 00              ??         00h
        003010c9 04              ??         04h
        003010ca 00              ??         00h
        003010cb 04              ??         04h
        003010cc 00              ??         00h
        003010cd 09              ??         09h
        003010ce 00              ??         00h
        003010cf 3d              ??         3Dh    =
        003010d0 00              ??         00h
        003010d1 03              ??         03h
        003010d2 00              ??         00h
        003010d3 17              ??         17h
        003010d4 00              ??         00h
        003010d5 3c              ??         3Ch    <
        003010d6 00              ??         00h
        003010d7 05              ??         05h
        003010d8 00              ??         00h
        003010d9 3e              ??         3Eh    >
        003010da 00              ??         00h
        003010db 14              ??         14h
        003010dc 00              ??         00h
        003010dd 03              ??         03h
        003010de 00              ??         00h
        003010df 03              ??         03h
        003010e0 00              ??         00h
        003010e1 36              ??         36h    6
        003010e2 00              ??         00h
        003010e3 0f              ??         0Fh
        003010e4 00              ??         00h
        003010e5 4e              ??         4Eh    N
        003010e6 00              ??         00h
        003010e7 55              ??         55h    U
        003010e8 00              ??         00h

Which might look a bit confusing if you are doing something like this for the first time.
It Really only lists all the elements of the array tho, so this can be read as:

1
global_array = [0x1E ,0x00 ,0x1A ,0x00 ,0x00 ,0x00 ,0x36 ,0x00 ,0x0A ,0x00 ,0x10 ,0x00 ,0x54 ,0x00 ,0x00 ,0x00 ,0x01 ,0x00 ,0x33 ,0x00 ,0x17 ,0x00 ,0x1C ,0x00 ,0x00 ,0x00 ,0x09 ,0x00 ,0x14 ,0x00 ,0x1E ,0x00 ,0x39 ,0x00 ,0x34 ,0x00 ,0x2A ,0x00 ,0x05 ,0x00 ,0x04 ,0x00 ,0x04 ,0x00 ,0x09 ,0x00 ,0x3D ,0x00 ,0x03 ,0x00 ,0x17 ,0x00 ,0x3C ,0x00 ,0x05 ,0x00 ,0x3E ,0x00 ,0x14 ,0x00 ,0x03 ,0x00 ,0x03 ,0x00 ,0x36 ,0x00 ,0x0F ,0x00 ,0x4E ,0x00 ,0x55 ,0x00]

To find the flag we had to xor the altered string that we found with gdb and the global_array, which we had to slice before.

1
2
3
4
5
6
7
8
9
10
11
12
13
not_flag_string = "ton_ebyam_ro_galf__flag_or_maybe_not"

global_array = [0x1E ,0x00 ,0x1A ,0x00 ,0x00 ,0x00 ,0x36 ,0x00 ,0x0A ,0x00 ,0x10 ,0x00 ,0x54 ,0x00 ,0x00 ,0x00 ,0x01 ,0x00 ,0x33 ,0x00 ,0x17 ,0x00 ,0x1C ,0x00 ,0x00 ,0x00 ,0x09 ,0x00 ,0x14 ,0x00 ,0x1E ,0x00 ,0x39 ,0x00 ,0x34 ,0x00 ,0x2A ,0x00 ,0x05 ,0x00 ,0x04 ,0x00 ,0x04 ,0x00 ,0x09 ,0x00 ,0x3D ,0x00 ,0x03 ,0x00 ,0x17 ,0x00 ,0x3C ,0x00 ,0x05 ,0x00 ,0x3E ,0x00 ,0x14 ,0x00 ,0x03 ,0x00 ,0x03 ,0x00 ,0x36 ,0x00 ,0x0F ,0x00 ,0x4E ,0x00 ,0x55 ,0x00]

flag = ''

sliced_array = global_array[::2]
for i in range(len(sliced_array)):
    ascii_number = sliced_array[i] ^ ord(not_flag_string[i])
    flag += chr(ascii_number)


print(flag)

This prints: junior-alles_nur_kuchenblech_mafia!!

1
2
3
4
./chal1-a27148a64d65f6d6fd062a09468c4003 'junior-alles_nur_kuchenblech_mafia!!'
wrong!
aber es ist nur noch eine sache von sekunden!
correct!

0x04 Closing words

This was my first time participating in a CTF “on site”, had a great time at the event mostly thanks to the people from @hackthissite who invited me to play with them. Also thanks to the @hxpctf guys for hosting this amazing CTF, looking forward to solve more challenges next year.

This post is licensed under CC BY 4.0 by the author.