Home > Mac administration, macOS, Scripting > Silently uninstalling system extensions on macOS Monterey and earlier

Silently uninstalling system extensions on macOS Monterey and earlier

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.

Screen Shot 2021 10 26 at 8 25 37 AM

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.

Screen Shot 2021 10 26 at 11 41 26 AM

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.

Screen Shot 2021 10 26 at 8 24 59 AM

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:

https://github.com/google/santa/blob/d2b6c2b6c2de33ba2267c54f9affcbf592046050/Source/santa/main.m#L64-L78

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"&gt;
<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>

view raw

gistfile1.txt

hosted with ❤ by GitHub

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

view raw

gistfile1.txt

hosted with ❤ by GitHub

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

view raw

gistfile1.txt

hosted with ❤ by GitHub

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 ~ %

view raw

gistfile1.txt

hosted with ❤ by GitHub

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 ~ %

view raw

gistfile1.txt

hosted with ❤ by GitHub

  1. Daniel Greening
    October 26, 2021 at 7:59 pm

    Can you clarify the OS supported for “SystemExtensions.RemovableSystemExtensions”? The Apple Dev KB says that this is supported on 10.15+. https://developer.apple.com/documentation/devicemanagement/systemextensions/removablesystemextensions?changes=l_2#properties

    • October 26, 2021 at 10:25 pm

      That’s a discrepancy you’ll have to take up with Apple. The documentation I’ve linked to has the following note associated with RemovableSystemExtensions: “Available in macOS 12 and later.”

  2. November 6, 2021 at 8:21 pm

    This is a real pain point. I need to remove SEP from many Mac’s and I seriously doubt that SEP 14.3 utilized that Apple RemovableSystemExtensions API call. They offer two removal scripts one runs as root and doesn’t remove the system extension. The other script will remove it but requires a local admin user at the console to continue and authenticate to remove the system extension.

  3. cgrant
    November 9, 2021 at 2:05 am

    Is there any way to do this for Sophos Endpoint?

    • November 19, 2021 at 11:57 am

      @cgrant @rtrouton I also would like a version for Sophos, unfortunately I suspect Sophos have not implemented this capability.

      @jbrickley presuming SEP means Sophos EndPoint which is the script that requires local admin? Currently this will be the ‘least worse’ option.

      • davidiwanickips
        November 30, 2021 at 8:28 pm

        SEP stands for Symantec Endpoint Protection

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: