-
-
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) | |
| } |
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.
I'm trying to run this on macOS 26 (Tahoe), and I'm getting the following error message: