hacks/dump-imessages/iphone-dataprotection/python_scripts/nand/nand.py

426 lines
18 KiB
Python

from crypto.aes import AESdecryptCBC
from firmware.img2 import IMG2
from firmware.img3 import Img3, extract_img3s
from firmware.scfg import parse_SCFG
from hfs.emf import EMFVolume
from hfs.hfs import HFSVolume
from image import NANDImageSplitCEs, NANDImageFlat
from keystore.effaceable import check_effaceable_header, EffaceableLockers
from legacyftl import FTL
from partition_tables import GPT_partitions, parse_lwvm, parse_mbr, parse_gpt, \
APPLE_ENCRYPTED
from progressbar import ProgressBar
from remote import NANDRemote, IOFlashStorageKitClient
from structs import *
from util import sizeof_fmt, write_file, load_pickle, save_pickle, hexdump, \
makedirs
from util.bdev import FTLBlockDevice
from vfl import VFL
from vsvfl import VSVFL
from yaftl import YAFTL
import math
import os
import plistlib
import struct
def ivForPage(page):
iv = ""
for _ in xrange(4):
if (page & 1):
page = 0x80000061 ^ (page >> 1);
else:
page = page >> 1;
iv += struct.pack("<L", page)
return iv
#iOS 3
def getEMFkeyFromCRPT(data, key89B):
assert data.startswith("tprc")
z = AESdecryptCBC(data[4:0x44], key89B)
assert z.startswith("TPRC"), "wrong key89B"
#last_byte = struct.unpack("<Q", z[4:4+8])[0]
emf = z[16:16+32]
return emf
class NAND(object):
H2FMI_HASH_TABLE = gen_h2fmi_hash_table()
def __init__(self, filename, device_infos, ppn=False):
self.device_infos = device_infos
self.partition_table = None
self.lockers = {}
self.iosVersion = 0
self.hasMBR = False
self.metadata_whitening = False
self.filename = filename
self.encrypted = device_infos["hwModel"] not in ["M68AP", "N45AP", "N82AP", "N72AP"]
self.initGeometry(device_infos["nand"])
if os.path.basename(filename).startswith("ce_"):
self.image = NANDImageSplitCEs(os.path.dirname(filename), device_infos["nand"])
elif filename == "remote":
self.image = NANDRemote(self.pageSize, self.metaSize, self.pagesPerBlock, self.bfn)
else:
self.image = NANDImageFlat(filename, device_infos["nand"])
s, page0 = self.readPage(0,0)
self.nandonly = (page0 != None) and page0.startswith("ndrG")
if self.nandonly:
self.encrypted = True
magics = ["DEVICEINFOBBT"]
nandsig = None
if page0 and page0[8:14] == "Darwin":
print "Found old style signature", page0[:8]
nandsig = page0
else:
magics.append("NANDDRIVERSIGN")
#sp0 = {}
sp0 = self.readSpecialPages(0, magics)
print "Found %s special pages in CE 0" % (", ".join(sp0.keys()))
if not self.nandonly:
print "Device does not boot from NAND (=> has a NOR)"
vfltype = '1' #use VSVFL by default
if not nandsig:
nandsig = sp0.get("NANDDRIVERSIGN")
if not nandsig:
print "NANDDRIVERSIGN not found, assuming metadata withening = %d" % self.metadata_whitening
else:
nSig, flags = struct.unpack("<LL", nandsig[:8])
#assert nandsig[3] == chr(0x43)
vfltype = nandsig[1]
self.metadata_whitening = (flags & 0x10000) != 0
print "NAND signature 0x%x flags 0x%x withening=%d, epoch=%s" % (nSig, flags, self.metadata_whitening, nandsig[0])
if not self.nandonly:
if self.device_infos.has_key("lockers"):
self.lockers = EffaceableLockers(self.device_infos.lockers.data)
else:
unit = self.findLockersUnit()
if unit:
self.lockers = EffaceableLockers(unit[0x40:])
self.lockers.display()
if not self.device_infos.has_key("lockers"):
self.device_infos.lockers = plistlib.Data(unit[0x40:0x40+960])
EMF = self.getEMF(device_infos["key89B"].decode("hex"))
dkey = self.getDKey(device_infos["key835"].decode("hex"))
self.device_infos.EMF = EMF.encode("hex")
self.device_infos.DKey = dkey.encode("hex")
deviceuniqueinfo = sp0.get("DEVICEUNIQUEINFO")
if not deviceuniqueinfo:
print "DEVICEUNIQUEINFO not found"
else:
scfg = parse_SCFG(deviceuniqueinfo)
print "Found DEVICEUNIQUEINFO, serial number=%s" % scfg.get("SrNm","SrNm not found !")
if vfltype == '0':
print "Using legacy VFL"
self.vfl = VFL(self)
self.ftl = FTL(self, self.vfl)
elif not ppn:
print "Using VSVFL"
self.vfl = VSVFL(self)
self.ftl = YAFTL(self.vfl)
def initGeometry(self, d):
self.metaSize = d.get("meta-per-logical-page", 0)
if self.metaSize == 0:
self.metaSize = 12
dumpedPageSize = d.get("dumpedPageSize", d["#page-bytes"] + self.metaSize + 8)
self.dump_size= d["#ce"] * d["#ce-blocks"] * d["#block-pages"] * dumpedPageSize
self.totalPages = d["#ce"] * d["#ce-blocks"] * d["#block-pages"]
nand_size = d["#ce"] * d["#ce-blocks"] * d["#block-pages"] * d["#page-bytes"]
hsize = sizeof_fmt(nand_size)
self.bfn = d.get("boot-from-nand", False)
self.dumpedPageSize = dumpedPageSize
self.pageSize = d["#page-bytes"]
self.bootloaderBytes = d.get("#bootloader-bytes", 1536)
self.emptyBootloaderPage = "\xFF" * self.bootloaderBytes
self.blankPage = "\xFF" * self.pageSize
self.nCEs =d["#ce"]
self.blocksPerCE = d["#ce-blocks"]
self.pagesPerBlock = d["#block-pages"]
self.pagesPerCE = self.blocksPerCE * self.pagesPerBlock
self.vendorType = d["vendor-type"]
self.deviceReadId = d.get("device-readid", 0)
self.banks_per_ce_vfl = d["banks-per-ce"]
if d.has_key("metadata-whitening"):
self.metadata_whitening = (d["metadata-whitening"].data == "\x01\x00\x00\x00")
if nand_chip_info.has_key(self.deviceReadId):
self.banks_per_ce_physical = nand_chip_info.get(self.deviceReadId)[7]
else:
#raise Exception("Unknown deviceReadId %x" % self.deviceReadId)
print "!!! Unknown deviceReadId %x, assuming 1 physical bank /CE, will probably fail" % self.deviceReadId
self.banks_per_ce_physical = 1
print "Chip id 0x%x banks per CE physical %d" % (self.deviceReadId, self.banks_per_ce_physical)
self.blocks_per_bank = self.blocksPerCE / self.banks_per_ce_physical
if self.blocksPerCE & (self.blocksPerCE-1) == 0:
self.bank_address_space = self.blocks_per_bank
self.total_block_space = self.blocksPerCE
else:
bank_address_space = next_power_of_two(self.blocks_per_bank)
self.bank_address_space = bank_address_space
self.total_block_space = ((self.banks_per_ce_physical-1)*bank_address_space) + self.blocks_per_bank
self.bank_mask = int(math.log(self.bank_address_space * self.pagesPerBlock,2))
print "NAND geometry : %s (%d CEs (%d physical banks/CE) of %d blocks of %d pages of %d bytes data, %d bytes metdata)" % \
(hsize, self.nCEs, self.banks_per_ce_physical, self.blocksPerCE, self.pagesPerBlock, self.pageSize, d["meta-per-logical-page"])
def unwhitenMetadata(self, meta, pagenum):
if len(meta) != 12:
return None
s = list(struct.unpack("<LLL", meta))
for i in xrange(3):
s[i] ^= NAND.H2FMI_HASH_TABLE[(i+pagenum) % len(NAND.H2FMI_HASH_TABLE)]
return struct.pack("<LLL", s[0], s[1],s[2])
def readBootPage(self, ce, page):
s,d=self.readPage(ce, page)
if d:
return d[:self.bootloaderBytes]
else:
#print "readBootPage %d %d failed" % (ce,page)
return self.emptyBootloaderPage
def readMetaPage(self, ce, block, page, spareType=SpareData):
return self.readBlockPage(ce, block, page, META_KEY, spareType=spareType)
def readBlockPage(self, ce, block, page, key=None, lpn=None, spareType=SpareData):
assert page < self.pagesPerBlock
pn = block * self.pagesPerBlock + page
return self.readPage(ce, pn, key, lpn, spareType=spareType)
def translateabsPage(self, page):
return page % self.nCEs, page/self.nCEs
def readAbsPage(self, page, key=None, lpn=None):
return self.readPage(page % self.nCEs, page/self.nCEs, key, lpn)
def readPage(self, ce, page, key=None, lpn=None, spareType=SpareData):
if ce > self.nCEs or page > self.pagesPerCE:
#hax physical banking
pass#raise Exception("CE %d Page %d out of bounds" % (ce, page))
if self.filename != "remote": #undo banking hax
bank = (page & ~((1 << self.bank_mask) - 1)) >> self.bank_mask
page2 = (page & ((1 << self.bank_mask) - 1))
page2 = bank * (self.blocks_per_bank) * self.pagesPerBlock + page2
spare, data = self.image.readPage(ce, page2)
else:
spare, data = self.image.readPage(ce, page)
if not data:
return None,None
if self.metadata_whitening and spare != "\x00"*12 and len(spare) == 12:
spare = self.unwhitenMetadata(spare, page)
spare = spareType.parse(spare)
if key and self.encrypted:
if lpn != None: pageNum = spare.lpn #XXX
else: pageNum = page
return spare, self.decryptPage(data, key, pageNum)
return spare, data
def decryptPage(self, data, key, pageNum):
return AESdecryptCBC(data, key, ivForPage(pageNum))
def unpackSpecialPage(self, data):
l = struct.unpack("<L", data[0x34:0x38])[0]
return data[0x38:0x38 + l]
def readSpecialPages(self, ce, magics):
print "Searching for special pages..."
specials = {}
if self.nandonly:
magics.append("DEVICEUNIQUEINFO")#, "DIAGCONTROLINFO")
magics = map(lambda s: s.ljust(16,"\x00"), magics)
lowestBlock = self.blocksPerCE - (self.blocksPerCE / 100)
for block in xrange(self.blocksPerCE - 1, lowestBlock, -1):
if len(magics) == 0:
break
#hax for physical banking
bank_offset = self.bank_address_space * (block / self.blocks_per_bank)
for page in xrange(self.pagesPerBlock,-1,-1):
page = (bank_offset + block % self.blocks_per_bank) * self.pagesPerBlock + page
s, data = self.readPage(ce, page)
if data == None:
continue
if data[:16] in magics:
self.encrypted = False
magics.remove(data[:16])
specials[data[:16].rstrip("\x00")] = self.unpackSpecialPage(data)
break
data = self.decryptPage(data, META_KEY, page)
#print data[:16]
if data[:16] in magics:
#print data[:16], block, page
self.encrypted = True
magics.remove(data[:16])
specials[data[:16].rstrip("\x00")] = self.unpackSpecialPage(data)
break
return specials
def readLPN(self, lpn, key):
return self.ftl.readLPN(lpn, key)
def readVPN(self, vpn, key=None, lpn=None):
return self.vfl.read_single_page(vpn, key, lpn)
def dumpSystemPartition(self, outputfilename):
return self.getPartitionBlockDevice(0).dumpToFile(outputfilename)
def dumpDataPartition(self, emf, outputfilename):
return self.getPartitionBlockDevice(1, emf).dumpToFile(outputfilename)
def isIOS5(self):
self.getPartitionTable()
return self.iosVersion == 5
def getPartitionTable(self):
if self.partition_table:
return self.partition_table
pt = None
for i in xrange(10):
d = self.readLPN(i, FILESYSTEM_KEY)
pt = parse_mbr(d)
if pt:
self.hasMBR = True
self.iosVersion = 3
break
gpt = parse_gpt(d)
if gpt:
off = gpt.partition_entries_lba - gpt.current_lba
d = self.readLPN(i+off, FILESYSTEM_KEY)
pt = GPT_partitions.parse(d)[:-1]
self.iosVersion = 4
break
pt = parse_lwvm(d, self.pageSize)
if pt:
self.iosVersion = 5
break
self.partition_table = pt
return pt
def getPartitionBlockDevice(self, partNum, key=None):
pt = self.getPartitionTable()
if self.hasMBR and pt[1].type == APPLE_ENCRYPTED and partNum == 1:
data = self.readLPN(pt[1].last_lba - 1, FILESYSTEM_KEY)
key = getEMFkeyFromCRPT(data, self.device_infos["key89B"].decode("hex"))
if key == None:
if partNum == 0:
key = FILESYSTEM_KEY
elif partNum == 1 and self.device_infos.has_key("EMF"):
key = self.device_infos["EMF"].decode("hex")
return FTLBlockDevice(self, pt[partNum].first_lba, pt[partNum].last_lba, key)
def getPartitionVolume(self, partNum, key=None):
bdev = self.getPartitionBlockDevice(partNum, key)
if partNum == 0:
return HFSVolume(bdev)
elif partNum == 1:
self.device_infos["dataVolumeOffset"] = self.getPartitionTable()[partNum].first_lba
return EMFVolume(bdev, self.device_infos)
def findLockersUnit(self):
if not self.nandonly:
return
for i in xrange(96,128):
for ce in xrange(self.nCEs):
s, d = self.readBlockPage(ce, 1, i)
if d and check_effaceable_header(d):
print "Found effaceable lockers in ce %d block 1 page %d" % (ce,i)
return d
def getLockers(self):
unit = self.findLockersUnit()
if unit:
return unit[0x40:0x40+960]
def getEMF(self, k89b):
return self.lockers.get_EMF(k89b)
def getDKey(self, k835):
return self.lockers.get_DKey(k835)
def readBootPartition(self, block_start, block_end):
res = ""
for i in xrange(block_start*self.pagesPerBlock, block_end*self.pagesPerBlock):
res += self.readBootPage(0, i)
return res
def get_img3s(self):
if not self.nandonly:
print "IMG3s are in NOR"
return []
blob = self.readBootPartition(8, 16)
hdr = IMG2.parse(blob[:0x100])
i = hdr.images_block * hdr.block_size + hdr.images_offset
img3s = extract_img3s(blob[i:i+hdr.images_length*hdr.block_size])
boot = self.readBootPartition(0, 1)
img3s = extract_img3s(boot[0xc00:]) + img3s
return img3s
def extract_img3s(self, outfolder=None):
if not self.nandonly:
print "IMG3s are in NOR"
return
if outfolder == None:
if self.filename != "remote": outfolder = os.path.join(os.path.dirname(self.filename), "img3")
else: outfolder = os.path.join(".", "img3")
makedirs(outfolder)
print "Extracting IMG3s to %s" % outfolder
for img3 in self.get_img3s():
#print img3.sigcheck(self.device_infos.get("key89A").decode("hex"))
print img3.shortname
write_file(outfolder+ "/%s.img3" % img3.shortname, img3.img3)
kernel = self.getPartitionVolume(0).readFile("/System/Library/Caches/com.apple.kernelcaches/kernelcache",returnString=True)
if kernel:
print "kernel"
write_file(outfolder + "/kernelcache.img3", kernel)
def extract_shsh(self, outfolder="."):
if not self.nandonly:
print "IMG3s are in NOR"
return
pass
def getNVRAM(self):
if not self.nandonly:
print "NVRAM is in NOR"
return
#TODO
nvrm = self.readBootPartition(2, 8)
def getBoot(self):
boot = self.readBootPartition(0, 1)
for i in xrange(0x400, 0x600, 16):
name = boot[i:i+4][::-1]
block_start, block_end, flag = struct.unpack("<LLL", boot[i+4:i+16])
if name == "none":
break
print name, block_start, block_end, flag
def cacheData(self, name, data):
if self.filename == "remote":
return None
save_pickle(self.filename + "." + name, data)
def loadCachedData(self, name):
try:
if self.filename == "remote":
return None
return load_pickle(self.filename + "." + name)
except:
return None
def dump(self, p):
#hax ioflashstoragekit can only handle 1 connexion
if self.filename == "remote":
del self.image
ioflash = IOFlashStorageKitClient()
ioflash.dump_nand(p)
#restore proxy
if self.filename == "remote":
self.image = NANDRemote(self.pageSize, self.metaSize, self.pagesPerBlock, self.bfn)