├── .gitignore ├── decode_iphone_SMS_db.pl ├── pretty_print_iphone_AddressBook.pl ├── parse_mbdb.py ├── pretty_print_iphone_SMS.pl └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | blib/ 2 | .build/ 3 | _build/ 4 | cover_db/ 5 | inc/ 6 | Build 7 | Build.bat 8 | .last_cover_stats 9 | Makefile 10 | Makefile.old 11 | MANIFEST.bak 12 | META.yml 13 | MYMETA.yml 14 | nytprof.out 15 | pm_to_blib 16 | -------------------------------------------------------------------------------- /decode_iphone_SMS_db.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | use Data::Dump qw/dump/; 5 | use DBI; 6 | 7 | =pod 8 | This script reads an SQLite database (as extracted by libmobiledecive) 9 | and lists the content of the SMSs. 10 | 11 | The SMS database is this file: 12 | 3d0d7e5fb2ce288813306e4d4636395e047a3d28 13 | 14 | Common workflow is: 15 | # Backup your device 16 | idevicebackup2 backup OUTPUT_DIRECTORY 17 | ./decode_iphone_SMS_db.pl OUTPUT_DIRECTORY/3d0d7e5fb2ce288813306e4d4636395e047a3d28 18 | =cut 19 | 20 | =pod 21 | iPhone SMS database decode 22 | Copyright (C) 2012 A. Gordon (assafgordon at gmail dot com) 23 | 24 | LICENSE: AGPLv3 25 | 26 | This program is free software: you can redistribute it and/or modify 27 | it under the terms of the GNU Affero General Public License as 28 | published by the Free Software Foundation, either version 3 of the 29 | License, or (at your option) any later version. 30 | 31 | This program is distributed in the hope that it will be useful, 32 | but WITHOUT ANY WARRANTY; without even the implied warranty of 33 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 34 | GNU Affero General Public License for more details. 35 | 36 | You should have received a copy of the GNU Affero General Public License 37 | along with this program. If not, see . 38 | =cut 39 | 40 | my $iphone_SMS_db = shift or die "Error: missing SQLite database file of iPhone SMSs\n"; 41 | die "Error: can't find file '$iphone_SMS_db'\n" unless -r $iphone_SMS_db; 42 | my $dbh = DBI->connect("dbi:SQLite:dbname=$iphone_SMS_db"); 43 | 44 | my $sth = $dbh->prepare("select address,date,flags,text from message order by date" ); 45 | $sth->execute; 46 | 47 | while ( my $ref = $sth->fetchrow_arrayref() ) { 48 | my ($address, $date, $flags, $text) = @$ref; 49 | my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($date); 50 | 51 | # print "\n"; 52 | my $human_date = sprintf("%02d:%02d %02d/%02d/%04d", $hour, $min, $mday, $mon+1, $year+1900 ); 53 | # print dump($ref),"\n"; 54 | 55 | print join("\t", $human_date, $address, (($flags==3)?"sent":"received"), $text),"\n"; 56 | } 57 | -------------------------------------------------------------------------------- /pretty_print_iphone_AddressBook.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | use Data::Dump qw/dump/; 5 | use DBI; 6 | 7 | =pod 8 | This script reads an AddressBook SQLite database (as extracted by libmobiledecive) 9 | and lists the content of the contacts 10 | 11 | The AddresBook database is this file: 12 | 31bb7ba8914766d4ba40d6dfb6113c8b614be442 13 | 14 | Common workflow is: 15 | # Backup your device 16 | idevicebackup2 backup OUTPUT_DIRECTORY 17 | ./pretty_print_iphone_AddressBook.pl OUTPUT_DIRECTORY/31bb7ba8914766d4ba40d6dfb6113c8b614be442 18 | =cut 19 | 20 | =pod 21 | iPhone Address Book database decode 22 | Copyright (C) 2012 A. Gordon (assafgordon at gmail dot com) 23 | 24 | LICENSE: AGPLv3 25 | 26 | 27 | This program is free software: you can redistribute it and/or modify 28 | it under the terms of the GNU Affero General Public License as 29 | published by the Free Software Foundation, either version 3 of the 30 | License, or (at your option) any later version. 31 | 32 | This program is distributed in the hope that it will be useful, 33 | but WITHOUT ANY WARRANTY; without even the implied warranty of 34 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 35 | GNU Affero General Public License for more details. 36 | 37 | You should have received a copy of the GNU Affero General Public License 38 | along with this program. If not, see . 39 | =cut 40 | 41 | ## 42 | ## Open the SMS and Addressbook databases 43 | ## 44 | my %address_book; 45 | 46 | my $iphone_Address_db = shift or die "Error: missing Address Book SQLite database file (e.g. 31bb7ba8914766d4ba40d6dfb6113c8b614be442)\n"; 47 | 48 | my $dbh = DBI->connect("dbi:SQLite:dbname=$iphone_Address_db"); 49 | my $AddressBook_sql = " 50 | Select 51 | ABPerson.First, 52 | ABPerson.Last, 53 | ABMultiValue.value 54 | from 55 | ABPerson, 56 | ABMultiValue 57 | where 58 | ABPerson.ROWID = ABMultiValue.record_id 59 | order by 60 | ABPerson.First, 61 | ABPerson.Last 62 | "; 63 | 64 | my $sth = $dbh->prepare($AddressBook_sql); 65 | $sth->execute; 66 | while ( my $ref = $sth->fetchrow_arrayref() ) { 67 | my ($first, $last, $value ) = @$ref ; 68 | 69 | $first = "" unless $first; 70 | $last = "" unless $last; 71 | 72 | print join("\t", $first, $last, $value),"\n"; 73 | 74 | } 75 | -------------------------------------------------------------------------------- /parse_mbdb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import hashlib 4 | 5 | mbdx = {} 6 | 7 | def getint(data, offset, intsize): 8 | """Retrieve an integer (big-endian) and new offset from the current offset""" 9 | value = 0 10 | while intsize > 0: 11 | value = (value<<8) + ord(data[offset]) 12 | offset = offset + 1 13 | intsize = intsize - 1 14 | return value, offset 15 | 16 | def getstring(data, offset): 17 | """Retrieve a string and new offset from the current offset into the data""" 18 | if data[offset] == chr(0xFF) and data[offset+1] == chr(0xFF): 19 | return '', offset+2 # Blank string 20 | length, offset = getint(data, offset, 2) # 2-byte length 21 | value = data[offset:offset+length] 22 | return value, (offset + length) 23 | 24 | def process_mbdb_file(filename): 25 | mbdb = {} # Map offset of info in this file => file info 26 | data = open(filename).read() 27 | if data[0:4] != "mbdb": raise Exception("This does not look like an MBDB file") 28 | offset = 4 29 | offset = offset + 2 # value x05 x00, not sure what this is 30 | while offset < len(data): 31 | fileinfo = {} 32 | fileinfo['start_offset'] = offset 33 | fileinfo['domain'], offset = getstring(data, offset) 34 | fileinfo['filename'], offset = getstring(data, offset) 35 | fileinfo['linktarget'], offset = getstring(data, offset) 36 | fileinfo['datahash'], offset = getstring(data, offset) 37 | fileinfo['unknown1'], offset = getstring(data, offset) 38 | fileinfo['mode'], offset = getint(data, offset, 2) 39 | fileinfo['unknown2'], offset = getint(data, offset, 4) 40 | fileinfo['unknown3'], offset = getint(data, offset, 4) 41 | fileinfo['userid'], offset = getint(data, offset, 4) 42 | fileinfo['groupid'], offset = getint(data, offset, 4) 43 | fileinfo['mtime'], offset = getint(data, offset, 4) 44 | fileinfo['atime'], offset = getint(data, offset, 4) 45 | fileinfo['ctime'], offset = getint(data, offset, 4) 46 | fileinfo['filelen'], offset = getint(data, offset, 8) 47 | fileinfo['flag'], offset = getint(data, offset, 1) 48 | fileinfo['numprops'], offset = getint(data, offset, 1) 49 | fileinfo['properties'] = {} 50 | for ii in range(fileinfo['numprops']): 51 | propname, offset = getstring(data, offset) 52 | propval, offset = getstring(data, offset) 53 | fileinfo['properties'][propname] = propval 54 | mbdb[fileinfo['start_offset']] = fileinfo 55 | fullpath = fileinfo['domain'] + '-' + fileinfo['filename'] 56 | id = hashlib.sha1(fullpath) 57 | mbdx[fileinfo['start_offset']] = id.hexdigest() 58 | return mbdb 59 | 60 | def modestr(val): 61 | def mode(val): 62 | if (val & 0x4): r = 'r' 63 | else: r = '-' 64 | if (val & 0x2): w = 'w' 65 | else: w = '-' 66 | if (val & 0x1): x = 'x' 67 | else: x = '-' 68 | return r+w+x 69 | return mode(val>>6) + mode((val>>3)) + mode(val) 70 | 71 | def fileinfo_str(f, verbose=False): 72 | if not verbose: return "(%s)%s::%s" % (f['fileID'], f['domain'], f['filename']) 73 | if (f['mode'] & 0xE000) == 0xA000: type = 'l' # symlink 74 | elif (f['mode'] & 0xE000) == 0x8000: type = '-' # file 75 | elif (f['mode'] & 0xE000) == 0x4000: type = 'd' # dir 76 | else: 77 | print >> sys.stderr, "Unknown file type %04x for %s" % (f['mode'], fileinfo_str(f, False)) 78 | type = '?' # unknown 79 | info = ("%s%s %08x %08x %7d %10d %10d %10d (%s)%s::%s" % 80 | (type, modestr(f['mode']&0x0FFF) , f['userid'], f['groupid'], f['filelen'], 81 | f['mtime'], f['atime'], f['ctime'], f['fileID'], f['domain'], f['filename'])) 82 | if type == 'l': info = info + ' -> ' + f['linktarget'] # symlink destination 83 | for name, value in f['properties'].items(): # extra properties 84 | info = info + ' ' + name + '=' + repr(value) 85 | return info 86 | 87 | verbose = True 88 | if __name__ == '__main__': 89 | mbdb = process_mbdb_file("Manifest.mbdb") 90 | for offset, fileinfo in mbdb.items(): 91 | if offset in mbdx: 92 | fileinfo['fileID'] = mbdx[offset] 93 | else: 94 | fileinfo['fileID'] = "" 95 | print >> sys.stderr, "No fileID found for %s" % fileinfo_str(fileinfo) 96 | print fileinfo_str(fileinfo, verbose) 97 | -------------------------------------------------------------------------------- /pretty_print_iphone_SMS.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | use Data::Dump qw/dump/; 5 | use DBI; 6 | 7 | =pod 8 | 9 | This script (tries) to print a usable list of SMSs, cross-referencing it with the iPhone's Address book. 10 | 11 | 12 | The AddresBook database is this file: 13 | 31bb7ba8914766d4ba40d6dfb6113c8b614be442 14 | The SMS database is this file: 15 | 3d0d7e5fb2ce288813306e4d4636395e047a3d28 16 | 17 | Common workflow is: 18 | # Backup your device 19 | idevicebackup2 backup OUTPUT_DIRECTORY 20 | # ./pretty_print_iphone_SMS.pl [ADDRESS-BOOK-FILE] [SMS-FILE] 21 | ./pretty_print_iphone_SMS.pl OUTPUT_DIRECTORY/31bb7ba8914766d4ba40d6dfb6113c8b614be442 OUTPUT_DIRECTORY/3d0d7e5fb2ce288813306e4d4636395e047a3d28 22 | 23 | =cut 24 | 25 | =pod 26 | iPhone SMS database printer 27 | Copyright (C) 2012 A. Gordon (assafgordon at gmail dot com) 28 | 29 | LICENSE: AGPLv3 30 | 31 | This program is free software: you can redistribute it and/or modify 32 | it under the terms of the GNU Affero General Public License as 33 | published by the Free Software Foundation, either version 3 of the 34 | License, or (at your option) any later version. 35 | 36 | This program is distributed in the hope that it will be useful, 37 | but WITHOUT ANY WARRANTY; without even the implied warranty of 38 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 39 | GNU Affero General Public License for more details. 40 | 41 | You should have received a copy of the GNU Affero General Public License 42 | along with this program. If not, see . 43 | =cut 44 | 45 | sub show_help() 46 | { 47 | print<connect("dbi:SQLite:dbname=$iphone_Address_db"); 80 | my $AddressBook_sql = " 81 | Select 82 | ABPerson.First, 83 | ABPerson.Last, 84 | ABMultiValue.value 85 | from 86 | ABPerson, 87 | ABMultiValue 88 | where 89 | ABPerson.ROWID = ABMultiValue.record_id"; 90 | 91 | my $sth = $dbh->prepare($AddressBook_sql); 92 | $sth->execute; 93 | while ( my $ref = $sth->fetchrow_arrayref() ) { 94 | my ($first, $last, $value ) = @$ref ; 95 | 96 | $first //= ""; 97 | $last //= ""; 98 | 99 | next unless $value =~ /^[\+\-\(\) 0-9]+$/; 100 | 101 | my $normalized_phone = $value; 102 | $normalized_phone =~ s/[\(\)\- ]+//g; 103 | 104 | if (length($normalized_phone)==10 && $normalized_phone !~ /^\+/) { 105 | $normalized_phone = "+1" . $normalized_phone; 106 | } 107 | 108 | $address_book{$normalized_phone} = "$first $last"; 109 | } 110 | 111 | ##DEBUG 112 | ##print dump(\%address_book),"\n"; 113 | 114 | 115 | ## 116 | ## Open the SMS database 117 | ## 118 | 119 | $dbh = DBI->connect("dbi:SQLite:dbname=$iphone_SMS_db"); 120 | $sth = $dbh->prepare("select address,date,flags,text from message order by date" ); 121 | $sth->execute; 122 | 123 | while ( my $ref = $sth->fetchrow_arrayref() ) { 124 | my ($address, $date, $flags, $text) = @$ref; 125 | my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($date); 126 | 127 | $address //= ""; 128 | $text //= ""; 129 | 130 | my $human_date = sprintf("%02d:%02d %02d/%02d/%04d", $hour, $min, $mday, $mon+1, $year+1900 ); 131 | my $human_name = $address_book{$address} // ""; 132 | 133 | my $quoted_text = $text // ""; 134 | $quoted_text =~ s/\r/ \\r /gs; 135 | $quoted_text =~ s/\n/ \\n /gs; 136 | 137 | print join("\t", $human_date, $human_name, $address, (($flags==3)?"sent":"received"), $quoted_text),"\n"; 138 | } 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | iOS_backup_decode 2 | ================= 3 | 4 | Collection of scripts to decode an iOS backup (from libmobiledevice). 5 | 6 | The libMobileDevice ( http://www.libimobiledevice.org/ ) library, with the associated utilities (idevicebackup, ideviceinstaller, etc.) 7 | enables backing up an iOS device to a local directory. 8 | Each file/database on the iOS device has a unique ID. 9 | Soem of the files are standard SQLite databases, and these perl scripts reads the database files and dump some of the information in a friendly, readable format. 10 | 11 | Current handled databases: 12 | * AddressBook ( `31bb7ba8914766d4ba40d6dfb6113c8b614be442` ) 13 | * SMS database ( `3d0d7e5fb2ce288813306e4d4636395e047a3d28` ) 14 | 15 | Other databases which can be similary processed: 16 | * Notes ( `ca3bc056d4da0bbf88b5fb3be254f3b7147e639c` ) 17 | * Safari Bookmarks ( `d1f062e2da26192a6625d968274bfda8d07821e4` ) 18 | * Call History ( `2b2b0084a1bc3a5ac8c27afdf14afb42c61a19ca` ) 19 | 20 | 21 | Common workflow 22 | =============== 23 | 24 | Backup your device 25 | ------------------ 26 | 27 | To backup an iOS device, install 'libmobiledevice' and associated utilities, and run: 28 | 29 | $ idevicebackup backup [BACKUP-DIRECTORY] 30 | 31 | The directory will contain cryptic file names, such as: 32 | 33 | $ cd [BACKUP-DIRECTORY] 34 | $ ls 35 | fffa6225a88a50c7c48f04a3c8d58249a542bc85 36 | fff9fb13864a943dc7f3a5287a1163c3562da13c 37 | ffef48d7a403b6279276c7ca72ee48e7cebd454d 38 | ffec779ec78cdb982afb1cffeabd6408f225ec66 39 | ffea5ab4cbbeb59027e8893391920279b944391b 40 | ffe7d52c5a966918a9a86b2cbc6a25d948cc7557 41 | 42 | 43 | Deciphering directory content 44 | ----------------------------- 45 | 46 | The files in backup directory can be deciphered using the excellent `parse_mbdb.py` script (included in this package). 47 | I found the file on stack-exchange, here: http://stackoverflow.com/questions/3085153/how-to-parse-the-manifest-mbdb-file-in-an-ios-4-0-itunes-backup 48 | 49 | After backing up your device, run: 50 | 51 | $ cd [BACKUP-DIRECTORY] 52 | $ python parse_mbdb.py > manifest.txt 53 | 54 | or, to sort the files by their associated program 55 | 56 | $ cd [BACKUP-DIRECTORY] 57 | $ python parse_mbdb.py | sort -t')' -k2,2V > manifest.txt 58 | 59 | 60 | Manually explore a database file 61 | -------------------------------- 62 | 63 | Quickly find all backed-up files which are SQLite3 databases: 64 | 65 | $ cd [BACKUP-DIRECTORY] 66 | $ file * | grep -i sqlite 67 | 027cbce3ae649b49bfda37ebce598f567191df45: SQLite 3.x database 68 | 02ab955e766685f80fc7c4c1b989a1b15129b4fd: SQLite 3.x database 69 | 03261835a5da31173ebd1584fe9536520c624eb0: SQLite 3.x database 70 | 04667ef88d54eba03a29147b0c22e1c908e70e3e: SQLite 3.x database 71 | 72 | Use either `sqlitebrowser` (GUI) or `sqlite3` (command-line) programs to view the database file: 73 | 74 | $ sqlitebrowser [FILE] 75 | $ sqlite3 [FILE] 76 | 77 | 78 | 79 | 80 | Using these scripts to pretty-print information 81 | ----------------------------------------------- 82 | 83 | **Installation note** 84 | 85 | You will need `Perl` and `Python`, and the `DBI`, `DBD::SQLite` perl modules. 86 | 87 | 88 | 89 | **List (some) information from the address book** (The address book DB schema is actually quite complicated, this script extracts the minimum amount of information. some missing fields may cause "use of uninitialized values" warnings.) 90 | 91 | $ cd [BACKUP-DIRECTORY] 92 | $ pretty_print_iphone_AddressBook.pl 31bb7ba8914766d4ba40d6dfb6113c8b614be442 > AddressBook.txt 93 | 94 | **list SMSs, without cross-referecing the addressbook** (this should always work) 95 | 96 | $ decode_iphone_SMS_db.pl 3d0d7e5fb2ce288813306e4d4636395e047a3d28 > sms.txt 97 | 98 | **List SMSs, trying to cross-reference with the address book** (Some missing fields may cause "use of uninitialized values" warnings) 99 | 100 | $ pretty_print_iphone_SMS.pl 31bb7ba8914766d4ba40d6dfb6113c8b614be442 3d0d7e5fb2ce288813306e4d4636395e047a3d28 > SMS2.txt 101 | 102 | 103 | 104 | LICENSE 105 | ======= 106 | 107 | LICENSE: AGPLv3 108 | Copyright (C) 2012 A. Gordon (assafgordon at gmail dot com) 109 | 110 | This program is free software: you can redistribute it and/or modify 111 | it under the terms of the GNU Affero General Public License as 112 | published by the Free Software Foundation, either version 3 of the 113 | License, or (at your option) any later version. 114 | 115 | This program is distributed in the hope that it will be useful, 116 | but WITHOUT ANY WARRANTY; without even the implied warranty of 117 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 118 | GNU Affero General Public License for more details. 119 | --------------------------------------------------------------------------------