├── .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 |
--------------------------------------------------------------------------------