Infos


Description

“Password changes every millisecond, brute force impossible… think outside the password.”


Initial Reconnaissance

As always, we’ll first run the strings command:

/lib64/ld-linux-x86-64.so.2
snprintf
puts
clock_gettime
__libc_start_main
__cxa_finalize
__isoc99_scanf
strcmp

We get two hints running strings

  • strcmp is used, maybe to compare the input to a password?
  • scanf is used, which is known for its security vulnerability when it comes to buffer overflows

Static Analysis | IDA

Our main functions just calls one function, which I will reference to as main from now on:

int sub_1311()
{
  char input[8]; // [rsp+4h] [rbp-Ch] BYREF
  int successBool; // [rsp+Ch] [rbp-4h] BYREF

  successBool = 0;
  printf("Enter password: ");
  __isoc99_scanf("%s");
  mainCompareLogic(input, &successBool);
  if ( successBool == 1 )
    return print_success_msg();
  else
    return puts("Authentication Failed");
}

and here’s the mainCompareLogic function, althought we won’t be going too deep into the function:

int __fastcall mainCompareLogic(const char *input, _DWORD *a2)
{
  int cmp_result; // eax
  char s2[32]; // [rsp+10h] [rbp-140h] BYREF
  struct timespec tp; // [rsp+30h] [rbp-120h] BYREF
  char inputAndTime[264]; // [rsp+40h] [rbp-110h] BYREF
  time_t v6; // [rsp+148h] [rbp-8h]

  v6 = time(0);
  clock_gettime(0, &tp);
  snprintf(inputAndTime, 0x100u, "%s_%ld%09ld", input, tp.tv_sec, tp.tv_nsec);
  strcpy(s2, "admin_1732276800123456789");
  cmp_result = strcmp(inputAndTime, s2);
  if ( !cmp_result )
  {
    *a2 = 1;
    return (int)a2;
  }
  return cmp_result;
}

Looking at the main, we already see the function we want the program to reach, the “print_success_msg” function. print_success_msg does nothing apart from printing “Access Granted!”.

We have multiple options here:

  • simply patch the successBool check to always return true
  • load our own strcmp function with LD_PRELOAD which already returns true
  • change our local time to match the hardcoded timestamp (1732276800 seconds and 123456789 nanoseconds), which is practically infeasible
  • use buffer overflow to set the successBool variable to 1

Buffer Overflow

The main() function reads the password using scanf("%s", input), which performs no bounds checking. Since input is only 8 bytes long, this allows us to overflow the buffer.

Looking at the stack layout:

  char input[8]; // [rsp+4h] [rbp-Ch] BYREF
  int successBool; // [rsp+Ch] [rbp-4h] BYREF

The successBool variable is located immediately after input on the stack. By providing more than 8 bytes of input, we can overwrite successBool.

The program only sets successBool to 1 if the password comparison succeeds. On failure, the variable remains unchanged. This allows us to pre-set it via a buffer overflow before the comparison occurs.

To do this, we provide:

  • 8 bytes of padding
  • followed by the 4-byte little-endian value 1

Since the value 0x01 is not printable, we use printf to construct the payload:

printf "AAAAAAAA\x01\x00\x00\x00" | ./crackme
Enter password: Access Granted!

Conclusion

The challenge was fun, but I honestly wouldn’t give the challenge a rating of 3 when it comes to difficulty. I feel like the Unix challenges get higher ratings compared to the Windows challenges I’m used to - there was no obfuscation, no anti debugging and no encryption involved. With a simple byte change this challenge could have been completed.