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
@ -138,12 +141,24 @@ class HexLoader:
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

@ -21,7 +21,6 @@ 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
@ -29,8 +28,24 @@ class IntelHexArea:
def getData(self): def getData(self):
return self.data 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
class IntelHexParser: class IntelHexParser:
# order by start address
def _addArea(self, area):
self.areas = insertAreaSorted(self.areas, area)
def __init__(self, fileName): def __init__(self, fileName):
self.bootAddr = 0
self.areas = [] self.areas = []
lineNumber = 0 lineNumber = 0
startZone = None startZone = None
@ -45,7 +60,7 @@ class IntelHexParser:
continue continue
if data[0] != ':': if data[0] != ':':
raise Exception("Invalid data at line %d" % lineNumber) raise Exception("Invalid data at line %d" % lineNumber)
data = bytearray.fromhex(data[1:]) #binascii.unhexlify(data[1:]) data = bytearray.fromhex(data[1:])
count = data[0] count = data[0]
address = (data[1] << 8) + data[2] address = (data[1] << 8) + data[2]
recordType = data[3] recordType = data[3]
@ -56,7 +71,7 @@ class IntelHexParser:
startFirst = address startFirst = address
current = startFirst current = startFirst
if address != current: if address != current:
self.areas.append(IntelHexArea((startZone << 16) + startFirst, zoneData)) self._addArea(IntelHexArea((startZone << 16) + startFirst, zoneData))
zoneData = "" zoneData = ""
startFirst = address startFirst = address
current = address current = address
@ -64,7 +79,7 @@ class IntelHexParser:
current += count current += count
if recordType == 0x01: if recordType == 0x01:
if len(zoneData) != 0: if len(zoneData) != 0:
self.areas.append(IntelHexArea((startZone << 16) + startFirst, zoneData)) self._addArea(IntelHexArea((startZone << 16) + startFirst, zoneData))
zoneData = "" zoneData = ""
startZone = None startZone = None
startFirst = None startFirst = None
@ -75,7 +90,7 @@ class IntelHexParser:
raise Exception("Unsupported record 03") raise Exception("Unsupported record 03")
if recordType == 0x04: if recordType == 0x04:
if len(zoneData) != 0: if len(zoneData) != 0:
self.areas.append(IntelHexArea((startZone << 16) + startFirst, zoneData)) self._addArea(IntelHexArea((startZone << 16) + startFirst, zoneData))
zoneData = "" zoneData = ""
startZone = None startZone = None
startFirst = None startFirst = None
@ -98,20 +113,50 @@ class IntelHexParser:
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 addArea(self, startaddress, data):
#order by start address
#self.areas.append(IntelHexArea(startaddress, data))
self.areas = insertAreaSorted(self.areas, IntelHexArea(startaddress, data))
def __init__(self, parser=None, eol="\r\n"): def __init__(self, parser=None, eol="\r\n"):
self.areas = [] self.areas = []
self.eol = eol self.eol = eol
self.bootAddr = 0 self.bootAddr = 0
# build bound to the parser # build bound to the parser
if (parser): if (parser):
self.areas = parser.areas for a in parser.areas:
self.addArea(a.start, a.data);
self.bootAddr = parser.bootAddr self.bootAddr = parser.bootAddr
def addArea(self, startaddress, data): def getAreas(self):
self.areas.append(IntelHexArea(startaddress, data)) return self.areas
def getBootAddr(self):
return self.bootAddr
def maxAddr(self):
addr = 0
for a in self.areas:
if (a.start+len(a.data) > addr):
addr = a.start+len(a.data)
return addr
def minAddr(self):
addr = 0xFFFFFFFF
for a in self.areas:
if (a.start < addr):
addr = a.start
return addr
def setBootAddr(self, bootAddr): def setBootAddr(self, bootAddr):
self.bootAddr = int(bootAddr) self.bootAddr = int(bootAddr)
@ -125,9 +170,15 @@ class IntelHexPrinter:
def _emit_binary(self, file, bin): def _emit_binary(self, file, bin):
cks = self.checksum(bin) cks = self.checksum(bin)
file.write((":" + binascii.hexlify(bin) + hex(0x100+cks)[3:] + self.eol).upper()) 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): def writeTo(self, fileName, blocksize=32):
file = None
if(fileName != None):
file = open(fileName, "w") file = open(fileName, "w")
for area in self.areas: for area in self.areas:
off = 0 off = 0
@ -148,9 +199,18 @@ class IntelHexPrinter:
off += blocksize 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
if (file != None):
file.write(s)
else:
print(s)
if (file != None):
file.close() 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()
@ -117,6 +122,33 @@ else:
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)
if args.deployLegacy: if args.deployLegacy:
@ -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)