The following repository contains samples for the 2024 Objective By the Sea v7.0 talk: "Mac, Where's My Bootstrap?" by Brandon Dalton (@PartyD0lphin) and Csaba Fitzl (@theevilbit).
This app demonstrates the ability to detect common classes of XPC exploits by validating code signing properties on both sides of the connection and pivoting off of macOS 14's XPC_CONNECT
Endpoint Security event.
Here you'll find the Xcode project XPC2Proc which will build to XPC2Proc.app
and calls into our LaunchCtl.swift
. This file enables programatic XPC service name to path resolution we leverage for detection:
func resolveProgramPath(from machServiceName: String, in domain: Domain) -> String
- Grab a copy from the releases page
- Since this app leverages ES it needs to be run as root with FDA on the hosting process (e.g.
Terminal.app
):sudo XPC2Proc.app/Contents/MacOS/XPC2Proc
- Optionally, you can test a detection.
- Switch to the build directory:
tests/build/
- Compile the test with
tests/build/build.sh
- Test a detection with:
./tests/bin/xpcConnTest com.xpc.example.agent.hello
- Switch to the build directory:
Follow along at XPC2Proc/XPC2Proc/SwiftLaunchCtl/entry.swift
. These examples leverage an ES client as well (from the cmdl).
- Using the sample code provided by Apple here: Updating your app package installer to use the new Service Management API
{"id":"5C57EA67-91BB-407D-8466-9CCFDAD065F5","programPath":"/System/Library/PrivateFrameworks/TextInputUIMacHelper.framework/Versions/A/XPCServices/CursorUIViewService.xpc/Contents/MacOS/CursorUIViewService","xpcDomain":{"user":{"_0":501}},"xpcServiceName":"com.apple.TextInputUI.xpc.CursorUIViewService"}
{"id":"8C569FE0-6DF8-4154-BCB5-92F1962C2D2F","programPath":"/usr/sbin/cfprefsd","xpcDomain":{"system":{}},"xpcServiceName":"com.apple.cfprefsd.daemon"}
{"id":"ED20CC35-D012-48C8-AFF8-7F5E20EE2A31","programPath":"/Users/pegasus/Downloads/SMAppServiceSampleCode.app/Contents/Resources/SampleLaunchAgent","xpcDomain":{"user":{"_0":501}},"xpcServiceName":"com.xpc.example.agent.hello"}
{"id":"98D6EC49-5006-4E26-8248-0A9453052F28","programPath":"/System/Library/PrivateFrameworks/TextInputUIMacHelper.framework/Versions/A/XPCServices/CursorUIViewService.xpc/Contents/MacOS/CursorUIViewService","xpcDomain":{"user":{"_0":501}},"xpcServiceName":"com.apple.TextInputUI.xpc.CursorUIViewService"}
{"id":"7C484C9A-92C9-4213-AC9B-D3A96AFF0CA4","programPath":"/System/Library/PrivateFrameworks/TCC.framework/Support/tccd","xpcDomain":{"system":{}},"xpcServiceName":"com.apple.tccd.system"}
{"id":"76598196-F791-4525-8379-6106E5B07B07","programPath":"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/XPCServices/com.apple.hiservices-xpcservice.xpc/Contents/MacOS/com.apple.hiservices-xpcservice","xpcDomain":{"user":{"_0":501}},"xpcServiceName":"com.apple.hiservices-xpcservice"}
{"id":"30FDE1B6-2343-4F3B-AB7E-D16358723970","programPath":"/usr/libexec/runningboardd","xpcDomain":{"system":{}},"xpcServiceName":"com.apple.runningboard"}
{"id":"490E7007-281D-490A-BF4A-E02677DBAF8A","programPath":"/Applications/Microsoft Teams.app/Contents/XPCServices/com.microsoft.teams2.notificationcenter.xpc","xpcDomain":{"pid":{"_0":11009}},"xpcServiceName":"com.microsoft.teams2.notificationcenter"}
launchd
services: such as Launch Daemons and Agents are jobs defined in a property list to be managed by the system. These jobs are backed by a single hosting program (see theProgram
key in theInfo.plist
). These programs can host multiple Mach Services to facilitate communication.- Mach Services: Low level atomic IPC channels managed / claimed by a program (see the
MachServices
/SBMachServices
key in theInfo.plist
)
Follow along at XPC2Proc/XPC2Proc/SwiftLaunchCtl/entry.swift
. You'll need to modify the code like so:
let launchCtl = LaunchCtl()
//
//// XPC (Mach) service name to program path
let xpcServiceName: String = "com.apple.dt.Xcode.DeveloperSystemPolicyService"
//// The domain it's in
let domain: Domain = Domain.pid(16764)
//
//// Let's do our magic!
let resolvedProgramPath = launchCtl.resolveProgramPath(
from: xpcServiceName,
in: domain
)
print("\(xpcServiceName) ==> \(resolvedProgramPath)")
//// System domain service target example
//if let response = launchCtl.executeLaunchdRequest(domain: .system, operation: .printServiceTarget(serviceName: "com.apple.accessoryupdaterd")) {
// print(response)
//}
//
//// Per-pid domain service target example
if let response = launchCtl.executeLaunchdRequest(
domain: .pid(34496),
operation: .printServiceTarget(serviceName: "com.microsoft.teams2.notificationcenter")
) {
print(response)
}
// Explicit usage -- research use-cases
// User domain target example
//
// type: The domain we’re targeting. 1=system, 2=user, 3=login, 5=pid, 8=gui
// handle: For system/user/gui domains use the UID (e.g. 501), for login use the ASID, for pid use the pid.
// subsystem: 2=print service target info, 3=print domain target info
// routine: 708=print a service target, 828=print a domain target
// and name: The service name if service target (subsystem == 2 && routine == 708)
if let response = launchCtl.executeLaunchdRequest(handle: 100016, type: 1, routine: 828, subsystem: 3, name: "com.apple.accessoryupdaterd") {
print("Service Response (explicit parameters): \(response)")
}
Standing on the shoulders of giants
- "MacOS and iOS Internals (MOXiI) Volume I - User Mode" by Jonathan Levin
- "Launchd: One Program to Rule them All" / "Managing Processes with launchd" by Dave Zarzycki (author of launchd)
- "Approaching Escape Velocity with launchd"
- "Bits of Launchd" by Samuel Groß (@5aelo)
- "Mach Ports" by Darling Docs
- "Baby's first Rust with extra steps (XPC, launchd, and FFI)!" by David Stancu
- "launchk: Cursive TUI that queries XPC to peek at launchd state" by David Stancu
- "Getting Started with launchd for Sys Admins" by Matt Hansen
- Red Canary Mac Monitor's AtomicESClient by Brandon Dalton