SarCTF 2020 Crossw0rd

3 minute read

This is one of the reversing problems that I solved during SarCTF a couple months back in February of 2020. I decided to write this up since I believe it to a good beginner problem for people looking to get into reverse engineering.

Problem Description

While the children were playing toys, Sherlock was solving crosswords in large volumes.

Reversing the Binary

Using file we can see we’re given an unstripped 64-bit ELF file: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=2e45dc319e3736db1643abb283b0ed9a18681261, not stripped. Running the binary, we’re presented with the following output:

➜  sarctf ./crossw0rd 
Welcome. You're in function check. Please Enter a password to continue. 1 attempt remaining:
password
Wrong password! Your attempt is over.

Since we need to reverse engineer the executable to get the password, let’s go ahead and throw this in a disassembler/decompiler of your choice, I’ll be using Ghidra.

After analyzing the binary, we can see several exported symbols. Let’s start with main():

Main

undefined8 main(void)
{
  check();
  return 0;
}

Only one call to follow so let’s look at check(). We can clearly see that this function is responsible for prompting the user, taking input, and finally verifying said input. check() also calls another function named e(), and checks against it’s return value. If it’s FALSE, we lose.

check

void check(void)

{
  char cVar1;
  long in_FS_OFFSET;
  char local_28 [24];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  puts(
      "Welcome. You\'re in function check. Please Enter a password to continue. 1 attemptremaining:"
      );
  scanf("%s",local_28);
  cVar1 = e(local_28);
  if (cVar1 == '\0') {
    puts("Wrong password! Your attempt is over.");
  }
  else {
    puts("You cracked the system!");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

E

ulong e(char *param_1)

{
  byte bVar1;
  char cVar2;
  
  if ((((param_1[7] == '5') && (param_1[0x11] == 'g')) && (param_1[2] == 'A')) &&
     (cVar2 = b(param_1), cVar2 != '\0')) {
    bVar1 = 1;
  }
  else {
    bVar1 = 0;
  }
  return (ulong)bVar1;
}

e() checks 4 things: 8th character must equal 5, 18th character must equal g, string passed into this function must not be NULL, and b() must return TRUE. Let’s take a look at what b() expects.

This time, b() checks 5 things: 16th character must equal i, 10th character must equal r, 2nd character must equal L, d() must return TRUE, and string passed in cannot be NULL.

B

ulong b(char *param_1)

{
  byte bVar1;
  char cVar2;
  
  if ((((param_1[0xf] == 'i') && (param_1[9] == 'r')) && (param_1[1] == 'L')) &&
     (cVar2 = d(param_1), cVar2 != '\0')) {
    bVar1 = 1;
  }
  else {
    bVar1 = 0;
  }
  return (ulong)bVar1;
}

This continues on for 4 more functions, the steps moving forward are exactly the same. There are 8 functions in total worth looking at: main(), check(), and a()-f().

D

ulong d(char *param_1)

{
  byte bVar1;
  char cVar2;
  
  if ((((param_1[10] == '3') && (param_1[0x12] == '}')) && (param_1[6] == 'a')) &&
     (cVar2 = f(param_1), cVar2 != '\0')) {
    bVar1 = 1;
  }
  else {
    bVar1 = 0;
  }
  return (ulong)bVar1;
}

F

ulong f(char *param_1)

{
  byte bVar1;
  char cVar2;
  
  if ((((*param_1 == 'F') && (param_1[0xe] == '5')) && (param_1[0x10] == 'n')) &&
     (cVar2 = c(param_1), cVar2 != '\0')) {
    bVar1 = 1;
  }
  else {
    bVar1 = 0;
  }
  return (ulong)bVar1;
}

C

ulong c(char *param_1)

{
  byte bVar1;
  char cVar2;
  
  if ((((param_1[3] == 'G') && (param_1[0xb] == 'v')) && (param_1[5] == '3')) &&
     (cVar2 = a(param_1), cVar2 != '\0')) {
    bVar1 = 1;
  }
  else {
    bVar1 = 0;
  }
  return (ulong)bVar1;
}

A

undefined8 a(char *param_1)

{
  undefined8 uVar1;
  
  if ((((param_1[4] == '{') && (param_1[0xc] == '3')) && (param_1[8] == 'y')) &&
     (param_1[0xd] == 'r')) {
    uVar1 = 1;
  }
  else {
    uVar1 = 0;
  }
  return uVar1;
}

Putting It Together

After reversing through functions a()-f(), you should be able to string together the expected password. We run the binary one more time, giving it the correct password, and we get the flag: FLAG{3a5yr3v3r5ing}

Author: medarkus

Updated: