hacks/dump-imessages/iphone-dataprotection/python_scripts/hfs/journal.py

152 lines
6.1 KiB
Python
Raw Permalink Normal View History

from crypto.aes import AESencryptCBC, AESdecryptCBC
from emf import cprotect_xattr, EMFFile
from structs import *
from util import write_file, sizeof_fmt
import hashlib
"""
Implementation of the following paper :
Using the HFS+ Journal For Deleted File Recovery. Aaron Burghardt, Adam Feldman. DFRWS 2008
http://www.dfrws.org/2008/proceedings/p76-burghardt.pdf
http://www.dfrws.org/2008/proceedings/p76-burghardt_pres.pdf
"""
def carveBtreeNode(node, kClass, dClass):
try:
btnode = BTNodeDescriptor.parse(node)
if btnode.kind == kBTLeafNode:
off = BTNodeDescriptor.sizeof()
recs = []
offsets = Array(btnode.numRecords, UBInt16("off")).parse(node[-2*btnode.numRecords:])
for i in xrange(btnode.numRecords):
off = offsets[btnode.numRecords-i-1]
k = kClass.parse(node[off:])
off += 2 + k.keyLength
d = dClass.parse(node[off:])
recs.append((k,d))
return recs
return []
except:
return []
"""
for standard HFS volumes
"""
def carveHFSVolumeJournal(volume):
journal = volume.readJournal()
hdr = journal_header.parse(journal)
sector_size = hdr.jhdr_size
nodeSize = volume.catalogTree.nodeSize
f={}
for i in xrange(0,len(journal), sector_size):
for k,v in carveBtreeNode(journal[i:i+nodeSize],HFSPlusCatalogKey, HFSPlusCatalogData):
if v.recordType == kHFSPlusFileRecord:
name = getString(k)
h = hashlib.sha1(HFSPlusCatalogKey.build(k)).digest()
if f.has_key(h):
continue
if volume.catalogTree.searchByCNID(v.data.fileID) == (None, None):
if volume.isBlockInUse(v.data.dataFork.HFSPlusExtentDescriptor[0].startBlock) == False:
print "deleted file", v.data.fileID, name
fileid = v.data.fileID
f[h]=(name, v)
return f.values()
magics=["SQLite", "bplist", "<?xml", "\xFF\xD8\xFF", "\xCE\xFA\xED\xFE", "\x89PNG", "\x00\x00\x00\x1CftypM4A",
"\x00\x00\x00\x14ftypqt"]
"""
HAX: should do something better like compute entropy or something
"""
def isDecryptedCorrectly(data):
for m in magics:
if data.startswith(m):
return True
return False
"""
carve the journal for deleted cprotect xattrs and file records
"""
def carveEMFVolumeJournal(volume):
journal = volume.readJournal()
print "Journal size : %s" % sizeof_fmt(len(journal))
hdr = journal_header.parse(journal)
sector_size = hdr.jhdr_size
nodeSize = volume.catalogTree.nodeSize
print "Collecting existing file ids"
fileIds = volume.listAllFileIds()
print "%d file IDs" % len(fileIds.keys())
files = {}
keys = {}
for i in xrange(0,len(journal),sector_size):
for k,v in carveBtreeNode(journal[i:i+nodeSize],HFSPlusCatalogKey, HFSPlusCatalogData):
if v.recordType == kHFSPlusFileRecord:
name = getString(k)
h = hashlib.sha1(HFSPlusCatalogKey.build(k)).digest()
if files.has_key(h):
continue
if not fileIds.has_key(v.data.fileID):
#we only keep files where the first block is not marked as in use
if volume.isBlockInUse(v.data.dataFork.HFSPlusExtentDescriptor[0].startBlock) == False:
print "Found deleted file record", v.data.fileID, name
files[h] = (name,v)
for k,v in carveBtreeNode(journal[i:i+nodeSize],HFSPlusAttrKey, HFSPlusAttrData):
if getString(k) == "com.apple.system.cprotect":
if not fileIds.has_key(k.fileID):
filekeys = keys.setdefault(k.fileID, [])
try:
cprotect = cprotect_xattr.parse(v.data)
except:
continue
#assert cprotect.xattr_major_version == 2
filekey = volume.keybag.unwrapKeyForClass(cprotect.persistent_class, cprotect.persistent_key)
if filekey and not filekey in filekeys:
print "Found key for file", k.fileID
filekeys.append(filekey)
return files.values(), keys
"""
"bruteforce" method, tries to decrypt all unallocated blocks with provided file keys
this is a hack, don't expect interesting results with this
"""
def carveEMFemptySpace(volume, file_keys, outdir):
for lba, block in volume.unallocatedBlocks():
iv = volume.ivForLBA(lba)
for filekey in file_keys:
ciphertext = AESencryptCBC(block, volume.emfkey, iv)
clear = AESdecryptCBC(ciphertext, filekey, iv)
if isDecryptedCorrectly(clear):
print "Decrypted stuff at lba %x" % lba
open(outdir+ "/%x.bin" % lba, "wb").write(clear)
def do_emf_carving(volume, carveokdir, carvenokdir):
deletedFiles, filekeys = carveEMFVolumeJournal(volume)
print "Journal carving done, trying to extract deleted files"
n = 0
for name, vv in deletedFiles:
for filekey in filekeys.get(vv.data.fileID, []):
ff = EMFFile(volume,vv.data.dataFork, vv.data.fileID, filekey, deleted=True)
data = ff.readAllBuffer()
if isDecryptedCorrectly(data):
write_file(carveokdir + "%d_%s" % (vv.data.fileID, name.replace("/","_")),data)
n += 1
else:
write_file(carvenokdir + "%d_%s" % (vv.data.fileID, name.replace("/","_")),data)
if not filekeys.has_key(vv.data.fileID):
print "Missing file key for", name
else:
del filekeys[vv.data.fileID]
print "Done, extracted %d files" % n
if False:
fks = set(reduce(lambda x,y: x+y, filekeys.values()))
print "%d file keys left, try carving empty space (slow) ? CTRL-C to exit" % len(fks)
raw_input()
carveEMFemptySpace(volume, fks)