Designed as a possible last step before a MDM “Lock Computer” command,
FSWL.bash
*may aid in keeping a Mac computer online for investigation, while discouraging end-user tampering
Background
When a macOS computer is lost, stolen or involved in a security breach, the Mobile Device Management (MDM) Lock Computer command can be used as an “atomic” option to quickly bring some peace of mind to what are typically stressful situations, while the MDM Wipe Computer command can be used as the “nuclear” option.
For occasions where first forensically securing a macOS computer are preferred, the following approach may aid in keeping a device online for investigation, while discouraging end-user tampering.
Configuration
Complete the following steps to potentially forensically secure a macOS computer with CrowdStrike Falcon and Jamf Pro, which leverages the macOS 15 Sequoia (and earlier) built-in option to lock or unlock a user’s screen, which is based on a previous post: Actionable messages with Jamf Helper.
(*This approach may aid in keeping a device online for investigation, while discouraging end-user tampering and should not be used in production without thorough Red Team testing. Caveat emptor.)
A. Customize the FSWL.bash script for your environment
- Review and adjust the Global Variables as required for your environment
scriptLog
(i.e., the location of your client-side logs)
############################################################################################ # # Global Variables # ############################################################################################ export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin/ # Script Version scriptVersion="0.0.2" # Client-side Log scriptLog="/var/log/org.churchofjesuschrist.log" # jamfHelper Location JH="/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper"
- Set your preferred script defaults (which will be trumped in a policy’s script parameters)
- Parameter 4:
heading
- Parameter 5:
icon
(i.e., the absolute path to client-side icon) - Parameter 6:
description
(i.e., the message to be displayed to the end-user; See: Bonus: Line-breaking Voodoo)
- Parameter 4:
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Jamf Pro Script Parameters # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Sets the heading of the window to the specified string heading="${4:-"Heading [Parameter 4]"}" # Absolute path to client-side icon icon="${5:-"/System/Library/CoreServices/Diagnostics Reporter.app/Contents/Resources/AppIcon.icns"}" # Sets the main contents of the window to the specified string description="${6:-"Description [Parameter 6]"}"
- Set your preferred Organization Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Organization Variables # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Script Human-readabale Name humanReadableScriptName="Forensically Sound Workstation Lockout" # Organization's Script Name organizationScriptName="FSWL"
FSWL.bash
#!/bin/bash # shellcheck disable=SC2034,SC2317 #################################################################################################### # # Jamf Pro Forensically Sound Workstation Lockout # # Purpose: Leverages the built-in macOS LockScreen binary to prevent end-user interaction # #################################################################################################### # # HISTORY # # Version 0.0.1, 12-Sep-2024, Dan K. Snelson (@dan-snelson) # - Original, proof-of-concept version, based on: https://snelson.us/2023/01/jamf-helper/ # # Version 0.0.2, 13-Sep-2024, Dan K. Snelson (@dan-snelson) # - Standardized script # #################################################################################################### # # Global Variables # #################################################################################################### export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin/ # Script Version scriptVersion="0.0.2" # Client-side Log scriptLog="/var/log/org.churchofjesuschrist.log" # jamfHelper Location JH="/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper" # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Jamf Pro Script Parameters # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Sets the heading of the window to the specified string heading="${4:-"Heading [Parameter 4]"}" # Absolute path to client-side icon icon="${5:-"/System/Library/CoreServices/Diagnostics Reporter.app/Contents/Resources/AppIcon.icns"}" # Sets the main contents of the window to the specified string description="${6:-"Message [Parameter 6]"}" # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Organization Variables # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Script Human-readabale Name humanReadableScriptName="Forensically Sound Workstation Lockout" # Organization's Script Name organizationScriptName="FSWL" #################################################################################################### # # Functions # #################################################################################################### # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Client-side Logging # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # function updateScriptLog() { echo "${organizationScriptName} ($scriptVersion): $( date +%Y-%m-%d\ %H:%M:%S ) - ${1}" | tee -a "${scriptLog}" } function preFlight() { updateScriptLog "[PRE-FLIGHT] ${1}" } function logComment() { updateScriptLog " ${1}" } function notice() { updateScriptLog "[NOTICE] ${1}" } function info() { updateScriptLog "[INFO] ${1}" } function errorOut(){ updateScriptLog "[ERROR] ${1}" } function error() { updateScriptLog "[ERROR] ${1}" (( errorCount++ )) || true } function warning() { updateScriptLog "[WARNING] ${1}" (( errorCount++ )) || true } function fatal() { updateScriptLog "[FATAL ERROR] ${1}" exit 1 } function quitOut(){ updateScriptLog "[QUIT] ${1}" } #################################################################################################### # # Pre-flight Checks # #################################################################################################### # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Pre-flight Check: Client-side Logging # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # if [[ ! -f "${scriptLog}" ]]; then touch "${scriptLog}" if [[ -f "${scriptLog}" ]]; then preFlight "Created specified scriptLog" else fatal "Unable to create specified scriptLog; exiting.\n\n(Is this script running as 'root' ?)" fi else preFlight "Specified scriptLog exists; writing log entries to it" fi # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Pre-flight Check: Logging Preamble # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # preFlight "\n\n###\n# $humanReadableScriptName (${scriptVersion})\n###\n" preFlight "Initiating …" # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Pre-flight Check: Confirm script is running as root # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # if [[ $(id -u) -ne 0 ]]; then fatal "This script must be run as root; exiting." fi # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Pre-flight Check: Ensure computer does not go to sleep during SYM (thanks, @grahampugh!) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # caffeinatedPID="$$" preFlight "Caffeinating this script (PID: $caffeinatedPID)" caffeinate -dimsu -w $caffeinatedPID & # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Pre-flight Check: Complete # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # preFlight "Complete!" #################################################################################################### # # Program # #################################################################################################### # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Display Message # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # notice "The following message will be displayed to the end-user:" logComment "${heading} ${description}" notice "Lock screen with specified message" osascript -e "set Volume 10" afplay /System/Library/Sounds/Blow.aiff /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/MacOS/LockScreen & displayMessage=$( "$JH" -windowType "fs" -icon "${icon}" -heading "${heading}" -description "${description}" ) & # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Exit # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # quitOut "Exit" exit 0
B. Add the FSWL.bash script to your Jamf Pro server
- Add the
FSWL.bash
script to your Jamf Pro server - Specify the following for Options > Parameter Labels
- Parameter 4:
Heading
- Parameter 5:
Icon (absolute path)
- Parameter 6:
Message
- Parameter 4:
- Click Save
C. Create a Jamf Pro Policy to execute FSWL.bash
- Create a new Jamf Pro Policy, using the following as a guide for Options > General:
- Set Display Name to
Forensically Sound Workstation Lockout (0.0.2)
- Enable Trigger > Custom and enter a secure name
- Set Execution Frequency to
Ongoing
- Set Display Name to
- Select the Scripts payload and add the
FSWL.bash
script, specifying the following Parameter Values- Heading:
Contact Company SecOps
- Icon (absolute path):
/System/Library/CoreServices/Diagnostics Reporter.app/Contents/Resources/AppIcon.icns
- Message:
Your Mac may be involved in a security incident. Please contact Church SecOps immediately: +1 (801) 555-1212 and mention Code 8675-309.
See: Bonus: Line-breaking Voodoo
- Heading:
- Scope the policy to All Computers
- Click Save
D. Add Scripts to CrowdStrike Falcon Real Time Response
Add the following two scripts to Falcon Real Time Response:
CrowdStrike Falcon Forensically Sound Workstation Lockout.zsh
Adjust the customEventNameGoesHere
to match the value specified in Step C.
#!/bin/zsh # macOS Forensically Sound Workstation Lockout (0.0.2) # (Estimated Duration: 0h:0m:01s) echo -e "\n\n\n###\n# macOS Forensically Sound Workstation Lockout (0.0.2)\n# (Estimated Duration: 0h:0m:01s)\n###" # Initialize SECONDS SECONDS="0" # Jamf binary-related Variables jamfVersion=$( /usr/local/bin/jamf -version | cut -d "=" -f2 ) jamfPid=$( pgrep -a "jamf" | head -n 1 ) if [[ -n "${jamfPid}" ]]; then echo "• jamf ${jamfVersion} binary already running …" ps -p "${jamfPid}" echo "• Killing ${jamfPid} …" kill "${jamfPid}" fi echo "• Executing Jamf Pro Policy Trigger …" /usr/local/bin/jamf policy -trigger customEventNameGoesHere -verbose echo -e "\nExecution Duration: $(printf '%dh:%dm:%ds\n' $((SECONDS/3600)) $((SECONDS%3600/60)) $((SECONDS%60)))"
CrowdStrike Falcon Forensically Sound Workstation Release.zsh
#!/bin/zsh # macOS Forensically Sound Workstation Release (0.0.1) # (Estimated Duration: 0h:0m:17s) echo -e "\n\n\n###\n# macOS Forensically Sound Workstation Release (0.0.1)\n# (Estimated Duration: 0h:0m:17s)\n###" # Initialize SECONDS SECONDS="0" #################################################################################################### # # Functions # #################################################################################################### # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Kill a specified process # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # function killProcess() { process="$1" if process_pid=$( pgrep -a "${process}" 2>/dev/null ) ; then echo "Attempting to terminate the '$process' process …" echo "(Termination message indicates success.)" kill "$process_pid" 2> /dev/null if pgrep -a "$process" >/dev/null ; then echo "ERROR: '$process' could not be terminated." fi else echo "The '$process' process isn't running." fi } # Release the Forensically Sound Workstation Lockout killall -v LockScreen killall -v jamfHelper echo -e "\nExecution Duration: $(printf '%dh:%dm:%ds\n' $((SECONDS/3600)) $((SECONDS%3600/60)) $((SECONDS%60)))"
E. Testing
Jamf Pro
Once the script, policy and message are set in Jamf Pro, the “lockout magic” is enabled / disabled from Falcon Real Time Response.
CrowdStrike Falcon Real Time Response
Falcon Real Time Response commands are used to:
- Lock the Mac:
CrowdStrike Falcon Forensically Sound Workstation Lockout.zsh
securely calls the Jamf Pro policy to execute theFSWL.bash
script to prevent the computer from accepting keyboard or mouse input - Investigate the incident
- Release the lock:
CrowdStrike Falcon Forensically Sound Workstation Release.zsh
terminates the processes locking the Mac