From f61a42fa5968f1e69f0d121063f632f8113786cb Mon Sep 17 00:00:00 2001 From: lightningitoid Date: Mon, 27 Oct 2025 07:46:35 +0700 Subject: [PATCH] updated sheesh --- receiver/challenges/Sheesh.py | 108 +++++++++++++++++++--------------- services/sheesh/dist/chall.py | 103 ++++++++++++++++---------------- services/sheesh/src/chall.py | 103 ++++++++++++++++---------------- 3 files changed, 164 insertions(+), 150 deletions(-) diff --git a/receiver/challenges/Sheesh.py b/receiver/challenges/Sheesh.py index 31f0da7..602e2da 100644 --- a/receiver/challenges/Sheesh.py +++ b/receiver/challenges/Sheesh.py @@ -5,6 +5,7 @@ import time import re import os import binascii +import secrets class Sheesh(Challenge): flag_location = 'flags/sheesh.txt' @@ -33,7 +34,7 @@ class Sheesh(Challenge): bufsize=0, ) - def _read_exact_line(self, proc, timeout=5.0): + def _read_exact_line(self, proc, timeout=6.0): start = time.time() buf = [] r = proc.stdout.read @@ -47,13 +48,13 @@ class Sheesh(Challenge): if ch == "\n": return "".join(buf) - def _read_until_prompt(self, proc, token: str, timeout=5.0): + def _read_until_prompt(self, proc, token: str, timeout=6.0): start = time.time() acc = [] r = proc.stdout.read while True: if time.time() - start > timeout: - raise TimeoutError(f"Timeout waiting for '{token}'") + raise TimeoutError(f"Timeout waiting for '{token}'")inp ch = r(1) if ch == "" and proc.poll() is not None: raise RuntimeError("Process ended while waiting for prompt") @@ -72,6 +73,36 @@ class Sheesh(Challenge): assert self._HEX_RE.match(hx), f"{label} is not hex" return hx + def test1(self, proc, b16_hex: str) -> str: + assert len(b16_hex) == 32 + self._read_until_prompt(proc, "inp: ", timeout=6.0) + self._send_line(proc, b16_hex) + line = self._read_exact_line(proc, timeout=4.0) + assert line.startswith("blk:"), "Expected blk: for 16-byte input" + hx = self._expect_hex_field(line, "blk") + # chall prints an extra blank line after blk: + _ = self._read_exact_line(proc, timeout=4.0) + return hx.lower() + + def test2(self, proc, iv_hex: str, ct_hex: str) -> str: + self._read_until_prompt(proc, "inp: ", timeout=6.0) + self._send_line(proc, iv_hex + ct_hex) + ans = self._read_exact_line(proc, timeout=4.0).strip().lower() + assert ans in ("ok", "zzz"), "Padding oracle did not return ok/zzz" + return ans + + def test3(self, proc, iv_hex: str, ct_hex: str, trials=9) -> tuple[int, int, str]: + ok_cnt = 0 + zzz_cnt = 0 + for _ in range(trials): + ans = self.test2(proc, iv_hex, ct_hex) + if ans == "ok": + ok_cnt += 1 + else: + zzz_cnt += 1 + final = "ok" if ok_cnt > zzz_cnt else "zzz" + return ok_cnt, zzz_cnt, final + def distribute(self, flag): try: os.makedirs(os.path.dirname(self.flag_location), exist_ok=True) @@ -90,7 +121,6 @@ class Sheesh(Challenge): def check(self): try: - # parity check: host vs container flag with open(self.flag_location, 'r') as f: host_flag = f.read().strip() container_flag = self._read_container_flag() @@ -99,9 +129,9 @@ class Sheesh(Challenge): proc = self._spawn() - line1 = self._read_exact_line(proc, timeout=5.0) - line2 = self._read_exact_line(proc, timeout=5.0) - blank = self._read_exact_line(proc, timeout=5.0) + line1 = self._read_exact_line(proc, timeout=6.0) + line2 = self._read_exact_line(proc, timeout=6.0) + blank = self._read_exact_line(proc, timeout=6.0) assert line1.startswith("iv:") and line2.startswith("ct:"), "Missing banner" iv_hex = self._expect_hex_field(line1, "iv") ct_hex = self._expect_hex_field(line2, "ct") @@ -110,54 +140,36 @@ class Sheesh(Challenge): assert len(iv0) == 16 and len(ct0) >= 16 and len(ct0) % 16 == 0, "Bad banner sizes" self.logger.info("[ok] banner parsed") - self._read_until_prompt(proc, "blob: ", timeout=5.0) + block00 = "00" * 16 + blk1 = self.test1(proc, block00) + blk2 = self.test1(proc, block00) + blk3 = self.test1(proc, block00) + assert blk1 == blk2 == blk3, "ECB-masked oracle not deterministic for same input" + self.logger.info("[ok] blk determinism (3x same input)") - qA = "00" * 16 - self._send_line(proc, qA) - lA = self._read_exact_line(proc, timeout=3.0) - assert lA.startswith("blk:"), "Expected blk: for 16-byte blob" - blk1 = self._expect_hex_field(lA, "blk") - _ = self._read_exact_line(proc, timeout=3.0) - - self._read_until_prompt(proc, "blob: ", timeout=5.0) - self._send_line(proc, qA) - lA2 = self._read_exact_line(proc, timeout=3.0) - blk2 = self._expect_hex_field(lA2, "blk") - _ = self._read_exact_line(proc, timeout=3.0) - assert blk1 == blk2, "ECB-masked oracle not deterministic for same input" - self.logger.info("[ok] blk determinism") - - self._read_until_prompt(proc, "blob: ", timeout=5.0) - qB = "01" * 16 - self._send_line(proc, qB) - lB = self._read_exact_line(proc, timeout=3.0) - blk3 = self._expect_hex_field(lB, "blk") - _ = self._read_exact_line(proc, timeout=3.0) - assert blk3 != blk1, "ECB-masked oracle returned same block for different input" + block01 = "01" * 16 + blk4 = self.test1(proc, block01) + assert blk4 != blk1, "ECB-masked oracle returned same block for different input" self.logger.info("[ok] blk diversity") - self._read_until_prompt(proc, "blob: ", timeout=5.0) - self._send_line(proc, "zzzz") # invalid hex - line_err = self._read_exact_line(proc, timeout=3.0) + self._read_until_prompt(proc, "inp: ", timeout=6.0) + self._send_line(proc, "zzzz") # invalid hex + line_err = self._read_exact_line(proc, timeout=4.0) assert "err" in line_err.lower(), "Expected 'err' for non-hex input" - self.logger.info("[ok] error handling on non-hex") + maybe_hmmm = self._read_exact_line(proc, timeout=4.0).strip().lower() + if "hmmm" not in maybe_hmmm: + self.logger.warning(f"[warn] expected 'hmmm' after err, got: {maybe_hmmm!r}") + self.logger.info("[ok] error handling on non-hex input") - self._read_until_prompt(proc, "blob: ", timeout=5.0) - self._send_line(proc, (iv_hex + ct_hex)) - ans1 = self._read_exact_line(proc, timeout=3.0).strip().lower() - assert ans1 in ("ok", "no"), "Padding oracle did not return ok/no for iv||ct" - self.logger.info(f"[ok] padding oracle responds ({ans1}) on banner bundle") + ok_cnt, zzz_cnt, final = self.test3(proc, iv_hex, ct_hex, trials=9) + self.logger.info(f"[ok] padding oracle responded on banner (ok={ok_cnt}, zzz={zzz_cnt}, final={final})") - self._read_until_prompt(proc, "blob: ", timeout=5.0) - iv_synth = os.urandom(16).hex() - ct_synth = ("00" * (6 * 16)) - self._send_line(proc, iv_synth + ct_synth) - ans2 = self._read_exact_line(proc, timeout=3.0).strip().lower() - assert ans2 in ("ok", "no"), "Padding oracle did not return ok/no on synthetic 6-block ct" - self.logger.info(f"[ok] padding oracle responds ({ans2}) on synthetic payload") - - self._read_until_prompt(proc, "blob: ", timeout=5.0) + iv_synth = secrets.token_hex(16) + ct_synth = "00" * (6 * 16) # 6 blocks + ok_cnt2, zzz_cnt2, final2 = self.test3(proc, iv_synth, ct_synth, trials=9) + self.logger.info(f"[ok] padding oracle responded on synthetic (ok={ok_cnt2}, zzz={zzz_cnt2}, final={final2})") + self._read_until_prompt(proc, "inp: ", timeout=6.0) proc.kill() self.logger.info('Check passed for sheesh') return True diff --git a/services/sheesh/dist/chall.py b/services/sheesh/dist/chall.py index 032ef46..b183fe8 100644 --- a/services/sheesh/dist/chall.py +++ b/services/sheesh/dist/chall.py @@ -1,80 +1,77 @@ #!/usr/bin/env python3 -import os, sys, signal, binascii, random +import os, sys, binascii, random from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from Crypto.Util.number import bytes_to_long, long_to_bytes random.seed(os.urandom(16)) -K0 = os.urandom(16) -K1 = os.urandom(16) -S0 = os.urandom(16) -S1 = os.urandom(16) -M0 = os.urandom(16) -M1 = os.urandom(16) +k0 = os.urandom(16) +k1 = os.urandom(16) +s0 = os.urandom(16); S1 = os.urandom(16) +m0 = os.urandom(16); M1 = os.urandom(16) with open("/flag.txt","rb") as f: flag = f.read() def hex_input(q): s = input(q).strip() - try: return binascii.unhexlify(s) + try: return binascii.unhexlify(s) except: print("err"); return None -def enc1(b16: bytes) -> bytes: - x = AES.new(K1, AES.MODE_ECB).encrypt(b16) - return bytes(a ^ b for a, b in zip(x, b16)) +def xex(b16): + x = AES.new(k1, AES.MODE_ECB).encrypt(b16) + return bytes(a ^ b for a,b in zip(x, b16)) -def enc2(iv: bytes, m: bytes) -> bytes: - return AES.new(K0, AES.MODE_CBC, iv=iv).encrypt(pad(m, 16)) +def enc1(iv, msg): + return AES.new(k0, AES.MODE_CBC, iv=iv).encrypt(pad(msg,16)) -def enc3(m: bytes) -> bytes: - return AES.new(K1, AES.MODE_CBC, iv=b"\x00"*16).encrypt(pad(m, 16))[-16:] +def dec1(iv, c): + return unpad(AES.new(k0, AES.MODE_CBC, iv=iv).decrypt(c), 16) -def T(iv: bytes, ct: bytes): +def enc2(msg): + return AES.new(k1, AES.MODE_CBC, iv=b"\x00"*16).encrypt(pad(msg,16))[-16:] + +def F(iv, ct): n = len(ct) - if n < 96 or (n & 15): - return None - v = memoryview(ct) - W = [bytes(v[i:i+16]) for i in range(0, n, 16)] + if n < 96 or (n & 15): return None + W = [ct[i:i+16] for i in range(0, n, 16)] m = len(W) - r = ((iv[0] & 7) + 2) % m - if r: - W = W[r:] + W[:r] + digest = enc2(iv) + r = ((iv[0]^iv[-1]) & 7) + 2 + r %= m + if r: W = W[r:] + W[:r] - j = 1 + (W[0][0] & 1) - if len(W) <= j: - return None - del W[j] - if len(W) < 2: - return None + if len(W) < 3: return None + A0 = long_to_bytes(bytes_to_long(W[0]) ^ bytes_to_long(m0)) + A1 = long_to_bytes(bytes_to_long(W[1]) ^ bytes_to_long(M1)) + j = 1 + (digest[0] & 1) + del W[j] + if len(W) < 2: return None - a0 = long_to_bytes(bytes_to_long(W[0]) ^ bytes_to_long(M0)) - a1 = long_to_bytes(bytes_to_long(W[1]) ^ bytes_to_long(M1)) - return b"".join((S0, a0, S1, a1, *W[2:])) + return b"".join((s0, A0, S1, A1, *W[2:])) -def C(iv: bytes, ct: bytes) -> bool: - z = T(iv, ct) +def O(iv, ct): + z = F(iv, ct) if z is None: ok = False else: try: - x = AES.new(K0, AES.MODE_CBC, iv=iv).decrypt(z) - unpad(x, 16) + dec1(iv, z) ok = True except: ok = False - if random.random() < 0.08: + if random.random() < 0.10: ok = not ok return ok iv = os.urandom(16) -MK = enc3(iv + iv) -H0 = (flag + b"\x00"*16)[:16] -H1 = bytes(a ^ b for a, b in zip(H0, MK)) -pt = H1 + flag[16:] -ct = enc2(iv, pt) +MK = enc2(iv + iv) +h0 = (flag + b"\x00"*16)[:16] +h1 = bytes(a ^ b for a,b in zip(h0, MK)) +pt = h1 + flag[16:] +ct = enc1(iv, pt) print("iv:", iv.hex()) print("ct:", ct.hex()) @@ -82,17 +79,21 @@ print() while True: try: - blob = hex_input("blob: ") - if blob is None: - print("err\n"); continue - L = len(blob) - if L == 16: - y = enc1(blob) + inp = hex_input("inp: ") + if inp is None: + print("hmmm\n"); continue + L = len(inp) + + if L == 16: + y = xex(inp) print("blk:", y.hex()); print() + elif L >= 32 and (L % 16) == 0: - iv, ct = blob[:16], blob[16:] - print("ok\n" if C(iv, ct) else "no\n") + ivq, ctq = inp[:16], inp[16:] + print("ok\n" if O(ivq, ctq) else "zzz\n") + else: - print("err\n") + print("hmmm\n") + except EOFError: break diff --git a/services/sheesh/src/chall.py b/services/sheesh/src/chall.py index 2080e20..a69ed3e 100644 --- a/services/sheesh/src/chall.py +++ b/services/sheesh/src/chall.py @@ -1,80 +1,77 @@ #!/usr/bin/env python3 -import os, sys, signal, binascii, random +import os, sys, binascii, random from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from Crypto.Util.number import bytes_to_long, long_to_bytes random.seed(os.urandom(16)) -K0 = os.urandom(16) -K1 = os.urandom(16) -S0 = os.urandom(16) -S1 = os.urandom(16) -M0 = os.urandom(16) -M1 = os.urandom(16) +k0 = os.urandom(16) +k1 = os.urandom(16) +s0 = os.urandom(16); S1 = os.urandom(16) +m0 = os.urandom(16); M1 = os.urandom(16) with open("/flag.txt","rb") as f: flag = f.read() def hex_input(q): s = input(q).strip() - try: return binascii.unhexlify(s) + try: return binascii.unhexlify(s) except: print("err"); return None -def enc1(b16: bytes) -> bytes: - x = AES.new(K1, AES.MODE_ECB).encrypt(b16) - return bytes(a ^ b for a, b in zip(x, b16)) +def xex(b16): + x = AES.new(k1, AES.MODE_ECB).encrypt(b16) + return bytes(a ^ b for a,b in zip(x, b16)) -def enc2(iv: bytes, m: bytes) -> bytes: - return AES.new(K0, AES.MODE_CBC, iv=iv).encrypt(pad(m, 16)) +def enc1(iv, msg): + return AES.new(k0, AES.MODE_CBC, iv=iv).encrypt(pad(msg,16)) -def enc3(m: bytes) -> bytes: - return AES.new(K1, AES.MODE_CBC, iv=b"\x00"*16).encrypt(pad(m, 16))[-16:] +def dec1(iv, c): + return unpad(AES.new(k0, AES.MODE_CBC, iv=iv).decrypt(c), 16) -def T(iv: bytes, ct: bytes): +def enc2(msg): + return AES.new(k1, AES.MODE_CBC, iv=b"\x00"*16).encrypt(pad(msg,16))[-16:] + +def F(iv, ct): n = len(ct) - if n < 96 or (n & 15): - return None - v = memoryview(ct) - W = [bytes(v[i:i+16]) for i in range(0, n, 16)] + if n < 96 or (n & 15): return None + W = [ct[i:i+16] for i in range(0, n, 16)] m = len(W) - r = ((iv[0] & 7) + 2) % m - if r: - W = W[r:] + W[:r] + digest = enc2(iv) + r = ((iv[0]^iv[-1]) & 7) + 2 + r %= m + if r: W = W[r:] + W[:r] - j = 1 + (W[0][0] & 1) - if len(W) <= j: - return None - del W[j] - if len(W) < 2: - return None + if len(W) < 3: return None + A0 = long_to_bytes(bytes_to_long(W[0]) ^ bytes_to_long(m0)) + A1 = long_to_bytes(bytes_to_long(W[1]) ^ bytes_to_long(M1)) + j = 1 + (digest[0] & 1) + del W[j] + if len(W) < 2: return None - a0 = long_to_bytes(bytes_to_long(W[0]) ^ bytes_to_long(M0)) - a1 = long_to_bytes(bytes_to_long(W[1]) ^ bytes_to_long(M1)) - return b"".join((S0, a0, S1, a1, *W[2:])) + return b"".join((s0, A0, S1, A1, *W[2:])) -def C(iv: bytes, ct: bytes) -> bool: - z = T(iv, ct) +def O(iv, ct): + z = F(iv, ct) if z is None: ok = False else: try: - x = AES.new(K0, AES.MODE_CBC, iv=iv).decrypt(z) - unpad(x, 16) + dec1(iv, z) ok = True except: ok = False - if random.random() < 0.08: + if random.random() < 0.10: ok = not ok return ok iv = os.urandom(16) -MK = enc3(iv + iv) -H0 = (flag + b"\x00"*16)[:16] -H1 = bytes(a ^ b for a, b in zip(H0, MK)) -pt = H1 + flag[16:] -ct = enc2(iv, pt) +MK = enc2(iv + iv) +h0 = (flag + b"\x00"*16)[:16] +h1 = bytes(a ^ b for a,b in zip(h0, MK)) +pt = h1 + flag[16:] +ct = enc1(iv, pt) print("iv:", iv.hex()) print("ct:", ct.hex()) @@ -82,17 +79,21 @@ print() while True: try: - blob = hex_input("blob: ") - if blob is None: - print("err\n"); continue - L = len(blob) - if L == 16: - y = enc1(blob) + inp = hex_input("inp: ") + if inp is None: + print("hmmm\n"); continue + L = len(inp) + + if L == 16: + y = xex(inp) print("blk:", y.hex()); print() + elif L >= 32 and (L % 16) == 0: - iv, ct = blob[:16], blob[16:] - print("ok\n" if C(iv, ct) else "no\n") + ivq, ctq = inp[:16], inp[16:] + print("ok\n" if O(ivq, ctq) else "zzz\n") + else: - print("err\n") + print("hmmm\n") + except EOFError: break