Last active
April 29, 2020 18:39
-
-
Save Alkenso/aa8d9cc680596cb70ed1b84186c1c8a5 to your computer and use it in GitHub Desktop.
Observe KEXT: IOKit driver
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import IOKit | |
let serviceName = "FSGuardService" | |
let watcher = IOKitKEXTWatcher(ioServiceName: serviceName) | |
watcher.updateHandler = { | |
if $0 != nil { | |
NSLog("IOKit service loaded: \(serviceName).") | |
} else { | |
NSLog("IOKit service unloaded: \(serviceName).") | |
} | |
} | |
if !watcher.start(RunLoop.main.getCFRunLoop()) { | |
fatalError("Failed to start observing.") | |
} | |
RunLoop.main.run() | |
class IOKitKEXTWatcher { | |
private let _notificationPort = IONotificationPortCreate(kIOMasterPortDefault) | |
private let _ioServiceName: String | |
private var _runLoop: CFRunLoop? | |
private var _firstMatchNotification: AnyObject? | |
private var _terminatedNotification: AnyObject? | |
/// Is triggered every time IOService is loaded/unloaded (if Watcher started). | |
var updateHandler: ((IOObjectGuard?) -> Void)? | |
/// - Parameters: | |
/// - ioServiceName: IO class service name (found in Info.plist/IOKitPersonalities/{SERVICE}/IOClass. | |
init(ioServiceName: String) { | |
_ioServiceName = ioServiceName | |
} | |
deinit { | |
cancel() | |
IONotificationPortDestroy(_notificationPort) | |
} | |
func start(_ runLoop: CFRunLoop) -> Bool { | |
guard let firstMatchNotification = subscribeFirstMatchNotification(), | |
let terminatedNotification = subscribeTerminatedNotification() else { | |
return false | |
} | |
_runLoop = runLoop | |
let notificationSource = IONotificationPortGetRunLoopSource(_notificationPort).takeUnretainedValue() | |
CFRunLoopAddSource(runLoop, notificationSource, .defaultMode) | |
_firstMatchNotification = firstMatchNotification | |
_terminatedNotification = terminatedNotification | |
return true | |
} | |
func cancel() { | |
guard let runLoop = _runLoop else { return } | |
let notificationSource = IONotificationPortGetRunLoopSource(_notificationPort).takeUnretainedValue() | |
CFRunLoopRemoveSource(runLoop, notificationSource, .defaultMode) | |
} | |
private func subscribeFirstMatchNotification() -> AnyObject? { | |
let firstMatchCallback: IOServiceMatchingCallback = { (refCon, iterator) in | |
let instance = refCon.flatMap { Unmanaged<IOKitKEXTWatcher>.fromOpaque($0).takeUnretainedValue() } | |
instance?.handleFirstMatchNotification(iterator: iterator) | |
} | |
return subscribeMatchingNotification(kIOFirstMatchNotification, callback: firstMatchCallback) | |
} | |
private func subscribeTerminatedNotification() -> AnyObject? { | |
let terminatedCallback: IOServiceMatchingCallback = { (refCon, iterator) in | |
let instance = refCon.flatMap { Unmanaged<IOKitKEXTWatcher>.fromOpaque($0).takeUnretainedValue() } | |
instance?.handleTerminatedNotification(iterator: iterator) | |
} | |
return subscribeMatchingNotification(kIOTerminatedNotification, callback: terminatedCallback) | |
} | |
private func subscribeMatchingNotification(_ notification: String, callback: @escaping IOServiceMatchingCallback) -> AnyObject? { | |
var object = io_object_t() | |
let result = IOServiceAddMatchingNotification(_notificationPort, | |
notification, | |
IOServiceMatching(_ioServiceName), | |
callback, | |
Unmanaged.passUnretained(self).toOpaque(), | |
&object) | |
if result == kIOReturnSuccess, let objectGuard = IOObjectGuard(object) { | |
// IOKit expects to iterate over the received 'object' to arm notification. | |
let _ = handleMatchingNotification(objectGuard.object) | |
return objectGuard | |
} else { | |
return nil | |
} | |
} | |
private func handleFirstMatchNotification(iterator: io_iterator_t) { | |
if let service = handleMatchingNotification(iterator) { | |
updateHandler?(service) | |
} | |
} | |
private func handleTerminatedNotification(iterator: io_iterator_t) { | |
if handleMatchingNotification(iterator) != nil { | |
updateHandler?(nil) | |
} | |
} | |
private func handleMatchingNotification(_ iterator: io_iterator_t) -> IOObjectGuard? { | |
var matchedService: IOObjectGuard? = nil | |
while let service = IOObjectGuard(IOIteratorNext(iterator)) { | |
let nameMaxLength = MemoryLayout<io_name_t>.stride | |
var name = UnsafeMutablePointer<Int8>.allocate(capacity: nameMaxLength) | |
memset(name, 0, nameMaxLength) | |
defer { name.deallocate() } | |
guard kIOReturnSuccess == IOObjectGetClass(service.object, name) else { continue } | |
guard _ioServiceName == String(cString: name) else { continue } | |
matchedService = service | |
} | |
return matchedService | |
} | |
} | |
/// RAII guard around io_object. | |
class IOObjectGuard { | |
let object: io_object_t | |
init?(_ object: io_object_t) { | |
guard object != IO_OBJECT_NULL else { return nil } | |
self.object = object | |
} | |
deinit { | |
IOObjectRelease(object) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment