aboutsummaryrefslogtreecommitdiffstats
path: root/checker
diff options
context:
space:
mode:
Diffstat (limited to 'checker')
-rw-r--r--checker/.gitignore1
-rw-r--r--checker/enoreq.py116
-rw-r--r--checker/src/checker.py40
-rw-r--r--checker/test.sh65
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