chahinebrini 01374c426e feat(supervise-magic): TechLockdown-clone v1 — supervise iPhones without erase
Implementiert eigenen MobileBackup2-Restore-Trick zur Supervision-Übernahme
von aktivierten iOS-Geräten ohne Factory-Reset. Foundation für DiGA-Phase-G
Lock-Layer-Stack (non-removable Apps, non-removable Profiles, OnDemand-VPN-
Toggle-Lock) auf Consumer-iPhones.

Verifiziert end-to-end auf:
- iPhone Air (iPhone18,4, iOS 26.5): TL→ReBreak re-supervise 
- Olfa iPhone 14 Pro (iPhone15,3, iOS 26.4.2): TL→ReBreak re-supervise 

Key empirische Findings:
1. Find-My-iPhone MUSS off sein (ErrorCode 211 sonst)
   → Stolen Device Protection (SDP) zwingt FMI an seit iOS 17.3+
2. SupervisorHostCertificates DARF NICHT in CloudConfigurationDetails sein
   für fresh-supervise auf activated unsupervised devices (sonst partial-apply)
3. MCInstall.SetCloudConfiguration firet 14002 auf allen activated devices
   → MobileBackup2-Restore-Trick ist der einzige Weg
4. TL's extracted-embed-bytes != TL's wire-output (Runtime-Mutation)
   → Verbatim-Kopieren reicht nicht

Reverse-Engineering basiert komplett auf:
- Apple's public protocol docs (devicemanagement, mobilebackup2 schemas)
- libimobiledevice (open-source reference impl)
- TL public-distributed binary (interop-RE, legal per US-DMCA-1201 + EU-2009/24)

