Skip to content

Commit

Permalink
Merge pull request #10 from hazcod/feat/kext
Browse files Browse the repository at this point in the history
Feature: kernel extension & system extension support
  • Loading branch information
hazcod authored Dec 3, 2020
2 parents a015840 + cdc27b3 commit d9379df
Show file tree
Hide file tree
Showing 2 changed files with 206 additions and 12 deletions.
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

# maclaunch

Lists and controls your macOS startup items and their startup policy.
Lists and controls all your macOS startup items and their startup policy.

Take back control of your macOS system!

Expand Down Expand Up @@ -31,8 +31,13 @@ Take back control of your macOS system!

## How does it work?

Lists XML/json/binary plist files in LaunchAgents and LaunchDaemons folders which are loaded by launchctl.
When disabling an item, it uses launchctl to natively stop loading that service.
maclaunch will list 3 distinct types of entries on your macOS system that can be persistently installed:

1. Configuration files for LaunchAgents and LaunchDaemons which are loaded by launchctl.
2. Kernel extensions loaded in the kernel.
3. System extensions loaded in userspace.

When disabling an item, it uses `launchctl`, `kextutil` or `systemextensionsctl` to natively stop loading that service.
It does **not** alter the contents in any way or moves the file, so it should work with practically any service.

The name you provide can either be specific to that service or function as a filter to work on multiple services simultaneously.
Expand Down
207 changes: 198 additions & 9 deletions maclaunch.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
#!/usr/bin/env bash

#set -e
#set -x

startup_dirs=(/Library/LaunchAgents /Library/LaunchDaemons ~/Library/LaunchAgents ~/Library/LaunchDaemons)
system_dirs=(/System/Library/LaunchAgents /System/Library/LaunchDaemons)

Expand All @@ -12,6 +9,10 @@ YELLOW='\033[1;33m'
NC='\033[0m'
BOLD='\033[1m'

#
#--------------------------------------------------------------------------------------------------------------------------------------
#

function join_by { local IFS="$1"; shift; echo "$*"; }

function usage {
Expand Down Expand Up @@ -50,7 +51,182 @@ function getScriptUser {
grep '<key>UserName</key>' -C1 "$scriptPath" | tail -n1 | cut -d '>' -f 2 | cut -d '<' -f 1
}

function listItems {
function getKernelExtensions {
kmutil showloaded --no-kernel-components --list-only --sort --show loaded 2>/dev/null | tr -s ' ' | grep -v 'com\.apple\.'
}

function listKernelExtensions {
local filter="$1"

getKernelExtensions | while IFS= read -r kextLine; do

kextLoaded="$(echo "$kextLine" | cut -d ' ' -f 3)"
kextName="$(echo "$kextLine" | cut -d ' ' -f 7)"
kextVersion="$(echo "$kextLine" | grep -o '\((.*)\)')"

if [ "$filter" == "disabled" ] && [ "$kextLoaded" != "0" ]; then
continue
fi

if [ "$filter" == "enabled" ] && [ "$kextLoaded" == "0" ]; then
continue
fi

if [ -n "$filter" ] && [ "$filter" != "system" ] && [ "$filter" != "enabled" ] && [ "$filter" != "disabled" ]; then
if [[ "$kextName" != *"$filter"* ]]; then
continue
fi
fi

kernelPath="$(kextfind -system-extensions "$kextName" 2>/dev/null)"
if [ -z "$kernelPath" ]; then
kernelPath="n/a"
fi

local loaded
if [ "$kextLoaded" == "0" ]; then
loaded="${GREEN}${BOLD}disabled${NC}"
else
loaded="${RED}Always${NC}"
fi

echo -e "${BOLD}> ${kextName}${NC} ${kextVersion}"
echo -e " Type : ${RED}kernel extension${NC}"
echo -e " User : ${RED}root${NC}"
echo -e " Launch: ${loaded}"
echo " File : ${kernelPath}"

done
}

function disableKernelExtensions {
local filter="$1"

getKernelExtensions | while IFS= read -r kextLine; do

kextLoaded="$(echo "$kextLine" | cut -d ' ' -f 3)"
kextName="$(echo "$kextLine" | cut -d ' ' -f 7)"

if ! [[ "$kextName" =~ $filter ]]; then
continue
fi

if [ "$kextLoaded" == "0" ]; then
#error "kernel extension is already unloaded"
continue
fi

if ! kmutil load -b "$kextName" 1>/dev/null; then
error "could not disable kernel extension"
fi

echo -e "${GREEN}Disabled ${STRONG}${kextName}${NC}"
done
}

function enableKernelExtensions {
local filter="$1"

getKernelExtensions | while IFS= read -r kextLine; do

kextLoaded="$(echo "$kextLine" | cut -d ' ' -f 3)"
kextName="$(echo "$kextLine" | cut -d ' ' -f 7)"

if ! [[ "$kextName" =~ $filter ]]; then
continue
fi

if ! [ "$kextLoaded" == "0" ]; then
#error "kernel extension is already loaded"
continue
fi

if ! kmutil unload -b "$kextName" 1>/dev/null; then
error "could not disable kernel extension"
fi

echo -e "${GREEN}Enabled ${STRONG}${kextName}${NC}"
done
}

function getSystemExtensions {
systemextensionsctl list 2>/dev/null | tail -n+2 | grep -v '^---' | grep -v '^enabled' | tr -s ' '
}

function listSystemExtensions {
local filter="$1"

getSystemExtensions | while IFS= read -r extLine; do

fullName="$(echo "$extLine" | cut -d$'\t' -f 4)"
extName="$(echo "$fullName" | cut -d ' ' -f 1)"
extVersion="$(echo "$fullName" | grep -o '\((.*)\)')"

if [ -n "$filter" ] && ! [[ "$extName" =~ $filter ]]; then
continue
fi

local loaded
if [ "$(echo "$extLine" | cut -d$'\t' -f 2)" == "*" ]; then
loaded="${ORANGE}enabled${NC}"
else
loaded="${GREEN}disabled${NC}"
fi

echo -e "${BOLD}> ${extName}${NC} ${extVersion}"
echo -e " Type : system extension"
echo -e " User : $(whoami)"
echo -e " Launch: ${loaded}"
echo " File : n/a"
done
}

function enableSystemExtensions {
local filter="$1"

getSystemExtensions | while IFS= read -r extLine; do

extName="$(echo "$extLine" | cut -d$'\t' -f 4 | cut -d ' ' -f 1)"

if ! [[ "$extName" =~ $filter ]]; then
continue
fi

if [ "$(echo "$extLine" | cut -d$'\t' -f 2)" == "*" ]; then
# error "this system extension is already enabled"
continue
fi

#TODO: implement load system extension via CLI
error "enabling system extensions is not yet implemented"
done
}

function disableSystemExtensions {
local filter="$1"

getSystemExtensions | while IFS= read -r extLine; do

extName="$(echo "$extLine" | cut -d$'\t' -f 4 | cut -d ' ' -f 1)"

if ! [[ "$extName" =~ $filter ]]; then
continue
fi

if ! [ "$(echo "$extLine" | cut -d$'\t' -f 2)" == "*" ]; then
# error "this system extension is already disabled"
continue
fi

if ! systemextensionsctl uninstall '-' "$extName"; then
error "could not disable system extension"
fi

echo -e "${GREEN}Enabled ${STRONG}${extName}${NC}"
done
}

function listLaunchItems {
local filter="$2"

itemDirectories=("${startup_dirs[@]}")
Expand Down Expand Up @@ -186,7 +362,7 @@ function listItems {
done< <(find "${itemDirectories[@]}" -type f -iname '*.plist*' -print0 2>/dev/null)
}

function enableItems {
function enableLaunchItems {
disabled_items="$(launchctl print-disabled user/"$(id -u)")"

while IFS= read -r -d '' startupFile; do
Expand Down Expand Up @@ -220,7 +396,7 @@ function enableItems {
done< <(find "${startup_dirs[@]}" "${system_dirs[@]}" \( -iname "*$1*.plist" -o -iname "*$1*.plist.disabled" \) -print0 2>/dev/null)
}

function disableItems {
function disableLaunchItems {
disabled_items="$(launchctl print-disabled user/"$(id -u)")"

while IFS= read -r -d '' startupFile; do
Expand Down Expand Up @@ -254,6 +430,10 @@ function disableItems {
done< <(find "${startup_dirs[@]}" "${system_dirs[@]}" \( -iname "*$1*.plist" -o -iname "*$1*.plist.disabled" \) -print0 2>/dev/null)
}

#
#--------------------------------------------------------------------------------------------------------------------------------------
#

if [ $# -lt 1 ] || [ $# -gt 2 ]; then
usage
fi
Expand All @@ -265,20 +445,29 @@ case "$1" in
usage
fi
fi
listItems "$1" "$2"
listLaunchItems "$1" "$2"
listKernelExtensions "$2"
listSystemExtensions "$2"
;;

"disable")
if [ $# -ne 2 ]; then
usage
fi
disableItems "$2"
disableLaunchItems "$2"
disableKernelExtensions "$2"
disableSystemExtensions "$2"
;;

"enable")
if [ $# -ne 2 ]; then
usage
fi
enableItems "$2"
enableLaunchItems "$2"
enableKernelExtensions "$2"
enableSystemExtensions "$2"
;;

*)
usage
;;
Expand Down

0 comments on commit d9379df

Please sign in to comment.