Created
April 19, 2020 22:15
-
-
Save Strikeskids/7d2ba252a4eeffa9729a644c90107021 to your computer and use it in GitHub Desktop.
PlaidCTF 2020 golf.so: server source
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8" /> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> | |
<meta name="viewport" | |
content="width=device-width, initial-scale=1" /> | |
<title>{% block title %}{% endblock %}</title> | |
<link rel="stylesheet" | |
href="{{ url_for('static', filename='main.css') }}" /> | |
</head> | |
<body> | |
<div class="main-header"> | |
<nav class="topbar-navigation"> | |
<a class="button" href="{{ url_for('scoreboard') }}"> | |
Scoreboard | |
</a> | |
<h1>golf.so</h1> | |
<a class="button" href="{{ url_for('upload') }}"> | |
Upload | |
</a> | |
</nav> | |
</div> | |
{% with messages = get_flashed_messages() %}{% if messages %} | |
<div class="center-box"> | |
{% for message in messages %} | |
<div class="flashed-message">{{ message }}</div> | |
{% endfor %} | |
</div> | |
{% endif %}{% endwith %} | |
<div class="main-content"> | |
{% block content %}{% endblock %} | |
</div> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
PCTF{th0ugh_wE_have_cl1mBed_far_we_MusT_St1ll_c0ntinue_oNward} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
PCTF{t0_get_a_t1ny_elf_we_5tick_1ts_hand5_in_its_ears_rtmlpntyea} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
from flask import ( | |
Flask, g, request, redirect, escape, get_flashed_messages, flash, | |
session, render_template, url_for | |
) | |
import enum | |
import os | |
import sqlite3 | |
import subprocess | |
import tempfile | |
import hashlib | |
import hashcash | |
app = Flask(__name__) | |
app.config['UPLOAD_FOLDER'] = 'uploads' | |
app.config['MAX_CONTENT_LENGTH'] = 2000 | |
app.config['BITS'] = 0 | |
app.config['RESOURCE'] = 'golf.so' | |
app.config['EXECUTION_TIMEOUT'] = 5 | |
app.config['DATABASE'] = 'db.sqlite3' | |
app.config['FLAG_FILE'] = 'flag.txt' | |
app.config['EASY_FLAG_FILE'] = 'flag-easy.txt' | |
app.secret_key = 'todo: really secret' | |
def get_db(): | |
db = getattr(g, '_database', None) | |
if db is None: | |
db = g._database = sqlite3.connect(app.config['DATABASE']) | |
db.row_factory = sqlite3.Row | |
return db | |
@app.teardown_appcontext | |
def close_connection(exception): | |
db = getattr(g, '_database', None) | |
if db is not None: | |
db.close() | |
def get_scoreboard(): | |
cur = get_db().execute( | |
''' | |
SELECT MIN(id) as id, MIN(size) as size, MIN(submission_time) as submission_time, team, hash | |
FROM uploads | |
WHERE success = 1 | |
GROUP BY team | |
ORDER BY size ASC, submission_time ASC | |
LIMIT 50 | |
''' | |
) | |
results = cur.fetchall() | |
cur.close() | |
return results | |
def add_upload_to_db(team, submission, response, hashcash_stamp): | |
size = len(submission) | |
hash_ = hashlib.sha256(submission).hexdigest() | |
db = get_db() | |
db.execute( | |
''' | |
INSERT INTO uploads ( | |
submission_time, size, team, hash, | |
submission, success, response, hashcash | |
) VALUES (datetime('now'), ?, ?, ?, ?, ?, ?, ?) | |
''', | |
( | |
size, team, hash_, submission, response == ExitCode.WINNER, | |
response.name, hashcash_stamp, | |
), | |
) | |
db.commit() | |
@app.route('/', methods=['GET']) | |
def scoreboard(): | |
return render_template('scoreboard.html', results=get_scoreboard()) | |
@app.route('/upload', methods=['GET', 'POST']) | |
def upload(): | |
if request.method == 'POST': | |
return do_upload() | |
return render_template('upload.html') | |
@app.errorhandler(413) | |
def request_entity_too_large(e): | |
flash('This is golf.so, not asteriods.so.') | |
return redirect(request.url) | |
def flag_for_submission(submission): | |
easy_required_submission_bytes = 300 | |
required_submission_bytes = 194 | |
total_flags = 2 | |
LEVELS = ( | |
('non-trivial', app.config['MAX_CONTENT_LENGTH']), | |
('considerable', 500), | |
('thoughtful', easy_required_submission_bytes), | |
('hand-crafted', 224), | |
('flag-worthy', required_submission_bytes), | |
('record-breaking', 193), | |
('astounding', 176), | |
('impossible', 112), | |
('actually-impossible', 0), | |
) | |
submission_length = len(submission) | |
level = max(i for i, c in enumerate(LEVELS) if c[1] >= submission_length) | |
cur_level_name, _ = LEVELS[level] | |
next_level_name, next_level_bytes = LEVELS[level+1] | |
bytes_remaining = submission_length - next_level_bytes | |
bytes_word = 'bytes' if bytes_remaining != 1 else 'byte' | |
given_flags = [] | |
if submission_length <= easy_required_submission_bytes: | |
with open(app.config['EASY_FLAG_FILE'], 'r') as f: | |
given_flags.append(f.read()) | |
if submission_length <= required_submission_bytes: | |
with open(app.config['FLAG_FILE'], 'r') as f: | |
given_flags.append(f.read()) | |
response = ' '.join([ | |
f'You made it to level {level}: {cur_level_name}!', | |
f'You have {bytes_remaining} {bytes_word} left to be {next_level_name}.', | |
f'This effort is worthy of {len(given_flags)}/{total_flags} flags.' | |
] + given_flags) | |
return response | |
def linear_scale(value, srca, srcb, dsta, dstb): | |
result = (value - srca) * (dstb - dsta) // (srcb - srca) + dsta | |
dstlo, dsthi = min(dsta, dstb), max(dsta, dstb) | |
return min(max(result, dstlo), dsthi) | |
def load_hashcash_ds_table(): | |
return set( | |
row['hashcash'] | |
for row in get_db().execute('select hashcash from uploads;') | |
) | |
with app.app_context(): | |
hashcash_ds = load_hashcash_ds_table() | |
def check_ds(token): | |
if token in hashcash_ds: | |
return True | |
hashcash_ds.add(token) | |
return False | |
def do_upload(): | |
team = request.form.get('team', '') | |
if team: session['team'] = team | |
hashcash_stamp = request.form.get('hashcash', None) | |
if 'file' not in request.files: | |
flash('File missing') | |
return redirect(request.url) | |
file = request.files['file'] | |
if not file or not file.filename: | |
flash('File missing') | |
return redirect(request.url) | |
if app.config['BITS'] > 0: | |
if not hashcash_stamp: | |
flash('Hashcash missing') | |
return redirect(request.url) | |
if not hashcash.check(hashcash_stamp, app.config['RESOURCE'], | |
app.config['BITS'], ds_callback=check_ds): | |
flash('Hashcash invalid') | |
return redirect(request.url) | |
userso = tempfile.NamedTemporaryFile( | |
suffix='.so', dir=app.config['UPLOAD_FOLDER'], delete=False) | |
file.save(userso) | |
userso.close() | |
os.chmod(userso.name, 0o755) | |
with open(userso.name, 'rb') as f: | |
userso_data = f.read() | |
response, details = test_userso(userso.name) | |
os.remove(userso.name) | |
add_upload_to_db(team, userso_data, response, hashcash_stamp) | |
if response == ExitCode.WINNER: | |
flash(flag_for_submission(userso_data)) | |
return redirect(url_for('scoreboard')) | |
if response == ExitCode.ASSERTION: | |
message = ( | |
'Something went wrong, try again. If this persists, ' | |
'contact an admin.' | |
) | |
elif response == ExitCode.LOSER: | |
message = ( | |
'Your .so died unexpectedly.' | |
) | |
elif response == ExitCode.NOTHING: | |
message = ( | |
'Your .so didn\'t seem to do anything.' | |
) | |
elif response == ExitCode.PROGRESS: | |
message = ( | |
'That pwned something, all right. Remember the goal.' | |
) | |
else: | |
assert response == ExitCode.TIMEOUT | |
message = ( | |
'Your program timed out while running. Remember the goal.' | |
) | |
if details is not None: | |
message += ' ' + details | |
flash(message) | |
return redirect(request.url) | |
@enum.unique | |
class ExitCode(enum.Enum): | |
ASSERTION = 5 | |
LOSER = 6 | |
NOTHING = 7 | |
PROGRESS = 8 | |
WINNER = 9 | |
TIMEOUT = -1 | |
def test_userso(filename): | |
with subprocess.Popen( | |
['./runso', filename], | |
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, | |
stderr=subprocess.DEVNULL, | |
close_fds=True, | |
) as p: | |
try: | |
message, _ = p.communicate(timeout=app.config['EXECUTION_TIMEOUT']) | |
message = message.decode('ascii', 'backslashreplace') | |
return ExitCode(p.returncode), message | |
except ValueError: | |
return ExitCode.ASSERTION, None | |
except subprocess.TimeoutExpired: | |
return ExitCode.TIMEOUT, None | |
finally: | |
p.kill() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
"""Implement Hashcash version 1 protocol in Python | |
+-------------------------------------------------------+ | |
| Written by David Mertz; released to the Public Domain | | |
+-------------------------------------------------------+ | |
Very hackily modified | |
Double spend database not implemented in this module, but stub | |
for callbacks is provided in the 'check()' function | |
The function 'check()' will validate hashcash v1 and v0 tokens, as well as | |
'generalized hashcash' tokens generically. Future protocol version are | |
treated as generalized tokens (should a future version be published w/o | |
this module being correspondingly updated). | |
A 'generalized hashcash' is implemented in the '_mint()' function, with the | |
public function 'mint()' providing a wrapper for actual hashcash protocol. | |
The generalized form simply finds a suffix that creates zero bits in the | |
hash of the string concatenating 'challenge' and 'suffix' without specifying | |
any particular fields or delimiters in 'challenge'. E.g., you might get: | |
>>> from hashcash import mint, _mint | |
>>> mint('foo', bits=16) | |
'1:16:040922:foo::+ArSrtKd:164b3' | |
>>> _mint('foo', bits=16) | |
'9591' | |
>>> from sha import sha | |
>>> sha('foo9591').hexdigest() | |
'0000de4c9b27cec9b20e2094785c1c58eaf23948' | |
>>> sha('1:16:040922:foo::+ArSrtKd:164b3').hexdigest() | |
'0000a9fe0c6db2efcbcab15157735e77c0877f34' | |
Notice that '_mint()' behaves deterministically, finding the same suffix | |
every time it is passed the same arguments. 'mint()' incorporates a random | |
salt in stamps (as per the hashcash v.1 protocol). | |
""" | |
import sys | |
from string import ascii_letters | |
from math import ceil, floor | |
from hashlib import sha1 as sha | |
from random import choice | |
from time import strftime, localtime, time | |
ERR = sys.stderr # Destination for error messages | |
DAYS = 60 * 60 * 24 # Seconds in a day | |
tries = [0] # Count hashes performed for benchmark | |
def check(stamp, resource=None, bits=None, | |
check_expiration=None, ds_callback=None): | |
"""Check whether a stamp is valid | |
Optionally, the stamp may be checked for a specific resource, and/or | |
it may require a minimum bit value, and/or it may be checked for | |
expiration, and/or it may be checked for double spending. | |
If 'check_expiration' is specified, it should contain the number of | |
seconds old a date field may be. Indicating days might be easier in | |
many cases, e.g. | |
>>> from hashcash import DAYS | |
>>> check(stamp, check_expiration=28*DAYS) | |
NOTE: Every valid (version 1) stamp must meet its claimed bit value | |
NOTE: Check floor of 4-bit multiples (overly permissive in acceptance) | |
""" | |
try: | |
stamp_bytes = stamp.encode('ascii') | |
except UnicodeError: | |
ERR.write('Invalid hashcash stamp!\n') | |
return False | |
if stamp.startswith('0:'): # Version 0 | |
try: | |
date, res, suffix = stamp[2:].split(':') | |
except ValueError: | |
ERR.write("Malformed version 0 hashcash stamp!\n") | |
return False | |
if resource is not None and resource != res: | |
return False | |
elif check_expiration is not None: | |
good_until = strftime("%y%m%d%H%M%S", localtime(time()-check_expiration)) | |
if date < good_until: | |
return False | |
elif callable(ds_callback) and ds_callback(stamp): | |
return False | |
elif type(bits) is not int: | |
return True | |
else: | |
hex_digits = int(floor(bits/4)) | |
return sha(stamp_bytes).hexdigest().startswith('0'*hex_digits) | |
elif stamp.startswith('1:'): # Version 1 | |
try: | |
claim, date, res, ext, rand, counter = stamp[2:].split(':') | |
stamp_bytes = stamp.encode('ascii') | |
except ValueError: | |
ERR.write("Malformed version 1 hashcash stamp!\n") | |
return False | |
if resource is not None and resource != res: | |
return False | |
elif type(bits) is int and bits > int(claim): | |
return False | |
elif check_expiration is not None: | |
good_until = strftime("%y%m%d%H%M%S", localtime(time()-check_expiration)) | |
if date < good_until: | |
return False | |
elif callable(ds_callback) and ds_callback(stamp): | |
return False | |
else: | |
hex_digits = int(floor(int(claim)/4)) | |
return sha(stamp_bytes).hexdigest().startswith('0'*hex_digits) | |
else: # Unknown ver or generalized hashcash | |
ERR.write("Unknown hashcash version: Minimal authentication!\n") | |
if type(bits) is not int: | |
return True | |
elif resource is not None and stamp.find(resource) < 0: | |
return False | |
else: | |
hex_digits = int(floor(bits/4)) | |
return sha(stamp_bytes).hexdigest().startswith('0'*hex_digits) | |
def is_doublespent(stamp): | |
"""Placeholder for double spending callback function | |
The check() function may accept a 'ds_callback' argument, e.g. | |
check(stamp, "mertz@gnosis.cx", bits=20, ds_callback=is_doublespent) | |
This placeholder simply reports stamps as not being double spent. | |
""" | |
return False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
html { | |
box-sizing: border-box; | |
font-size: 100%; | |
font-family: sans-serif; | |
-webkit-text-size-adjust: 100%; | |
} | |
*, *:before, *:after { | |
box-sizing: inherit; | |
} | |
body { | |
font-size: 1em; | |
margin: 0; | |
background-color: #F1F8E9; | |
} | |
.center-box, .main-content, .main-header { | |
max-width: 40em; | |
margin: 0 auto; | |
} | |
.main-content { | |
padding: 0 .2em; | |
} | |
.main-header > .topbar-navigation { | |
margin: 10px 0; | |
} | |
.topbar-navigation { | |
display: flex; | |
flex-flow: row wrap; | |
justify-content: space-between; | |
align-items: center; | |
} | |
.topbar-navigation > h1 { | |
margin: 3px; | |
} | |
.topbar-navigation > .button { | |
padding: .4em; | |
margin: .2em; | |
flex: 0 auto; | |
border-radius: 5px; | |
text-decoration: none; | |
color: inherit; | |
background-color: rgba(230, 230, 230, .6); | |
} | |
.scoreboard { | |
width: 100%; | |
display: grid; | |
grid-column-gap: 1em; | |
grid-row-gap: .5em; | |
grid-template-columns: [rank] 2em [team] 1fr [size] 3em [hash] 5em; | |
} | |
.scoreboard > .sep { | |
grid-column: 1 / 5; | |
border-bottom: solid 1px black; | |
} | |
.scoreboard > .rank { | |
grid-column: rank; | |
justify-self: right; | |
} | |
.scoreboard > .size { | |
grid-column: size; | |
justify-self: left; | |
} | |
.scoreboard > .hash { | |
grid-column: hash; | |
} | |
.scoreboard > .team { | |
grid-column: team; | |
} | |
pre { | |
margin-left: 3em; | |
font-size: inherit; | |
} | |
button, input { | |
margin: 0; | |
font-family: inherit; | |
font-size: inherit; | |
line-height: inherit; | |
} | |
input { | |
display: block; | |
width: 100%; | |
padding: .375rem .75rem; | |
border-radius: 5px; | |
border: 1px solid #cecece; | |
} | |
label { | |
display: inline-block; | |
margin-bottom: .5em; | |
} | |
form { | |
padding: 1em; | |
background-color: #F1F8E9; | |
border-radius: 5px; | |
border: 1px solid #cecece; | |
} | |
button { | |
padding: .375rem .75rem; | |
border-radius: 5px; | |
} | |
.form-group { | |
margin-bottom: .75em; | |
} | |
.form-group:last-child { | |
margin-bottom: 0; | |
} | |
.flashed-message { | |
border: 1px solid #cecece; | |
background-color: #fff7f0; | |
padding: .375rem .75rem; | |
margin-bottom: 1rem; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
all: runso | |
runso: LDLIBS += -lseccomp | |
.PHONY: all |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copied from https://flask.palletsprojects.com/en/1.1.x/deploying/wsgi-standalone/ | |
server { | |
listen 80; | |
server_name _; | |
access_log /var/log/nginx/access.log; | |
error_log /var/log/nginx/error.log; | |
location / { | |
proxy_pass http://127.0.0.1:8000/; | |
proxy_redirect off; | |
proxy_set_header Host $host; | |
proxy_set_header X-Real-IP $remote_addr; | |
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |
proxy_set_header X-Forwarded-Proto $scheme; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <fcntl.h> | |
#include <seccomp.h> | |
#include <signal.h> | |
#include <stdbool.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <syscall.h> | |
#include <sys/ptrace.h> | |
#include <sys/prctl.h> | |
#include <sys/user.h> | |
#include <sys/wait.h> | |
#include <unistd.h> | |
#define BUFSIZE 1024 | |
#define EXIT_ASSERTION 5 | |
#define EXIT_LOSER 6 | |
#define EXIT_NOTHING 7 | |
#define EXIT_PROGRESS 8 | |
#define EXIT_WINNER 9 | |
#define FATAL(...) \ | |
do { \ | |
fprintf(stderr, __VA_ARGS__); \ | |
fputc('\n', stderr); \ | |
exit(EXIT_ASSERTION); \ | |
} while (0) | |
#undef assert | |
#define assert(exp) \ | |
do { \ | |
if (!(exp)) \ | |
FATAL("assertion failed (%s:%d) %s", __FILE__, __LINE__, #exp); \ | |
} while (0) | |
void run_exec(char *preload) { | |
int res; | |
res = ptrace(PTRACE_TRACEME, 0, 0, 0); | |
assert(res == 0); | |
res = raise(SIGSTOP); | |
assert(res == 0); | |
/* Initialize seccomp */ | |
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL); assert(ctx != NULL); | |
res = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(access), 0); assert(res == 0); | |
res = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(arch_prctl), 0); assert(res == 0); | |
res = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0); assert(res == 0); | |
res = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0); assert(res == 0); | |
res = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fstat), 0); assert(res == 0); | |
res = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getcwd), 0); assert(res == 0); | |
res = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0); assert(res == 0); | |
res = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect), 0); assert(res == 0); | |
res = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(munmap), 0); assert(res == 0); | |
res = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0); assert(res == 0); | |
res = seccomp_rule_add(ctx, SCMP_ACT_TRACE(0), SCMP_SYS(execve), 0); assert(res == 0); | |
res = seccomp_rule_add(ctx, SCMP_ACT_TRACE(0), SCMP_SYS(openat), 0); assert(res == 0); | |
res = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); | |
assert(res == 0); | |
res = seccomp_load(ctx); | |
assert(res == 0); | |
char preload_env[BUFSIZE]; | |
int envlen = snprintf(preload_env, BUFSIZE, "LD_PRELOAD=%s", preload); | |
assert(envlen <= BUFSIZE); | |
char *env[] = {preload_env, NULL}; | |
execle("/bin/true", "/bin/true", NULL, env); | |
} | |
size_t read_str(int memfd, char *buf, size_t bufsize, off_t addr) { | |
ssize_t count = 0, total = 0; | |
while ((count = pread(memfd, buf + total, bufsize - total, addr + total)) > 0) { | |
size_t len = strnlen(buf + total, count); | |
if (len < count) { | |
return len + total; | |
} | |
total += count; | |
assert(total <= bufsize); | |
if (total == bufsize) { | |
buf[bufsize-1] = 0; | |
return bufsize - 1; | |
} | |
} | |
FATAL("failed to read string"); | |
return 0; | |
} | |
int check_execve(struct user_regs_struct *regs, int memfd) { | |
char buf[BUFSIZE]; | |
read_str(memfd, buf, BUFSIZE, regs->rdi); | |
if (strcmp(buf, "/bin/true") == 0) { | |
return 0; | |
} | |
if (strcmp(buf, "/bin/sh") == 0) { | |
off_t pairs[2]; | |
ssize_t bytes = pread(memfd, &pairs, sizeof(pairs), regs->rsi); | |
if (bytes != sizeof(pairs)) { | |
printf("It looks like you tried to " | |
"execve(\"/bin/sh\", garbage, ...).\n"); | |
return EXIT_PROGRESS; | |
} | |
if (pairs[0] == 0) { | |
printf("It looks like you tried to " | |
"execve(\"/bin/sh\", [], ...).\n"); | |
return EXIT_PROGRESS; | |
} | |
read_str(memfd, buf, BUFSIZE, pairs[0]); | |
if (strstr(buf, "sh") == NULL || pairs[1] != 0) { | |
printf("It looks like you tried to " | |
"execve(\"/bin/sh\", [\"%s\", ...], ...).", buf); | |
return EXIT_PROGRESS; | |
} | |
return EXIT_WINNER; | |
} | |
printf("It looks like you tried to execve(\"%s\", ...).\n", buf); | |
return EXIT_PROGRESS; | |
} | |
int check_open(int memfd, off_t pathaddr, int oflag) { | |
if ((oflag|O_WRONLY) == O_WRONLY || (oflag|O_RDWR) == O_RDWR) { | |
printf("It looks like you tried to open something.\n"); | |
return EXIT_PROGRESS; | |
} | |
char buf[BUFSIZE]; | |
read_str(memfd, buf, BUFSIZE, pathaddr); | |
if (strstr(buf, "flag") != NULL) { | |
printf("It looks like you tried to open(%s).\n", buf); | |
return EXIT_PROGRESS; | |
} | |
if (strstr(buf, "/proc") != NULL) { | |
printf("It looks like you tried to open(%s).\n", buf); | |
return EXIT_PROGRESS; | |
} | |
return 0; | |
} | |
int open_mem(pid_t pid) { | |
char filename[BUFSIZE]; | |
int res = snprintf(filename, BUFSIZE, "/proc/%d/mem", pid); | |
assert(res <= BUFSIZE); | |
return open(filename, O_RDONLY); | |
} | |
int run_parent(pid_t pid) { | |
/* Wait for the PTRACE_TRACEME */ | |
int wstatus; | |
pid_t wpid; | |
int res; | |
wpid = waitpid(pid, &wstatus, 0); | |
assert(wpid == pid && WIFSTOPPED(wstatus)); | |
int memfd = open_mem(pid); | |
assert(memfd > 0); | |
res = ptrace(PTRACE_SETOPTIONS, pid, NULL, | |
PTRACE_O_EXITKILL|PTRACE_O_TRACESECCOMP); | |
assert(res == 0); | |
while (true) { | |
res = ptrace(PTRACE_CONT, pid, 0, 0); | |
assert(res == 0); | |
wpid = waitpid(pid, &wstatus, 0); | |
assert(wpid == pid); | |
if (WIFEXITED(wstatus)) { | |
if (WEXITSTATUS(wstatus) == EXIT_ASSERTION) { | |
return EXIT_ASSERTION; | |
} | |
if (WEXITSTATUS(wstatus) == EXIT_SUCCESS) { | |
return EXIT_NOTHING; | |
} | |
return EXIT_LOSER; | |
} | |
if (WIFSIGNALED(wstatus)) { | |
if (WTERMSIG(wstatus) == SIGKILL) { | |
printf("It looks like you might have tripped the seccomp.\n"); | |
} | |
return EXIT_LOSER; | |
} | |
assert(WIFSTOPPED(wstatus)); | |
switch (WSTOPSIG(wstatus)) { | |
case SIGSEGV: | |
printf("You segfaulted.\n"); | |
return EXIT_LOSER; | |
case SIGTRAP: | |
break; | |
default: | |
FATAL("Unexpected wstatus %x", wstatus); | |
} | |
assert((wstatus >> 8) == (SIGTRAP|(PTRACE_EVENT_SECCOMP<<8))); | |
struct user_regs_struct regs; | |
res = ptrace(PTRACE_GETREGS, pid, NULL, ®s); | |
assert(res == 0); | |
switch (regs.orig_rax) { | |
case SYS_execve: { | |
res = check_execve(®s, memfd); | |
if (res) return res; | |
/* Need to continue because ptrace stops after exec */ | |
res = ptrace(PTRACE_CONT, pid, 0, 0); | |
assert(res == 0); | |
wpid = waitpid(pid, &wstatus, 0); | |
assert(wpid == pid); | |
/* Need to re-open mem after exec */ | |
close(memfd); | |
memfd = open_mem(pid); | |
assert(memfd > 0); | |
break; | |
} | |
case SYS_openat: { | |
res = check_open(memfd, regs.rsi, regs.rdx); | |
if (res) return res; | |
break; | |
} | |
default: { | |
FATAL("Unexpected syscall trap: %llu", regs.orig_rax); | |
} | |
} | |
} | |
} | |
int main(int argc, char **argv) { | |
assert(argc == 2); | |
pid_t pid = fork(); | |
if (pid == -1) FATAL("fork"); | |
if (pid == 0) { | |
run_exec(argv[1]); | |
return EXIT_FAILURE; | |
} else { | |
return run_parent(pid); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CREATE TABLE uploads ( | |
id INTEGER PRIMARY KEY, | |
submission_time TEXT NOT NULL, | |
size INTEGER NOT NULL, | |
team TEXT, | |
hash TEXT NOT NULL, | |
submission BLOB NOT NULL, | |
response TEXT NOT NULL, | |
success BOOLEAN NOT NULL, | |
hashcash TEXT | |
); | |
CREATE INDEX scoreboard_relevance ON uploads ( | |
success, size, submission_time, team, hash); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% extends "base.html" %} | |
{% block title %}golf.so – Scoreboard{% endblock %} | |
{% block content %} | |
<div class="scoreboard"> | |
<div class="team">Name</div> | |
<div class="size">Bytes</div> | |
<div class="hash">Hash</div> | |
{% for result in results %} | |
<div class="sep"></div> | |
<div class="rank">{{ loop.index }}</div> | |
<div class="team">{{ result.team }}</div> | |
<div class="size">{{ result.size }}</div> | |
<div class="hash">{{ result.hash[:8] }}</div> | |
{% endfor %} | |
</div> | |
{% endblock %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% extends "base.html" %} | |
{% block title %}golf.so – Upload{% endblock %} | |
{% block content %} | |
<h1>Upload</h1> | |
<p> | |
Upload a 64-bit ELF shared object of size at most | |
1024 bytes. It should spawn a shell (execute | |
<code>execve("/bin/sh", ["/bin/sh"], ...)</code>) | |
when used like | |
</p> | |
<pre><code>LD_PRELOAD=<upload> /bin/true</code></pre> | |
<form method="POST" enctype="multipart/form-data" class="upload-form"> | |
<div class="form-group"> | |
<label for="team">Team</label> | |
<input type="text" id="team" name="team" | |
value="{{ session.get('team') or '' }}" | |
placeholder="Enter your team" /> | |
</div> | |
<div class="form-group"> | |
<label for="file">File</label> | |
<input type="file" id="file" name="file" required="required" /> | |
</div> | |
{% if config.BITS > 0 %} | |
<div class="form-group"> | |
<label for="hashcash"> | |
<code>hashcash -m -b {{ config.BITS }} -s {{ config.RESOURCE }}</code> | |
</label> | |
<input type="text" id="hashcash" name="hashcash" | |
placeholder="An unused token" required="required" /> | |
</div> | |
{% endif %} | |
<div class="form-group"> | |
<button type="submit"> | |
Upload | |
</button> | |
</div> | |
</form> | |
{% endblock %} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment