-
Notifications
You must be signed in to change notification settings - Fork 5
/
iPhoneBup.py
302 lines (240 loc) · 11.1 KB
/
iPhoneBup.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
#!/usr/bin/env python
"""
iPhoneBup - a iPhone backup media extractor written in Python by Nathan Trujillo github.com/bo01ean
What it does:
This script recursively scans your backup directories, searches for media and extracts them into folder of the users choosing.
It uses iPhone's Manifest.mbdb file which later versions of IOS use to store backup information
It also uses a spoofed hash list to backup media files
Why did you write this:
After upgrading my iPhone a few days ago, I lost my backup after iTunes forgot to create a Manifest file for it.
Since the files were still on disk, though obfuscated, I knew there was a way to get them back, and this script will do it for you.
Warning:
I am not responsible if this script kicks your dog, scratches your new car or deletes your files. Please use at your own risk.
To Use:
Check it out from git: https://github.com/bo01ean/iphone-tools.git
be sure python is installed and in your path
run this script from the command line and this script should do the rest, auto-magically
Notes:
the safeMode flag makes no changes to the filesystem, only flip to False if you are completely sure!
this script does not backup app data since there are naming convention issues
This script uses code by galloglass on stackoverflow
User Robert Munafo posted an updated version:
http://stackoverflow.com/questions/3085153/how-to-parse-the-manifest-mbdb-file-in-an-ios-4-0-itunes-backup
"""
import sys
import hashlib
import re
import os
import platform
import shutil
"""
User defined variables
"""
safeMode = False
outputDir = "Extracted"
minSize = 1 * 1024 * 1024 # size in k of the smallest file you want to backup
"""
Do not change anything below this line unless you know what you are doing :)
"""
iPhoneImageDigestBank, mbdx, sizes, extracted = {}, {}, {}, {}
"""
We try to determine home directory -> backup location
"""
# http://stackoverflow.com/questions/626796/how-do-i-find-the-windows-common-application-data-folder-using-python
try:
from win32com.shell import shellcon, shell
homedir = shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, 0, 0)
except ImportError: # quick semi-nasty fallback for non-windows/win32com case
homedir = os.path.expanduser("~")
workingDir = {"DDarwin": "/Volumes/KELUSB-2012/iTunes Backups",
"Darwin": "~/Library/Application Support/MobileSync/Backup",
"Windows": homedir + "\\AppData\\Roaming\\Apple Computer\\MobileSync\\Backup\\"}
"""
We spoof media paths
"""
def buildiPhoneImageDigestBank(): # Media/DCIM/100APPLE/IMG_0001.MOV|JPG
print "Spoofing .."
path = "Media/DCIM/"
camStart = 100
imgInc = 1;
types = {"JPG", "MOV"}
i = 0
for camInc in range(camStart, 105):
# print camInc
i += 1
for imgInc in range(((i * 1000) - 1000), 1000 * i):
for t in types:
fullpath = path + "{0:03d}".format(camInc) + "APPLE" + "/IMG_" + "{0:04d}".format(imgInc) + "." + t
encryptMe = "MediaDomain-" + fullpath
hash = hashlib.sha1(encryptMe)
iPhoneImageDigestBank[hash.hexdigest()] = fullpath;
"""
We extract files from iPhone backup database
Mediadomain is most interesting
"""
def backupFromDatabase(file):
mbdb = process_mbdb_file(file)
for offset, fileinfo in mbdb.items():
if offset in mbdx:
fileinfo['fileID'] = mbdx[offset]
if not re.match(ur"^App", fileinfo['domain']) and fileinfo['filelen'] >= minSize and (
(fileinfo['mode'] & 0xE000) == 0x8000):
# print fileinfo['domain']
backupFileCopy(fileinfo['fileID'], fileinfo['filename'], file)
# extracted[fileinfo['domain']] = extracted.get(fileinfo['domain'],0) + fileinfo['filelen']
else:
fileinfo['fileID'] = "<nofileID>"
print >> sys.stderr, "No fileID found for %s" % fileinfo_str(fileinfo)
if fileinfo['filelen'] > 0:
if (fileinfo['mode'] & 0xE000) == 0x8000:
sizes[fileinfo['domain']] = sizes.get(fileinfo['domain'], 0) + fileinfo['filelen']
def backupFileCopy(hash, name, file, dumpDir=outputDir):
dest = os.path.join(os.getcwd(), dumpDir)
# dest = "./" + dumpDir
for p in name.split("/"):
dest = os.path.join(dest, p)
DIR = os.path.split(dest)[0]
if not safeMode:
try:
os.stat(DIR)
except:
os.makedirs(DIR)
src = os.path.join(os.path.split(file)[0], hash)
if (os.path.exists(src)):
if (os.path.exists(dest)):
if (os.path.getsize(src) > os.path.getsize(dest)):
print ">>> src > dest so copying " + dest
shutil.copyfile(src, dest)
else:
print ">>> dest doesn't exist, so copying " + src + "->" + dest
shutil.copyfile(src, dest)
"""
We scan through hashes and copy to file system if necessary
"""
def extractMediaFromSpoofedHashes(dir):
print "Extracting via hash names from " + dir
for hash, path in iPhoneImageDigestBank.items():
if (os.path.exists(dir + "//" + hash)):
backupFileCopy(hash, path, dir + "//pizza", outputDir + "-viahashes")
if (os.path.exists(dir + "//" + hash + ".mdbackup")):
backupFileCopy(hash + ".mdbackup", path, dir + "//pizza", outputDir + "-viahashes")
def getint(data, offset, intsize):
"""Retrieve an integer (big-endian) and new offset from the current offset"""
value = 0
while intsize > 0:
value = (value << 8) + ord(data[offset])
offset = offset + 1
intsize = intsize - 1
return value, offset
def getstring(data, offset):
"""Retrieve a string and new offset from the current offset into the data"""
if data[offset] == chr(0xFF) and data[offset + 1] == chr(0xFF):
return '', offset + 2 # Blank string
length, offset = getint(data, offset, 2) # 2-byte length
value = data[offset:offset + length]
return value, (offset + length)
def process_mbdb_file(filename):
mbdb = {} # Map offset of info in this file => file info
data = open(filename, "rb").read()
if data[0:4] != "mbdb": raise Exception("This does not look like an MBDB file")
offset = 4
offset = offset + 2 # value x05 x00, not sure what this is
while offset < len(data):
fileinfo = {}
fileinfo['start_offset'] = offset
fileinfo['domain'], offset = getstring(data, offset)
fileinfo['filename'], offset = getstring(data, offset)
fileinfo['linktarget'], offset = getstring(data, offset)
fileinfo['datahash'], offset = getstring(data, offset)
fileinfo['unknown1'], offset = getstring(data, offset)
fileinfo['mode'], offset = getint(data, offset, 2)
fileinfo['unknown2'], offset = getint(data, offset, 4)
fileinfo['unknown3'], offset = getint(data, offset, 4)
fileinfo['userid'], offset = getint(data, offset, 4)
fileinfo['groupid'], offset = getint(data, offset, 4)
fileinfo['mtime'], offset = getint(data, offset, 4)
fileinfo['atime'], offset = getint(data, offset, 4)
fileinfo['ctime'], offset = getint(data, offset, 4)
fileinfo['filelen'], offset = getint(data, offset, 8)
fileinfo['flag'], offset = getint(data, offset, 1)
fileinfo['numprops'], offset = getint(data, offset, 1)
fileinfo['properties'] = {}
for ii in range(fileinfo['numprops']):
propname, offset = getstring(data, offset)
propval, offset = getstring(data, offset)
fileinfo['properties'][propname] = propval
mbdb[fileinfo['start_offset']] = fileinfo
fullpath = fileinfo['domain'] + '-' + fileinfo['filename']
id = hashlib.sha1(fullpath)
mbdx[fileinfo['start_offset']] = id.hexdigest()
return mbdb
def modestr(val):
def mode(val):
if (val & 0x4):
r = 'r'
else:
r = '-'
if (val & 0x2):
w = 'w'
else:
w = '-'
if (val & 0x1):
x = 'x'
else:
x = '-'
return r + w + x
return mode(val >> 6) + mode((val >> 3)) + mode(val)
def fileinfo_str(f, verbose=False):
if not verbose: return "(%s)%s::%s %s" % (f['fileID'], f['domain'], f['filename'], f['start_offset'])
if (f['mode'] & 0xE000) == 0xA000:
type = 'l' # symlink
elif (f['mode'] & 0xE000) == 0x8000:
type = '-' # file
elif (f['mode'] & 0xE000) == 0x4000:
type = 'd' # dir
else:
print >> sys.stderr, "Unknown file type %04x for %s" % (f['mode'], fileinfo_str(f, False))
type = '?' # unknown
info = ("%s%s %08x %08x %7d %10d %10d %10d (%s)%s::%s" %
(type, modestr(f['mode'] & 0x0FFF), f['userid'], f['groupid'], f['filelen'],
f['mtime'], f['atime'], f['ctime'], f['fileID'], f['domain'], f['filename']))
if type == 'l': info = info + ' -> ' + f['linktarget'] # symlink destination
for name, value in f['properties'].items(): # extra properties
info = info + ' ' + name + '=' + repr(value)
return info
if __name__ == '__main__':
# change to iTunes backup directory, wherever that may be
os.chdir(os.path.expanduser(workingDir[platform.system()]))
print os.getcwd()
buildiPhoneImageDigestBank()
if not safeMode:
try:
os.stat(outputDir)
except:
os.makedirs(outputDir)
top = os.getcwd();
for root, subFolders, files in os.walk("."):
for ff in files:
if re.match(ur"((?!Snapshot).)*$", root): # ignore snapshot directories
if re.match(ur"^Manifest\.mbdb$", ff):
print " ^-^ " + root + "\\" + ff
backupFromDatabase(os.path.join(root, ff))
if re.match(ur"((?!Extract).)*$",
root) and root != ".": # we don't check our extracted folder, or current folder
print " ^-^ " + root
extractMediaFromSpoofedHashes(root)
for domain in sorted(sizes, key=sizes.get):
if (sizes[domain] > 1024 * 1024):
print "%-60s : (%dMB)" % (domain, int(sizes[domain] / 1024 / 1024))
"""
File notes
SMS / Text messages 1-6 sms.db 3d0d7e5fb2ce288813306e4d4636395e047a3d28 SQLite 3
Contacts / address book 2-6 AddressBook.sqlitedb 31bb7ba8914766d4ba40d6dfb6113c8b614be442 SQLite 3
Calendar 2-6 Calendar.sqlitedb 2041457d5fe04d39d0ab481178355df6781e6858 SQLite 3
Notes 4-6 notes.sqlite ca3bc056d4da0bbf88b5fb3be254f3b7147e639c SQLite 3
Call history 4-6 call_history.db 2b2b0084a1bc3a5ac8c27afdf14afb42c61a19ca SQLite 3
Locations 4 - 6 consolidated.db 4096c9ec676f2847dc283405900e284a7c815836 SQLite 3
dsfdasf 12b144c0bd44f2b3dffd9186d3f9c05b917cee25 lkjsadflkjasdf iPhoto
adsfasdf b03b6432c8e753323429e15bc9ec0a8040763424 lkjsdf iPhoto backup
photos, SMS, call log and contacts
"""