I’ve been once told that my name is difficult to pronounce and since then I’m using it as a password for everything.

Category: Reversing

Solver: t0b1

Writeup

We get a binary called my_name_is. Running the file command tells us that it is a 32-bit, dynamically linked executable. It also shows that the binary is not stripped, which is useful when decompiling it.

$ file my_name_is
my_name_is: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=c8d536794885d0c91e2270d7c6b9a9f14dda9739, not stripped

Running the binary itself gives us the following output.

Who are you?
No you are not the right person

As this does not tell us much, we decompile the binary using Ghidra and check what it does.

The main function starts with the following lines.

__uid = geteuid();
ppVar1 = getpwuid(__uid);

The manpages of the functions tell us what they do.

The geteuid() function shall return the effective user ID of the calling process.

The getpwuid() function returns a pointer to a structure containing the broken-out fields of the record in the password database that matches the user ID uid.

After those lines is a ptrace check that prevents us from debugging the binary in the first place. However, we can patch that in the binary to skip those checks if we needed to debug the program.

Then we find the following code.

ctx = (EVP_PKEY_CTX *)ppVar1->pw_name;
iVar3 = strcmp((char *)ctx,username);
if (iVar3 == 0) {
  local_28 = 0;
  while (local_28 < 0x198) {
    if ((*(uint *)(main + local_28) & 0xff) == breakpointvalue) {
      puts("What\'s this now?");
                /* WARNING: Subroutine does not return */
      exit(1);
    }
    local_28 = local_28 + 1;
  }
  sVar4 = strlen(encrypted_flag);
  outlen = (size_t *)malloc((sVar4 + 1) * 4);
  decrypt(ctx,encrypted_flag,outlen,in,in_stack_ffffffd0);
  *(undefined *)((int)outlen + sVar4) = 0;
  puts((char *)outlen);
}
else {
  puts("No you are not the right person");
}

The program accesses the pw_name attribute that contains our linux username and checks whether it is equal to ~#L-:4;f. If that is true, the flag is decrypted and printed to us.

So we thought: This is easy, just create a new user with the desired name and run the program as that user. But the username is not randomly chosen. Trying to create the user using useradd "~#L-:4;f" results in useradd: invalid user name '~#L-:4;f'. Linux does not allow certain special characters in usernames.

However, as we noticed above, the binary is linked dynamically. Using the ltrace tool we can also find out that the binary calls the getpwuid function dynamically.

$ ltrace ./my_name_is
...
getpwuid(1000, 0xffffffff, 0xf7d923fc, 0x804888e)                = 0xf7eae9e4
...

That means we can use the LD_PRELOAD injection, to modify the behaviour of getpwuid to always return our desired username.

We create the following C program that serves as our library for that.

#include <sys/types.h>
#include <stdio.h>

struct passwd {
    char   *pw_name;       /* username */
    char   *pw_passwd;     /* user password */
    uid_t   pw_uid;        /* user ID */
    gid_t   pw_gid;        /* group ID */
    char   *pw_gecos;      /* user information */
    char   *pw_dir;        /* home directory */
    char   *pw_shell;      /* shell program */
};

struct passwd user = {
    "~#L-:4;f",
    "",
    0,
    0,
    "",
    "",
    ""
};

struct passwd *getpwuid(uid_t uid){
    return &user;
}

As the program only accesses the username, we can safely ignore the other values. We can now compile the program as a shared library using gcc -shared -fPIC -m32 -o getpwuid_preload.so getpwuid_preload.c. The -m32 is important here, as the challenge is a 32-bit binary.

Now running the challenge binary using LD_PRELOAD=./getpwuid_preload.so ./my_name_is gives the following output and the flag.

Who are you?
HTB{L00k1ng_f0r_4_w31rd_n4m3}

flag