diff options
| -rw-r--r-- | tools/idevicebackup.c | 243 |
1 files changed, 241 insertions, 2 deletions
diff --git a/tools/idevicebackup.c b/tools/idevicebackup.c index 15b8d05..0a7fd70 100644 --- a/tools/idevicebackup.c +++ b/tools/idevicebackup.c | |||
| @@ -76,6 +76,62 @@ static int compare_hash(const unsigned char *hash1, const unsigned char *hash2, | |||
| 76 | return 1; | 76 | return 1; |
| 77 | } | 77 | } |
| 78 | 78 | ||
| 79 | static void compute_datahash(const char *path, const char *destpath, uint8_t greylist, const char *domain, const char *appid, const char *version, unsigned char *hash_out) | ||
| 80 | { | ||
| 81 | gcry_md_hd_t hd = NULL; | ||
| 82 | gcry_md_open(&hd, GCRY_MD_SHA1, 0); | ||
| 83 | if (!hd) { | ||
| 84 | printf("ERROR: Could not initialize libgcrypt/SHA1\n"); | ||
| 85 | return; | ||
| 86 | } | ||
| 87 | gcry_md_reset(hd); | ||
| 88 | |||
| 89 | FILE *f = fopen(path, "rb"); | ||
| 90 | if (f) { | ||
| 91 | unsigned char buf[16384]; | ||
| 92 | size_t len; | ||
| 93 | while ((len = fread(buf, 1, 16384, f)) > 0) { | ||
| 94 | gcry_md_write(hd, buf, len); | ||
| 95 | } | ||
| 96 | fclose(f); | ||
| 97 | gcry_md_write(hd, destpath, strlen(destpath)); | ||
| 98 | gcry_md_write(hd, ";", 1); | ||
| 99 | if (greylist == 1) { | ||
| 100 | gcry_md_write(hd, "true", 4); | ||
| 101 | } else { | ||
| 102 | gcry_md_write(hd, "false", 5); | ||
| 103 | } | ||
| 104 | gcry_md_write(hd, ";", 1); | ||
| 105 | if (domain) { | ||
| 106 | gcry_md_write(hd, domain, strlen(domain)); | ||
| 107 | } else { | ||
| 108 | gcry_md_write(hd, "(null)", 6); | ||
| 109 | } | ||
| 110 | gcry_md_write(hd, ";", 1); | ||
| 111 | if (appid) { | ||
| 112 | gcry_md_write(hd, appid, strlen(appid)); | ||
| 113 | } else { | ||
| 114 | gcry_md_write(hd, "(null)", 6); | ||
| 115 | } | ||
| 116 | gcry_md_write(hd, ";", 1); | ||
| 117 | if (version) { | ||
| 118 | gcry_md_write(hd, version, strlen(version)); | ||
| 119 | } else { | ||
| 120 | gcry_md_write(hd, "(null)", 6); | ||
| 121 | } | ||
| 122 | unsigned char *newhash = gcry_md_read(hd, GCRY_MD_SHA1); | ||
| 123 | memcpy(hash_out, newhash, 20); | ||
| 124 | } | ||
| 125 | gcry_md_close(hd); | ||
| 126 | } | ||
| 127 | |||
| 128 | static void print_hash(const unsigned char *hash, int len) | ||
| 129 | { | ||
| 130 | int i; | ||
| 131 | for (i = 0; i < len; i++) { | ||
| 132 | printf("%02x", hash[i]); | ||
| 133 | } | ||
| 134 | } | ||
| 79 | 135 | ||
| 80 | static void notify_cb(const char *notification, void *userdata) | 136 | static void notify_cb(const char *notification, void *userdata) |
| 81 | { | 137 | { |
| @@ -399,6 +455,157 @@ static int mobilebackup_delete_backup_file_by_hash(const char *backup_directory, | |||
| 399 | return ret; | 455 | return ret; |
| 400 | } | 456 | } |
| 401 | 457 | ||
| 458 | static int mobilebackup_check_file_integrity(const char *backup_directory, const char *hash, plist_t filedata) | ||
| 459 | { | ||
| 460 | char *datapath; | ||
| 461 | char *infopath; | ||
| 462 | plist_t mdinfo = NULL; | ||
| 463 | struct stat st; | ||
| 464 | unsigned char file_hash[20]; | ||
| 465 | |||
| 466 | datapath = mobilebackup_build_path(backup_directory, hash, ".mddata"); | ||
| 467 | if (stat(datapath, &st) != 0) { | ||
| 468 | printf("\r\n"); | ||
| 469 | printf("ERROR: '%s.mddata' is missing!\n", hash); | ||
| 470 | free(datapath); | ||
| 471 | return 0; | ||
| 472 | } | ||
| 473 | |||
| 474 | infopath = mobilebackup_build_path(backup_directory, hash, ".mdinfo"); | ||
| 475 | plist_read_from_filename(&mdinfo, infopath); | ||
| 476 | free(infopath); | ||
| 477 | if (!mdinfo) { | ||
| 478 | printf("\r\n"); | ||
| 479 | printf("ERROR: '%s.mdinfo' is missing or corrupted!\n", hash); | ||
| 480 | free(datapath); | ||
| 481 | return 0; | ||
| 482 | } | ||
| 483 | |||
| 484 | /* sha1 hash verification */ | ||
| 485 | plist_t node = plist_dict_get_item(filedata, "DataHash"); | ||
| 486 | if (!node || (plist_get_node_type(node) != PLIST_DATA)) { | ||
| 487 | printf("\r\n"); | ||
| 488 | printf("ERROR: Could not get DataHash for file entry '%s'\n", hash); | ||
| 489 | plist_free(mdinfo); | ||
| 490 | free(datapath); | ||
| 491 | return 0; | ||
| 492 | } | ||
| 493 | |||
| 494 | node = plist_dict_get_item(mdinfo, "Metadata"); | ||
| 495 | if (!node && (plist_get_node_type(node) != PLIST_DATA)) { | ||
| 496 | printf("\r\n"); | ||
| 497 | printf("ERROR: Could not find Metadata in plist '%s.mdinfo'\n", hash); | ||
| 498 | plist_free(mdinfo); | ||
| 499 | free(datapath); | ||
| 500 | return 0; | ||
| 501 | } | ||
| 502 | |||
| 503 | char *meta_bin = NULL; | ||
| 504 | uint64_t meta_bin_size = 0; | ||
| 505 | plist_get_data_val(node, &meta_bin, &meta_bin_size); | ||
| 506 | plist_t metadata = NULL; | ||
| 507 | if (meta_bin) { | ||
| 508 | plist_from_bin(meta_bin, (uint32_t)meta_bin_size, &metadata); | ||
| 509 | } | ||
| 510 | if (!metadata) { | ||
| 511 | printf("\r\n"); | ||
| 512 | printf("ERROR: Could not get Metadata from plist '%s.mdinfo'\n", hash); | ||
| 513 | plist_free(mdinfo); | ||
| 514 | free(datapath); | ||
| 515 | return 0; | ||
| 516 | } | ||
| 517 | |||
| 518 | char *version = NULL; | ||
| 519 | node = plist_dict_get_item(metadata, "Version"); | ||
| 520 | if (node && (plist_get_node_type(node) == PLIST_STRING)) { | ||
| 521 | plist_get_string_val(node, &version); | ||
| 522 | } | ||
| 523 | |||
| 524 | char *destpath = NULL; | ||
| 525 | node = plist_dict_get_item(metadata, "Path"); | ||
| 526 | if (node && (plist_get_node_type(node) == PLIST_STRING)) { | ||
| 527 | plist_get_string_val(node, &destpath); | ||
| 528 | } | ||
| 529 | |||
| 530 | uint8_t greylist = 0; | ||
| 531 | node = plist_dict_get_item(metadata, "Greylist"); | ||
| 532 | if (node && (plist_get_node_type(node) == PLIST_BOOLEAN)) { | ||
| 533 | plist_get_bool_val(node, &greylist); | ||
| 534 | } | ||
| 535 | |||
| 536 | char *domain = NULL; | ||
| 537 | node = plist_dict_get_item(metadata, "Domain"); | ||
| 538 | if (node && (plist_get_node_type(node) == PLIST_STRING)) { | ||
| 539 | plist_get_string_val(node, &domain); | ||
| 540 | } | ||
| 541 | |||
| 542 | char *fnstr = malloc(strlen(domain) + 1 + strlen(destpath) + 1); | ||
| 543 | strcpy(fnstr, domain); | ||
| 544 | strcat(fnstr, "-"); | ||
| 545 | strcat(fnstr, destpath); | ||
| 546 | unsigned char fnhash[20]; | ||
| 547 | char fnamehash[41]; | ||
| 548 | char *p = fnamehash; | ||
| 549 | sha1_of_data(fnstr, strlen(fnstr), fnhash); | ||
| 550 | free(fnstr); | ||
| 551 | int i; | ||
| 552 | for ( i = 0; i < 20; i++, p += 2 ) { | ||
| 553 | snprintf (p, 3, "%02x", (unsigned char)fnhash[i] ); | ||
| 554 | } | ||
| 555 | if (strcmp(fnamehash, hash)) { | ||
| 556 | printf("\r\n"); | ||
| 557 | printf("WARNING: filename hash does not match for entry '%s'\n", hash); | ||
| 558 | } | ||
| 559 | |||
| 560 | char *auth_version = NULL; | ||
| 561 | node = plist_dict_get_item(mdinfo, "AuthVersion"); | ||
| 562 | if (node && (plist_get_node_type(node) == PLIST_STRING)) { | ||
| 563 | plist_get_string_val(node, &auth_version); | ||
| 564 | } | ||
| 565 | |||
| 566 | if (strcmp(auth_version, "1.0")) { | ||
| 567 | printf("\r\n"); | ||
| 568 | printf("WARNING: Unknown AuthVersion '%s', DataHash cannot be verified!\n", auth_version); | ||
| 569 | } | ||
| 570 | |||
| 571 | node = plist_dict_get_item(filedata, "DataHash"); | ||
| 572 | if (!node || (plist_get_node_type(node) != PLIST_DATA)) { | ||
| 573 | printf("\r\n"); | ||
| 574 | printf("WARNING: Could not get DataHash key from file info data for entry '%s'\n", hash); | ||
| 575 | } | ||
| 576 | |||
| 577 | int res = 1; | ||
| 578 | unsigned char *data_hash = NULL; | ||
| 579 | uint64_t data_hash_len = 0; | ||
| 580 | plist_get_data_val(node, (char**)&data_hash, &data_hash_len); | ||
| 581 | int hash_ok = 0; | ||
| 582 | if (data_hash && (data_hash_len == 20)) { | ||
| 583 | compute_datahash(datapath, destpath, greylist, domain, NULL, version, file_hash); | ||
| 584 | hash_ok = compare_hash(data_hash, file_hash, 20); | ||
| 585 | } else if (data_hash_len == 0) { | ||
| 586 | /* no datahash present */ | ||
| 587 | hash_ok = 1; | ||
| 588 | } | ||
| 589 | |||
| 590 | g_free(domain); | ||
| 591 | g_free(version); | ||
| 592 | g_free(destpath); | ||
| 593 | |||
| 594 | if (!hash_ok) { | ||
| 595 | printf("\r\n"); | ||
| 596 | printf("ERROR: The hash for '%s.mddata' does not match DataHash entry in Manifest\n", hash); | ||
| 597 | printf("datahash: "); | ||
| 598 | print_hash(data_hash, 20); | ||
| 599 | printf("\nfilehash: "); | ||
| 600 | print_hash(file_hash, 20); | ||
| 601 | printf("\n"); | ||
| 602 | res = 0; | ||
| 603 | } | ||
| 604 | g_free(data_hash); | ||
| 605 | plist_free(mdinfo); | ||
| 606 | return res; | ||
| 607 | } | ||
| 608 | |||
| 402 | static void do_post_notification(const char *notification) | 609 | static void do_post_notification(const char *notification) |
| 403 | { | 610 | { |
| 404 | uint16_t nport = 0; | 611 | uint16_t nport = 0; |
| @@ -983,8 +1190,40 @@ int main(int argc, char *argv[]) | |||
| 983 | printf("Could not read plist from Manifest.plist Data key!\n"); | 1190 | printf("Could not read plist from Manifest.plist Data key!\n"); |
| 984 | break; | 1191 | break; |
| 985 | } | 1192 | } |
| 986 | /* loop over Files entries in Manifest data plist */ | 1193 | plist_t files = plist_dict_get_item(backup_data, "Files"); |
| 987 | /* make sure both .mddata/.mdinfo files are available for each entry */ | 1194 | if (files && (plist_get_node_type(files) == PLIST_DICT)) { |
| 1195 | plist_dict_iter iter = NULL; | ||
| 1196 | plist_dict_new_iter(files, &iter); | ||
| 1197 | if (iter) { | ||
| 1198 | /* loop over Files entries in Manifest data plist */ | ||
| 1199 | char *hash = NULL; | ||
| 1200 | int file_ok = 0; | ||
| 1201 | int total_files = plist_dict_get_size(files); | ||
| 1202 | int cur_file = 1; | ||
| 1203 | node = NULL; | ||
| 1204 | plist_dict_next_item(files, iter, &hash, &node); | ||
| 1205 | while (node) { | ||
| 1206 | printf("Verifying files %d/%d (%d%%) \r", cur_file, total_files, (cur_file*100/total_files)); | ||
| 1207 | cur_file++; | ||
| 1208 | /* make sure both .mddata/.mdinfo files are available for each entry */ | ||
| 1209 | file_ok = mobilebackup_check_file_integrity(backup_directory, hash, node); | ||
| 1210 | node = NULL; | ||
| 1211 | free(hash); | ||
| 1212 | hash = NULL; | ||
| 1213 | if (!file_ok) { | ||
| 1214 | break; | ||
| 1215 | } | ||
| 1216 | plist_dict_next_item(files, iter, &hash, &node); | ||
| 1217 | } | ||
| 1218 | printf("\n"); | ||
| 1219 | free(iter); | ||
| 1220 | if (!file_ok) { | ||
| 1221 | plist_free(backup_data); | ||
| 1222 | break; | ||
| 1223 | } | ||
| 1224 | printf("All backup files appear to be valid\n"); | ||
| 1225 | } | ||
| 1226 | } | ||
| 988 | /* request restore from device with manifest (BackupMessageRestoreMigrate) */ | 1227 | /* request restore from device with manifest (BackupMessageRestoreMigrate) */ |
| 989 | /* loop over Files entries in Manifest data plist */ | 1228 | /* loop over Files entries in Manifest data plist */ |
| 990 | /* read mddata/mdinfo files and send to device using DLSendFile */ | 1229 | /* read mddata/mdinfo files and send to device using DLSendFile */ |
