[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-105406":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":17,"stars7d":17,"stars30d":17,"stars90d":16,"forks30d":16,"starsTrendScore":18,"compositeScore":19,"rankGlobal":10,"rankLanguage":10,"license":20,"archived":21,"fork":21,"defaultBranch":22,"hasWiki":21,"hasPages":21,"topics":23,"createdAt":10,"pushedAt":10,"updatedAt":38,"readmeContent":39,"aiSummary":10,"trendingCount":16,"starSnapshotCount":16,"syncStatus":15,"lastSyncTime":40,"discoverSource":41},105406,"idrive7-8-ssh-root","cauliflowermossgrape\u002Fidrive7-8-ssh-root","cauliflowermossgrape","Enable root SSH on BMW MGU head units (iDrive 7 \u002F ID8 BDC3 \u002F ID8 BCP) over ENET - flash the debug image, enroll, persistent SSH with no B7F8E5 DTC, and a unique per-car key.","",null,"Python",120,27,6,2,0,17,51,70.49,"The Unlicense",false,"main",[24,25,26,27,28,29,30,31,32,33,34,35,36,37],"bmw","car-hacking","developer-mode","enet","id8","idrive","idrive7","idrive8","mgu","reverse-engineering","root","ssh","uds","unlock","2026-11-01 00:16:02","**English** | [简体中文翻译](README.zh-CN.md)\n\n# iDrive7\u002F8 SSH root\n\nEnable the **root SSH service** on BMW MGU head units (iDrive 7 \u002F MGU ID7,\nID8-BDC3 and ID8-BCP) entirely over the car's **ENET** diagnostic interface -\njust an ENET cable and a laptop. It flashes the debug image, enrolls the SSH\nidentity and keeps SSH running with debug mode **off** (no `B7F8E5` DTC).\nOptionally (advanced, off by default) it can give a car its own **unique SSH key**.\n\n![MGU iDrive SSH Tool](docs\u002Fscreenshot.png)\n\n> **Disclaimer** - for use on **your own vehicle**, at your own risk. Flashing a\n> head unit can wipe its coding and, in the worst case, brick the module. Read\n> **Back up your coding before flashing** (below) first. The authors take no\n> responsibility for any damage.\n\n## Requirements\n\n- **Python 3.9+** - only if you run from source. The prebuilt **`.exe` needs no\n  Python** (see [GUI app](#gui-app)).\n- **A system OpenSSH client** (`ssh` on your `PATH`). It ships with Windows 10\u002F11\n  (*Settings → Apps → Optional features → \"OpenSSH Client\"*) and is standard on\n  WSL\u002FmacOS\u002FLinux. Check with `ssh -V`.\n- **Windows:** run your terminal \u002F the app **as Administrator** so Windows uses\n  the `169.254.x.x` link-local address on the ENET adapter.\n- An **ENET cable**, ignition on \u002F car awake.\n\n## Install (source)\n\n```\npip install -r requirements.txt     # one dependency: cryptography\n```\n\n## Quick start - the GUI (recommended)\n\n```\npython gui.py\n```\n\nThe window (above) has a VIN field + **Detect VIN** (auto-reads it from the car\nover ENET) and these buttons:\n\n- **Flash + Enable** - first-time setup: flash the debug image, enroll, make SSH\n  permanent.\n- **Enable** - re-open SSH on an already-flashed unit (no flashing).\n- **Disable** - revert persistence and stop debug\u002FSSH.\n- **Fix refusing SSH key** - repair for units that flash + enroll fine and open\n  port 22 but still refuse root login (`Permission denied (publickey)`), because\n  their stock `authorized_keys.root` holds a different key. Re-flashes the debug\n  image carrying **our** key in the one unsigned region - see\n  [Login still refused?](#login-still-refused-fix-refusing-ssh-key).\n\nTwo tick-boxes: **Turn debug mode off after enabling** (on by default; keeps SSH\nup with no `B7F8E5` DTC) and **Give this car its own unique SSH key** (off by\ndefault, not recommended - see [below](#a-key-thats-unique-to-the-car-optional-not-recommended)).\nKeys and a ready-to-run `ssh` command are written for you and their location is\nprinted in the log. No Python? Build \u002F download the **`.exe`** - see\n[GUI app](#gui-app).\n\n## Command line (alternative)\n\n```\npython ssh_enable.py status     # read the current debug\u002FSSH state (read-only)\npython ssh_enable.py flash --vin WBS1234ABCDX12345   # one-time: flash the\n                                 # debug package, then enable SSH\npython ssh_enable.py enable     # re-open SSH on an already-flashed unit (no flashing)\npython ssh_enable.py fix-key --vin WBS1234ABCDX12345  # re-flash carrying our key\n                                 # (repair for units that refuse login - issue #2)\npython ssh_enable.py persist    # make SSH survive debug mode being OFF\npython ssh_enable.py close      # turn the SSH service off again\npython ssh_enable.py disable    # revert persistence + stop debug\u002FSSH\n```\n\n## Login still refused? (Fix refusing SSH key)\n\nOn some software levels (seen on `BTLD 030.016.006` \u002F `SWFL 030.016.002`) the\nflash and enrollment both succeed and **port 22 opens**, but root login is still\nrefused with `Permission denied (publickey)`. That is because the key `sshd`\ntrusts lives in the signed Application SWFL `000056E5`\n(`\u002Fusr\u002Fshare\u002Fsshd\u002Fauthorized_keys.root`), and on those units it is a different\nkey from the one the tool hands you - nothing in a normal flash overwrites it.\n\n**Fix refusing SSH key** (`fix-key`) repairs this. It re-runs the exact same\nproven debug flash, but the one unsigned region the flash already carries (the\npart not covered by the BMW signature) now installs **our** key at\n`\u002Fusr\u002Fshare\u002Fsshd\u002Fauthorized_keys.root` instead of the stock debug cert. Only that\nregion's bytes change; every other frame - security access, the signed blob, the\nfinalize routines, the reset - is identical to a normal flash, so the realistic\nworst case is simply that the key does not take, **not** a brick.\n\n```\npython ssh_enable.py fix-key --vin \u003CYOUR_VIN> --verbose\n```\n\nor click **Fix refusing SSH key** in the GUI. Like any flash it writes the VIN,\nreboots the unit, and clears `CAFD` coding - restore it from your backup\nafterwards. Keep the `--verbose` log of the first run; it records exactly what\nthe unit did with the region.\n\n> First tried an older build that failed on Windows with\n> `REMOTE HOST IDENTIFICATION HAS CHANGED` \u002F `Offending ED25519 key in nul`? That\n> was a separate known-hosts bug (the login was aborted before the key was even\n> offered) and is already fixed - make sure you are on the current build.\n\n## ⚠️ Back up your coding before flashing ⚠️ \n\n**`flash` reprograms the head unit and can wipe its coding \u002F CAFD definitions.**\nYou will lose your saved settings.\nBefore the first `flash` on a car, make a full backup of the head unit's coding\nso you can put it back afterwards. This happened on a real car during\ndevelopment, and restoring from a backup fixed it.\n\n1. **Before flashing** - read and save the head unit's coding with a coding\n   tool. [BimmerCode](https:\u002F\u002Fbimmercode.app\u002F) worked here: connect, select the\n   head unit (HU \u002F MGU), and export\u002Fsave its coding (BimmerCode keeps a backup\n   of each module it writes; some tools also let you \"read FA\u002Fcoding\" and save\n   it). BMW dealer tools (ISTA\u002FE-Sys) can also save a full backup.\n2. **Flash + enable SSH** with this tool and grab the SSH key it saves for you.\n3. **After flashing** - if any functions misbehave or coding looks reset,\n   restore the saved coding for the head unit with the same tool. In BimmerCode\n   this is its restore\u002Fundo of the module you backed up.\n\nOnly `flash` carries this risk - `enable`, `persist`, `close`, `disable` and\n`status` do not reprogram the unit.\n\n## What it does\n\n- **flash** *(run once per head unit)* - writes the small debug package that\n  the SSH service needs (a dev CA certificate plus the debug-mode binaries,\n  ~131 KB, VIN-bound) and then enrolls the SSH identity. The head unit\n  reboots itself; the tool waits and re-checks the unit automatically. It also\n  writes the root SSH key out in every format (see *The SSH key*).\n- **enable** - re-activates the SSH service on an already-flashed unit\n  without flashing anything. Enrolls the unit identity\n  (`31 01 A6 71` challenge, generated certificate, `31 01 A0 FD` install,\n  `22 17 2A` readback) five times back to back - the SSH daemon comes up a\n  few seconds after that.\n- **close** - stops the SSH service again (`31 02 A0 FD`).\n- **status** - reads the software version (SVK), the head unit IP, the\n  debug-state DTC (`B7F8E5`) and probes port 22.\n\nEverything runs against the gateway at `169.254.254.36:6801` by default;\nthe gateway is found automatically via UDP broadcast, so normally no\nsettings are needed.\n\n## Check the connection\n\nWith the ENET cable in and the car awake, confirm the tool can reach the unit\n(read-only, changes nothing):\n\n```\npython ssh_enable.py status\n```\n\n## Everyday use\n\nOpen SSH (after the one-time `flash`):\n\n```\npython ssh_enable.py enable\n```\n\nYou should see the enrollment cycles, `MGU SSH port 22 is open`, and then\n`SSH OK as root: uid=0(root)...` as final confirmation.\n\nConnect with this car's own key (printed and saved during `enable`\u002F`flash`) -\ne.g. from WSL:\n\n```\nssh -i data\u002Fenrollments\u002F\u003CVIN>\u002Fid_rsa root@169.254.36.47\n```\n\n(The IP is the one the tool prints during `status`\u002F`enable`. With PuTTY or\nWinSCP use `data\\enrollments\\\u003CVIN>\\id_rsa.ppk`.) See *The SSH key*.\n\nClose it when done:\n\n```\npython ssh_enable.py close\n```\n\n## Options\n\n```\n--gateway 169.254.254.36   ENET gateway address (\"auto\" = UDP discovery)\n--mgu-ip 169.254.36.47     head unit IP override for the SSH probe\n--vin                     17-char VIN (required for \"flash\"; it is written\n                           to the head unit, so it must match the car)\n--arch                    id7 (default), id8-bdc3 or id8-bcp\n--yes                     skip confirmation prompts\n--verbose                 print every diagnostic frame on the wire\n```\n\n## The SSH key (and connect info)\n\nRoot SSH on the MGU is authorised against **one fixed key that is baked into the\nflashed debug image** - shipped as `data\u002Fassets\u002F05_SSH_ROOT_KEY.pem`. Enrollment\nis a separate step: it installs a certificate built from the *enrollment* key\n(`data\u002Fassets\u002F01_PRIVATE_KEY.pem`) and signed by the PMO CA, which is only the\naccess gate the unit checks during the `A6 71` \u002F `A0 FD` handshake - that key is\n**not** a login key. The unit accepts the baked root key (asset 05) and only that\nkey for SSH. A per-car *unique* key is therefore **not possible** at enrollment\ntime - the unit will reject any key it doesn't already trust (confirmed against a\nreal head unit, a captured session of the official tool, and the working\nOpenSSH\u002FFileZilla login key, which matches asset 05).\n\nSo on `flash` \u002F `enable` the tool enrolls with asset 01 and then writes out the\nasset-05 login key -\nin every format you might need to connect - under `data\u002Fenrollments\u002F\u003CVIN>\u002F`,\ntogether with a `connect.txt` (the MGU IP + a ready-to-run `ssh` command):\n\n```\nid_rsa             OpenSSH private key   -> ssh -i id_rsa root@\u003Cip>\nid_rsa.pkcs1.pem   PKCS#1 PEM            (-----BEGIN RSA PRIVATE KEY-----)\nid_rsa.pkcs8.pem   PKCS#8 PEM            (-----BEGIN PRIVATE KEY-----)\nid_rsa.ppk         PuTTY v2             (PuTTY \u002F WinSCP)\nid_rsa.pub         OpenSSH public       (authorized_keys line)\nid_rsa.pub.pem     SubjectPublicKeyInfo PEM\nfingerprints.txt   SHA256 + MD5 fingerprints\nconnect.txt        MGU IP + ssh command\nmetadata.json      VIN, arch, mgu_ip, challenge, timestamp, fingerprints\n```\n\nPrivate keys are `chmod 600`, and `data\u002Fenrollments\u002F` is git-ignored.\n\n### A key that's unique to the car (optional, not recommended)\n\n> **Off by default.** The key swap can only be done **once**. If you reflash the\n> unit, run the tool again, or lose the per-car key, you **cannot create a new\n> identity** and may lose SSH access. Only turn this on if you know exactly why\n> you need it and will keep the key safe.\n\nSince enrollment can't change which key the unit trusts, the only way to a\nper-car key is to log in with the baked key and edit the unit's real\n`AuthorizedKeysFile` over SSH. On this hardware sshd reads keys from\n`\u002Fusr\u002Fshare\u002Fsshd\u002Fauthorized_keys.root` (per `sshd_config`), **not**\n`~\u002F.ssh\u002Fauthorized_keys`, and the rootfs is mounted read-only - so the tool\nresolves the actual `AuthorizedKeysFile`, remounts `\u002F` read-write just while it\nedits, writes the key, and remounts read-only again. The tool does this only\nwhen you pass `--unique-key` (or tick the GUI box), as a **replace**. By default\nthe shared baked key is kept.\n\n```\npython ssh_enable.py enable                 # keep the shared dev key (default)\npython ssh_enable.py enable --unique-key    # swap in a per-car key (one-time, advanced)\n```\n\n1. enroll + log in with the baked key (as usual);\n2. generate a key unique to this car and add it to the real `AuthorizedKeysFile`\n   (remounting the ro rootfs rw just for the write), so the unit now trusts both;\n3. verify login with the new key over a fresh connection;\n4. **only then** remove the baked key line, so the car accepts only its own key.\n\n> **If the new key doesn't connect right away, force-restart the head unit:**\n> press and **hold the volume \u002F power knob for ~20-30 s** until the screen goes\n> dark and the unit reboots. It comes back up loaded with the new key. (This was\n> needed on a real car after the key swap.)\n\nThe per-car key (all formats) + `connect.txt` replace the dev-key bundle in\n`data\u002Fenrollments\u002F\u003CVIN>\u002F`. Safety: if step 3 can't be verified, the dev key is\nleft in place so you never lose access; and if `authorized_keys` sits on a\nread-only image, the removal may not stick (the tool says so) - the unique key\nstill works, with the dev key as fallback. Re-running `enable` always restores\naccess via the dev key.\n\n## Persistent SSH (no more B7F8E5 DTC)\n\nStock behaviour ties `sshd.socket` to `debug.target`, so SSH only runs while\ndebug mode is active - and active debug mode sets the `B7F8E5` (\"debugging\nactive\") DTC. `persist` fixes that in one shot:\n\n```\npython ssh_enable.py persist           # on an already-enabled unit\npython ssh_enable.py enable  --persist  # enable, then persist\npython ssh_enable.py flash --vin ... --persist   # flash, enable, then persist\n```\n\nIt logs in over SSH with this car's key, confirms root, uploads and runs\n`sshd-always-on.sh install` on the head unit, then turns debug mode off\n(`31 02 A0 FD`). Stock SSH dies without debug mode because of three separate\ngates, all handled by the script (nothing in `\u002Flib` or `\u002Fusr` is changed - it\nonly adds files under `\u002Fetc`, which `remove` deletes):\n\n1. **Start** - `sshd.socket` is `WantedBy=debug.target` only, and the boot\n   target is `early.target`, so the `\u002Fetc` override is also wanted by\n   `early.target`, `sockets.target` and `multi-user.target`.\n2. **Stop** - `sshd.socket` has `PartOf=debug.target`; the override drops it so\n   leaving debug mode no longer stops the socket.\n3. **Firewall** - port 22 is only accepted by a rule `firewalldZ` adds while\n   debug mode is on; without it the nft input chain drops SSH. A small keeper\n   service (`sshd-always-on-fw.service`) keeps a `tcp dport 22 accept` rule at\n   the top of the ip\u002Fip6 input chains, re-checked every 15 s.\n\nThe result: SSH keeps starting on every boot and stays reachable with debug\nmode off, and the `B7F8E5` DTC clears. **Turning debug off is automatic** - you\ndo not need a separate step. Because the firewall keeper only re-checks every\n~15 s, `persist` waits and re-probes port 22 for a few seconds after disabling\ndebug before reporting. If making SSH persistent fails, the tool deliberately\nleaves debug mode ON so you do not lose access.\n\nWant SSH persistent but debug mode left on? Add `--keep-debug`:\n\n```\npython ssh_enable.py enable --persist --keep-debug   # persistent SSH, DTC stays\n```\n\nTo go back to stock entirely, use `disable` (reverts the override and stops\ndebug), or run `sh sshd-always-on.sh remove` on the unit by hand.\n\n## GUI app\n\nRun it from source with `python gui.py` (see [Quick start](#quick-start---the-gui-recommended)),\nor use the standalone Windows `.exe` - **no Python needed**. It runs the exact\nsame code as the command line and streams the log into the window. Connection\ndetails (gateway \u002F variant \u002F MGU IP) live in a small optional row.\n\n**Prebuilt:** download **`MGU-SSH-windows.zip`** from the\n[Releases page](https:\u002F\u002Fgithub.com\u002Fcauliflowermossgrape\u002Fidrive7-8-ssh-root\u002Freleases),\nunzip anywhere, and run `MGU-SSH\\MGU-SSH.exe`. Keep the whole folder\ntogether. (Built with PyInstaller from these sources; rebuild it\nyourself any time with `build_exe.bat`.)\n\n### Build the Windows .exe\n\nFrom a normal Command Prompt in the project root, with Python on `PATH`:\n\n```\nbuild_exe.bat\n```\n\nThat installs PyInstaller, builds `dist\\MGU-SSH\\MGU-SSH.exe` (one-folder app via\n`MGU-SSH.spec`), and copies `data\\assets` and `sshd-always-on.sh` next to the\nexe. **Keep the whole `dist\\MGU-SSH\\` folder together** - SSH keys \u002F connect info\nare written to `dist\\MGU-SSH\\data\\enrollments\\`. Run it **as Administrator** so\nWindows uses the `169.254.x.x` link-local address on the ENET adapter.\n\n## Folder contents\n\n```\nssh_enable.py               the command-line tool\ngui.py                      Tkinter GUI (VIN auto-detect, one-click)\nsshd-always-on.sh           runs on the head unit to make SSH persistent\nmgu7\u002F                       diagnostic + flashing library\nmgu7\u002Fsshcli.py              talks to the unit with the system OpenSSH client\nmgu7\u002Fidentity.py            SSH key all-format export (OpenSSH\u002FPEM\u002FPPK) + PPK writer\ndata\u002Fassets\u002F                update material + PMO CA used by the tool\ndata\u002Fenrollments\u002F\u003CVIN>\u002F     SSH key (all formats) + connect.txt per car (git-ignored)\ndocs\u002Fscreenshot.png         the GUI screenshot used in this README\nrequirements.txt            cryptography (the SSH steps use the system ssh client)\nMGU-SSH.spec, build_exe.bat PyInstaller build of the Windows .exe\n```\n\nKeep `data\u002F` next to `ssh_enable.py`; the private keys are what get you into\nthe unit - treat them like passwords.\n\n## License\n\nReleased into the **public domain** under [The Unlicense](LICENSE) - do whatever\nyou want with it, no attribution required.\n","2026-10-31 04:26:42","CREATED_QUERY"]