Compatibility with Blue firmware 2.0 (production release)

This commit is contained in:
BTChip 2017-01-22 18:04:38 +01:00
parent 69c42dabfb
commit 3738a758e3
4 changed files with 252 additions and 153 deletions

View File

@ -22,6 +22,9 @@ import struct
import hashlib import hashlib
import binascii import binascii
LOAD_SEGMENT_CHUNK_HEADER_LENGTH = 3
MIN_PADDING_LENGTH = 1
class HexLoader: class HexLoader:
def __init__(self, card, cla=0xF0, secure=False, key=None, relative=True): def __init__(self, card, cla=0xF0, secure=False, key=None, relative=True):
self.card = card self.card = card
@ -89,7 +92,7 @@ class HexLoader:
while (len(paddedData) % 16) != 0: while (len(paddedData) % 16) != 0:
paddedData += b'\x00' paddedData += b'\x00'
cipher = AES.new(self.key, AES.MODE_CBC, self.iv) cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
encryptedData = cipher.encrypt(paddedData) encryptedData = cipher.encrypt(str(paddedData))
self.iv = encryptedData[len(encryptedData) - 16:] self.iv = encryptedData[len(encryptedData) - 16:]
return encryptedData return encryptedData
@ -136,15 +139,27 @@ class HexLoader:
if (signature != None): if (signature != None):
data += chr(len(signature)) + signature data += chr(len(signature)) + signature
data = self.encryptAES(data) data = self.encryptAES(data)
self.exchange(self.cla, 0x00, 0x00, 0x00, data) self.exchange(self.cla, 0x00, 0x00, 0x00, data)
def createApp(self, appflags, applength, appname, icon=None, path=None): def createApp(self, appflags, applength, appname, icon=None, path=None, iconOffset=None, iconSize=None, appversion=None):
data = b'\x0B' + struct.pack('>I', applength) + struct.pack('>I', appflags) + struct.pack('>B', len(appname)) + appname data = b'\x0B' + struct.pack('>I', applength) + struct.pack('>I', appflags) + struct.pack('>B', len(appname)) + appname
if (icon != None): if iconOffset is None:
data += struct.pack('>B', len(icon))+ icon if not (icon is None):
if (path != None): data += struct.pack('>B', len(icon)) + icon
else:
data += b'\x00'
if not (path is None):
data += struct.pack('>B', len(path)) + path data += struct.pack('>B', len(path)) + path
else:
data += b'\x00'
if not iconOffset is None:
data += struct.pack('>I', iconOffset) + struct.pack('>H', iconSize)
if not appversion is None:
data += struct.pack('>B', len(appversion)) + appversion
data = self.encryptAES(data) data = self.encryptAES(data)
self.exchange(self.cla, 0x00, 0x00, 0x00, data) self.exchange(self.cla, 0x00, 0x00, 0x00, data)
@ -175,12 +190,12 @@ class HexLoader:
result.append(item) result.append(item)
return result return result
def load(self, erase_u8, max_length_per_apdu, hexAreas, bootaddr): def load(self, erase_u8, max_length_per_apdu, hexFile):
initialAddress = 0 initialAddress = 0
if (len(hexAreas) != 0) and self.relative: if self.relative:
initialAddress = hexAreas[0].getStart() initialAddress = hexFile.minAddr()
sha256 = hashlib.new('sha256') sha256 = hashlib.new('sha256')
for area in hexAreas: for area in hexFile.getAreas():
startAddress = area.getStart() - initialAddress startAddress = area.getStart() - initialAddress
data = area.getData() data = area.getData()
self.selectSegment(startAddress) self.selectSegment(startAddress)
@ -192,8 +207,8 @@ class HexLoader:
offset = 0 offset = 0
length = len(data) length = len(data)
while (length > 0): while (length > 0):
if length > max_length_per_apdu: if length > max_length_per_apdu - LOAD_SEGMENT_CHUNK_HEADER_LENGTH - MIN_PADDING_LENGTH:
chunkLen = max_length_per_apdu chunkLen = max_length_per_apdu - LOAD_SEGMENT_CHUNK_HEADER_LENGTH - MIN_PADDING_LENGTH
else: else:
chunkLen = length chunkLen = length
chunk = data[offset : offset + chunkLen] chunk = data[offset : offset + chunkLen]
@ -205,9 +220,9 @@ class HexLoader:
self.crcSegment(0, len(data), crc) self.crcSegment(0, len(data), crc)
return sha256.hexdigest() return sha256.hexdigest()
def run(self, hexAreas, bootaddr, signature=None): def run(self, hexFile, bootaddr, signature=None):
initialAddress = 0 initialAddress = 0
if (len(hexAreas) != 0) and self.relative: if self.relative:
initialAddress = hexAreas[0].getStart() initialAddress = hexFile.minAddr()
self.boot(bootaddr - initialAddress, signature) self.boot(bootaddr - initialAddress, signature)

View File

@ -18,139 +18,199 @@
""" """
class IntelHexArea: class IntelHexArea:
def __init__(self, start, data): def __init__(self, start, data):
self.start = start self.start = start
self.data = data self.data = data
self.bootAddr = 0
def getStart(self): def getStart(self):
return self.start return self.start
def getData(self):
return self.data
def insertAreaSorted(areas, area):
i=0
while i < len(areas):
if area.start < areas[i].start:
break
i+=1
#areas = areas[0:i] + [area]
areas[i:i] = [area]
return areas
def getData(self):
return self.data
class IntelHexParser: class IntelHexParser:
def __init__(self, fileName): # order by start address
self.areas = [] def _addArea(self, area):
lineNumber = 0 self.areas = insertAreaSorted(self.areas, area)
startZone = None
startFirst = None
current = None
zoneData = b''
file = open(fileName, "r")
for data in file:
lineNumber += 1
data = data.rstrip('\r\n')
if len(data) == 0:
continue
if data[0] != ':':
raise Exception("Invalid data at line %d" % lineNumber)
data = bytearray.fromhex(data[1:]) #binascii.unhexlify(data[1:])
count = data[0]
address = (data[1] << 8) + data[2]
recordType = data[3]
if recordType == 0x00:
if startZone == None:
raise Exception("Data record but no zone defined at line " + lineNumber)
if startFirst == None:
startFirst = address
current = startFirst
if address != current:
self.areas.append(IntelHexArea((startZone << 16) + startFirst, zoneData))
zoneData = ""
startFirst = address
current = address
zoneData += data[4:4 + count]
current += count
if recordType == 0x01:
if len(zoneData) != 0:
self.areas.append(IntelHexArea((startZone << 16) + startFirst, zoneData))
zoneData = ""
startZone = None
startFirst = None
current = None
if recordType == 0x02:
raise Exception("Unsupported record 02")
if recordType == 0x03:
raise Exception("Unsupported record 03")
if recordType == 0x04:
if len(zoneData) != 0:
self.areas.append(IntelHexArea((startZone << 16) + startFirst, zoneData))
zoneData = ""
startZone = None
startFirst = None
current = None
startZone = (data[4] << 8) + data[5]
if recordType == 0x05:
self.bootAddr = ((data[4]&0xFF) << 24) + ((data[5]&0xFF) << 16) + ((data[6]&0xFF) << 8) + (data[7]&0xFF)
file.close()
def getAreas(self): def __init__(self, fileName):
return self.areas self.bootAddr = 0
self.areas = []
lineNumber = 0
startZone = None
startFirst = None
current = None
zoneData = b''
file = open(fileName, "r")
for data in file:
lineNumber += 1
data = data.rstrip('\r\n')
if len(data) == 0:
continue
if data[0] != ':':
raise Exception("Invalid data at line %d" % lineNumber)
data = bytearray.fromhex(data[1:])
count = data[0]
address = (data[1] << 8) + data[2]
recordType = data[3]
if recordType == 0x00:
if startZone == None:
raise Exception("Data record but no zone defined at line " + lineNumber)
if startFirst == None:
startFirst = address
current = startFirst
if address != current:
self._addArea(IntelHexArea((startZone << 16) + startFirst, zoneData))
zoneData = ""
startFirst = address
current = address
zoneData += data[4:4 + count]
current += count
if recordType == 0x01:
if len(zoneData) != 0:
self._addArea(IntelHexArea((startZone << 16) + startFirst, zoneData))
zoneData = ""
startZone = None
startFirst = None
current = None
if recordType == 0x02:
raise Exception("Unsupported record 02")
if recordType == 0x03:
raise Exception("Unsupported record 03")
if recordType == 0x04:
if len(zoneData) != 0:
self._addArea(IntelHexArea((startZone << 16) + startFirst, zoneData))
zoneData = ""
startZone = None
startFirst = None
current = None
startZone = (data[4] << 8) + data[5]
if recordType == 0x05:
self.bootAddr = ((data[4]&0xFF) << 24) + ((data[5]&0xFF) << 16) + ((data[6]&0xFF) << 8) + (data[7]&0xFF)
file.close()
def getBootAddr(self): def getAreas(self):
return self.bootAddr return self.areas
def getBootAddr(self):
return self.bootAddr
def maxAddr(self): def maxAddr(self):
addr = 0 addr = 0
for a in self.areas: for a in self.areas:
if (a.start+len(a.data) > addr): if (a.start+len(a.data) > addr):
addr = a.start+len(a.data) addr = a.start+len(a.data)
return addr return addr
def minAddr(self):
addr = 0xFFFFFFFF
for a in self.areas:
if (a.start < addr):
addr = a.start
return addr
import binascii import binascii
class IntelHexPrinter: class IntelHexPrinter:
def __init__(self, parser=None, eol="\r\n"): def addArea(self, startaddress, data):
self.areas = [] #order by start address
self.eol = eol #self.areas.append(IntelHexArea(startaddress, data))
self.bootAddr = 0 self.areas = insertAreaSorted(self.areas, IntelHexArea(startaddress, data))
# build bound to the parser
if (parser):
self.areas = parser.areas
self.bootAddr = parser.bootAddr
def addArea(self, startaddress, data):
self.areas.append(IntelHexArea(startaddress, data))
def setBootAddr(self, bootAddr): def __init__(self, parser=None, eol="\r\n"):
self.bootAddr = int(bootAddr) self.areas = []
self.eol = eol
self.bootAddr = 0
# build bound to the parser
if (parser):
for a in parser.areas:
self.addArea(a.start, a.data);
self.bootAddr = parser.bootAddr
def checksum(self, bin): def getAreas(self):
cks = 0 return self.areas
for b in bin:
cks += b
cks = (-cks) & 0x0FF
return cks
def _emit_binary(self, file, bin): def getBootAddr(self):
cks = self.checksum(bin) return self.bootAddr
file.write((":" + binascii.hexlify(bin) + hex(0x100+cks)[3:] + self.eol).upper())
def writeTo(self, fileName, blocksize=32): def maxAddr(self):
file = open(fileName, "w") addr = 0
for area in self.areas: for a in self.areas:
off = 0 if (a.start+len(a.data) > addr):
# force the emission of selection record at start addr = a.start+len(a.data)
oldoff = area.start + 0x10000 return addr
while off < len(area.data):
# emit a offset selection record
if ((off & 0xFFFF0000) != (oldoff & 0xFFFF0000) ):
self._emit_binary(file, bytearray(("02000004" + hex(0x10000+(area.start>>16))[3:7]).decode('hex')))
# emit data record def minAddr(self):
if (off+blocksize > len(area.data)): addr = 0xFFFFFFFF
self._emit_binary(file, bytearray((hex(0x100+(len(area.data)-off))[3:] + hex(0x10000+off+(area.start&0xFFFF))[3:] + "00").decode('hex')) + area.data[off:len(area.data)]) for a in self.areas:
else: if (a.start < addr):
self._emit_binary(file, bytearray((hex(0x100+blocksize)[3:] + hex(0x10000+off+(area.start&0xFFFF))[3:] + "00").decode('hex')) + area.data[off:off+blocksize]) addr = a.start
return addr
oldoff = off; def setBootAddr(self, bootAddr):
off += blocksize self.bootAddr = int(bootAddr)
def checksum(self, bin):
cks = 0
for b in bin:
cks += b
cks = (-cks) & 0x0FF
return cks
def _emit_binary(self, file, bin):
cks = self.checksum(bin)
s = (":" + binascii.hexlify(bin) + hex(0x100+cks)[3:] + self.eol).upper()
if (file != None):
file.write(s)
else:
print(s)
def writeTo(self, fileName, blocksize=32):
file = None
if(fileName != None):
file = open(fileName, "w")
for area in self.areas:
off = 0
# force the emission of selection record at start
oldoff = area.start + 0x10000
while off < len(area.data):
# emit a offset selection record
if ((off & 0xFFFF0000) != (oldoff & 0xFFFF0000) ):
self._emit_binary(file, bytearray(("02000004" + hex(0x10000+(area.start>>16))[3:7]).decode('hex')))
# emit data record
if (off+blocksize > len(area.data)):
self._emit_binary(file, bytearray((hex(0x100+(len(area.data)-off))[3:] + hex(0x10000+off+(area.start&0xFFFF))[3:] + "00").decode('hex')) + area.data[off:len(area.data)])
else:
self._emit_binary(file, bytearray((hex(0x100+blocksize)[3:] + hex(0x10000+off+(area.start&0xFFFF))[3:] + "00").decode('hex')) + area.data[off:off+blocksize])
oldoff = off;
off += blocksize
bootAddrHex = hex(0x100000000+self.bootAddr)[3:] bootAddrHex = hex(0x100000000+self.bootAddr)[3:]
file.write(":04000005"+bootAddrHex+hex(0x100+self.checksum( bytearray(("04000005"+bootAddrHex).decode('hex'))))[3:]+self.eol) s = ":04000005"+bootAddrHex+hex(0x100+self.checksum( bytearray(("04000005"+bootAddrHex).decode('hex'))))[3:]+self.eol
if (file != None):
file.write(s)
else:
print(s)
file.write(":00000001FF"+self.eol) s = ":00000001FF"+self.eol
file.close() if (file != None):
file.write(s)
else:
print(s)
if (file != None):
file.close()

View File

@ -17,9 +17,11 @@
******************************************************************************** ********************************************************************************
""" """
DEFAULT_ALIGNMENT = 1024
from .ecWrapper import PrivateKey from .ecWrapper import PrivateKey
from .comm import getDongle from .comm import getDongle
from .hexParser import IntelHexParser from .hexParser import IntelHexParser, IntelHexPrinter
from .hexLoader import HexLoader from .hexLoader import HexLoader
from .deployed import getDeployedSecretV1, getDeployedSecretV2 from .deployed import getDeployedSecretV1, getDeployedSecretV2
import argparse import argparse
@ -59,6 +61,9 @@ parser.add_argument("--rootPrivateKey", help="Set the root private key")
parser.add_argument("--apdu", help="Display APDU log", action='store_true') parser.add_argument("--apdu", help="Display APDU log", action='store_true')
parser.add_argument("--deployLegacy", help="Use legacy deployment API", action='store_true') parser.add_argument("--deployLegacy", help="Use legacy deployment API", action='store_true')
parser.add_argument("--apilevel", help="Use given API level when interacting with the device", type=auto_int) parser.add_argument("--apilevel", help="Use given API level when interacting with the device", type=auto_int)
parser.add_argument("--delete", help="Delete app before installing it", action='store_true')
parser.add_argument("--params", help="Store icon and install parameters in a parameter section before the code", action='store_true')
parser.add_argument("--appVersion", help="Set the application version (text)")
args = parser.parse_args() args = parser.parse_args()
@ -115,7 +120,34 @@ else:
if len(args.path) > 1: if len(args.path) > 1:
print("Multiple path levels not supported using this API level, ignoring") print("Multiple path levels not supported using this API level, ignoring")
else: else:
path = parse_bip32_path(args.path[0], args.apilevel) path = parse_bip32_path(args.path[0], args.apilevel)
icon = None
if not args.icon is None:
icon = bytearray.fromhex(args.icon)
signature = None
if not args.signature is None:
signature = bytearray.fromhex(args.signature)
#prepend app's data with the icon content (could also add other various install parameters)
printer = IntelHexPrinter(parser)
#todo build a TLV zone to keep install params
#todo dney nvm_write in that section ?
paramsSectionContent = []
if icon:
paramsSectionContent = icon
# prepend the param section (arbitrary)
if (args.params):
#take care of aligning the parameters sections to avoid possible invalid dereference of aligned words in the program nvram.
#also use the default MPU alignment
param_start = printer.minAddr()-len(paramsSectionContent)-(DEFAULT_ALIGNMENT-(len(paramsSectionContent)%DEFAULT_ALIGNMENT))
printer.addArea(param_start, paramsSectionContent)
# account for added regions (install parameters, icon ...)
appLength = printer.maxAddr() - printer.minAddr()
dongle = getDongle(args.apdu) dongle = getDongle(args.apdu)
@ -125,23 +157,15 @@ else:
secret = getDeployedSecretV2(dongle, bytearray.fromhex(args.rootPrivateKey), args.targetId) secret = getDeployedSecretV2(dongle, bytearray.fromhex(args.rootPrivateKey), args.targetId)
loader = HexLoader(dongle, 0xe0, True, secret) loader = HexLoader(dongle, 0xe0, True, secret)
if (not (args.appFlags & 2)): if (not (args.appFlags & 2)) and args.delete:
loader.deleteApp(args.appName) loader.deleteApp(args.appName)
appLength = 0 #heuristic to guess how to pass the icon
for area in parser.getAreas(): if (args.params):
appLength += len(area.getData()) loader.createApp(args.appFlags, appLength, args.appName, None, path, 0, len(paramsSectionContent), args.appVersion)
else:
loader.createApp(args.appFlags, appLength, args.appName, icon, path, None, None, args.appVersion)
icon = None hash = loader.load(0x0, 0xF0, printer)
if args.icon != None:
icon = bytes(bytearray.fromhex(args.icon))
signature = None
if args.signature != None:
signature = bytes(bytearray.fromhex(args.signature))
loader.createApp(args.appFlags, appLength, args.appName, icon, path)
hash = loader.load(0x0, 0xE0, parser.getAreas(), args.bootAddr)
print("Application hash : " + hash) print("Application hash : " + hash)
loader.run(parser.getAreas(), args.bootAddr, signature) loader.run(printer, args.bootAddr, signature)

View File

@ -47,5 +47,5 @@ dongle = getDongle(args.apdu)
loader = HexLoader(dongle, 0xe0, False, None, False) loader = HexLoader(dongle, 0xe0, False, None, False)
loader.validateTargetId(args.targetId) loader.validateTargetId(args.targetId)
hash = loader.load(0xFF, 0xF0, parser.getAreas(), args.bootAddr) hash = loader.load(0xFF, 0xF0, parser)
loader.run(parser.getAreas(), args.bootAddr) loader.run(parser.getAreas(), args.bootAddr)