# US CyberGames RE-Cruise 4

![Problem Description](https://30521890-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LWRAm7V9PvgumFt5f6A%2F-MiswgCGMZRnEHBygxf4%2F-Misx57hviy0qwUU_RGm%2Fimage.png?alt=media\&token=d1c189dc-85b1-45fb-aaa8-73369abbdb96)

This reverse engineering problem was the capstone problem for the "Cruise" set of reverse engineering problems for the US Cyber games final CTF. I've been messaged by a bunch of people asking how I solved it, so I've put together this walk through describing the vulnerabilities in the program and I how used them to solve it without `sigqueue` .

```bash
$ file princess
princess: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=43925fc945c2d6258b7ba11547fbd3236340cd62, for GNU/Linux 3.2.0, stripped
```

The problem set contains a number of challenges that rely on using signals to interact with the challenge problem. The problem on the server is a setuid program that when in the correct program state will print the flag. This binary has `striped` symbols, but otherwise follows the same format as the previous cruise problems of:

```
get_rand_bytes() ->get_rule() -> get_signals() -> read signal_sequence -> print flag
```

![Running the program](https://30521890-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LWRAm7V9PvgumFt5f6A%2F-MiswgCGMZRnEHBygxf4%2F-MiszijbV4eC204FWPI7%2Fimage.png?alt=media\&token=42887511-4d67-45a5-bbc8-136c9f42239c)

## Program vulnerabilities

### First bug - Controlling /dev/urandom

The first issue within this challenge lies in the getRandBytes function. Where the returned `fd` from the `open` call is not checked and is instead directly read from. This is a classic `ulimit -n` exploitation case. The `ulimit` command will enable us to set program limits and restrict the number of open file descriptors, enabling us to force this call to `open` to fail. Since the return value from `read` isn't checked either, we can ensure that 0 bytes are read in the returned unsigned int.

![Ghidra decompilation for get\_rand\_bytes() function](https://30521890-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LWRAm7V9PvgumFt5f6A%2F-MiswgCGMZRnEHBygxf4%2F-Misy_TkKl080yv9_aVI%2Fimage.png?alt=media\&token=1ccd83cd-e7bf-48d7-9544-4b91301db4db)

### Second bug - Controlling rules.csv

The second issue is in the `open` call for the `rules.csv` file, where the given path is relative. Because of this we can run the `princess` binary in a `/tmp/something` directory and supply our own rules.csv file. There is a similar bug to flag.txt, and so in that `/tmp/something` directory we need to symlink `flag.txt` to `~/flag.txt`.

![Ghidra decompilation for get\_rule()](https://30521890-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LWRAm7V9PvgumFt5f6A%2F-MiszlP3dr_7Wr7WtsN2%2F-Miszsv8TMHWdAPFJfgW%2Fimage.png?alt=media\&token=7e569c9e-e71e-425d-8d1c-6bd3ac0c36b1)

![Creating a custom rule for rules.csv](https://30521890-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LWRAm7V9PvgumFt5f6A%2F-MiszlP3dr_7Wr7WtsN2%2F-Mit0UjIh8aiBilBOyQ1%2Fimage.png?alt=media\&token=083fd7e2-ea0b-4f9f-905b-45e64a56de89)

Creating this custom rule enabled me to just set a single short blast as the rule every single time the program ran.&#x20;

### Third bug - Crashing the program

This bug ultimately wasn't useable, however in the `get_rule()` function, there is a loop that adds bytes from the file into a buffer until it reaches a new line character. Since this is a stack address that is passed into the function, this enables us to overflow the `rule_buffer` variable and clobber everything after it.&#x20;

![Rule buffer passed into the get\_rule() function](https://30521890-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LWRAm7V9PvgumFt5f6A%2F-MiszlP3dr_7Wr7WtsN2%2F-Mit18cdI8E5CDrvk-M2%2Fimage.png?alt=media\&token=13b4250b-5559-4f8d-8bab-6c65caa67789)

Unfortunately, the program ends with a call to exit(2) instead of returning, meaning any saved registers that are clobbered don't give us execution. The program would have also had full protections too, making exploitation incredibly difficult. One positive note though it that the execution environment is vulnerable to  [CVE-2016-3672](http://hmarco.org/bugs/CVE-2016-3672-Unlimiting-the-stack-not-longer-disables-ASLR.html) which would let us effectively turn off ASLR, so no leak would be needed to exploit the program.

```
checksec princess
[*] '/home/chris/ctf/cybergames/final/princess'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
```

![Using exit(2) at the end of the program prevents a ret call to the clobbered value](https://30521890-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LWRAm7V9PvgumFt5f6A%2F-MiszlP3dr_7Wr7WtsN2%2F-Mit2P0rScSvzNy-qLTP%2Fimage.png?alt=media\&token=db34d880-1fac-4c4e-b1a5-3873b149c938)

## The solve

The first trick is pretty cool for taking all randomness out of the program, however there is a value that gets checked based off those random bytes that needs to equal 1 and not 0, and the check producing the value from the random bytes uses shifts and can never produce a 1.

![Since the generated compare value is calculated through shifts. The first bug is useless](https://30521890-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LWRAm7V9PvgumFt5f6A%2F-MiszlP3dr_7Wr7WtsN2%2F-Mit37FsX7JnYFvR7XM-%2Fimage.png?alt=media\&token=3e1d0725-3f38-4595-ab6b-62a187dac464)

This leaves us with a brute-force approach toward solving the problem with the second bug. There is no python on the server, so we need to write in C a method for invoking the `princess` program and sending a signal calculated based off the rand value.&#x20;

The task becomes pretty straight forward as we need to fork a process, read and send data through a pipe, and send a signal to the process before it terminates.

We're relying on the randomness of `/dev/urandom` since it will occasionally provide a value from the `getCompareValue` function that passes the check. So we just need to run this program until it produces the flag with a bash one-liner of:

```bash
while true; do timeout 1 ./run_till_flag;done | grep uscg # only print flag
```

```c
#include<unistd.h>
#include<sys/wait.h>
#include<sys/prctl.h>
#include<signal.h>
#include<stdlib.h>
#include<string.h>
#include<stdio.h>

//#define PROG "/home/chris/ctf/cybergames/final/princess"
#define PROG "/tmp/something/princess"

int get_signal(unsigned int rand_val)
{
  int sig_num[] = {0x1a,0x12,1,10,0xb,0x15,0x1d,0xd,6,0x10,0x1f,0x1e,0x14,0x17,0xb};
  int index = rand_val >> 0x18 & 0xf;
  return sig_num[index];
}

int get_cmp_val(unsigned int num)
{
  return (num >> 0x10 & 0xff) * (num >> 0x18) - (num & 0xff ^ num >> 8 & 0xff);
}

int main(int argc, char** argv)
{
  pid_t pid = 0;
  int inpipefd[2];
  int outpipefd[2];
  char buf[2048];
  char msg[256];
  int status;

  pipe(inpipefd);
  pipe(outpipefd);
  pid = fork();
  if (pid == 0)
  {
    // Child
    dup2(outpipefd[0], STDIN_FILENO);
    dup2(inpipefd[1], STDOUT_FILENO);
    dup2(inpipefd[1], STDERR_FILENO);

    //ask kernel to deliver SIGTERM in case the parent dies
    prctl(PR_SET_PDEATHSIG, SIGTERM);

    execl(PROG, "princess", (char*) NULL);
    exit(1);
  }

  //close unused pipe ends
  close(outpipefd[0]);
  close(inpipefd[1]);

  // Read until rand value is printed
  int count = read(inpipefd[0], buf, 50);
  // Since we're running fast, we might
  // SIGBUS or SIGPIPE and we might as 
  // well restart
  if(count != 50)
  {
    usleep(100);
    kill(pid, SIGKILL); //send SIGKILL signal to the child process
    waitpid(pid, &status, 0);
    return -1;
  }
  memset(buf, '\x00',sizeof(buf));
  
  // Get the rand value printed
  read(inpipefd[0], buf, 8);

  unsigned int rand_val = (unsigned int)strtol(buf, NULL, 16);
  //printf("rand_val %X\n",rand_val);

  unsigned int signal_val = get_signal(rand_val);
  unsigned int cmp_val = get_cmp_val(rand_val);

  //printf("Signal %d\ncmp_val %X\n",signal_val, cmp_val);

  read(inpipefd[0], buf, 2048);
  //printf("%s\n", buf);
  memset(buf, '\x00',sizeof(buf));

  // I was only going to kill if it's the right cmp_val
  // but it's just as fast to run all of them
  if (cmp_val == 1 || 1)
  {
    kill(pid, signal_val);

    write(outpipefd[1], "\n\n", 2);
    close(outpipefd[1]);
    read(inpipefd[0], buf, 2048);
    // Should contain the flag
    printf("%s\n", buf);
    //printf("rand_val %X\n",rand_val);
    //printf("Signal %d\ncmp_val %X\n",signal_val, cmp_val);
    return -1;
  }

  kill(pid, SIGKILL); //send SIGKILL signal to the child process
  waitpid(pid, &status, 0);
}
```

### Speeding up the process

Since we're running this over a remote connection and the remote server has two cores, I opted to write a script that would run 32 separate instances of the program concurrently to minimize the chance of another player seeing my run folder or process running in memory and speed up the solve.

```python
from pwn import *
import multiprocessing

cmd = "cd /tmp/something/;while true;do timeout 1 ./run_till_flag;done | grep uscg"


def do_work(nothing):
    conn = ssh(user="challenger",host='0.cloud.chals.io',\
            port=28429,password="5f4dcc3b5aa765d61d8327deb882cf99")

    p = conn.shell(cmd)
    print(p.recv())


p = multiprocessing.Pool(32)
p.map(do_work,range(32))
```

## The real solve

The real solve follows the escalating process of how to use signals and wants you to pass some data with the signal to the signal hander.

![Setting up signal handlers inside main()](https://30521890-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LWRAm7V9PvgumFt5f6A%2F-MiszlP3dr_7Wr7WtsN2%2F-Mit6ZSxARmWhueCQ0ii%2Fimage.png?alt=media\&token=41600d8d-db27-4cfd-88d8-1902525ae56a)

![handling signals and setting the compared value](https://30521890-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LWRAm7V9PvgumFt5f6A%2F-MiszlP3dr_7Wr7WtsN2%2F-Mit6hhmfnCbTx7hIYe7%2Fimage.png?alt=media\&token=1b8a4d15-0c04-4f5a-a371-71054a742db1)

The intended solution uses `sigqueue` to add data to the signal to be processed.

![Sigqueue manpage](https://30521890-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LWRAm7V9PvgumFt5f6A%2F-MiszlP3dr_7Wr7WtsN2%2F-Mit6ysjAAZUvi1s09mF%2Fimage.png?alt=media\&token=da2c741a-8b09-4d03-b80d-35a966b69328)

The `value` argument can be set in a struct and passed in the sigqueue command, which enables us to set the compared value. From the man page:

```
The value argument is used to specify an accompanying item 
of data (either an integer or a pointer value) to be sent with the
signal, and has the following type:

           union sigval {
               int   sival_int;
               void *sival_ptr;
           };
```

```
uscg{a_signal_inside_a_signal__achievement_unlocked!}
```
