diff options
| -rw-r--r-- | checker/.gitignore | 1 | ||||
| -rw-r--r-- | checker/enoreq.py | 116 | ||||
| -rw-r--r-- | checker/src/checker.py | 40 | ||||
| -rw-r--r-- | checker/test.sh | 65 |
4 files changed, 186 insertions, 36 deletions
diff --git a/checker/.gitignore b/checker/.gitignore index d0fc2ce..2a62f4e 100644 --- a/checker/.gitignore +++ b/checker/.gitignore @@ -1,3 +1,4 @@ data/ venv/ .data/ +fails diff --git a/checker/enoreq.py b/checker/enoreq.py new file mode 100644 index 0000000..66d0e7b --- /dev/null +++ b/checker/enoreq.py @@ -0,0 +1,116 @@ +import argparse +import hashlib +import sys + +import jsons +import requests +from enochecker_core import CheckerMethod, CheckerResultMessage, CheckerTaskMessage + +TASK_TYPES = [str(i) for i in CheckerMethod] + + +def add_arguments(parser: argparse.ArgumentParser) -> None: + _add_arguments(parser, hide_checker_address=True) + + +def _add_arguments(parser: argparse.ArgumentParser, hide_checker_address=False) -> None: + parser.add_argument("method", choices=TASK_TYPES, help="One of {} ".format(TASK_TYPES)) + if not hide_checker_address: + parser.add_argument("-A", "--checker_address", type=str, default="http://localhost", help="The URL of the checker") + parser.add_argument("-i", "--task_id", type=int, default=1, help="An id for this task. Must be unique in a CTF.") + parser.add_argument("-a", "--address", type=str, default="localhost", help="The ip or address of the remote team to check") + parser.add_argument("-j", "--json", type=bool, default=False, help="Raw JSON output") + parser.add_argument("-T", "--team_id", type=int, default=1, help="The Team_id belonging to the specified Team") + parser.add_argument("-t", "--team_name", type=str, default="team1", help="The name of the target team to check") + parser.add_argument("-r", "--current_round_id", type=int, default=1, help="The round we are in right now") + parser.add_argument( + "-R", + "--related_round_id", + type=int, + default=1, + help="The round in which the flag or noise was stored when method is getflag/getnoise. Equal to current_round_id otherwise.", + ) + parser.add_argument("-f", "--flag", type=str, default="ENOFLAGENOFLAG=", help="The flag for putflag/getflag or the flag to find in exploit mode") + parser.add_argument("-v", "--variant_id", type=int, default=0, help="The variantId for the method being called") + parser.add_argument( + "-x", "--timeout", type=int, default=30000, help="The maximum amount of time the script has to execute in milliseconds (default 30 000)" + ) + parser.add_argument("-l", "--round_length", type=int, default=300000, help="The round length in milliseconds (default 300 000)") + parser.add_argument( + "-I", + "--task_chain_id", + type=str, + default=None, + help="A unique Id which must be identical for all related putflag/getflag calls and putnoise/getnoise calls", + ) + parser.add_argument("--flag_regex", type=str, default=None, help="A regular expression matched by the flag, used only when running the exploit method") + parser.add_argument( + "--attack_info", type=str, default=None, help="The attack info returned by the corresponding putflag, used only when running the exploit method" + ) + + +def task_message_from_namespace(ns: argparse.Namespace) -> CheckerTaskMessage: + task_chain_id = ns.task_chain_id + method = CheckerMethod(ns.method) + if not task_chain_id: + option = None + if method in (CheckerMethod.PUTFLAG, CheckerMethod.GETFLAG): + option = "flag" + elif method in (CheckerMethod.PUTNOISE, CheckerMethod.GETNOISE): + option = "noise" + elif method == CheckerMethod.HAVOC: + option = "havoc" + elif method == CheckerMethod.EXPLOIT: + option = "exploit" + else: + raise ValueError(f"Unexpected CheckerMethod: {method}") + task_chain_id = f"{option}_s0_r{ns.related_round_id}_t{ns.team_id}_i{ns.variant_id}" + + flag_hash = None + if method == CheckerMethod.EXPLOIT: + flag_hash = hashlib.sha256(ns.flag.encode()).hexdigest() + + msg = CheckerTaskMessage( + task_id=ns.task_id, + method=method, + address=ns.address, + team_id=ns.team_id, + team_name=ns.team_name, + current_round_id=ns.current_round_id, + related_round_id=ns.related_round_id, + flag=ns.flag if method != CheckerMethod.EXPLOIT else None, + variant_id=ns.variant_id, + timeout=ns.timeout, + round_length=ns.round_length, + task_chain_id=task_chain_id, + flag_regex=ns.flag_regex, + flag_hash=flag_hash, + attack_info=ns.attack_info, + ) + + return msg + + +def json_task_message_from_namespace(ns: argparse.Namespace) -> str: + return jsons.dumps(task_message_from_namespace(ns), use_enum_name=False, key_transformer=jsons.KEY_TRANSFORMER_CAMELCASE, strict=True) + + +def main() -> None: + parser = argparse.ArgumentParser(description="Your friendly checker script") + _add_arguments(parser) + ns = parser.parse_args(sys.argv[1:]) + msg = json_task_message_from_namespace(ns) + + result = requests.post(ns.checker_address, data=msg, + headers={"content-type": "application/json"},) + if ns.json: + print(result.text) + else: + if result.ok: + result_msg = jsons.loads(result.content, CheckerResultMessage) + print(result_msg.result) + else: + print(result.status_code) + print(result.text) + +main() diff --git a/checker/src/checker.py b/checker/src/checker.py index 1dbfb3d..2e6bc87 100644 --- a/checker/src/checker.py +++ b/checker/src/checker.py @@ -55,11 +55,6 @@ class STLDoctorChecker(BaseChecker): prompt = b"$ " - def login_user(self, conn, password): - self.debug("Sending command to login.") - conn.write(f"login\n{password}\n") - conn.readline_expect(b"logged in!", recvuntil=self.prompt, exception_message="Failed to log in") - def openconn(self): conn = self.connect() resp = conn.recvuntil(self.prompt) @@ -83,6 +78,7 @@ class STLDoctorChecker(BaseChecker): 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) @@ -92,16 +88,15 @@ class STLDoctorChecker(BaseChecker): modelid = ensure_bytes(modelid) conn.write("list\n") resp = conn.recvuntil(self.prompt) - assert_in(modelid, resp, f"Uploaded model is missing from list command") + assert_in(modelid, resp, f"Uploaded model {modelid} is missing from list command") def querydb(self, *args): - self.debug("Querying db contents"); vals = [] for arg in args: try: val: str = self.chain_db[arg] except KeyError as ex: - raise BrokenServiceException("Invalid db contents") + raise BrokenServiceException(f"Invalid db contents, missing: {arg}") vals.append(val) return vals @@ -109,7 +104,7 @@ class STLDoctorChecker(BaseChecker): self.chain_db = kwdict def reverse_hash(self, hashstr): - return subprocess.check_output(os.getenv("REVHASH_PATH") + f" \"{hashstr}\"", shell=True)[:-1] + return subprocess.check_output([os.getenv("REVHASH_PATH"), hashstr])[:-1] def genfile_ascii(self, solidname): solidname = ensure_bytes(solidname) @@ -176,22 +171,21 @@ class STLDoctorChecker(BaseChecker): stlfile = self.genfile(filetype, solidname) # Upload file - self.debug("Sending command to submit file") + self.debug(f"Uploading model with name {modelname}") conn.write("upload\n") conn.write(f"{len(stlfile)}\n") conn.write(stlfile) conn.write(modelname + b"\n") # Parse ID - self.debug(conn.recvline()) + _ = conn.recvline() line = conn.recvline() self.debug(line) try: modelid = line.rsplit(b"!", 1)[0].split(b"with ID ", 1)[1] if modelid == b"": raise Exception except: - raise BrokenServiceException("Invalid data returned on file upload") - self.debug(f"Uploaded file with name {modelid}") + raise BrokenServiceException(f"Invalid response during upload of {modelname}") # Consume rest of data in this call conn.recvuntil(self.prompt) @@ -202,7 +196,7 @@ class STLDoctorChecker(BaseChecker): modelname = ensure_bytes(modelname) # Initiate download - self.debug(f"Sending command to retrieve file with name {modelname}") + self.debug(f"Retrieving model with name {modelname}") conn.write("search\n") conn.write(modelname + b"\n") conn.write("0\n") # first result @@ -276,7 +270,7 @@ class STLDoctorChecker(BaseChecker): self.closeconn(conn) self.postdb(modelid=modelid, modelname=modelname, auth=authstr) else: - raise EnoException("Invalid variant_id provided") + raise EnoException(f"Invalid variant_id ({self.variant_id}) provided") def getflag(self): # type: () -> None if self.variant_id == 0: @@ -293,7 +287,7 @@ class STLDoctorChecker(BaseChecker): assert_in(self.flag.encode(), resp, "Flag not found in file info nor contents") self.closeconn(conn) else: - raise EnoException("Invalid variant_id provided") + raise EnoException(f"Invalid variant_id ({self.variant_id}) provided") def putnoise(self): # type: () -> None if self.variant_id == 0: @@ -313,7 +307,7 @@ class STLDoctorChecker(BaseChecker): self.closeconn(conn) self.postdb(modelid=modelid, modelname=modelname, solidname=solidname, contents=contents, auth=authstr) else: - raise EnoException("Invalid variant_id provided") + raise EnoException(f"Invalid variant_id ({self.variant_id}) provided") def getnoise(self): # type: () -> None if self.variant_id == 0: @@ -328,7 +322,7 @@ class STLDoctorChecker(BaseChecker): self.check_getfile(conn, modelname, solidname, contents, modelid) self.closeconn(conn) else: - raise EnoException("Invalid variant_id provided") + raise EnoException(f"Invalid variant_id ({self.variant_id}) provided") def havoc(self): # type: () -> None if self.variant_id == 0: @@ -340,7 +334,7 @@ class STLDoctorChecker(BaseChecker): elif self.variant_id == 3: self.havoc_upload('bin', True) else: - raise EnoException("Invalid variant_id provided"); + raise EnoException(f"Invalid variant_id ({self.variant_id}) provided") def exploit(self): # type: () -> None if self.variant_id == 0: @@ -355,7 +349,6 @@ class STLDoctorChecker(BaseChecker): # Parse evil file conn = self.openconn() resp = self.getfile(conn, name, download=False) - conn.write("search last\n") filelist = [l.strip().split(b" : ") for l in conn.recvuntil("?").split(b"\n") if b" : " in l] if len(filelist) == 0: raise BrokenServiceException("Failed to list files through search") @@ -401,11 +394,10 @@ class STLDoctorChecker(BaseChecker): 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) if b"Welcome back" not in resp: - raise BrokenServiceException("Revhash returned invalid preimage") + raise BrokenServiceException(f"Reversing of hash {u} returned invalid preimage {user}") 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]) @@ -418,9 +410,9 @@ class STLDoctorChecker(BaseChecker): raise BrokenServiceException("Exploit for flagstore 2 failed") return found else: - raise EnoException("Invalid variant_id provided") + raise EnoException(f"Invalid variant_id ({self.variant_id}) provided") app = STLDoctorChecker.service # This can be used for uswgi. if __name__ == "__main__": - run(STLDoctorChecker, debug=False) + run(STLDoctorChecker) diff --git a/checker/test.sh b/checker/test.sh index d805d9d..8887f68 100644 --- a/checker/test.sh +++ b/checker/test.sh @@ -6,27 +6,53 @@ SCRIPTPATH="$(dirname $(readlink -f "$0"))" cd "$SCRIPTPATH" export REVHASH_PATH="$SCRIPTPATH/src/revhash/revhash" +nop() { :; } + +splitmsg() { + python3 -c " +import json,sys + +try: + instr = sys.stdin.read().strip() + jres = json.loads(instr) + print(jres['result']) + print(jres['message']) +except: + print('INVALID') + print('INVALID') + print('FAIL:', instr, file=sys.stderr) + " || nop +} + try() { cmd="$1" + tmpfile="/tmp/checker-log-$BASHPID" + [ -e "$tmpfile" ] && rm "$tmpfile" if [ $# -lt 2 ]; then variant=0 else variant=$2 fi - echo "Executing $cmd with variant $variant.." - 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 [ ! -z "$REMOTE" ]; then + python3 enoreq.py -j True -A http://localhost:9091 -a $REMOTE \ + --flag ENOTESTFLAG123= --flag_regex 'ENO.*=' \ + -x 4000 ${@:3} "$cmd" > "$tmpfile" + else + python3 src/checker.py -j run -v "$variant" -x 4000 \ + --flag ENOTESTFLAG123= --flag_regex 'ENO.*=' \ + ${@:3} "$cmd" > "$tmpfile" + fi + res="$(cat $tmpfile | splitmsg | head -n1)" + if [ "$res" != "OK" ]; then + newfile="fails/err-$(ls fails | wc -l)" + (echo "METHOD $@"; cat "$tmpfile") > "$newfile" + echo "[ $(date '+%T %F') ] ERROR >>>> $newfile" + cat "$tmpfile" + fi + echo -ne "Executing $cmd with variant $variant.. $res\n" } - -if [ $# -ge 2 ]; then - try $@ -elif [ "$1" == "test-exploits" ]; then - try exploit 0 - try exploit 1 -else +try-all() { try putflag 0 try getflag 0 @@ -46,6 +72,21 @@ else try exploit 0 try exploit 1 +} + +if [ $# -ge 2 ]; then + try $@ +elif [ "$1" == "test-exploits" ]; then + try exploit 0 + try exploit 1 +elif [ "$1" == "stress-test" ]; then + mkdir -p fails + while [ 1 ]; do + try-all & + sleep 2 + done +else + try-all fi exit 0 |
