enowars5-service-stldoctor

STL-Analyzing A/D Service for ENOWARS5 in 2021
git clone https://git.sinitax.com/sinitax/enowars5-service-stldoctor
Log | Files | Refs | README | LICENSE | sfeed.txt

commit d1d4462f5661e0d15176375ec297b3c59d0896c3
parent 7cc88b34e67b3d35ca10bcbf8b393dbc2713b63e
Author: Louis Burda <quent.burda@gmail.com>
Date:   Thu, 24 Jun 2021 19:34:08 +0200

add more havocs to test stl parsing

Diffstat:
Mchecker/src/checker.py | 105++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mchecker/src/requirements.txt | 24+-----------------------
Mservice/docker-compose.yml | 2+-
Mservice/src/main.c | 38+++++++++++++++++++-------------------
Mservice/src/stlfile.c | 30+++++++++++++++++++-----------
Msrc/main.c | 38+++++++++++++++++++-------------------
Msrc/stlfile.c | 30+++++++++++++++++++-----------
7 files changed, 155 insertions(+), 112 deletions(-)

diff --git a/checker/src/checker.py b/checker/src/checker.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from enochecker import BaseChecker, BrokenServiceException, EnoException, run from enochecker.utils import SimpleSocket, assert_equals, assert_in -import os, random, string, struct, subprocess, logging, selectors, time, socket +import math, os, random, string, struct, subprocess, logging, selectors, time, socket import numpy as np logging.getLogger("faker").setLevel(logging.WARNING) @@ -28,16 +28,18 @@ signal.signal(signal.SIGALRM, handler) 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 + facet normal 0 0 1.0 + outer loop + vertex 1 0 0 + vertex 1 1 0 + vertex 0 1 0 + endloop + endfacet +endsolid test\xff """ +generic_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmopqrstuvwxyz0123456789-+.!" + def ensure_bytes(v): if type(v) == bytes: return v @@ -52,7 +54,7 @@ class STLDoctorChecker(BaseChecker): flag_variants = 2 noise_variants = 2 - havoc_variants = 4 + havoc_variants = 8 exploit_variants = 2 prompt = b"$ " @@ -71,9 +73,8 @@ class STLDoctorChecker(BaseChecker): def fakeid(self): fake = Faker(["en_US"]) - allowed = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmopqrstuvwxyz0123456789-+.!" - idstr = "".join([c for c in fake.name().replace(' ','') if c in allowed][:12]).ljust(10, '.') - idstr += "".join([rand.choice(allowed) for i in range(8)]) + idstr = "".join([c for c in fake.name().replace(' ','') if c in generic_alphabet][:12]).ljust(10, '.') + idstr += "".join([rand.choice(generic_alphabet) for i in range(8)]) return idstr def havocid(self): @@ -110,34 +111,46 @@ class STLDoctorChecker(BaseChecker): def reverse_hash(self, hashstr): return subprocess.check_output([os.getenv("REVHASH_PATH"), hashstr])[:-1] - def genfile_ascii(self, solidname): + def genfile_ascii(self, solidname, malformed=False): solidname = ensure_bytes(solidname) + randchoice = rand.randint(0,2) if len(solidname) != 0: content = b"solid " + solidname + b"\n" else: content = b"solid\n" facet_count = rand.randint(4, 30) + indent = b" " * rand.randint(1, 4) for fi in range(facet_count): - content += b"facet normal " + if malformed and randchoice == 0: # malformed by wrong keyword + content += indent * 1 + b"facet nornal " + else: + content += indent * 1 + b"facet normal " vs = [[rand.random() for i in range(3)] for k in range(3)] norm = np.cross(np.subtract(vs[1], vs[0]), np.subtract(vs[2],vs[0])) norm = norm / np.linalg.norm(norm) content += " ".join([f"{v:.2f}" for v in norm]).encode() + b"\n" - content += b"outer loop\n" + if malformed and randchoice == 1: # malformed wrong keyword case + content += indent * 2 + b"outer lOop\n" + else: + content += indent * 2 + b"outer loop\n" for i in range(3): - content += b"vertex " + " ".join([f"{v:.2f}" for v in vs[i]]).encode() + b"\n" - content += b"endloop\n" - content += b"endfacet\n" - if solidname != b"": - content += b"endsolid " + solidname + b"\n" + content += indent * 3 + b"vertex " + " ".join([f"{v:.2f}" for v in vs[i]]).encode() + b"\n" + content += indent * 2 + b"endloop\n" + content += indent + b"endfacet\n" + if malformed and randchoice == 2: + content += b"" # malformed since no endsolid else: - content += b"endsolid\n" + if solidname != b"": + content += b"endsolid " + solidname + b"\n" + else: + content += b"endsolid\n" return content - def genfile_bin(self, solidname): + def genfile_bin(self, solidname, malformed=False): solidname = ensure_bytes(solidname) + randchoice = rand.randint(0, 3) if len(solidname) > 78: raise EnoException("Solidname to embed in header is larger than header itself") @@ -146,23 +159,36 @@ class STLDoctorChecker(BaseChecker): else: content = b"#" + b"\x00" * 79 facet_count = rand.randint(4, 30) - content += struct.pack("<I", facet_count) + if malformed and randchoice == 0: # malform by specifying more facets than are in the file + content += struct.pack("<I", facet_count + rand.randint(3, 7)) + else: + content += struct.pack("<I", facet_count) for fi in range(facet_count): vs = [[rand.random() for i in range(3)] for k in range(3)] norm = np.cross(np.subtract(vs[1], vs[0]), np.subtract(vs[2],vs[0])) + if malformed and randchoice == 2: # malform by setting invalid float in norm + norm[rand.randint(0,2)] = math.inf + elif malformed and randchoice == 3: # same malformation, but in vec + vs[rand.randint(0,2)][rand.randint(0,2)] = math.inf for i in range(3): content += struct.pack("<f", norm[i]) for k in range(3): for i in range(3): content += struct.pack("<f", vs[k][i]) content += b"\x00\x00" + if malformed and randchoice == 1: # malform by adding extra data to the end of the file + content += bytes([rand.randint(0, 255) for i in range(30)]) return content - def genfile(self, filetype, solidname): + def genfile(self, filetype, solidname, malformed=False): if filetype == "ascii": - return self.genfile_ascii(solidname) + return self.genfile_ascii(solidname, malformed=malformed) elif filetype == "bin": - return self.genfile_bin(solidname) + return self.genfile_bin(solidname, malformed=malformed) + elif filetype == "garbage-tiny": + return ("".join([rand.choice(generic_alphabet) for i in range(rand.randint(3, 8))])).encode() + elif filetype == "garbage": + return ("".join([rand.choice(generic_alphabet) for i in range(rand.randint(100, 300))])).encode() else: raise EnoException("Invalid file type supplied"); @@ -258,6 +284,22 @@ class STLDoctorChecker(BaseChecker): self.check_listed(conn, modelid) self.closeconn(conn) + def malformed_upload(self, filetype): + conn = self.openconn() + solidname = self.fakeid() + modelname = self.fakeid() + contents = self.genfile(filetype, solidname, malformed = True) + conn.write("upload\n") + conn.write(f"{len(contents)}\n") + conn.write(contents) + conn.write(modelname + "\n") + if filetype == "garbage-tiny": + conn.recvuntil("ERR: File too small") + else: + conn.recvuntil("ERR:") + conn.recvuntil(self.prompt) + self.closeconn(conn) + def putflag(self): # type: () -> None if self.variant_id == 0: conn = self.openconn() @@ -337,6 +379,14 @@ class STLDoctorChecker(BaseChecker): self.havoc_upload('ascii', True) elif self.variant_id == 3: self.havoc_upload('bin', True) + elif self.variant_id == 4: + self.malformed_upload('ascii') + elif self.variant_id == 5: + self.malformed_upload('bin') + elif self.variant_id == 6: + self.malformed_upload('garbage') + elif self.variant_id == 7: + self.malformed_upload('garbage-tiny') else: raise EnoException(f"Invalid havoc variant ({self.variant_id}) provided") @@ -372,7 +422,6 @@ class STLDoctorChecker(BaseChecker): raise BrokenServiceException("Download size is not a valid integer") resp += conn.recvn(size) resp += conn.recvuntil("? ") - self.debug(resp) found = self.search_flag_bytes(resp) if found is not None or i == len(filelist) - 1: break diff --git a/checker/src/requirements.txt b/checker/src/requirements.txt @@ -1,28 +1,6 @@ -certifi==2020.12.5 -chardet==4.0.0 -click==7.1.2 -dnspython==1.16.0 -# enochecker==0.4.2 -# git+https://github.com/enowars/enochecker@37981175f3125bd552c3c351494186fe9ce35e0b + git+https://github.com/Sinitax/enochecker@f04cab0fd57fbc927809e88c97a1dd37579089ee -enochecker-cli==0.7.0 -enochecker-core==0.10.0 eventlet==0.30.2 -Flask==1.1.2 -greenlet==1.0.0 gunicorn==20.1.0 -idna==2.10 -itsdangerous==1.1.0 -Jinja2==2.11.3 -jsons==1.4.2 -MarkupSafe==1.1.1 -pycryptodome==3.10.1 -pymongo==3.11.3 -requests==2.25.1 -six==1.15.0 -typish==1.9.2 -urllib3==1.26.5 -Werkzeug==1.0.1 numpy==1.20.1 Faker==8.1.4 -pwntools==4.5.0 diff --git a/service/docker-compose.yml b/service/docker-compose.yml @@ -1,7 +1,7 @@ version: '2.1' services: - printdoc: + stldoctor: ulimits: core: hard: 0 diff --git a/service/src/main.c b/service/src/main.c @@ -108,12 +108,12 @@ handle_download(const char *scandir) infopath = aprintf("%s/%s", scandir, "info"); if (!(f = fopen(infopath, "r"))) { - printf("Selected result is missing!\n"); + printf("ERR: Selected result is missing!\n"); goto cleanup; } free_info(&cached); if (load_info(&cached, f) != OK) { - printf("Failed to parse info file!\n"); + printf("ERR: Failed to parse info file!\n"); goto cleanup; } fclose(f); @@ -124,14 +124,14 @@ handle_download(const char *scandir) if (strchr(ask("Download the model? "), 'y')) { modelpath = aprintf("%s/%s", scandir, "model"); if (!(f = fopen(modelpath, "r"))) { - printf("Failed to access file!\n"); + printf("ERR: Failed to access file!\n"); goto cleanup; } fseek(f, 0, SEEK_END); size = ftell(f); fseek(f, 0, SEEK_SET); if (size > MAXFILESIZE) { - printf("File is too large to send!\n"); + printf("ERR: File is too large!\n"); goto cleanup; } printf("Here you go.. (%liB)\n", size); @@ -194,28 +194,28 @@ echo_cmd(const char *arg) void upload_cmd(const char *arg) { - const char *bufp; + const char *resp; char *end, *contents; size_t len; - bufp = ask("How large is your file? "); - len = strtoul(bufp, &end, 10); + resp = ask("How large is your file? "); + len = strtoul(resp, &end, 10); if (len <= 0 || len >= MAXFILESIZE || *end) { - printf("Invalid file length!\n"); + printf("ERR: Invalid file length!\n"); return; } printf("Ok! Im listening..\n"); contents = checkp(malloc(len + 1)); if (fread(contents, 1, len, stdin) != len) { - printf("Hm, I'm missing some bytes.. try again!\n"); + printf("ERR: Not enough data received!\n"); goto cleanup; } contents[len] = '\0'; if ((cached.valid = parse_file(&cached, contents, len))) { if (save_submission(&cached, contents, len) != OK) - printf("Failed to save your submission!\n"); + printf("ERR: Failed to save your submission!\n"); else printf("Your file was saved with ID %s!\n", cached.hash); } @@ -235,7 +235,7 @@ search_cmd(const char *arg) if (arg && !strcmp(arg, "last")) { if (!cached.valid) { - printf("No cached info report available\n"); + printf("ERR: No cached info report available\n"); return; } hash = cached.hash; @@ -244,7 +244,7 @@ search_cmd(const char *arg) } if (!(d = opendir(resultdir))) { - printf("Unable to access upload directory!\n"); + printf("ERR: Unable to access upload directory!\n"); return; } @@ -263,7 +263,7 @@ search_cmd(const char *arg) closedir(d); if (pathc == 0) { - printf("Sorry, couldnt find a matching scan result!\n"); + printf("ERR: Couldn't find a matching scan result!\n"); goto cleanup; } @@ -272,7 +272,7 @@ search_cmd(const char *arg) if (strchr(resp, 'q')) break; which = strtoul(resp, &end, 10); if (which >= pathc || which < 0 || *end) { - printf("Invalid index!\n"); + printf("ERR: Invalid index!\n"); goto cleanup; } @@ -301,7 +301,7 @@ list_cmd(const char *arg) DIR *d; if (!loggedin) { - printf("Not logged in!\n"); + printf("ERR: Not logged in!\n"); return; } @@ -315,7 +315,7 @@ list_cmd(const char *arg) if (load_info(&info, f) == OK) print_info(&info); else - printf("Failed to read saved file info!\n"); + printf("ERR: Failed to read saved file info!\n"); fclose(f); } free(path); @@ -331,7 +331,7 @@ auth_cmd(const char *arg) int ret; if (loggedin) { - printf("Already logged in!\n"); + printf("ERR: Already logged in!\n"); return; } @@ -343,7 +343,7 @@ auth_cmd(const char *arg) } else if (ret && errno == EEXIST) { printf("Success!\nWelcome back!\n"); } else { - printf("Auth failed!\n"); + printf("ERR: Auth failed!\n"); return; } @@ -369,7 +369,7 @@ main() int exit, i, cmdlen; if (!(envstr = getenv("RESULTDIR"))) { - printf("RESULTDIR not defined\n"); + printf("ERR: RESULTDIR not defined\n"); return 1; } diff --git a/service/src/stlfile.c b/service/src/stlfile.c @@ -203,6 +203,9 @@ parse_file_ascii(struct parseinfo *info, char *buf, size_t len) if (states.count) PARSE_FAIL("Expected keyword, got:\n%.*s...\n", 30, bp); + bp = skipws(bp); + if (*bp) PARSE_FAIL("Extraneous data at end of file\n"); + stack_free(&states); return OK; @@ -215,7 +218,7 @@ int parse_file_bin(struct parseinfo *info, char *buf, size_t len) { char *bp, *end = buf + len; - int i, k, m; + int i, k; float v; info->type = TYPE_BIN; @@ -225,11 +228,13 @@ parse_file_bin(struct parseinfo *info, char *buf, size_t len) memcpy(info->header, buf, 80); - if (strlen(buf + 1)) + if (*buf == '#' && strlen(buf + 1)) info->solidname = checkp(strdup(buf + 1)); bp = buf + 80; + info->loopcount = le32toh(*(uint32_t*)bp); + bp += 4; if (!info->loopcount) { memset(info->bbmax, 0, sizeof(float) * 3); @@ -245,17 +250,20 @@ parse_file_bin(struct parseinfo *info, char *buf, size_t len) for (i = 0; i < info->loopcount; i++) { if (bp + 50 > end) PARSE_FAIL("Truncated data! (loops missing)\n"); - bp += 12; - for (k = 0; k < 3; k++, bp += 12) { - for (m = 0; m < 3; m++) { - v = fle32toh(*(float*)(bp + 4 * m)); - info->bbmin[m] = MIN(info->bbmin[m], v); - info->bbmax[m] = MAX(info->bbmax[m], v); + for (k = 0; k < 12; k++, bp += 4) { + v = fle32toh(*(float*)bp); + if (v == INFINITY || v == NAN) + PARSE_FAIL("Encountered invalid float\n"); + if (k >= 3) { + info->bbmin[k % 3] = MIN(info->bbmin[k % 3], v); + info->bbmax[k % 3] = MAX(info->bbmax[k % 3], v); } } bp += 2; } + if (bp != end) PARSE_FAIL("Extraneous data at end of file\n"); + return OK; fail: @@ -271,8 +279,8 @@ parse_file(struct parseinfo *info, char *buf, size_t len) if (info->valid) free_info(info); - if (len < 7) { - printf("File too small!\n"); + if (len < 10) { + printf("ERR: File too small!\n"); return FAIL; } @@ -289,7 +297,7 @@ parse_file(struct parseinfo *info, char *buf, size_t len) if (!info->modelname) { resp = ask("Please enter your model name: "); if (strlen(resp) < 4) { - printf("Model name is too short!\n"); + printf("ERR: Model name is too short!\n"); return FAIL; } info->modelname = checkp(strdup(resp)); diff --git a/src/main.c b/src/main.c @@ -108,12 +108,12 @@ handle_download(const char *scandir) infopath = aprintf("%s/%s", scandir, "info"); if (!(f = fopen(infopath, "r"))) { - fprintf(stderr, "Selected result is missing!\n"); + fprintf(stderr, "ERR: Selected result is missing!\n"); goto cleanup; } free_info(&cached); if (load_info(&cached, f) != OK) { - fprintf(stderr, "Failed to parse info file!\n"); + fprintf(stderr, "ERR: Failed to parse info file!\n"); goto cleanup; } fclose(f); @@ -124,14 +124,14 @@ handle_download(const char *scandir) if (strchr(ask("Download the model? "), 'y')) { modelpath = aprintf("%s/%s", scandir, "model"); if (!(f = fopen(modelpath, "r"))) { - fprintf(stderr, "Failed to access file!\n"); + fprintf(stderr, "ERR: Failed to access file!\n"); goto cleanup; } fseek(f, 0, SEEK_END); size = ftell(f); fseek(f, 0, SEEK_SET); if (size > MAXFILESIZE) { - fprintf(stderr, "File is too large to send!\n"); + fprintf(stderr, "ERR: File is too large!\n"); goto cleanup; } printf("Here you go.. (%liB)\n", size); @@ -194,28 +194,28 @@ echo_cmd(const char *arg) void upload_cmd(const char *arg) { - const char *bufp; + const char *resp; char *end, *contents; size_t len; - bufp = ask("How large is your file? "); - len = strtoul(bufp, &end, 10); + resp = ask("How large is your file? "); + len = strtoul(resp, &end, 10); if (len <= 0 || len >= MAXFILESIZE || *end) { - fprintf(stderr, "Invalid file length!\n"); + fprintf(stderr, "ERR: Invalid file length!\n"); return; } printf("Ok! Im listening..\n"); contents = checkp(malloc(len + 1)); if (fread(contents, 1, len, stdin) != len) { - fprintf(stderr, "Hm, I'm missing some bytes.. try again!\n"); + fprintf(stderr, "ERR: Not enough data received!\n"); goto cleanup; } contents[len] = '\0'; if ((cached.valid = parse_file(&cached, contents, len))) { if (save_submission(&cached, contents, len) != OK) - fprintf(stderr, "Failed to save your submission!\n"); + fprintf(stderr, "ERR: Failed to save your submission!\n"); else printf("Your file was saved with ID %s!\n", cached.hash); } @@ -235,7 +235,7 @@ search_cmd(const char *arg) if (arg && !strcmp(arg, "last")) { if (!cached.valid) { - fprintf(stderr, "No cached info report available\n"); + fprintf(stderr, "ERR: No cached info report available\n"); return; } hash = cached.hash; @@ -244,7 +244,7 @@ search_cmd(const char *arg) } if (!(d = opendir(resultdir))) { - fprintf(stderr, "Unable to access upload directory!\n"); + fprintf(stderr, "ERR: Unable to access upload directory!\n"); return; } @@ -263,7 +263,7 @@ search_cmd(const char *arg) closedir(d); if (pathc == 0) { - fprintf(stderr, "Sorry, couldnt find a matching scan result!\n"); + fprintf(stderr, "ERR: Couldn't find a matching scan result!\n"); goto cleanup; } @@ -272,7 +272,7 @@ search_cmd(const char *arg) if (strchr(resp, 'q')) break; which = strtoul(resp, &end, 10); if (which >= pathc || which < 0 || *end) { - fprintf(stderr, "Invalid index!\n"); + fprintf(stderr, "ERR: Invalid index!\n"); goto cleanup; } @@ -301,7 +301,7 @@ list_cmd(const char *arg) DIR *d; if (!loggedin) { - fprintf(stderr, "Not logged in!\n"); + fprintf(stderr, "ERR: Not logged in!\n"); return; } @@ -315,7 +315,7 @@ list_cmd(const char *arg) if (load_info(&info, f) == OK) print_info(&info); else - fprintf(stderr, "Failed to read saved file info!\n"); + fprintf(stderr, "ERR: Failed to read saved file info!\n"); fclose(f); } free(path); @@ -331,7 +331,7 @@ auth_cmd(const char *arg) int ret; if (loggedin) { - fprintf(stderr, "Already logged in!\n"); + fprintf(stderr, "ERR: Already logged in!\n"); return; } @@ -343,7 +343,7 @@ auth_cmd(const char *arg) } else if (ret && errno == EEXIST) { printf("Success!\nWelcome back!\n"); } else { - fprintf(stderr, "Auth failed!\n"); + fprintf(stderr, "ERR: Auth failed!\n"); return; } @@ -369,7 +369,7 @@ main() int exit, i, cmdlen; if (!(envstr = getenv("RESULTDIR"))) { - fprintf(stderr, "RESULTDIR not defined\n"); + fprintf(stderr, "ERR: RESULTDIR not defined\n"); return 1; } diff --git a/src/stlfile.c b/src/stlfile.c @@ -203,6 +203,9 @@ parse_file_ascii(struct parseinfo *info, char *buf, size_t len) if (states.count) PARSE_FAIL("Expected keyword, got:\n%.*s...\n", 30, bp); + bp = skipws(bp); + if (*bp) PARSE_FAIL("Extraneous data at end of file\n"); + stack_free(&states); return OK; @@ -215,7 +218,7 @@ int parse_file_bin(struct parseinfo *info, char *buf, size_t len) { char *bp, *end = buf + len; - int i, k, m; + int i, k; float v; info->type = TYPE_BIN; @@ -225,11 +228,13 @@ parse_file_bin(struct parseinfo *info, char *buf, size_t len) memcpy(info->header, buf, 80); - if (strlen(buf + 1)) + if (*buf == '#' && strlen(buf + 1)) info->solidname = checkp(strdup(buf + 1)); bp = buf + 80; + info->loopcount = le32toh(*(uint32_t*)bp); + bp += 4; if (!info->loopcount) { memset(info->bbmax, 0, sizeof(float) * 3); @@ -245,17 +250,20 @@ parse_file_bin(struct parseinfo *info, char *buf, size_t len) for (i = 0; i < info->loopcount; i++) { if (bp + 50 > end) PARSE_FAIL("Truncated data! (loops missing)\n"); - bp += 12; - for (k = 0; k < 3; k++, bp += 12) { - for (m = 0; m < 3; m++) { - v = fle32toh(*(float*)(bp + 4 * m)); - info->bbmin[m] = MIN(info->bbmin[m], v); - info->bbmax[m] = MAX(info->bbmax[m], v); + for (k = 0; k < 12; k++, bp += 4) { + v = fle32toh(*(float*)bp); + if (v == INFINITY || v == NAN) + PARSE_FAIL("Encountered invalid float\n"); + if (k >= 3) { + info->bbmin[k % 3] = MIN(info->bbmin[k % 3], v); + info->bbmax[k % 3] = MAX(info->bbmax[k % 3], v); } } bp += 2; } + if (bp != end) PARSE_FAIL("Extraneous data at end of file\n"); + return OK; fail: @@ -271,8 +279,8 @@ parse_file(struct parseinfo *info, char *buf, size_t len) if (info->valid) free_info(info); - if (len < 7) { - fprintf(stderr, "File too small!\n"); + if (len < 10) { + fprintf(stderr, "ERR: File too small!\n"); return FAIL; } @@ -290,7 +298,7 @@ parse_file(struct parseinfo *info, char *buf, size_t len) if (!info->modelname) { resp = ask("Please enter your model name: "); if (strlen(resp) < 4) { - fprintf(stderr, "Model name is too short!\n"); + fprintf(stderr, "ERR: Model name is too short!\n"); return FAIL; } info->modelname = checkp(strdup(resp));