-
-
Save airy10/5205dc851fbd0715fcd7a5cdde25e7c8 to your computer and use it in GitHub Desktop.
| // | |
| // airtag-decryptor.swift | |
| // | |
| // Decrypt all beacons files from ~/Library/com.apple.icloud.searchpartyd - updated when FindMy is running | |
| // Results in /tmp/com.apple.icloud.searchpartyd - same file hierarchy | |
| // | |
| // Created by Matus on 28/01/2024. - https://gist.github.com/YeapGuy/f473de53c2a4e8978bc63217359ca1e4 | |
| // Modified by Airy | |
| // | |
| import Cocoa | |
| import Foundation | |
| import CryptoKit | |
| extension URL { | |
| var isDirectory: Bool { | |
| (try? resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == true | |
| } | |
| } | |
| // Function to decrypt using AES-GCM | |
| func decryptRecordFile(fileURL: URL, key: SymmetricKey) throws -> [String: Any] { | |
| // Read data from the file | |
| let data = try Data(contentsOf: fileURL) | |
| // Convert data to a property list (plist) | |
| guard let plist = try PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [Any] else { | |
| throw MyError.invalidFileFormat | |
| } | |
| // Extract nonce, tag, and ciphertext | |
| guard plist.count >= 3, | |
| let nonceData = plist[0] as? Data, | |
| let tagData = plist[1] as? Data, | |
| let ciphertextData = plist[2] as? Data else { | |
| throw MyError.invalidPlistFormat | |
| } | |
| let sealedBox = try AES.GCM.SealedBox(nonce: AES.GCM.Nonce(data: nonceData), ciphertext: ciphertextData, tag: tagData) | |
| // Decrypt using AES-GCM | |
| let decryptedData = try AES.GCM.open(sealedBox, using: key) | |
| // Convert decrypted data to a property list | |
| guard let decryptedPlist = try PropertyListSerialization.propertyList(from: decryptedData, options: [], format: nil) as? [String: Any] else { | |
| throw MyError.invalidDecryptedData | |
| } | |
| return decryptedPlist | |
| } | |
| func decryptDirectory(filePath: String, outputPath: String, key: SymmetricKey) throws { | |
| let baseURL = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first | |
| if let contentURL = baseURL?.appending(path: filePath) { | |
| if contentURL.isDirectory { | |
| if let urls = try? FileManager.default.contentsOfDirectory(at: contentURL, includingPropertiesForKeys: nil) { | |
| for url in urls { | |
| let path = (filePath as NSString).appendingPathComponent(url.lastPathComponent) | |
| try decryptDirectory(filePath: path, outputPath: outputPath, key: key) | |
| } | |
| } | |
| } else { | |
| do { | |
| let decryptedPlist = try decryptRecordFile(fileURL: contentURL, key: key) | |
| // Save decrypted plist as a file in the current directory | |
| let name = contentURL.lastPathComponent as NSString | |
| if let outputName = (name.deletingPathExtension as NSString).appendingPathExtension("plist") { | |
| let dir = (filePath as NSString).deletingLastPathComponent | |
| let outputDirPath = (outputPath as NSString).appendingPathComponent(dir) | |
| try FileManager.default.createDirectory(atPath: outputDirPath, withIntermediateDirectories: true) | |
| let outputURL = URL(fileURLWithPath: outputDirPath).appending(path: outputName) | |
| try PropertyListSerialization.data(fromPropertyList: decryptedPlist, format: .xml, options: 0).write(to: outputURL) | |
| } | |
| } catch { | |
| print("Error:", error) | |
| } | |
| print(filePath) | |
| } | |
| } | |
| } | |
| // -> Hex format key from `security find-generic-password -l 'BeaconStore' -w` | |
| let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword, | |
| kSecAttrLabel as String: "BeaconStore", | |
| kSecMatchLimit as String: kSecMatchLimitOne, | |
| kSecReturnAttributes as String: true, | |
| kSecReturnData as String: true] | |
| var item: CFTypeRef? | |
| let status = SecItemCopyMatching(query as CFDictionary, &item) | |
| guard status != errSecItemNotFound else { throw MyError.noPassword } | |
| guard status == errSecSuccess else { throw MyError.keychainError(status: status) } | |
| guard let existingItem = item as? [String : Any] else { throw MyError.invalidItem } | |
| if let keyData = existingItem[kSecValueData as String] as? Data { | |
| let key = SymmetricKey(data: keyData) | |
| let basePath = "com.apple.icloud.searchpartyd" | |
| let outputPath = NSTemporaryDirectory() | |
| try decryptDirectory(filePath: basePath, outputPath: outputPath, key: key) | |
| let resultURL = URL(filePath: basePath, relativeTo: URL(filePath: outputPath)) | |
| NSWorkspace.shared.open(resultURL) | |
| } | |
| enum MyError: Error { | |
| case invalidFileFormat | |
| case invalidPlistFormat | |
| case invalidDecryptedData | |
| case noPassword | |
| case invalidItem | |
| case keychainError(status: OSStatus) | |
| } |
I'm trying to run this on macOS 26 (Tahoe), and I'm getting the following error message:
Swift/ErrorType.swift:254: Fatal error: Error raised at top level: main.MyError.noPassword
fish: Job 1, './airtag-decryptor' terminated by signal SIGTRAP (Trace or breakpoint trap)
Currently incompatible with Sequoia+ - Apple has limited access to the password needed to decrypt the FindMy files
Aha - this is even if you disable SIP, right? (I tested this with SIP temporarily disabled).
That's not enough - not tested but you might try that : https://github.com/Pnut-GGG/FMIPDataManager-extractor
The missing part should be :
sudo nvram boot-args="amfi_get_out_of_my_way=1"
Hi, I'm trying to import my OEM airtags into OpenHaystack (my real end-goal is logging positions because I have a stolen item that I'm trying to keep track of for authorities), but while it seems I need to use this (and I'm on OS 14.5 so it still works), I get a bunch of terminal output but no results in /tmp/, and I don't know what I need to do. I did download and install xcode and allow the permissions it needed, and formed an executable with swiftc and did chmod +x, but t hen I get a bunch of UUIDs in various folders and do not know what to do with it.
Any assistance is appreciated.
Key rotation needs to be done from these initial keys