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 a8f375bede6c397ae99558df3265a0f603f3dfd5
parent 14ac78e63dbd2233d3dd577a0684a3dd8566234a
Author: Louis Burda <quent.burda@gmail.com>
Date:   Fri, 25 Jun 2021 17:13:56 +0200

large refactor of checker, added more havocs to test listing and search, added motd to service welcome banner

Diffstat:
Mchecker/src/checker.py | 554+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mchecker/src/revhash/main.c | 1+
Mchecker/test.sh | 19+++++++++++++------
Mservice/src/main.c | 60++++++++++++++++++++++++++++++++++++++++++++++--------------
Aservice/src/msgs/banner | 4++++
Aservice/src/msgs/motd | 15+++++++++++++++
Dservice/src/msgs/welcome | 6------
Mservice/src/stlfile.c | 14++++----------
Mservice/src/stlfile.h | 4++--
Msrc/main.c | 60++++++++++++++++++++++++++++++++++++++++++++++--------------
Asrc/msgs/banner | 4++++
Asrc/msgs/motd | 15+++++++++++++++
Dsrc/msgs/welcome | 6------
Msrc/stlfile.c | 15+++++----------
Msrc/stlfile.h | 4++--
15 files changed, 503 insertions(+), 278 deletions(-)

diff --git a/checker/src/checker.py b/checker/src/checker.py @@ -12,20 +12,6 @@ rand = random.SystemRandom() from faker import Faker -# DEBUGING MEMORY ISSUES# -import tracemalloc, signal - -tracemalloc.start() - -def handler(signum, frame): - print("Received SIG!") - snapshot = tracemalloc.take_snapshot() - top_stats = snapshot.statistics('lineno') - open(f"malloc-log-{os.getpid()}", "w+").write("\n".join([str(v) for v in top_stats[:10]])) - -signal.signal(signal.SIGALRM, handler) -# END DEBUG # - evil_file = b""" solid test\xff facet normal 0 0 1.0 @@ -48,52 +34,60 @@ def ensure_bytes(v): else: raise BrokenServiceException("Tried to pass non str/bytes to bytes arg") +def includes_all(resp, targets): + for m in targets: + if ensure_bytes(m) not in resp: + return False + return True + +def includes_any(resp, targets): + for m in targets: + if ensure_bytes(m) in resp: + return True + return False + +def fakeid(): + fake = Faker(["en_US"]) + idstr = bytes([ord(c) for c in fake.name().replace(" ","") if c in generic_alphabet][:12]).ljust(10, b".") + idstr += bytes([ord(rand.choice(generic_alphabet)) for i in range(8)]) + return idstr + +def havocid(): + idlen = rand.randint(10, 40) + return bytes([rand.randint(32, 127) for i in range(idlen)]) + +def reverse_hash(hashstr): + if type(hashstr) is bytes: + hashstr = hashstr.decode() + data = subprocess.check_output([os.getenv("REVHASH_PATH"), hashstr])[:-1] + if data == b"": + raise BrokenServiceException(f"Failed to find hash preimage of {hashstr}") + return data + +def check_line(conn, context): + line = conn.recvline() + if b"ERR:" in line: + raise EnoException(f"{context}: Unexpected error message\n") + return line + +def parse_int(intstr): + try: + return int(intstr) + except: + return None + class STLDoctorChecker(BaseChecker): service_name = "stldoctor" port = 9090 flag_variants = 2 noise_variants = 2 - havoc_variants = 8 + havoc_variants = 16 exploit_variants = 2 - prompt = b"$ " - - def openconn(self): - conn = self.connect() - resp = conn.recvuntil(self.prompt) - return conn - - def closeconn(self, conn): - self.debug("Sending exit command") - conn.write("exit\n") - # ensure it is a clean exit - conn.recvuntil("bye!") - conn.close() - - def fakeid(self): - fake = Faker(["en_US"]) - 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): - idlen = rand.randint(10, 40) - return "".join([chr(rand.randint(32, 127)) for i in range(idlen)]) - - def do_auth(self, conn, authstr): - authstr = ensure_bytes(authstr) - self.debug(f"Logging in with {authstr}") - conn.write("auth\n") - conn.write(authstr + b"\n") - resp = conn.recvuntil(self.prompt) - assert_in(b"Success!", resp, f"Login with pass {authstr} failed"); + prompt = b"\r$ " - def check_listed(self, conn, modelid): - modelid = ensure_bytes(modelid) - conn.write("list\n") - resp = conn.recvuntil(self.prompt) - assert_in(modelid, resp, f"Uploaded model {modelid} is missing from list command") + # HELPER FUNCS # def querydb(self, *args): vals = [] @@ -108,39 +102,43 @@ class STLDoctorChecker(BaseChecker): def postdb(self, **kwdict): self.chain_db = kwdict - def reverse_hash(self, hashstr): - return subprocess.check_output([os.getenv("REVHASH_PATH"), hashstr])[:-1] - - def genfile_ascii(self, solidname, malformed=False): + def genfile_ascii(self, solidname, malformed = None): + indent = bytes([rand.choice(b"\t ") for i in range(rand.randint(1, 4))]) solidname = ensure_bytes(solidname) - randchoice = rand.randint(0,2) + facet_count = rand.randint(4, 30) if len(solidname) != 0: content = b"solid " + solidname + b"\n" else: content = b"solid\n" - facet_count = rand.randint(4, 30) - indent = bytes([rand.choice(b"\t ") for i in range(rand.randint(1, 4))]) + for fi in range(facet_count): - if malformed and randchoice == 0: # malformed by wrong keyword + # MALFORM 1: wrong keyword + if malformed == 1: 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" - if malformed and randchoice == 1: # malformed wrong keyword case + + # MALFORM 2: wrong keyword case + if malformed == 2: content += indent * 2 + b"outer lOop\n" else: content += indent * 2 + b"outer loop\n" + for i in range(3): 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: + + # MALFORM 3: no endsolid keyword + if malformed != 3: if solidname != b"": content += b"endsolid " + solidname + b"\n" else: @@ -148,9 +146,9 @@ class STLDoctorChecker(BaseChecker): return content - def genfile_bin(self, solidname, malformed=False): + def genfile_bin(self, solidname, malformed = None): solidname = ensure_bytes(solidname) - randchoice = rand.randint(0, 3) + facet_count = rand.randint(4, 30) if len(solidname) > 78: raise EnoException("Solidname to embed in header is larger than header itself") @@ -158,17 +156,20 @@ class STLDoctorChecker(BaseChecker): content = b"#" + solidname.ljust(78, b"\x00") + b"\x00" else: content = b"#" + b"\x00" * 79 - facet_count = rand.randint(4, 30) - if malformed and randchoice == 0: # malform by specifying more facets than are in the file + + # MALFORM 1: specify more facets than are in the file + if malformed == 1: 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 + + # MALFORM 2: invalid float for norm / vec + if malformed == 2: + norm[rand.randint(0,2)] = math.nan vs[rand.randint(0,2)][rand.randint(0,2)] = math.inf for i in range(3): content += struct.pack("<f", norm[i]) @@ -176,229 +177,366 @@ class STLDoctorChecker(BaseChecker): 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 + + # MALFORM 3: add extra data to the end of the file + if malformed == 3: content += bytes([rand.randint(0, 255) for i in range(30)]) + return content - def genfile(self, filetype, solidname, malformed=False): + def genfile(self, solidname, filetype, malformed = None): if filetype == "ascii": - return self.genfile_ascii(solidname, malformed=malformed) + return self.genfile_ascii(solidname, malformed = malformed) elif filetype == "bin": - return self.genfile_bin(solidname, malformed=malformed) + 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"); + raise EnoException("Invalid file type supplied") - def putfile(self, conn, modelname, solidname, filetype="ascii", stlfile=None): - solidname = ensure_bytes(solidname) - modelname = ensure_bytes(modelname) + def openconn(self): + conn = self.connect() + resp = conn.recvuntil(b'\n' + self.prompt) + return conn - # Generate file contents - if stlfile is None: - stlfile = self.genfile(filetype, solidname) + def closeconn(self, conn): + self.debug("Sending exit command") + conn.write("exit\n") + conn.recvuntil("bye!") # ensure clean exit + conn.close() + + def do_auth(self, conn, authstr, check = True): + authstr = ensure_bytes(authstr) + self.debug(f"Logging in with {authstr}") + conn.write("auth\n") + conn.write(authstr + b"\n") + + # Check for errors + resp = conn.recvline() + if b"ERR:" in resp: + if check: + raise EnoException(f"Failed to login with {authstr}:\n{resp}") + return None + + # Also check success message + resp += conn.recvuntil(self.prompt) + if b"Success!" not in resp: + raise EnoException(f"Login with pass {authstr} failed") + return b"Welcome back" in resp + + def do_list(self, conn, check = True): + conn.write("list\n") + resp = conn.recvuntil(self.prompt) + + # Check for errors + if b"ERR:" in resp and b">> " not in resp: + if check: + raise EnoException(f"Failed to list private files:\n{resp}") + return None + + return resp + + def do_upload(self, conn, modelname, stlfile, check = True): + modelname = ensure_bytes(modelname) # Upload file self.debug(f"Uploading model with name {modelname}") conn.write("upload\n") + conn.write(modelname + b"\n") conn.write(f"{len(stlfile)}\n") conn.write(stlfile) - conn.write(modelname + b"\n") - # Parse ID - _ = conn.recvline() + # Check for errors + _ = conn.recvline() # Modelname: line = conn.recvline() + if b"ERR:" in line: + if check: + raise EnoException(f"Failed to upload model {modelname}:\n{line}") + conn.recvuntil(self.prompt) + return None + + # Parse ID try: modelid = line.rsplit(b"!", 1)[0].split(b"with ID ", 1)[1] if modelid == b"": raise Exception except: raise BrokenServiceException(f"Invalid response during upload of {modelname}:\n{line}") - # Consume rest of data in this call conn.recvuntil(self.prompt) + return modelid - return stlfile, modelid - - def getfile(self, conn, modelname=None, download=True): + def do_search(self, conn, modelname, download = False, check = True): modelname = ensure_bytes(modelname) # Initiate download self.debug(f"Retrieving model with name {modelname}") - conn.write("search\n") - conn.write(modelname + b"\n") + conn.write(b"search " + modelname + b"\n") conn.write("0\n") # first result - conn.write("y\n" if download else "\n") + conn.write("y\n" if download else "n\n") conn.write("q\n") # quit - # Wait for end of info box - resp = conn.recvuntil("================== \n") + # Check if an error occured + line = conn.recvline() + if b"ERR:" in line: + if check: + raise EnoException(f"Failed to retrieve model {modelname}:\n{line}") + if b"Couldn't find a matching scan result" in line: + # collect all the invalid commands sent after (hacky) + conn.recvuntil(self.prompt) + conn.recvuntil(self.prompt) + conn.recvuntil(self.prompt) + conn.recvuntil(self.prompt) + return None - # Ask for download if desired - 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") + # Recv until end of info box + fileinfo = line + conn.recvuntil("================== \n") + + stlfile = b"" + if download: # Parse file contents + conn.recvuntil(b"Here you go.. (") + size = parse_int(conn.recvuntil(b"B)\n")[:-3]) + if size is None: + raise BrokenServiceException(f"Received invalid download size, response:\n{resp}") self.debug(f"Download size: {size}") - contents = conn.recvn(size) - self.debug("File contents:\n" + str(contents)) - resp += contents + stlfile = conn.recvn(size) conn.recvuntil(self.prompt) + return fileinfo, stlfile + + # CHECK WRAPPERS # + + def check_listed(self, conn, includes): + resp = self.do_list(conn, check = True) + if not includes_all(resp, includes): + raise EnoException(f"Failed to find {includes} in listing:\n{resp}") return resp - def check_getfile(self, conn, modelname, solidname, contents, modelid = None): - resp = self.getfile(conn, modelname = modelname) - if modelid: - assert_in(ensure_bytes(modelid), resp, f"Model id {modelid} not returned / correctly parsed") - assert_in(ensure_bytes(modelname), resp, f"Model name {modelname} not returned / correctly parsed") - assert_in(ensure_bytes(solidname), resp, f"Solid name {solidname} not returned / correctly parsed") - assert_in(ensure_bytes(contents), resp, f"STL File contents not returned / correctly parsed") + def check_not_listed(self, conn, excludes, fail = False): + resp = self.do_list(conn, check = False) + if fail and resp: + raise EnoException(f"Expected list to fail, but returned:\n{resp}") + if not fail and not resp: + raise EnoException(f"List failed unexpectedly:\n{resp}") + if resp and includes_any(resp, excludes): + raise EnoException(f"Unexpectedly found one of {excludes} in listing:\n{resp}") + return resp - def havoc_upload(self, filetype, register): - # Cant be havocid with ascii since might mess with stl parsing - solidname = self.fakeid() if filetype == 'ascii' else self.havocid() - modelname = self.havocid() - authstr = self.havocid() + def check_in_search(self, conn, modelname, includes, download = False): + info, stlfile = self.do_search(conn, modelname, download, check = True) + if not includes_all(info + stlfile, includes): + raise EnoException(f"Retrieved info for {modelname} is missing {includes}: {resp}") + return info, stlfile + + def check_not_in_search(self, conn, modelname, excludes, download = False, fail = False): + resp = self.do_search(conn, modelname, download, check = False) + if resp: + combined = resp[0]+resp[1] + if fail and resp: + raise EnoException("Search for {modelname} succeeded unexpectedly:\n{combined}") + if not fail and not resp: + raise EnoException(f"Search for {modelname} failed unexpectedly:\n{resp}") + if resp and includes_any(resp[0] + resp[1], excludes): + raise EnoException(f"Unexpectedly {modelname} info contains one of {includes}: {combined}") + return resp + + # TEST METHODS # + + def test_good_upload(self, filetype, register): + # ASCII Solidname cant be havocid since it might mess with parsing + solidname = fakeid() if filetype == "ascii" else havocid() + modelname = havocid() + authstr = havocid() + stlfile = self.genfile(solidname, filetype) # Create new session and user and upload file conn = self.openconn() if register: self.do_auth(conn, authstr) - contents, modelid = self.putfile(conn, modelname, solidname, filetype) - self.check_getfile(conn, modelname, solidname, contents) + modelid = self.do_upload(conn, modelname, stlfile) + expected = [modelname, solidname, stlfile, modelid] + resp = self.check_in_search(conn, modelname, expected, download = True) if register: - self.check_listed(conn, modelid) + resp = self.check_listed(conn, [modelname, modelid]) self.closeconn(conn) # Try getting file from a new session conn = self.openconn() if register: + self.check_not_in_search(conn, modelname, expected, download = True, fail = True) self.do_auth(conn, authstr) - self.check_getfile(conn, modelname, solidname, contents) - if register: - self.check_listed(conn, modelid) + self.check_in_search(conn, modelname, expected, download = True) + self.check_listed(conn, [modelid, modelname]) + else: + self.check_in_search(conn, modelname, expected, download = True) + self.closeconn(conn) - def malformed_upload(self, filetype): + def test_bad_upload(self, filetype, variant): + stlfile = self.genfile(fakeid(), filetype, malformed = variant) + 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) + if self.do_upload(conn, fakeid(), stlfile, check = False): + raise EnoException(f"Able to upload malformed file:\n{stlfile}") self.closeconn(conn) - def putflag(self): # type: () -> None - if self.variant_id == 0: + def test_search(self, registered = False): + solidname = fakeid() + modelname = fakeid() + modelname2 = fakeid() + authstr = fakeid() + stlfile = self.genfile(solidname, "bin") + + conn = self.openconn() + if registered: + self.do_auth(conn, authstr) + modelid = self.do_upload(conn, modelname, stlfile) + self.check_not_in_search(conn, modelname2, [modelname, modelid], + download = True, fail = True) + self.check_in_search(conn, modelname, [modelname, modelid], download = True) + self.closeconn(conn) + + def test_list(self, registered = False): + solidname = fakeid() + modelname = fakeid() + authstr = fakeid() + authstr2 = fakeid() + stlfile = self.genfile(solidname, "bin") + + conn = self.openconn() + self.do_auth(conn, authstr) + modelid = self.do_upload(conn, modelname, stlfile) + self.check_listed(conn, [modelid, modelname]) + self.closeconn(conn) + + if registered: conn = self.openconn() - modelname = self.fakeid() - stlfile, modelid = self.putfile(conn, modelname, self.flag, filetype = "ascii") + if self.do_auth(conn, authstr2): + raise EnoException("New authstr {authstr2} has user dir") + self.check_not_listed(conn, [modelid, modelname]) self.closeconn(conn) - self.postdb(modelid=modelid, modelname=modelname) - elif self.variant_id == 1: + else: conn = self.openconn() - modelname = self.fakeid() - authstr = self.fakeid() - self.do_auth(conn, authstr) - stlfile, modelid = self.putfile(conn, modelname, self.flag, filetype = "bin") + self.check_not_listed(conn, [modelid, modelname], fail = True) self.closeconn(conn) - self.postdb(modelid=modelid, modelname=modelname, auth=authstr) + + # CHECKER METHODS # + + def putflag(self): # type: () -> None + if self.variant_id in (0, 1): + modelname = fakeid() + types = ["ascii", "bin"] + registered = (self.variant_id == 1) + stlfile = self.genfile(self.flag, types[self.variant_id]) + authstr = fakeid() if registered else "" + + conn = self.openconn() + if registered: + self.do_auth(conn, authstr) + modelid = self.do_upload(conn, modelname, stlfile) + self.closeconn(conn) + + self.postdb(modelid = modelid, modelname = modelname, authstr = authstr) else: raise EnoException(f"Invalid putflag variant ({self.variant_id}) provided") def getflag(self): # type: () -> None - if self.variant_id == 0: - modelid, modelname = self.querydb("modelid", "modelname") - conn = self.openconn() - resp = self.getfile(conn, modelname.encode()) - assert_in(self.flag.encode(), resp, "Flag not found in file info nor contents") - self.closeconn(conn) - elif self.variant_id == 1: - modelid, modelname, authstr = self.querydb("modelid", "modelname", "auth") + if self.variant_id in (0, 1): + modelid, modelname, authstr = self.querydb("modelid", "modelname", "authstr") + registered = (self.variant_id == 1) + conn = self.openconn() - self.do_auth(conn, authstr) - resp = self.getfile(conn, modelname.encode()) - assert_in(self.flag.encode(), resp, "Flag not found in file info nor contents") + if registered: + self.do_auth(conn, authstr) + info, stlfile = self.do_search(conn, modelname) + if self.flag.encode() not in (info + stlfile): + raise EnoException(f"Flag {self.flag} not found in search:\n{info}\n{stlfile}") self.closeconn(conn) else: raise EnoException(f"Invalid getflag variant ({self.variant_id}) provided") def putnoise(self): # type: () -> None - if self.variant_id == 0: - conn = self.openconn() - modelname = self.fakeid() - solidname = self.fakeid() - contents, modelid = self.putfile(conn, modelname, solidname, "bin") - self.closeconn(conn) - self.postdb(modelid=modelid, modelname=modelname, solidname=solidname, contents=contents) - elif self.variant_id == 1: + if self.variant_id in (0, 1): + modelname = fakeid() + solidname = fakeid() + types = ["bin", "ascii"] + registered = (self.variant_id == 1) + authstr = fakeid() if registered else "" + stlfile = self.genfile(solidname, types[self.variant_id]) + conn = self.openconn() - authstr = self.fakeid() - modelname = self.fakeid() - solidname = self.fakeid() - self.do_auth(conn, authstr) - contents, modelid = self.putfile(conn, modelname, solidname, "ascii") + if registered: + self.do_auth(conn, authstr) + modelid = self.do_upload(conn, modelname, stlfile) self.closeconn(conn) - self.postdb(modelid=modelid, modelname=modelname, solidname=solidname, contents=contents, auth=authstr) + + self.postdb(modelid = modelid, modelname = modelname, + solidname = solidname, stlfile = stlfile, authstr = authstr) else: raise EnoException(f"Invalid putnoise variant ({self.variant_id}) provided") def getnoise(self): # type: () -> None - if self.variant_id == 0: - modelid, modelname, solidname, contents = self.querydb("modelid", "modelname", "solidname", "contents") - conn = self.openconn() - self.check_getfile(conn, modelname, solidname, contents, modelid) - self.closeconn(conn) - elif self.variant_id == 1: - modelid, modelname, solidname, contents, authstr = self.querydb("modelid", "modelname", "solidname", "contents", "auth") + if self.variant_id in (0, 1): + modelid, modelname, solidname, stlfile, authstr \ + = self.querydb("modelid", "modelname", "solidname", "stlfile", "authstr") + registered = (self.variant_id == 1) + expected = [modelname, solidname, stlfile, modelid] + conn = self.openconn() - self.do_auth(conn, authstr) - self.check_getfile(conn, modelname, solidname, contents, modelid) + if registered: + self.do_auth(conn, authstr) + self.check_in_search(conn, modelname, expected, download = True) self.closeconn(conn) else: raise EnoException(f"Invalid noise variant ({self.variant_id}) provided") def havoc(self): # type: () -> None if self.variant_id == 0: - self.havoc_upload('ascii', False) + self.test_good_upload("ascii", False) elif self.variant_id == 1: - self.havoc_upload('bin', False) + self.test_good_upload("bin", False) elif self.variant_id == 2: - self.havoc_upload('ascii', True) + self.test_good_upload("ascii", True) elif self.variant_id == 3: - self.havoc_upload('bin', True) + self.test_good_upload("bin", True) elif self.variant_id == 4: - self.malformed_upload('ascii') + self.test_bad_upload("ascii", variant = 1) elif self.variant_id == 5: - self.malformed_upload('bin') + self.test_bad_upload("ascii", variant = 2) elif self.variant_id == 6: - self.malformed_upload('garbage') + self.test_bad_upload("ascii", variant = 3) elif self.variant_id == 7: - self.malformed_upload('garbage-tiny') + self.test_bad_upload("bin", variant = 1) + elif self.variant_id == 8: + self.test_bad_upload("bin", variant = 2) + elif self.variant_id == 9: + self.test_bad_upload("bin", variant = 3) + elif self.variant_id == 10: + self.test_bad_upload("garbage", variant = 1) + elif self.variant_id == 11: + self.test_bad_upload("garbage-tiny", variant = 1) + elif self.variant_id == 12: + self.test_search(False) + elif self.variant_id == 13: + self.test_search(True) + elif self.variant_id == 14: + self.test_list(False) + elif self.variant_id == 15: + self.test_list(True) else: raise EnoException(f"Invalid havoc variant ({self.variant_id}) provided") def exploit(self): # type: () -> None if self.variant_id == 0: - name = self.fakeid() + modelname = fakeid() # Upload evil file for parse via search for hash truncation + self.debug(f"Uploading evil file for hash truncation") conn = self.openconn() - resp,mid = self.putfile(conn, name, name, stlfile=evil_file) - self.debug(f"Uploaded evil file with id {mid}") - resp = self.getfile(conn, name, download=False) + modelid = self.do_upload(conn, modelname, stlfile = evil_file) + info, contents = self.do_search(conn, modelname, download = False) conn.write("search last\n") filelist = [l.strip().split(b" : ")[1] for l in conn.recvuntil("? ").split(b"\n") if b" : " in l] if len(filelist) == 0: @@ -406,7 +544,7 @@ class STLDoctorChecker(BaseChecker): # Use it to enumerate other files and grab contents found = None - self.debug("Targets:\n" + "\n".join([' - ' + l.decode('latin1') for l in filelist])) + self.debug("Targets:\n" + "\n".join([" - " + l.decode("latin1") for l in filelist])) for i, fhash in enumerate(filelist): self.debug(f"Retrieving file {fhash} at index {i}") conn.write(f"{i}\nn\n") @@ -416,6 +554,7 @@ class STLDoctorChecker(BaseChecker): if found is not None or i == len(filelist) - 1: break + # Done! conn.write("q\n") conn.recvuntil(self.prompt) self.closeconn(conn) @@ -431,8 +570,9 @@ class STLDoctorChecker(BaseChecker): 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) + + # Get private user hashes via 'list' + resp = self.do_list(conn) self.debug("List response: " + str(resp)) users = [l.split(b" .")[1] for l in resp.split(b"\n") if b">> ." in l] if len(users) == 0: @@ -442,27 +582,25 @@ class STLDoctorChecker(BaseChecker): # Login as each private user found = None for u in users: - conn = self.openconn() + # Find preimage of user hash self.debug(f"Logging in as user with id {u}") + user = reverse_hash(u) - user = self.reverse_hash(u.decode()) - if user == b"": - raise BrokenServiceException("Failed to find hash preimage") - conn.write(b"auth " + user + b"\n") - - resp = conn.recvuntil(self.prompt) - if b"Welcome back" not in resp: + # Authenticate and check if the user is new + conn = self.openconn() + if not self.do_auth(conn, user): self.closeconn(conn) - continue - # NOTE: dont raise an exception, could be that user dir was cleaned up just - # before we logged in, not necessarily because of invalid prehash + # We dont raise an exception, because it could be that user dir was cleaned + # up just before we logged in, not necessarily because of an invalid prehash # raise EnoException(f"Reversing of hash {u} returned invalid preimage {user}") + continue - conn.write("list\n") - resp = conn.recvuntil(self.prompt) - names = b"\n".join([l.split(b": ", 1)[1] for l in resp.split(b"\n") if b"Solid Name: " in l]) + # List all private files of user + resp = self.do_list(conn) self.closeconn(conn) + # Search for flag in solid names + 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 diff --git a/checker/src/revhash/main.c b/checker/src/revhash/main.c @@ -15,6 +15,7 @@ mhash(const char *str, size_t len) int i, k, v; char c, *bp; + if (!str || !*str) str = "."; if (len <= 0) return EXIT_FAILURE; for (v = 0, i = 0; i < len; i++) v += str[i]; diff --git a/checker/test.sh b/checker/test.sh @@ -24,6 +24,7 @@ except: " || nop } +taskid="" try() { cmd="$1" pid=$BASHPID @@ -34,7 +35,7 @@ try() { else variant=$2 fi - taskid=$pid + taskid="$pid" if [ ! -z "$REMOTE" ]; then python3 enoreq.py -j True -A http://localhost:9091 -a $REMOTE \ --flag ENOTESTFLAG123= --flag_regex 'ENO.*=' -i $taskid \ @@ -57,11 +58,17 @@ try() { docker-compose logs --tail=2000 | grep '"taskId": '$taskid | $ENOLOGMESSAGE_PARSER fi ) > "$newfile" + echo -ne "Executing $cmd with variant $variant.. $res (TASK: $taskid)\n" + return 1 + else + echo -ne "Executing $cmd with variant $variant.. $res (TASK: $taskid)\n" + return 0 fi - echo -ne "Executing $cmd with variant $variant.. $res (TASK: $taskid)\n" } try-all() { + set -e + try putflag 0 try getflag 0 @@ -74,10 +81,9 @@ try-all() { try putflag 1 try getflag 1 - try havoc 0 - try havoc 1 - try havoc 2 - try havoc 3 + for i in $(seq 0 15); do + try havoc $i + done try exploit 0 try exploit 1 @@ -92,6 +98,7 @@ one-of() { if one-of "$1" putflag getflag putnoise getnoise havoc exploit; then try $@ + [ $? -ne 0 -a -e "$EDITOR" ] && "$EDITOR" "/tmp/checker-log-$taskid" elif [ "$1" == "test-exploits" ]; then try exploit 0 try exploit 1 diff --git a/service/src/main.c b/service/src/main.c @@ -128,7 +128,7 @@ handle_download(const char *scandir) fseek(f, 0, SEEK_END); size = ftell(f); fseek(f, 0, SEEK_SET); - if (size > MAXFILESIZE) { + if (size >= MAXFILESIZE) { ERR("File is too large!\n"); goto fail; } @@ -150,6 +150,31 @@ fail: } void +motd() +{ + char linebuf[80]; + int msgc, msgi, i; + FILE *f; + + if (!(f = fopen("msgs/motd", "r"))) return; + + if (!fgets(linebuf, sizeof(linebuf), f)) + goto exit; + + srand(time(NULL)); + if ((msgc = atoi(linebuf))) { + msgi = rand() % msgc; + for (i = 0; i < msgi + 1; i++) + if (!fgets(linebuf, sizeof(linebuf), f)) + return; + printf("%s\n", linebuf); + } + +exit: + fclose(f); +} + +void cat_cmd(const char *arg) { if (arg && !strncmp(arg, "flag", 4)) @@ -193,34 +218,41 @@ echo_cmd(const char *arg) void upload_cmd(const char *arg) { + char *end, *contents, *modelname; const char *resp; - char *end, *contents; size_t len; + modelname = checkp(strdup(ask("Enter a model name: "))); + if (!strlen(modelname)) { + ERR("Empty model names are not allowed"); + goto exit; + } + resp = ask("How large is your file? "); len = strtoul(resp, &end, 10); if (len <= 0 || len >= MAXFILESIZE || *end) { ERR("Invalid file length!\n"); - return; + goto exit; } printf("Ok! Im listening..\n"); contents = checkp(malloc(len + 1)); if (fread(contents, 1, len, stdin) != len) { ERR("Not enough data received!\n"); - goto cleanup; + goto exit; } contents[len] = '\0'; - if ((cached.valid = parse_file(&cached, contents, len))) { + if ((cached.valid = parse_file(&cached, contents, len, &modelname))) { if (save_submission(&cached, contents, len) != OK) ERR("Failed to save your submission!\n"); else printf("Your file was saved with ID %s!\n", cached.hash); } -cleanup: +exit: free(contents); + free(modelname); } void @@ -264,7 +296,7 @@ search_cmd(const char *arg) if (pathc == 0) { ERR("Couldn't find a matching scan result!\n"); - goto cleanup; + goto exit; } while (1) { @@ -273,15 +305,15 @@ search_cmd(const char *arg) which = strtoul(resp, &end, 10); if (which >= pathc || which < 0 || *end) { ERR("Invalid index!\n"); - goto cleanup; + goto exit; } scandir = aprintf("%s/%s", resultdir, paths[which]); - if (handle_download(scandir) != OK) goto cleanup; + if (handle_download(scandir) != OK) goto exit; FREE(scandir); } -cleanup: +exit: FREE(scandir); for (i = 0; i < pathc; i++) free(paths[i]); @@ -355,7 +387,6 @@ auth_cmd(const char *arg) void cleanexit() { - printf("see you later!\n"); free_info(&cached); free(resultdir); } @@ -364,8 +395,8 @@ int main() { const char *cmd, *envstr; - char *cp, *arg; int exit, i, cmdlen; + char *cp, *arg; if (!(envstr = getenv("RESULTDIR"))) die("RESULTDIR not defined\n"); @@ -378,12 +409,13 @@ main() atexit(cleanexit); - dump("msgs/welcome"); + dump("msgs/banner"); + motd(); exit = 0; while (!exit) { errno = 0; - cmd = ask("$ "); + cmd = ask("\r$ "); if (!*cmd && errno == EBADMSG) break; if (!*cmd) continue; diff --git a/service/src/msgs/banner b/service/src/msgs/banner @@ -0,0 +1,4 @@ + ┌─┐┌┬┐┬ ┌┬┐┌─┐┌─┐┌┬┐┌─┐┬─┐ ┬ + └─┐ │ │ │││ ││ │ │ │├┬┘ ▓ + └─┘ ┴ ┴─┘─┴┘└─┘└─┘ ┴ └─┘┴└─ │ + diff --git a/service/src/msgs/motd b/service/src/msgs/motd @@ -0,0 +1,15 @@ +13 + We analyze your STL files! + STL: Standard Triangle Language + STL: Standard Template Library + STL: Solid Tesselation Language + STL: Standard Telegraph Level + A file upload service (now in 3D!) + Keep your files small, size matters! + Use 'search' to find models by name! + ENO.. is a popular solid name! + STeaLing flags since 2006! + C4n y0u r34d th1s? 🏁 ➡️ 📁 + ( *ಥ ⌂ * ) dont pwn pls + *finds file* UwU whats this? + diff --git a/service/src/msgs/welcome b/service/src/msgs/welcome @@ -1,6 +0,0 @@ - ┌─┐┌┬┐┬ ┌┬┐┌─┐┌─┐┌┬┐┌─┐┬─┐ ┬ - └─┐ │ │ │││ ││ │ │ │├┬┘ ▓ - └─┘ ┴ ┴─┘─┴┘└─┘└─┘ ┴ └─┘┴└─ │ - - We analyze your STL files! - diff --git a/service/src/stlfile.c b/service/src/stlfile.c @@ -310,7 +310,7 @@ fail: } int -parse_file(struct parseinfo *info, char *buf, size_t len) +parse_file(struct parseinfo *info, char *buf, size_t len, char **modelname) { int status; const char *resp; @@ -332,17 +332,11 @@ parse_file(struct parseinfo *info, char *buf, size_t len) : parse_file_bin(info, buf, len); if (status == FAIL) return FAIL; - if (!info->modelname) { - resp = ask("Please enter your model name: "); - if (strlen(resp) < 4) { - ERR("Model name is too short!\n"); - return FAIL; - } - info->modelname = checkp(strdup(resp)); - } - if (!info->solidname) info->solidname = checkp(strdup("")); + info->modelname = *modelname; + *modelname = NULL; + info->hash = checkp(strdup(mhash(info->modelname, -1))); return OK; diff --git a/service/src/stlfile.h b/service/src/stlfile.h @@ -9,7 +9,7 @@ #include "util.h" -#define FMT_ERR(...) ERR("FORMAT " __VA_ARGS__) +#define FMT_ERR(...) printf("FORMAT ERR: " __VA_ARGS__) enum { KW_INVALID = -1, @@ -47,7 +47,7 @@ struct parseinfo { int type, valid; }; -int parse_file(struct parseinfo *info, char *buf, size_t len); +int parse_file(struct parseinfo *info, char *buf, size_t len, char **modelname); int save_info(struct parseinfo *info, FILE *f); int load_info(struct parseinfo *info, FILE *f); void print_info(struct parseinfo *info); diff --git a/src/main.c b/src/main.c @@ -128,7 +128,7 @@ handle_download(const char *scandir) fseek(f, 0, SEEK_END); size = ftell(f); fseek(f, 0, SEEK_SET); - if (size > MAXFILESIZE) { + if (size >= MAXFILESIZE) { ERR("File is too large!\n"); goto fail; } @@ -150,6 +150,31 @@ fail: } void +motd() +{ + char linebuf[80]; + int msgc, msgi, i; + FILE *f; + + if (!(f = fopen("msgs/motd", "r"))) return; + + if (!fgets(linebuf, sizeof(linebuf), f)) + goto exit; + + srand(time(NULL)); + if ((msgc = atoi(linebuf))) { + msgi = rand() % msgc; + for (i = 0; i < msgi + 1; i++) + if (!fgets(linebuf, sizeof(linebuf), f)) + return; + printf("%s\n", linebuf); + } + +exit: + fclose(f); +} + +void cat_cmd(const char *arg) { if (arg && !strncmp(arg, "flag", 4)) @@ -193,34 +218,41 @@ echo_cmd(const char *arg) void upload_cmd(const char *arg) { + char *end, *contents, *modelname; const char *resp; - char *end, *contents; size_t len; + modelname = checkp(strdup(ask("Enter a model name: "))); + if (!strlen(modelname)) { + ERR("Empty model names are not allowed"); + goto exit; + } + resp = ask("How large is your file? "); len = strtoul(resp, &end, 10); if (len <= 0 || len >= MAXFILESIZE || *end) { ERR("Invalid file length!\n"); - return; + goto exit; } printf("Ok! Im listening..\n"); contents = checkp(malloc(len + 1)); if (fread(contents, 1, len, stdin) != len) { ERR("Not enough data received!\n"); - goto cleanup; + goto exit; } contents[len] = '\0'; - if ((cached.valid = parse_file(&cached, contents, len))) { + if ((cached.valid = parse_file(&cached, contents, len, &modelname))) { if (save_submission(&cached, contents, len) != OK) ERR("Failed to save your submission!\n"); else printf("Your file was saved with ID %s!\n", cached.hash); } -cleanup: +exit: free(contents); + free(modelname); } void @@ -264,7 +296,7 @@ search_cmd(const char *arg) if (pathc == 0) { ERR("Couldn't find a matching scan result!\n"); - goto cleanup; + goto exit; } while (1) { @@ -273,15 +305,15 @@ search_cmd(const char *arg) which = strtoul(resp, &end, 10); if (which >= pathc || which < 0 || *end) { ERR("Invalid index!\n"); - goto cleanup; + goto exit; } scandir = aprintf("%s/%s", resultdir, paths[which]); - if (handle_download(scandir) != OK) goto cleanup; + if (handle_download(scandir) != OK) goto exit; FREE(scandir); } -cleanup: +exit: FREE(scandir); for (i = 0; i < pathc; i++) free(paths[i]); @@ -355,7 +387,6 @@ auth_cmd(const char *arg) void cleanexit() { - printf("see you later!\n"); free_info(&cached); free(resultdir); } @@ -364,8 +395,8 @@ int main() { const char *cmd, *envstr; - char *cp, *arg; int exit, i, cmdlen; + char *cp, *arg; if (!(envstr = getenv("RESULTDIR"))) die("RESULTDIR not defined\n"); @@ -378,12 +409,13 @@ main() atexit(cleanexit); - dump("msgs/welcome"); + dump("msgs/banner"); + motd(); exit = 0; while (!exit) { errno = 0; - cmd = ask("$ "); + cmd = ask("\r$ "); if (!*cmd && errno == EBADMSG) break; if (!*cmd) continue; diff --git a/src/msgs/banner b/src/msgs/banner @@ -0,0 +1,4 @@ + ┌─┐┌┬┐┬ ┌┬┐┌─┐┌─┐┌┬┐┌─┐┬─┐ ┬ + └─┐ │ │ │││ ││ │ │ │├┬┘ ▓ + └─┘ ┴ ┴─┘─┴┘└─┘└─┘ ┴ └─┘┴└─ │ + diff --git a/src/msgs/motd b/src/msgs/motd @@ -0,0 +1,15 @@ +13 + We analyze your STL files! + STL: Standard Triangle Language + STL: Standard Template Library + STL: Solid Tesselation Language + STL: Standard Telegraph Level + A file upload service (now in 3D!) + Keep your files small, size matters! + Use 'search' to find models by name! + ENO.. is a popular solid name! + STeaLing flags since 2006! + C4n y0u r34d th1s? 🏁 ➡️ 📁 + ( *ಥ ⌂ * ) dont pwn pls + *finds file* UwU whats this? + diff --git a/src/msgs/welcome b/src/msgs/welcome @@ -1,6 +0,0 @@ - ┌─┐┌┬┐┬ ┌┬┐┌─┐┌─┐┌┬┐┌─┐┬─┐ ┬ - └─┐ │ │ │││ ││ │ │ │├┬┘ ▓ - └─┘ ┴ ┴─┘─┴┘└─┘└─┘ ┴ └─┘┴└─ │ - - We analyze your STL files! - diff --git a/src/stlfile.c b/src/stlfile.c @@ -310,7 +310,7 @@ fail: } int -parse_file(struct parseinfo *info, char *buf, size_t len) +parse_file(struct parseinfo *info, char *buf, size_t len, char **modelname) { int status; const char *resp; @@ -333,17 +333,12 @@ parse_file(struct parseinfo *info, char *buf, size_t len) : parse_file_bin(info, buf, len); if (status == FAIL) return FAIL; - if (!info->modelname) { - resp = ask("Please enter your model name: "); - if (strlen(resp) < 4) { - ERR("Model name is too short!\n"); - return FAIL; - } - info->modelname = checkp(strdup(resp)); - } - if (!info->solidname) info->solidname = checkp(strdup("")); + /* transfer ownership */ + info->modelname = *modelname; + *modelname = NULL; + info->hash = checkp(strdup(mhash(info->modelname, -1))); return OK; diff --git a/src/stlfile.h b/src/stlfile.h @@ -9,7 +9,7 @@ #include "util.h" -#define FMT_ERR(...) ERR("FORMAT " __VA_ARGS__) +#define FMT_ERR(...) fprintf(stderr, "FORMAT ERR: " __VA_ARGS__) enum { KW_INVALID = -1, @@ -47,7 +47,7 @@ struct parseinfo { int type, valid; }; -int parse_file(struct parseinfo *info, char *buf, size_t len); +int parse_file(struct parseinfo *info, char *buf, size_t len, char **modelname); int save_info(struct parseinfo *info, FILE *f); int load_info(struct parseinfo *info, FILE *f); void print_info(struct parseinfo *info);