Structure:
  cmd/supervise/         — main CLI (check, cloud-config, supervise, cert-info, unsupervise)
  cmd/dump-artifacts/    — diagnostic helper (no device needed)
  cmd/usbmux-proxy/      — MITM-proxy for TL-traffic-capture (debug)
  cmd/tl-patcher/        — patches TL's hard-coded usbmuxd path (debug)
  internal/dlmessage/    — DLMessage wire-protocol (4-byte BE length + plist)
  internal/mobilebackup2/— mobilebackup2-service impl (BaseVersionExchange,
                          Hello, Restore, ServeFiles + TL-extracted templates)
  internal/cloudconfig/  — CloudConfigurationDetails.plist builder (cert-less,
                          25 keys matching TL's runtime-output)
  internal/cert/         — auto-gen + persist supervisor-cert in ~/.rebreak-supervise/
  internal/mcinstall/    — MCInstall.GetCloudConfiguration für state-checks
  internal/device/       — go-ios DeviceEntry wrapper
  internal/afclock/      — AFC sync-lock auf /com.apple.itunes.lock_sync
  internal/notification_proxy/ — PostNotification (syncWillStart/etc)
  internal/preflight/    — FMI/Activation/OS-version pre-checks
  internal/supervise/    — End-to-end Flow-Orchestrierung (MobileBackup2 default,
                          MCInstall via REBREAK_FORCE_MCINSTALL=1)

Pending für volle Productization (Phase G):
- Fresh-supervise direkt-empirisch auf truly-unsupervised iPhone testen
  (heute Nacht nur durch Inferenz aus TL-Verhalten gestützt)
- Auto-MDM-enroll-Step nach Supervise (ConfigurationURL oder cfgutil-style)
- DiGA-Onboarding-Flow + Lyra-Coach für FMI/SDP-Disable
- Multi-Device-Validation (Modelle, iOS-Versionen)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 01:55:10 +02:00

325 lines
8.6 KiB
Go

// Command rebreak-supervise-magic ist die CLI für unsupervised→supervised
// Migration ohne Datenverlust via Apple MCInstall-Service.
package main
import (
"bufio"
"flag"
"fmt"
"os"
"sort"
"strings"
"time"
ios "github.com/danielpaulus/go-ios/ios"
"github.com/raynis/rebreak-supervise-magic/internal/cert"
"github.com/raynis/rebreak-supervise-magic/internal/device"
"github.com/raynis/rebreak-supervise-magic/internal/dlmessage"
"github.com/raynis/rebreak-supervise-magic/internal/mcinstall"
"github.com/raynis/rebreak-supervise-magic/internal/preflight"
"github.com/raynis/rebreak-supervise-magic/internal/supervise"
)
func init() {
// Wire authoritative IsSupervised-check via MCInstall.GetCloudConfiguration.
device.CheckSupervisedFunc = func(dev ios.DeviceEntry) (bool, error) {
mc, err := mcinstall.Open(dev)
if err != nil {
return false, nil // can't check → assume unsupervised
}
defer mc.Close()
_ = mc.HelloHostIdentifier()
cfg, err := mc.GetCloudConfiguration()
if err != nil || cfg == nil {
return false, nil
}
v, _ := cfg["IsSupervised"].(bool)
return v, nil
}
}
func dlmsgDebugSet() {
dlmessage.DebugMode = true
}
const usage = `rebreak-supervise-magic — iPhone/iPad-Supervision ohne Datenverlust
Usage:
rebreak-supervise-magic <command> [flags]
Commands:
check Print device info + supervision status. No writes.
cert-info Print supervision-cert path + status. No writes.
cloud-config Read current Cloud-Configuration via MCInstall. No writes.
supervise SetCloudConfiguration(IsSupervised=true) + reboot.
unsupervise Reverse-flow — IsSupervised=false + reboot.
Global flags:
-v Verbose / debug output.
-udid <id> Target specific device (default: first detected).
-org <name> OrganizationName shown in Settings (default: "ReBreak").
-dry-run Run all checks + plan but skip writes/reboot.
-yes Skip confirmation prompt (use in scripts).
-force Allow supervise on already-supervised devices (re-supervise / change orgname).
Examples:
rebreak-supervise-magic check
rebreak-supervise-magic cloud-config
rebreak-supervise-magic supervise -org "ReBreak" -yes
rebreak-supervise-magic -v -dry-run supervise
`
type cliOpts struct {
verbose bool
udid string
orgName string
dryRun bool
yes bool
force bool
}
func main() {
if len(os.Args) < 2 {
fmt.Fprint(os.Stderr, usage)
os.Exit(2)
}
fs := flag.NewFlagSet("rebreak-supervise-magic", flag.ExitOnError)
opts := &cliOpts{}
fs.BoolVar(&opts.verbose, "v", false, "verbose output")
fs.StringVar(&opts.udid, "udid", "", "target UDID (default: first device)")
fs.StringVar(&opts.orgName, "org", "ReBreak", "OrganizationName")
fs.BoolVar(&opts.dryRun, "dry-run", false, "skip writes/reboot")
fs.BoolVar(&opts.yes, "yes", false, "skip confirm prompt")
fs.BoolVar(&opts.force, "force", false, "allow supervise on already-supervised devices")
// Optional DLMessage debug-mode via env
if os.Getenv("REBREAK_DLMSG_DEBUG") == "1" {
dlmsgDebugSet()
}
if err := fs.Parse(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, "flag parse error:", err)
os.Exit(2)
}
positional := fs.Args()
if len(positional) == 0 {
fmt.Fprint(os.Stderr, "missing command\n\n")
fmt.Fprint(os.Stderr, usage)
os.Exit(2)
}
cmd := positional[0]
switch cmd {
case "check":
exitOnErr(runCheck(opts))
case "cert-info":
exitOnErr(runCertInfo(opts))
case "cloud-config":
exitOnErr(runCloudConfig(opts))
case "supervise":
exitOnErr(runSupervise(opts))
case "unsupervise":
exitOnErr(runUnsupervise(opts))
case "-h", "--help", "help":
fmt.Print(usage)
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n\n", cmd)
fmt.Fprint(os.Stderr, usage)
os.Exit(2)
}
}
func runCheck(opts *cliOpts) error {
conn, err := device.Connect(opts.udid)
if err != nil {
return err
}
defer conn.Close()
res, err := preflight.Run(conn)
if err != nil {
return err
}
fmt.Println("Device:")
fmt.Printf(" UDID: %s\n", res.Device.UDID)
fmt.Printf(" Name: %s\n", res.Device.DeviceName)
fmt.Printf(" Type: %s\n", res.Device.ProductType)
fmt.Printf(" iOS: %s\n", res.Device.ProductVersion)
fmt.Printf(" ActivationState: %s\n", res.Device.ActivationState)
fmt.Printf(" FindMyEnabled: %v\n", res.Device.FindMyEnabled)
fmt.Printf(" IsSupervised: %v\n", res.Device.IsSupervised)
fmt.Println()
if res.OK {
fmt.Println("Pre-Flight: ✓ ready for supervise")
} else {
fmt.Println("Pre-Flight: ✗ not ready:")
for _, r := range res.Reasons {
fmt.Printf(" - %s\n", r)
}
return fmt.Errorf("pre-flight failed")
}
return nil
}
func runCertInfo(opts *cliOpts) error {
dir := cert.DefaultDir()
fmt.Printf("Supervision identity directory: %s\n", dir)
id, err := cert.LoadOrCreate()
if err != nil {
return err
}
fmt.Printf(" Cert (DER): %d bytes\n", len(id.CertDER))
fmt.Printf(" Key (DER): %d bytes\n", len(id.PrivateKeyDER))
fmt.Println()
fmt.Println("Identity ready — usable for supervise command.")
return nil
}
func runCloudConfig(opts *cliOpts) error {
conn, err := device.Connect(opts.udid)
if err != nil {
return err
}
defer conn.Close()
mc, err := mcinstall.Open(conn.Device())
if err != nil {
return err
}
defer mc.Close()
if err := mc.HelloHostIdentifier(); err != nil {
return err
}
cfg, err := mc.GetCloudConfiguration()
if err != nil {
return err
}
if cfg == nil {
fmt.Println("(no cloud configuration set on device)")
return nil
}
keys := make([]string, 0, len(cfg))
for k := range cfg {
keys = append(keys, k)
}
sort.Strings(keys)
fmt.Println("Cloud Configuration (live from device MCInstall):")
for _, k := range keys {
val := cfg[k]
switch v := val.(type) {
case []byte:
fmt.Printf(" %-40s = [%d bytes binary]\n", k, len(v))
case [][]byte:
fmt.Printf(" %-40s = [%d certs]\n", k, len(v))
default:
fmt.Printf(" %-40s = %v\n", k, v)
}
}
return nil
}
func runSupervise(opts *cliOpts) error {
conn, err := device.Connect(opts.udid)
if err != nil {
return err
}
res, err := preflight.Run(conn)
conn.Close()
if err != nil {
return err
}
if !res.OK {
fmt.Println("Pre-Flight failed:")
for _, r := range res.Reasons {
fmt.Printf(" - %s\n", r)
}
return fmt.Errorf("pre-flight blocking supervise")
}
if res.Device.IsSupervised && !opts.force {
fmt.Printf("Device already supervised (UDID=%s). Use --force to re-supervise (overwrites existing supervisor cert).\n", res.Device.UDID)
return nil
}
fmt.Printf("About to supervise device:\n %s (%s, iOS %s)\n",
res.Device.DeviceName, res.Device.ProductType, res.Device.ProductVersion)
if res.Device.IsSupervised {
fmt.Println("⚠ Device IS already supervised — will OVERWRITE existing supervisor cert with our own.")
fmt.Println(" Existing cert cannot be restored without external tool (TechLockdown / Apple Configurator).")
}
fmt.Printf("OrganizationName: %s\n", opts.orgName)
fmt.Println("Device will reboot. No data loss expected.")
if !opts.yes && !opts.dryRun {
if !confirm("Continue? [y/N]: ") {
return fmt.Errorf("aborted by user")
}
}
return supervise.Supervise(res.Device.UDID, supervise.Options{
OrgName: opts.orgName,
DryRun: opts.dryRun,
Verbose: opts.verbose,
BackupPath: defaultBackupPath(res.Device.UDID),
})
}
func defaultBackupPath(udid string) string {
short := udid
if len(short) > 12 {
short = short[:12]
}
ts := time.Now().UTC().Format("20060102-150405")
return fmt.Sprintf("/tmp/rebreak-supervise-backup-%s-%s.json", short, ts)
}
func runUnsupervise(opts *cliOpts) error {
conn, err := device.Connect(opts.udid)
if err != nil {
return err
}
res, err := preflight.Run(conn)
conn.Close()
if err != nil {
return err
}
if !res.Device.IsSupervised {
fmt.Printf("Device already un-supervised (UDID=%s). Nothing to do.\n", res.Device.UDID)
return nil
}
fmt.Printf("About to UN-supervise device:\n %s (%s, iOS %s)\n",
res.Device.DeviceName, res.Device.ProductType, res.Device.ProductVersion)
if !opts.yes && !opts.dryRun {
if !confirm("Continue? [y/N]: ") {
return fmt.Errorf("aborted by user")
}
}
return supervise.Unsupervise(res.Device.UDID, supervise.Options{
OrgName: opts.orgName,
DryRun: opts.dryRun,
Verbose: opts.verbose,
})
}
func confirm(prompt string) bool {
fmt.Print(prompt)
reader := bufio.NewReader(os.Stdin)
line, _ := reader.ReadString('\n')
line = strings.TrimSpace(strings.ToLower(line))
return line == "y" || line == "yes"
}
func exitOnErr(err error) {
if err == nil {
return
}
if !strings.HasPrefix(err.Error(), "aborted") {
fmt.Fprintln(os.Stderr, "ERROR:", err)
}
os.Exit(1)
}