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”
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
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:
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:
./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.
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
$rax : 0x0000555555755020 → "ton_ebyam_ro_galf__flag_or_maybe_not"
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
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:
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:
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.
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!!
./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.