As part of the move from using kernel extensions to system extensions, there is an issue which can be a problem for Mac admins: Uninstalling a system extension from the command line usually involves a GUI window popping up and requesting admin authorization.
This can be a problem for admins because it requires the logged-in user to:
- Have admin rights.
- Understand what the dialog is telling them.
- Be willing to enter admin credentials when prompted.
For macOS Monterey, this issue has been addressed by the addition of the RemovableSystemExtensions property to the com.apple.system-extension-policy profile payload. This is used to identify system extensions which can be deactivated without requiring admin authorization.
However, the RemovableSystemExtensions property is new in macOS Monterey and does not apply to macOS Big Sur and earlier. In the past, Mac admins have dealt with this issue through user education, providing warnings like the one shown below, or (in macOS 11.3 and later) removing the profile which authorized the system extension. In the latter case, removing authorization will also unload the system extension.
However, there is a way to bypass the admin authorization. For more details, please see below the jump.
There are two parts to being able to silently uninstall a system extension. The first is that the app developer must have written into their code signed app a way to trigger Apple’s uninstall API for system extensions. An example of this can be found in the code of the open source Santa tool created by Google:
For Santa, this functionality can be triggered from the command line by running the following command:
/Applications/Santa.app/Contents/MacOS/santa --unload-system-extension
This call to Apple’s uninstall API must be from the same code-signed app which is using the system extension. Other applications or functions trying to call the uninstall function from outside the app will not be authorized to uninstall the system extension.
Assuming that the code is included in the app to trigger Apple’s uninstall API for system extensions, the next step is found in the authorization database as the setting which controls whether or not the logged-in user is prompted for admin credentials is located there. Running the following command should show the setting:
security authorizationdb read com.apple.system-extensions.admin
That should display output similar to what’s shown below:
<?xml version="1.0" encoding="UTF-8"?> | |
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |
<plist version="1.0"> | |
<dict> | |
<key>class</key> | |
<string>rule</string> | |
<key>created</key> | |
<real>600880872.76305306</real> | |
<key>modified</key> | |
<real>656531090.20857704</real> | |
<key>rule</key> | |
<array> | |
<string>authenticate-admin-nonshared</string> | |
</array> | |
<key>version</key> | |
<integer>0</integer> | |
</dict> | |
</plist> |
The rule key’s value is what determines whether the logged-in user is asked for admin authorization:
<key>rule</key> | |
<array> | |
<string>authenticate-admin-nonshared</string> | |
</array> |
By default, this value is set to the following:
authenticate-admin-nonshared
https://gist.github.com/rtrouton/bb4e4136af5ee1b8160f1638b68f1a86
However, this value can be changed. Setting it to the value shown below will stop the request for admin authorization:
allow
https://gist.github.com/rtrouton/dd26f434ff28edaac455b6b396b26e44
To change com.apple.system-extensions.admin‘s rule key value to allow, you can use the commands shown below:
security authorizationdb read com.apple.system-extensions.admin > /tmp/com.apple.system-extensions.admin.plist | |
/usr/libexec/PlistBuddy -c "Set rule:0 allow" /tmp/com.apple.system-extensions.admin.plist | |
security authorizationdb write com.apple.system-extensions.admin < /tmp/com.apple.system-extensions.admin.plist |
To revert back to the default value of authenticate-admin-nonshared, you can use the commands shown below:
security authorizationdb read com.apple.system-extensions.admin > /tmp/com.apple.system-extensions.admin.plist | |
/usr/libexec/PlistBuddy -c "Set rule:0 authenticate-admin-nonshared" /tmp/com.apple.system-extensions.admin.plist | |
security authorizationdb write com.apple.system-extensions.admin < /tmp/com.apple.system-extensions.admin.plist |
An example of how this can be used is with Microsoft Defender, which may deploy multiple system extensions. The example script below will uninstall Microsoft Defender and includes deactivating the system extensions without prompting the logged-in user for admin credentials:
#!/bin/bash | |
# Uninstall Microsoft Defender | |
# unload the launchd plist for the current user | |
currentUser=$(/bin/ls -l /dev/console | /usr/bin/awk '{ print $3 }') | |
# Temp plist files used for import and export from authorization database. | |
management_db_original_setting="$(mktemp).plist" | |
management_db_edited_setting="$(mktemp).plist" | |
management_db_check_setting="$(mktemp).plist" | |
# Expected settings from management database for com.apple.system-extensions.admin | |
original_setting="authenticate-admin-nonshared" | |
updated_setting="allow" | |
ManagementDatabaseUpdatePreparation() { | |
# Create temp plist files | |
touch "$management_db_original_setting" | |
touch "$management_db_edited_setting" | |
touch "$management_db_check_setting" | |
# Create backup of the original com.apple.system-extensions.admin settings from the management database | |
/usr/bin/security authorizationdb read com.apple.system-extensions.admin > "$management_db_original_setting" | |
# Create copy of the original com.apple.system-extensions.admin settings from the management database for editing. | |
/usr/bin/security authorizationdb read com.apple.system-extensions.admin > "$management_db_edited_setting" | |
} | |
UpdateManagementDatabase() { | |
if [[ -r "$management_db_edited_setting" ]] && [[ $(/usr/libexec/PlistBuddy -c "Print rule:0" "$management_db_edited_setting") = "$original_setting" ]]; then | |
/usr/libexec/PlistBuddy -c "Set rule:0 $updated_setting" "$management_db_edited_setting" | |
if [[ $(/usr/libexec/PlistBuddy -c "Print rule:0" "$management_db_edited_setting" ) = "$updated_setting" ]]; then | |
echo "Edited $management_db_edited_setting is set to allow system extensions to be uninstalled without password prompt." | |
echo "Now importing setting into authorization database." | |
/usr/bin/security authorizationdb write com.apple.system-extensions.admin < "$management_db_edited_setting" | |
if [[ $? -eq 0 ]]; then | |
echo "Updated setting successfully imported." | |
UpdatedAuthorizationSettingInstalled=1 | |
fi | |
else | |
echo "Failed to update $management_db_edited_setting file with the correct setting to allow system extension uninstallation without prompting for admin credentials." | |
fi | |
fi | |
} | |
RestoreManagementDatabase() { | |
/usr/bin/security authorizationdb read com.apple.system-extensions.admin > "$management_db_check_setting" | |
if [[ ! $(/usr/libexec/PlistBuddy -c "Print rule:0" "$management_db_check_setting") = "$original_setting" ]]; then | |
if [[ -r "$management_db_original_setting" ]] && [[ $(/usr/libexec/PlistBuddy -c "Print rule:0" "$management_db_original_setting") = "$original_setting" ]]; then | |
echo "Restoring original settings to allow system extension uninstallation only after prompting for admin credentials." | |
echo "Now importing setting into authorization database." | |
/usr/bin/security authorizationdb write com.apple.system-extensions.admin < "$management_db_original_setting" | |
if [[ $? -eq 0 ]]; then | |
echo "Original setting successfully imported." | |
OriginalAuthorizationSettingInstalled=1 | |
fi | |
else | |
echo "Failed to update the authorization database with the correct setting to allow system extension uninstallation only after prompting for admin credentials." | |
fi | |
fi | |
} | |
if [[ -n "$currentUser" && "$currentUser" != "root" ]]; then | |
/bin/launchctl bootout gui/$(/usr/bin/id -u "$currentUser") /Library/LaunchAgents/com.microsoft.wdav.tray.plist | |
fi | |
# Unload the launchd plist for the daemon | |
/bin/launchctl bootout system /Library/LaunchDaemons/com.microsoft.fresno.plist | |
/bin/launchctl bootout system /Library/LaunchDaemons/com.microsoft.fresno.uninstall.plist | |
# unload the kernel extension | |
/sbin/kextunload -v 6 -b com.microsoft.wdavkext | |
# Check for loaded system extensions. | |
wdavExtensions=$(/usr/bin/systemextensionsctl list | /usr/bin/grep -Eo "com.microsoft.wdav.[^[:space:]]+" | /usr/bin/uniq) | |
if [[ -n "$wdavExtensions" ]]; then | |
# Prepare to update authorization database to allow system extensions to be uninstalled without password prompt. | |
ManagementDatabaseUpdatePreparation | |
# Update authorization database with new settings. | |
UpdateManagementDatabase | |
# Uninstall the system extensions | |
# | |
# Note: If the updated settings to allow system extensions to be uninstalled without password prompt were not | |
# added successfully, this will prompt the user to enter their admin credentials, so a message will be displayed | |
# to let the user know. | |
if [[ -z UpdatedAuthorizationSettingInstalled ]]; then | |
/usr/bin/osascript -e 'display dialog "As part of the uninstall process for Microsoft" & "\nDefender, please enter your admin password when prompted." & "\n" & "\nYou may be prompted up to three times."buttons {"Understood"} default button 1 with icon Caution' | |
fi | |
# The system extensions will now be uninstalled. If needed, a message will be displayed to warn the user | |
# to enter their admin credentials. | |
# | |
# After the message is displayed, the user will be prompted for the password to authorize removal of Defender's system extensions. | |
for anExtension in ${wdavExtensions}; do | |
"/Applications/Microsoft Defender ATP.app/Contents/MacOS/wdavdaemon" uninstall-system-extension "$anExtension" | |
done | |
# Once the system extensions are uninstalled, the relevant settings for the authorization database will be restored from backup to their prior state. | |
if [[ -n UpdatedAuthorizationSettingInstalled ]]; then | |
RestoreManagementDatabase | |
if [[ -n "$OriginalAuthorizationSettingInstalled" ]]; then | |
echo "com.apple.system-extensions.admin settings in the authorization database successfully restored to $original_setting." | |
rm -rf "$management_db_original_setting" | |
rm -rf "$management_db_edited_setting" | |
rm -rf "$management_db_check_setting" | |
fi | |
fi | |
fi | |
# kill Microsoft Defender | |
/usr/bin/killall -SIGKILL "Microsoft Defender" "Microsoft Defender ATP" | |
# remove the global stuff | |
/bin/rm -rf "/Applications/Microsoft Defender ATP.app" \ | |
/Library/Logs/Microsoft/mdatp \ | |
"/Library/Application Support/Microsoft/Defender" \ | |
"/Library/Application Support/Microsoft Defender ATP" \ | |
/var/log/fresno*.log \ | |
/Library/Extensions/com.microsoft.wdavkext \ | |
/Library/Extensions/wdavkext.kext \ | |
/Library/LaunchDaemons/com.microsoft.fresno.* \ | |
/Library/LaunchAgents/com.microsoft.wdav.tray.plist | |
# remove stuff in users folders | |
localUsers=$(/usr/bin/dscl . -list /Users | /usr/bin/grep -v "^_") | |
for userName in ${localUsers}; do | |
# get path to user's home directory | |
userHome=$(/usr/bin/dscl . -read "/Users/$userName" NFSHomeDirectory 2>/dev/null | /usr/bin/sed 's/^[^\/]*//g') | |
if [[ -d "$userHome" && "$userHome" != "/var/empty" ]]; then | |
/bin/rm -rf "$userHome/Library/Saved Application State/com.microsoft.wdav.savedState" \ | |
"$userHome/Library/Preferences/com.microsoft.wdav.plist" \ | |
"$userHome/Library/Preferences/com.microsoft.wdavtray.plist" \ | |
"$userHome/Library/Caches/Microsoft/uls/com.microsoft.wdav" | |
fi | |
done | |
# remove the mdatp user and group | |
/usr/bin/dscl /Local/Default -delete /Users/_mdatp | |
/usr/bin/dscl /Local/Default -delete /Groups/_mdatp | |
# forget the packages | |
allPKGS=$(/usr/sbin/pkgutil –pkgs="com.microsoft.wdav") | |
for aPKG in ${allPKGS}; do | |
/usr/sbin/pkgutil –forget "$aPKG" | |
done | |
exit 0 |
Before Microsoft Defender uninstallation:
username@computername ~ % systemextensionsctl list | |
2 extension(s) | |
— com.apple.system_extension.network_extension | |
enabled active teamID bundleID (version) name [state] | |
* * UBF8T346G9 com.microsoft.wdav.netext (101.47.27/101.47.27) Microsoft Defender ATP Network Extension [activated enabled] | |
— com.apple.system_extension.endpoint_security | |
enabled active teamID bundleID (version) name [state] | |
* * UBF8T346G9 com.microsoft.wdav.epsext (101.47.27/101.47.27) Microsoft Defender ATP Endpoint Security Extension [activated enabled] | |
username@computername ~ % |
After Microsoft Defender uninstallation:
username@computername ~ % systemextensionsctl list | |
2 extension(s) | |
— com.apple.system_extension.network_extension | |
enabled active teamID bundleID (version) name [state] | |
UBF8T346G9 com.microsoft.wdav.netext (101.47.27/101.47.27) Microsoft Defender ATP Network Extension [terminated waiting to uninstall on reboot] | |
— com.apple.system_extension.endpoint_security | |
enabled active teamID bundleID (version) name [state] | |
UBF8T346G9 com.microsoft.wdav.epsext (101.47.27/101.47.27) Microsoft Defender ATP Endpoint Security Extension [terminated waiting to uninstall on reboot] | |
username@computername ~ % |