def get_files(self, backupUDID, snapshotId, files): r = [] h = {} for file in files: if file.Size == 0: continue ff = MBSFile() ff.FileID = file.FileID h[file.FileID] = file.Signature r.append(ff) self.files[file.Signature] = file body = encode_protobuf_array(r) z = self.mobile_backup_request("POST", MBS[self.dsPrsID][backupUDID.encode("hex")][snapshotId].getFiles(), None, body) tokens = decode_protobuf_array(z, MBSFileAuthToken) z = MBSFileAuthTokens() for token in tokens: toto = z.tokens.add() toto.FileID = h[token.FileID] toto.AuthToken = token.AuthToken return z
def get_files(self, backupUDID, snapshotId, files): r = [] h = {} for file in files: if file.Size == 0: continue ff = MBSFile() ff.FileID = file.FileID h[file.FileID] = file.Signature r.append(ff) self.files[file.Signature] = file body = encode_protobuf_array(r) z = self.mobile_backup_request( "POST", MBS[self.dsPrsID][backupUDID.encode("hex")][snapshotId].getFiles(), None, body) tokens = decode_protobuf_array(z, MBSFileAuthToken) z = MBSFileAuthTokens() for token in tokens: toto = z.tokens.add() toto.FileID = h[token.FileID] toto.AuthToken = token.AuthToken return z
def list_files(self, backupUDID, snapshotId): limit = 5000 files = "" offset = 0 new_files = self.mobile_backup_request("GET", MBS[self.dsPrsID][backupUDID.encode("hex")][snapshotId].listFiles(offset=offset, limit=limit)) while new_files: files = files + new_files offset += limit new_files = self.mobile_backup_request("GET", MBS[self.dsPrsID][backupUDID.encode("hex")][snapshotId].listFiles(offset=offset, limit=limit)) print "\tShifting offset: ", offset return decode_protobuf_array(files, MBSFile)
def list_files(self, backupUDID, snapshotId): limit = 5000 files = "" offset = 0 new_files = self.mobile_backup_request("GET", MBS[self.dsPrsID][backupUDID.encode("hex")][snapshotId]['listFiles'](offset=offset, limit=limit)) while new_files: files = files + new_files offset += limit new_files = self.mobile_backup_request("GET", MBS[self.dsPrsID][backupUDID.encode("hex")][snapshotId].listFiles(offset=offset, limit=limit)) print "\tShifting offset: ", offset return decode_protobuf_array(files, MBSFile)
def listFiles(self, backupUDID, snapshotId): files = self.mobileBackupRequest("GET", "/mbs/%d/%s/%d/listFiles?offset=0&limit=100" % (self.dsPrsID, backupUDID.encode("hex"), snapshotId)) i = 100 files2 = 1 while files2: files2 = self.mobileBackupRequest("GET", "/mbs/%d/%s/%d/listFiles?offset=%s&limit=100" % (self.dsPrsID, backupUDID.encode("hex"), snapshotId,str(i))) if files2: i = i + 100 files = files + files2 print "\tShifting offset: ", i #print files return decode_protobuf_array(files, MBSFile)
def listFiles(self, backupUDID, snapshotId): files = self.mobileBackupRequest( "GET", "/mbs/%d/%s/%d/listFiles?offset=0&limit=100" % (self.dsPrsID, backupUDID.encode("hex"), snapshotId)) i = 100 files2 = 1 while files2: files2 = self.mobileBackupRequest( "GET", "/mbs/%d/%s/%d/listFiles?offset=%s&limit=100" % (self.dsPrsID, backupUDID.encode("hex"), snapshotId, str(i))) if files2: i = i + 100 files = files + files2 print "\tShifting offset: ", i #print files return decode_protobuf_array(files, MBSFile)
def getFiles(self, backupUDID, snapshotId, files): r = [] h = {} for f in files: if f.Size == 0: continue ff = MBSFile() ff.FileID = f.FileID h[f.FileID] = f.Signature r.append(ff) self.files[f.Signature] = f body = encode_protobuf_array(r) z = self.mobileBackupRequest("POST", "/mbs/%d/%s/%d/getFiles" % (self.dsPrsID, backupUDID.encode("hex"), snapshotId), None, body) tokens = decode_protobuf_array(z, MBSFileAuthToken) z = MBSFileAuthTokens() for t in tokens: toto = z.tokens.add() toto.FileID = h[t.FileID] #use signature toto.AuthToken = t.AuthToken return z
def getFiles(self, backupUDID, snapshotId, files): r = [] h = {} for f in files: if f.Size == 0: continue ff = MBSFile() ff.FileID = f.FileID h[f.FileID] = f.Signature r.append(ff) self.files[f.Signature] = f body = encode_protobuf_array(r) z = self.mobileBackupRequest( "POST", "/mbs/%d/%s/%d/getFiles" % (self.dsPrsID, backupUDID.encode("hex"), snapshotId), None, body) tokens = decode_protobuf_array(z, MBSFileAuthToken) z = MBSFileAuthTokens() for t in tokens: toto = z.tokens.add() toto.FileID = h[t.FileID] toto.AuthToken = t.AuthToken return z
def listFiles(self, backupUDID, snapshotId): files = self.mobileBackupRequest("GET", "/mbs/%d/%s/%d/listFiles" % (self.dsPrsID, backupUDID.encode("hex"), snapshotId)) return decode_protobuf_array(files, MBSFile)
def main(): # TODO can we retrieve these? global device_ID global device_name device_ID = random_bytes(32) device_name = 'My iPhone' # Parse arguments arguments = docopt(__doc__, version='iOS9_iCloud_POC 1.0') apple_id = arguments['<appleid>'] apple_pw = arguments['<password>'] if arguments['<token>']: dsPrsID, mmeAuthToken = arguments['<token>'].split(':') SKIP_AUTH = True else: SKIP_AUTH = False device_index = int(arguments['--device'] or 0) snapshot_index = int(arguments['--snapshot'] or 0) manifest_index = int(arguments['--manifest'] or 0) #################################################################################################################### # Step 1: Authenticaton #################################################################################################################### if not SKIP_AUTH: debug('Step 1: Authenticaton') auth = 'Basic %s' % base64.b64encode('%s:%s' % (apple_id, apple_pw)) authenticateResponse = plist_request('setup.icloud.com', 'POST', '/setup/authenticate/$APPLE_ID$', '', {'Authorization': auth, 'Connection': 'Keep-Alive'}) if not authenticateResponse: debug('Invalid Apple ID/password?') sys.exit(1) pprint(authenticateResponse) dsPrsID = str(authenticateResponse['appleAccountInfo']['dsPrsID']) mmeAuthToken = authenticateResponse['tokens']['mmeAuthToken'] if arguments['--token']: print '\nToken: %s:%s' % (dsPrsID, mmeAuthToken) sys.exit(1) # Cookies don't seem to be required #cookie = result_headers['set-cookie'].split(';')[0] else: debug('Skipping Step 1 (Authentication)') # noinspection PyUnboundLocalVariable auth = 'Basic %s' % base64.b64encode('%s:%s' % (dsPrsID, mmeAuthToken)) #################################################################################################################### # STEP 2. Account settings. #################################################################################################################### debug('\nSTEP 2. Account settings.') if not SKIP_AUTH: account_settings = plist_request('setup.icloud.com', 'POST', '/setup/get_account_settings', '', {'Authorization': auth, 'X-MMe-Client-Info': Client_Info, 'User-Agent': USER_AGENT_UBD # 'Cookie': cookie }) else: account_settings = plist_request('setup.icloud.com', 'POST', '/setup/get_account_settings', '', {'Authorization': auth, 'X-MMe-Client-Info': Client_Info, 'User-Agent': USER_AGENT_UBD}) pprint(account_settings) cloud_kit_token = account_settings['tokens']['cloudKitToken'] # if SKIP_AUTH: # cookie = result_headers['set-cookie'].split(';')[0] #################################################################################################################### # STEP 3. CloudKit Application Initialization. #################################################################################################################### debug('\nSTEP 3. CloudKit Application Initialization.') # Note, we aren't passing all the headers that Inflatable does or even that the real iphone does # But our response seem to be the same cloudkit_init = json_request('setup.icloud.com', 'POST', '/setup/ck/v1/ckAppInit?container=com.apple.backup.ios', '', {'Authorization': auth, 'X-MMe-Client-Info': Client_Info, 'X-CloudKit-AuthToken': cloud_kit_token, 'X-CloudKit-ContainerId': 'com.apple.backup.ios', 'X-CloudKit-BundleId': 'com.apple.backupd', 'X-CloudKit-Environment': 'production', 'X-CloudKit-Partition': 'production', 'User-Agent': USER_AGENT_UBD # 'Cookie': cookie }) pprint(cloudkit_init) ckdatabase_host = urlparse(cloudkit_init['cloudKitDatabaseUrl']).hostname cloudkit_user_id = cloudkit_init['cloudKitUserId'] #################################################################################################################### # STEP 4. Record zones. #Returns record zone data which needs further analysis. #################################################################################################################### debug('\nSTEP 4. Record zones.') requestOperation = retrieve_request(201) # zoneRetrieveRequest zrr = requestOperation.zoneRetrieveRequest zrr.zoneIdentifier.value.name = 'mbksync' zrr.zoneIdentifier.value.type = 6 zrr.zoneIdentifier.ownerIdentifier.name = cloudkit_user_id zrr.zoneIdentifier.ownerIdentifier.type = 7 debug(requestOperation) body = encode_protobuf_array([requestOperation]) cloudkit_header = {'X-MMe-Client-Info': Client_Info, 'X-Apple-Request-UUID': random_guid(), 'X-CloudKit-UserId': cloudkit_user_id, 'X-CloudKit-AuthToken': cloud_kit_token, 'X-CloudKit-ContainerId': 'com.apple.backup.ios', 'X-CloudKit-BundleId': 'com.apple.backupd', 'X-CloudKit-ProtocolVersion': 'client=1;comments=1;device=1;presence=1;records=1;sharing=1;subscriptions=1;users=1;mescal=1;', 'Accept': 'application/x-protobuf', 'Content-Type': 'application/x-protobuf; desc="https://p33-ckdatabase.icloud.com:443/static/protobuf/CloudDB/CloudDBClient.desc"; messageType=RequestOperation; delimited=true', 'User-Agent': USER_AGENT_UBD # 'Cookie': cookie } pbuf_string = request(ckdatabase_host, 'POST', '/api/client/record/retrieve', body, cloudkit_header) zone_retrieve_response = decode_protobuf_array(pbuf_string, ResponseOperation)[0] debug(zone_retrieve_response) #################################################################################################################### #STEP 5. Backup list #Returns device data/ backups. #################################################################################################################### debug('\nSTEP 5. Backup list.') requestOperation = retrieve_request(211) # recordRetrieveRequest rrr = requestOperation.recordRetrieveRequest rrr.recordID.value.name = 'BackupAccount' rrr.recordID.value.type = 1 rrr.recordID.zoneIdentifier.value.name = 'mbksync' rrr.recordID.zoneIdentifier.value.type = 6 rrr.recordID.zoneIdentifier.ownerIdentifier.name = cloudkit_user_id rrr.recordID.zoneIdentifier.ownerIdentifier.type = 7 rrr.f6.value = 1 debug(requestOperation) body = encode_protobuf_array([requestOperation]) cloudkit_header['X-Apple-Request-UUID'] = random_guid() pbuf_string = request(ckdatabase_host, 'POST', '/api/client/record/retrieve', body, cloudkit_header) record_retrieve_response = decode_protobuf_array(pbuf_string, ResponseOperation)[0] debug(record_retrieve_response) # What is this thing? Is it really an id associated with a particular backup? devices = find_records_with_identifier( record_retrieve_response.recordRetrieveResponse.record.recordField, 'devices' ) if device_index >= len(devices.recordFieldValue): print 'No such device. Available devices: %s' % devices sys.exit(1) backup_id = devices.recordFieldValue[device_index].referenceValue.recordIdentifier.value.name #################################################################################################################### #STEP 6. Snapshot list (+ Keybag) # Message type 211 with the required backup uuid, protobuf array encoded. # Returns device/ snapshots/ keybag information. # Timestamps are hex encoded double offsets to 01 Jan 2001 00:00:00 GMT (Cocoa/ Webkit reference date). #################################################################################################################### debug('\nSTEP 6. Snapshot list (+ Keybag)') requestOperation = retrieve_request(211) # recordRetrieveRequest rrr = requestOperation.recordRetrieveRequest rrr.recordID.value.name = backup_id rrr.recordID.value.type = 1 rrr.recordID.zoneIdentifier.value.name = 'mbksync' rrr.recordID.zoneIdentifier.value.type = 6 rrr.recordID.zoneIdentifier.ownerIdentifier.name = cloudkit_user_id rrr.recordID.zoneIdentifier.ownerIdentifier.type = 7 rrr.f6.value = 1 debug(requestOperation) body = encode_protobuf_array([requestOperation]) cloudkit_header['X-Apple-Request-UUID'] = random_guid() pbuf_string = request(ckdatabase_host, 'POST', '/api/client/record/retrieve', body, cloudkit_header) record_retrieve_response = decode_protobuf_array(pbuf_string, ResponseOperation)[0] debug(record_retrieve_response) snapshots = find_records_with_identifier( record_retrieve_response.recordRetrieveResponse.record.recordField, 'snapshots' ) if snapshot_index >= len(snapshots.recordFieldValue): print 'No such snapshot. Available snapshots: %s' % snapshots sys.exit(0) a_snapshot = snapshots.recordFieldValue[snapshot_index].referenceValue.recordIdentifier.value.name current_keybag_UUID = find_records_with_identifier( record_retrieve_response.recordRetrieveResponse.record.recordField, 'currentKeybagUUID' ).stringValue #################################################################################################################### # STEP 7. Manifest list. # # Url/ headers as step 6. # Message type 211 with the required snapshot uuid, protobuf array encoded. # Returns system/ backup properties (bytes ? format ?? proto), quota information and manifest details. #################################################################################################################### debug('\nSTEP 7. Manifest list') requestOperation = retrieve_request(211) # recordRetrieveRequest rrr = requestOperation.recordRetrieveRequest rrr.recordID.value.name = a_snapshot rrr.recordID.value.type = 1 rrr.recordID.zoneIdentifier.value.name = 'mbksync' rrr.recordID.zoneIdentifier.value.type = 6 rrr.recordID.zoneIdentifier.ownerIdentifier.name = cloudkit_user_id rrr.recordID.zoneIdentifier.ownerIdentifier.type = 7 rrr.f6.value = 1 debug(requestOperation) body = encode_protobuf_array([requestOperation]) cloudkit_header['X-Apple-Request-UUID'] = random_guid() pbuf_string = request(ckdatabase_host, 'POST', '/api/client/record/retrieve', body, cloudkit_header) record_retrieve_response = decode_protobuf_array(pbuf_string, ResponseOperation)[0] debug(record_retrieve_response) manifest_ids = find_records_with_identifier( record_retrieve_response.recordRetrieveResponse.record.recordField, 'manifestIDs' ) if manifest_index >= len(manifest_ids.recordFieldValue): print 'No such manifest. Available manifests: %s' % manifest_ids a_manifest_id = manifest_ids.recordFieldValue[manifest_index].stringValue ######################################################################################################################## # STEP 8. Retrieve list of files. # # Url/ headers as step 7. # Message type 211 with the required manifest, protobuf array encoded. # Returns system/ backup properties (bytes ? format ?? proto), quota information and manifest details. # # Returns a rather somewhat familiar looking set of results but with encoded bytes. ######################################################################################################################## debug('\nSTEP 8. Retrieve list of files.') requestOperation = retrieve_request(211) # recordRetrieveRequest rrr = requestOperation.recordRetrieveRequest rrr.recordID.value.name = a_manifest_id + ':0' rrr.recordID.value.type = 1 rrr.recordID.zoneIdentifier.value.name = '_defaultZone' rrr.recordID.zoneIdentifier.value.type = 6 rrr.recordID.zoneIdentifier.ownerIdentifier.name = cloudkit_user_id rrr.recordID.zoneIdentifier.ownerIdentifier.type = 7 rrr.f6.value = 1 debug(requestOperation) body = encode_protobuf_array([requestOperation]) cloudkit_header['X-Apple-Request-UUID'] = random_guid() pbuf_string = request(ckdatabase_host, 'POST', '/api/client/record/retrieve', body, cloudkit_header) record_retrieve_response = decode_protobuf_array(pbuf_string, ResponseOperation)[0] debug(record_retrieve_response) asset_tokens = find_records_with_identifier( record_retrieve_response.recordRetrieveResponse.record.recordField, 'files' ) if asset_tokens is None: print 'No files found' sys.exit(0) # Right now just grabbing the first file. # InflatableDonkey looks for the first file that is non 0 length # an_asset_token = asset_tokens.recordFieldValue[0].referenceValue.recordIdentifier.value.name length = 0 an_asset_token = None for record_field_value in asset_tokens.recordFieldValue: an_asset_token = record_field_value.referenceValue.recordIdentifier.value.name # F:UUID:token:length:x _, uuid, token, length, x = an_asset_token.split(':') if int(length) > 0: break if int(length) == 0: print 'All files are 0 length' sys.exit(0) ######################################################################################################################## # STEP 9. Retrieve asset tokens. # # Url/ headers as step 8. # Message type 211 with the required file, protobuf array encoded. ######################################################################################################################## debug('\nSTEP 9. Retrieve asset tokens.') requestOperation = retrieve_request(211) # recordRetrieveRequest rrr = requestOperation.recordRetrieveRequest rrr.recordID.value.name = an_asset_token rrr.recordID.value.type = 1 rrr.recordID.zoneIdentifier.value.name = '_defaultZone' rrr.recordID.zoneIdentifier.value.type = 6 rrr.recordID.zoneIdentifier.ownerIdentifier.name = cloudkit_user_id rrr.recordID.zoneIdentifier.ownerIdentifier.type = 7 rrr.f6.value = 1 debug(requestOperation) body = encode_protobuf_array([requestOperation]) cloudkit_header['X-Apple-Request-UUID'] = random_guid() pbuf_string = request(ckdatabase_host, 'POST', '/api/client/record/retrieve', body, cloudkit_header) record_retrieve_response = decode_protobuf_array(pbuf_string, ResponseOperation)[0] debug(record_retrieve_response) value = find_records_with_identifier( record_retrieve_response.recordRetrieveResponse.record.recordField, 'contents' ) # I think these are file attributes try: asset_value = value.assetValue except AttributeError: print 'No asset token found.' sys.exit(0) #################################################################################################################### # STEP 10. AuthorizeGet. # # Process somewhat different to iOS8. # # New headers/ mmcs auth token. See AuthorizeGetRequestFactory for details. # Returns a ChunkServer.FileGroup protobuf which is largely identical to iOS8 #################################################################################################################### debug('\nSTEP 10. AuthorizeGet.') mmcsAuthToken = '%s %s %s' % ( asset_value.fileChecksum.encode('hex'), asset_value.fileSignature.encode('hex'), asset_value.downloadToken ) headers = { 'Accept': 'application/vnd.com.apple.me.ubchunk+protobuf', 'Content-Type': 'application/vnd.com.apple.me.ubchunk+protobuf', 'x-apple-mmcs-dataclass': 'com.apple.Dataclass.CloudKit', 'X-CloudKit-Container': 'com.apple.backup.ios', 'X-CloudKit-Zone': '_defaultZone', 'x-apple-mmcs-auth': mmcsAuthToken, 'x-apple-mme-dsid': dsPrsID, 'User-Agent': USER_AGENT_UBD, 'x-apple-mmcs-proto-version': '4.0', 'X-Mme-Client-Info': '<iPhone5,3> <iPhone OS;9.0.1;13A404> <com.apple.cloudkit.CloudKitDaemon/479 (com.apple.cloudd/479)>' } # The body is a protobuf object FileTokens file_tokens = FileTokens() file_token = file_tokens.fileTokens.add() file_token.fileChecksum = asset_value.fileChecksum file_token.token = asset_value.downloadToken file_token.fileSignature = asset_value.fileSignature body = file_tokens.SerializeToString() host = urlparse(asset_value.contentBaseURL).hostname url = '/' + dsPrsID + '/authorizeGet' pbuf_string = request(host, 'POST', url, body, headers) file_groups = FileGroups() file_groups.ParseFromString(pbuf_string) debug(file_groups) #################################################################################################################### # STEP 11. ChunkServer.FileGroups. # # TODO. #################################################################################################################### #################################################################################################################### # STEP 12. Assemble assets/ files. #################################################################################################################### debug('\nSTEP 12. Assemble assets/files.') requestOperation = retrieve_request(220) # recordRetrieveRequest qrr = requestOperation.queryRetrieveRequest record_type = qrr.query.type.add() record_type.name = 'PrivilegedBatchRecordFetch' query_filter = qrr.query.filter.add() query_filter.fieldName.name = '___recordID' query_filter.fieldValue.type = 5 query_filter.fieldValue.referenceValue.recordIdentifier.value.name = 'K:' + current_keybag_UUID query_filter.fieldValue.referenceValue.recordIdentifier.value.type = 1 query_filter.fieldValue.referenceValue.recordIdentifier.zoneIdentifier.value.name = 'mbksync' query_filter.fieldValue.referenceValue.recordIdentifier.zoneIdentifier.value.type = 6 query_filter.fieldValue.referenceValue.recordIdentifier.zoneIdentifier.ownerIdentifier.name = cloudkit_user_id query_filter.fieldValue.referenceValue.recordIdentifier.zoneIdentifier.ownerIdentifier.type = 7 query_filter.type = 1 qrr.zoneIdentifier.value.name = 'mbksync' qrr.zoneIdentifier.value.type = 6 qrr.zoneIdentifier.ownerIdentifier.name = cloudkit_user_id qrr.zoneIdentifier.ownerIdentifier.type = 7 qrr.f6.value = 1 debug(requestOperation) body = encode_protobuf_array([requestOperation]) cloudkit_header['X-Apple-Request-UUID'] = random_guid() pbuf_string = request(ckdatabase_host, 'POST', '/api/client/query/retrieve', body, cloudkit_header) record_retrieve_response = decode_protobuf_array(pbuf_string, ResponseOperation)[0] debug(record_retrieve_response) debug('Done')
def listFiles(self, backupUDID, snapshotId): files = self.mobileBackupRequest( "GET", "/mbs/%d/%s/%d/listFiles" % (self.dsPrsID, backupUDID.encode("hex"), snapshotId)) return decode_protobuf_array(files, MBSFile)