11#! /bin/bash
2+ set -o pipefail
23
34# Define color codes
45RED=' \033[1;31m'
@@ -8,6 +9,7 @@ YEL='\033[1;33m'
89PUR=' \033[1;35m'
910CYAN=' \033[1;36m'
1011NC=' \033[0m'
12+ PB=/usr/libexec/PlistBuddy
1113
1214# Error handling function
1315error_exit () {
@@ -110,82 +112,76 @@ find_available_uid() {
110112 return 1
111113}
112114
113- # Function to detect system volumes with multiple fallback strategies
114- detect_volumes () {
115- local system_vol=" "
116- local data_vol=" "
117-
118- info " Detecting system volumes..." >&2
115+ # Locate the Data volume by APFS role and unlock it if FileVault-locked
116+ # (ported from bypass-mdm-v3 / PR #170). Echoes the real mount point on
117+ # stdout; all chatter goes to stderr.
118+ step () { echo -e " ${CYAN} ▸ $1 ${NC} " ; }
119+ resolve_data_volume () {
120+ local id data_dev mount_pt
121+
122+ step " Locating the Data volume by APFS role..." >&2
123+ # Position-independent: on the (Data)-role line, pull the diskNsM token by
124+ # PATTERN, not column (diskutil draws "|" tree chars that shift columns).
125+ id=$( diskutil apfs list 2> /dev/null \
126+ | awk ' /\(Data\)/{for(i=1;i<=NF;i++) if($i ~ /^disk[0-9]+s[0-9]+$/){print $i; exit}}' )
127+
128+ # Fallback 1: a volume literally named "Data" in `diskutil list`.
129+ if [ -z " $id " ]; then
130+ id=$( diskutil list 2> /dev/null \
131+ | awk ' /[[:space:]]Data[[:space:]]/{for(i=1;i<=NF;i++) if($i ~ /^disk[0-9]+s[0-9]+$/) v=$i} END{print v}' )
132+ fi
119133
120- # Strategy 1: Look for common macOS APFS volume patterns
121- # List all volumes and look for system volume (ends with or contains common names)
122- for vol in /Volumes/* ; do
123- if [ -d " $vol " ]; then
124- vol_name=$( basename " $vol " )
134+ # Fallback 2: ask the user, showing the disk layout.
135+ if [ -z " $id " ] || ! diskutil info " /dev/$id " > /dev/null 2>&1 ; then
136+ warn " Could not auto-detect the Data volume. Your disks:" >&2
137+ diskutil list >&2
138+ echo " " >&2
139+ read -p " Type the Data volume identifier (e.g. disk3s1): " id < /dev/tty
140+ id=" ${id#/ dev/ } "
141+ fi
125142
126- # Check if this looks like a system volume (not Data, not recovery)
127- if [[ ! " $vol_name " =~ " Data" $ ]] && [[ ! " $vol_name " =~ " Recovery" ]] && [ -d " $vol /System" ]; then
128- system_vol=" $vol_name "
129- info " Found system volume: $system_vol " >&2
130- break
131- fi
132- fi
133- done
143+ [ -n " $id " ] || error_exit " No Data volume identifier provided."
144+ data_dev=" /dev/$id "
145+ diskutil info " $data_dev " > /dev/null 2>&1 || error_exit " Not a valid disk: $data_dev "
146+ info " Data volume device: $data_dev " >&2
134147
135- # Strategy 2: If no system volume found, try looking for any volume with /System directory
136- if [ -z " $system_vol " ]; then
137- for vol in /Volumes/* ; do
138- if [ -d " $vol /System" ]; then
139- system_vol=$( basename " $vol " )
140- warn " Using volume with /System directory: $system_vol " >&2
141- break
142- fi
143- done
144- fi
148+ _mp () { diskutil info " $data_dev " 2> /dev/null | awk -F' : *' ' /Mount Point/{print $2}' | sed ' s/[[:space:]]*$//' ; }
149+ mount_pt=$( _mp)
145150
146- # Strategy 3: Check for Data volume
147- if [ -d " /Volumes/Data" ]; then
148- data_vol=" Data"
149- info " Found data volume: $data_vol " >&2
150- elif [ -n " $system_vol " ] && [ -d " /Volumes/$system_vol - Data" ]; then
151- data_vol=" $system_vol - Data"
152- info " Found data volume: $data_vol " >&2
153- else
154- # Look for any volume ending with "Data"
155- for vol in /Volumes/* Data; do
156- if [ -d " $vol " ]; then
157- data_vol=$( basename " $vol " )
158- warn " Found data volume: $data_vol " >&2
159- break
160- fi
161- done
151+ if [ -z " $mount_pt " ] || [ ! -d " $mount_pt " ]; then
152+ # Try a plain mount first (works for non-encrypted volumes).
153+ diskutil mount " $data_dev " >&2 2> /dev/null
154+ mount_pt=$( _mp)
162155 fi
163-
164- # Validate findings
165- if [ -z " $system_vol " ]; then
166- error_exit " Could not detect system volume. Please ensure you're running this in Recovery mode with a macOS installation present."
156+ if [ -z " $mount_pt " ] || [ ! -d " $mount_pt " ]; then
157+ # Still not mounted -> almost certainly FileVault-locked. Unlock it.
158+ warn " Data volume appears FileVault-locked — unlocking." >&2
159+ echo -e " ${YEL} Enter the password of an account on this Mac (or its FileVault recovery key):${NC} " >&2
160+ diskutil apfs unlockVolume " $data_dev " >&2 \
161+ || error_exit " Failed to unlock the Data volume. Re-run and enter a valid account password / recovery key."
162+ mount_pt=$( _mp)
167163 fi
168164
169- if [ -z " $data_vol " ]; then
170- error_exit " Could not detect data volume. Please ensure you're running this in Recovery mode with a macOS installation present."
171- fi
165+ [ -d " $mount_pt " ] || error_exit " Data volume mount point not found after mount/unlock."
166+ # Sanity: the dslocal node must exist on this volume.
167+ [ -d " $mount_pt /private/var/db/dslocal/nodes/Default" ] \
168+ || error_exit " This does not look like a macOS Data volume (no dslocal node at $mount_pt )."
172169
173- echo " $system_vol |$data_vol "
170+ success " Data volume mounted at: $mount_pt " >&2
171+ echo " $mount_pt "
174172}
175173
176- # Detect volumes at startup
177- volume_info=$( detect_volumes)
178- system_volume=$( echo " $volume_info " | cut -d' |' -f1)
179- data_volume=$( echo " $volume_info " | cut -d' |' -f2)
174+ # Locate + mount (FileVault-unlock if needed) the Data volume at startup
175+ data_mount=$( resolve_data_volume) || exit 1
176+ data_volume=$( basename " $data_mount " )
180177
181178# Display header
182179echo " "
183180echo -e " ${CYAN} ╔═══════════════════════════════════════════════╗${NC} "
184181echo -e " ${CYAN} ║ Bypass MDM By Assaf Dori (assafdori.com) ║${NC} "
185182echo -e " ${CYAN} ╚═══════════════════════════════════════════════╝${NC} "
186183echo " "
187- success " System Volume: $system_volume "
188- success " Data Volume: $data_volume "
184+ success " Data Volume: $data_mount "
189185echo " "
190186
191187# Prompt user for choice
@@ -200,26 +196,11 @@ select opt in "${options[@]}"; do
200196 echo -e " ${YEL} ═══════════════════════════════════════${NC} "
201197 echo " "
202198
203- # Normalize data volume name if needed
204- if [ " $data_volume " != " Data" ]; then
205- info " Renaming data volume to 'Data' for consistency..."
206- if diskutil rename " $data_volume " " Data" 2> /dev/null; then
207- success " Data volume renamed successfully"
208- data_volume=" Data"
209- else
210- warn " Could not rename data volume, continuing with: $data_volume "
211- fi
212- fi
213-
214- # Validate critical paths
215- info " Validating system paths..."
216-
217- system_path=" /Volumes/$system_volume "
218- data_path=" /Volumes/$data_volume "
199+ # Data volume already located + mounted (FileVault-unlocked if needed)
200+ # by resolve_data_volume(); data_mount is its real mount point.
201+ info " Validating paths..."
219202
220- if [ ! -d " $system_path " ]; then
221- error_exit " System volume path does not exist: $system_path "
222- fi
203+ data_path=" $data_mount "
223204
224205 if [ ! -d " $data_path " ]; then
225206 error_exit " Data volume path does not exist: $data_path "
@@ -342,21 +323,44 @@ select opt in "${options[@]}"; do
342323 success " User account created successfully"
343324 echo " "
344325
345- # Block MDM domains
326+ # Block MDM enrollment domains (SSV-aware; also blocks the org's own MDM
327+ # host read from the DEP record, plus IPv6). Reads the record here, before
328+ # it is removed later in this flow.
346329 info " Blocking MDM enrollment domains..."
347-
348- hosts_file=" $system_path /etc/hosts"
349- if [ ! -f " $hosts_file " ]; then
350- warn " Hosts file does not exist, creating it"
351- touch " $hosts_file " || error_exit " Failed to create hosts file"
330+ hosts_file=" $data_path /private/etc/hosts"
331+ record_found=" $data_path /private/var/db/ConfigurationProfiles/Settings/.cloudConfigRecordFound"
332+ mdm_host=" " org=" "
333+ if [ -f " $record_found " ]; then
334+ mdm_host=$( plutil -convert xml1 -o - " $record_found " 2> /dev/null \
335+ | grep -ioE ' https?://[a-z0-9._-]+' | sed -E ' s#https?://##' \
336+ | sort -u | grep -viE ' (^|\.)apple\.com$' | head -1)
337+ org=$( plutil -convert xml1 -o - " $record_found " 2> /dev/null \
338+ | grep -iA1 OrganizationName | tail -1 | sed -E ' s/.*<string>(.*)<\/string>.*/\1/' )
339+ [ -n " $org " ] && info " This device is assigned in Apple Business Manager to: $org "
340+ [ -n " $mdm_host " ] && info " Org MDM server host: $mdm_host (will also be blocked)"
341+ else
342+ info " No activation record currently present."
352343 fi
344+ echo " "
353345
354- # Check if entries already exist to avoid duplicates
355- grep -q " deviceenrollment.apple.com" " $hosts_file " 2> /dev/null || echo " 0.0.0.0 deviceenrollment.apple.com" >> " $hosts_file "
356- grep -q " mdmenrollment.apple.com" " $hosts_file " 2> /dev/null || echo " 0.0.0.0 mdmenrollment.apple.com" >> " $hosts_file "
357- grep -q " iprofiles.apple.com" " $hosts_file " 2> /dev/null || echo " 0.0.0.0 iprofiles.apple.com" >> " $hosts_file "
358-
359- success " MDM domains blocked in hosts file"
346+ # --- Block the DEP / enrollment domains on the DATA volume's hosts file ---
347+ step " Blocking DEP enrollment domains (Data-volume hosts file)"
348+ [ -f " $hosts_file " ] || { mkdir -p " $( dirname " $hosts_file " ) " ; touch " $hosts_file " ; }
349+ # iprofiles.apple.com = the device-side activation-record fetch (THE essential one).
350+ # device/mdm-enrollment = server-side DEP API (harmless to include).
351+ # acmdm = Apple cert/MDM endpoint. We deliberately DO NOT block:
352+ # gdmf.apple.com (breaks Software Update) or albert.apple.com (breaks iMessage/FaceTime).
353+ block_domains=(iprofiles.apple.com deviceenrollment.apple.com mdmenrollment.apple.com acmdm.apple.com)
354+ [ -n " $mdm_host " ] && block_domains+=(" $mdm_host " )
355+ grep -q " Added by bypass-mdm" " $hosts_file " 2> /dev/null || {
356+ echo " " >> " $hosts_file "
357+ echo " # Added by bypass-mdm — DEP enrollment block" >> " $hosts_file "
358+ }
359+ for d in " ${block_domains[@]} " ; do
360+ grep -qiE " [[:space:]]$d (\$ |[[:space:]])" " $hosts_file " 2> /dev/null && { info " $d already blocked" ; continue ; }
361+ printf ' 0.0.0.0 %s\n:: %s\n' " $d " " $d " >> " $hosts_file "
362+ success " blocked $d "
363+ done
360364 echo " "
361365
362366 # Remove configuration profiles
@@ -384,6 +388,23 @@ select opt in "${options[@]}"; do
384388 touch " $config_path /.cloudConfigProfileInstalled" 2> /dev/null && success " Created profile installed marker" || warn " Could not create profile marker"
385389 touch " $config_path /.cloudConfigRecordNotFound" 2> /dev/null && success " Created record not found marker" || warn " Could not create not found marker"
386390
391+ # Disable the enrollment daemon via a launchd override (durable: lives on
392+ # the Data volume, so it survives the System-volume reseal a macOS update
393+ # performs). macOS 26 moved this work to com.apple.ManagedClient.enroll.
394+ info " Disabling the enrollment daemon (durable override on Data volume)..."
395+ launchd_disabled=" $data_path /private/var/db/com.apple.xpc.launchd/disabled.plist"
396+ mkdir -p " $( dirname " $launchd_disabled " ) " 2> /dev/null
397+ [ -f " $launchd_disabled " ] || printf ' <?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict/></plist>\n' > " $launchd_disabled "
398+ for label in com.apple.ManagedClient.enroll com.apple.mdmclient.daemon.runatboot; do
399+ " $PB " -c " Add :$label bool true" " $launchd_disabled " 2> /dev/null \
400+ || " $PB " -c " Set :$label true" " $launchd_disabled " 2> /dev/null
401+ done
402+ if [ -f " $launchd_disabled " ]; then
403+ success " Enrollment daemon disabled via $launchd_disabled "
404+ else
405+ warn " Could not write launchd override (daemon not disabled)"
406+ fi
407+
387408 echo " "
388409 echo -e " ${GRN} ╔═══════════════════════════════════════════════╗${NC} "
389410 echo -e " ${GRN} ║ MDM Bypass Completed Successfully! ║${NC} "
0 commit comments