Created
April 24, 2024 17:44
-
-
Save macshome/4521fba072abfeeb9dd00a1f5ce4c895 to your computer and use it in GitHub Desktop.
Get crazy and hook MobileGestalt in a Swift Playground!
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
// 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.")") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Sample output: