Skip to content

Instantly share code, notes, and snippets.

@macshome
Created April 24, 2024 17:44
Show Gist options
  • Save macshome/4521fba072abfeeb9dd00a1f5ce4c895 to your computer and use it in GitHub Desktop.
Save macshome/4521fba072abfeeb9dd00a1f5ce4c895 to your computer and use it in GitHub Desktop.
Get crazy and hook MobileGestalt in a Swift Playground!
// Get crazy and hook MobileGestalt in a Swift Playground!
//
// If you are a LONG time Mac developer you know that the Gestalt system
// used to be a way to get info about your Mac. These days it's a private
// thing that Apple locks away and you shouldn't really touch. Most of the info
// that you need can probaby be found in IOKit, but some values, like
// the provisioningUDID are not avaliable any other way.
//
// That said, it's a fun exersize to see how to do some various things in Swift.
// Things like loading a dylib or calling private C functions.
//
// If you want to learn about the old Gestalt then reset your A5 and take a look
// at: Technical Note OV16 -- Gestalt & _SysEnvirons - A Never-Ending Story
// https://developer.apple.com/library/archive/technotes/ov/ov_16.html#//apple_ref/doc/uid/DTS10002614
import Foundation
// We are going to make a class and use it here just because it will be easier that way.
class MobileGestalt {
// An enum to make accessing the plaintext gestalt keys easy.
//
// On devices, all of these keys are encrypted. Boffins have put in the
// hard work of decrypting them all with hashcat. You can read about it
// here: https://blog.timac.org/2018/1126-deobfuscated-libmobilegestalt-keys-ios-12/
enum Query: String {
case provisioningUDID = "ProvisioningUniqueDeviceID"
case hasNeuralEngine = "HasAppleNeuralEngine"
case productName = "ProductName"
case wifiVendor = "WifiVendor"
case deviceClass = "DeviceClass"
}
// This typealias lets us define a C method signature and name
// in Swift. The name should match what the C name is in the
// library we are going to call.
typealias MGCopyAnswer = @convention(c) (CFString) -> CFTypeRef?
// This is a pointer to hold the location of the C function in the library.
// This lets us call it at the right place without having the headers.
let copyAnswerRef: MGCopyAnswer?
// This pointer is a handle to load the library into.
let handle: UnsafeMutableRawPointer?
// When we init the class we do a few things.
// 1. Try to get a handle on the library with dlopen()
// 2. Use that handle to find the location of the function we want
// 3. Bitcast that location into a callable function.
init() {
handle = dlopen("/usr/lib/libMobileGestalt.dylib", RTLD_GLOBAL | RTLD_LAZY)
if let handleRef = handle,
let sym = dlsym(handleRef, "MGCopyAnswer") {
copyAnswerRef = unsafeBitCast(sym, to: MGCopyAnswer.self)
} else {
copyAnswerRef = nil
}
}
// Clean things up and be tidy. It's a good practice to put
// deinit and defer cleanup calls close to where things are allocated.
deinit {
guard let handleRef = handle else { return }
dlclose(handleRef)
}
// Now we are going to lookup the value in the MobileGestalt database.
// It returns a CFTypeRef that we can then convert into a string.
func getValue(for query: Query) -> String? {
guard let mgc = copyAnswerRef else { return nil }
let ref = mgc(query.rawValue as NSString)
return cfStringer(ref)
}
// A helper method to convert a CFTypeRef to a String.
// It simply checks the underlaying type of the reference
// and then casts it to a String using the appropriate API.
func cfStringer(_ ref: CFTypeRef?) -> String? {
let typeID = CFGetTypeID(ref)
if typeID == CFStringGetTypeID(),
let iokString = ref as? String {
return iokString
}
if typeID == CFBooleanGetTypeID(),
let iokBool = ref as? Bool {
return String(iokBool)
}
if typeID == CFNumberGetTypeID(),
let iokNum = ref as? Int {
return String(iokNum)
}
if typeID == CFDataGetTypeID(),
let data = ref as? Data,
let iokString = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .newlines) {
return iokString
}
return nil
}
}
// Create one of our objects
let gestalt = MobileGestalt()
// Ask it questions!
print("Value for DeviceClass: \(gestalt.getValue(for: .deviceClass) ?? "was not found.")")
print("Value for HasAppleNeuralEngine: \(gestalt.getValue(for: .hasNeuralEngine) ?? "was not found.")")
print("Value for ProvisioningUDID: \(gestalt.getValue(for: .provisioningUDID) ?? "was not found.")")
print("Value for ProductName: \(gestalt.getValue(for: .productName) ?? "was not found.")")
print("Value for WifiVendor: \(gestalt.getValue(for: .wifiVendor) ?? "was not found.")")
@macshome
Copy link
Author

Sample output:

Value for DeviceClass: Mac
Value for HasAppleNeuralEngine: true
Value for ProvisioningUDID: 00006000-00066XXXXXX1801X
Value for ProductName: macOS
Value for WifiVendor: USI

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment