helithumper re

The goal of this challenge is to get the flag. This was a challenge made by Helithumper (github.com/helithumper). Let's take a look at the binary:

$    ./rev
Welcome to the Salty Spitoon™, How tough are ya?
Tough as Joseph, but not Jotaro
Yeah right. Back to Weenie Hut Jr™ with ya
$    file rev
rev: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e4dbcb1281821db359d566c68fea7380aeb27378, for GNU/Linux 3.2.0, not stripped

So we can see that we are dealing with a 64 bit binary. When we run it, it prompts us for input. What is probably going on here, is it is scanning in data, and checking it. In order to get the flag, we will probably need to pass that check.

When we take a look at the main function in Ghidra, we see this (btw I cleaned up the code a little bit, what you see will probably look a little different):

ulong main(void)

{
  int check;
  void *ptr;
 
  ptr = calloc(0x32,1);
  puts("Welcome to the Salty Spitoon™, How tough are ya?");
  __isoc99_scanf(&DAT_0010203b,ptr);
  check = validate(ptr);
  if (check == 0) {
    puts("Yeah right. Back to Weenie Hut Jr™ with ya");
  }
  else {
    puts("Right this way...");
  }
  return (ulong)(check == 0);
}

So we can see that it is scanning in data to ptr, then running the validate function. We can see that the validate function does this:

undefined8 validate(char *input)

{
  long lVar1;
  size_t inputLen;
  undefined8 returnValue;
  long in_FS_OFFSET;
  int i;
  int checkValues [4];
 
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  checkValues[0] = 0x66;
  checkValues[1] = 0x6c;
  checkValues[2] = 0x61;
  checkValues[3] = 0x67;
  inputLen = strlen(input);
  i = 0;
  do {
    if ((int)inputLen <= i) {
      returnValue = 1;
LAB_001012b7:
      if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
        __stack_chk_fail();
      }
      return returnValue;
    }
    if ((int)input[(long)i] != checkValues[(long)i]) {
      returnValue = 0;
      goto LAB_001012b7;
    }
    i = i + 1;
  } while( true );
}

So we can see that it essentially takes our input, and runs it though a while true loop. For each iteration of this loop, we see that it checks one character of our input against a character in checkValues. The character it checks depends on which iteration of the loop it is. For instance iteration 0 will check the character of our input at index 0, iteration 2 will check the character of our input at index 2, and so on:

    if ((int)input[(long)i] != checkValues[(long)i]) {
      returnValue = 0;
      goto LAB_001012b7;

We also see there is a termination condition where if the iteration count exceeds the length of the string, it will exit. That is because it has finished checking the string:

    if ((int)inputLen <= i) {
      returnValue = 1;

Now this check will either return a 1, or a 0. In order to solve this challenge, we need it to ouput a 1. In order for that to happen, we can't fail any of the character checks. In order for that to happen our input needs to be the same as the characters it checks it against. Looking at the code, we see that the first four characters it sets. However looking at the assembly code shows us that there is more:

        00101205 c7 45 c0        MOV        dword ptr [RBP + checkValues[0]],0x66
                 66 00 00 00
        0010120c c7 45 c4        MOV        dword ptr [RBP + checkValues[1]],0x6c
                 6c 00 00 00
        00101213 c7 45 c8        MOV        dword ptr [RBP + checkValues[2]],0x61
                 61 00 00 00
        0010121a c7 45 cc        MOV        dword ptr [RBP + checkValues[3]],0x67
                 67 00 00 00
        00101221 c7 45 d0        MOV        dword ptr [RBP + local_38],0x7b
                 7b 00 00 00
        00101228 c7 45 d4        MOV        dword ptr [RBP + local_34],0x48
                 48 00 00 00
        0010122f c7 45 d8        MOV        dword ptr [RBP + local_30],0x75
                 75 00 00 00
        00101236 c7 45 dc        MOV        dword ptr [RBP + local_2c],0x43
                 43 00 00 00
        0010123d c7 45 e0        MOV        dword ptr [RBP + local_28],0x66
                 66 00 00 00
        00101244 c7 45 e4        MOV        dword ptr [RBP + local_24],0x5f
                 5f 00 00 00
        0010124b c7 45 e8        MOV        dword ptr [RBP + local_20],0x6c
                 6c 00 00 00
        00101252 c7 45 ec        MOV        dword ptr [RBP + local_1c],0x41
                 41 00 00 00
        00101259 c7 45 f0        MOV        dword ptr [RBP + local_18],0x62
                 62 00 00 00
        00101260 c7 45 f4        MOV        dword ptr [RBP + local_14],0x7d
                 7d 00 00 00

From this, we can get this list of bytes that our input needs to be:

0x66
0x6c
0x61
0x67
0x7b
0x48
0x75
0x43
0x66
0x5f
0x6c
0x41
0x62
0x7d

We can use python to convert them into ascii like so:

$    python
Python 2.7.16 (default, Apr  6 2019, 01:42:57)
[GCC 8.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> x = [0x66, 0x6c, 0x61, 0x67, 0x7b, 0x48, 0x75, 0x43, 0x66, 0x5f, 0x6c, 0x41, 0x62, 0x7d]
>>> input = ""
>>> for i in x:
...     input += chr(i)
...
>>> input
'flag{HuCf_lAb}'

So we can see that our needed input is flag{HuCf_lAb} which is probably the flag (we can tell this, since the flag is usually in a format similar to flag{x}, with x being some string):

$    ./rev
Welcome to the Salty Spitoon™, How tough are ya?
flag{HuCf_lAb}
Right this way...

Just like that we got the flag!