Skip to content

Instantly share code, notes, and snippets.

@Alkenso
Last active April 29, 2020 18:39
Show Gist options
  • Save Alkenso/aa8d9cc680596cb70ed1b84186c1c8a5 to your computer and use it in GitHub Desktop.
Save Alkenso/aa8d9cc680596cb70ed1b84186c1c8a5 to your computer and use it in GitHub Desktop.
Observe KEXT: IOKit driver
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