From eea78c8f5661faddaa55a9fda1500ccb541535a9 Mon Sep 17 00:00:00 2001 From: chenzitai Date: Tue, 17 Jul 2018 15:43:09 +0100 Subject: [PATCH 01/20] Decode sniffing data with parityBit in Python Software If parity bit check failed, ! is appended to the data and raw data is diaplayed Currently Software layer parity bit check and removal is only enabled in sniffing mode (cherry picked from commit 2cfd54a) --- Software/Chameleon/Log.py | 47 +++++++++++++++++++++++++++++++++++++++ Software/chamlog.py | 1 + 2 files changed, 48 insertions(+) diff --git a/Software/Chameleon/Log.py b/Software/Chameleon/Log.py index bcf7a24..168704c 100644 --- a/Software/Chameleon/Log.py +++ b/Software/Chameleon/Log.py @@ -2,6 +2,40 @@ import struct import binascii +import math + +def checkParityBit(data): + byteCount = len(data) + # Short frame, no parityBit + if (byteCount == 1): + return (True, data) + + # 9 bit is a group, validate bit count is calculated below + bitCount = int((byteCount*8)/9) * 9 + parsedData = bytearray(int(bitCount/9)) + + oneCounter = 0 # Counter for count ones in a byte + for i in range(0, bitCount): + # Get bit i in data + byteIndex = math.floor(i/8) + bitIndex = i % 8 + bit = (data[byteIndex] >> bitIndex) & 0x01 + + # Check parityBit + # Current bit is parityBit + if(i % 9 == 8): + # Even number of ones in current byte + if(oneCounter % 2 and bit == 1): + return (False, data) + # Odd number of ones in current byte + elif((not oneCounter % 2) and bit == 0): + return (False, data) + oneCounter = 0 + # Current bit is normal bit + else: + oneCounter += bit + parsedData[int(i/9)] |= bit << (i%9) + return (True, parsedData) def noDecoder(data): return "" @@ -12,6 +46,13 @@ def textDecoder(data): def binaryDecoder(data): return binascii.hexlify(data).decode() +def binaryParityDecoder(data): + isValid, checkedData = checkParityBit(data) + if(isValid): + return binascii.hexlify(checkedData).decode() + else: + return binascii.hexlify(checkedData).decode()+"!" + eventTypes = { 0x00: { 'name': 'EMPTY', 'decoder': noDecoder }, 0x10: { 'name': 'GENERIC', 'decoder': textDecoder }, @@ -25,6 +66,12 @@ eventTypes = { 0x42: { 'name': 'CODEC RX W/PARITY', 'decoder': binaryDecoder }, 0x43: { 'name': 'CODEC TX W/PARITY', 'decoder': binaryDecoder }, + 0x44: { 'name': 'CODEC RX SNI READER', 'decoder': binaryDecoder }, + 0x45: { 'name': 'CODEC RX SNI READER W/PARITY', 'decoder': binaryParityDecoder }, + 0x46: { 'name': 'CODEC RX SNI CARD', 'decoder': binaryDecoder }, + 0x47: { 'name': 'CODEC RX SNI CARD W/PARITY', 'decoder': binaryParityDecoder }, + + 0x80: { 'name': 'APP READ', 'decoder': binaryDecoder }, 0x81: { 'name': 'APP WRITE', 'decoder': binaryDecoder }, 0x84: { 'name': 'APP INC', 'decoder': binaryDecoder }, diff --git a/Software/chamlog.py b/Software/chamlog.py index e73d503..f644379 100755 --- a/Software/chamlog.py +++ b/Software/chamlog.py @@ -58,6 +58,7 @@ def main(): else: verboseFunc = None + print("\nNote: If parityBit check failed, '!' is appended to the decoded data and raw data with parity bit is displayed.\n") if (args.live): # Live logging mode if (args.port is not None): From 74010123cde6c99bcaeac64b5ab8b83a657f0c5c Mon Sep 17 00:00:00 2001 From: chenzitai Date: Thu, 19 Jul 2018 13:49:35 +0100 Subject: [PATCH 02/20] Add basic ISO14443 log data parser for Reader side traffic Can parse: SELECT, HALE, RATS, PPS, DESELECT (cherry picked from commit aa46598) --- Software/Chameleon/ISO14443.py | 87 ++++++++++++++++++++++++++++++++++ Software/Chameleon/Log.py | 12 ++++- Software/chamlog.py | 8 ++-- 3 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 Software/Chameleon/ISO14443.py diff --git a/Software/Chameleon/ISO14443.py b/Software/Chameleon/ISO14443.py new file mode 100644 index 0000000..a04d253 --- /dev/null +++ b/Software/Chameleon/ISO14443.py @@ -0,0 +1,87 @@ +import binascii + +ReaderTrafficTypes = { + "SEL":{ + 0x93: "SEL_CL1 ", + 0x95: "SEL_CL2 ", + 0x97: "SEL_CL3 ", + }, + # 1 byte commands + "SHORTFRAME": { + 0x26: "REQA", + 0x52: "WUPA", + 0x35: "Optional Timeslot Method", + # 40 - 4F Proprietary + # 78 - 7F proprietary + # Other RFU + }, + "FSDI":{ + 0x0: "FSD:16 ", + 0x1: "FSD:24 ", + 0x2: "FSD:32 ", + 0x3: "FSD:40 ", + 0x4: "FSD:48 ", + 0x5: "FSD:64 ", + 0x6: "FSD:96 ", + 0x7: "FSD:128 ", + 0x8: "FSD:256 " + }, +} + +CardTrafficTypes = { + +} +def parseReader(data): + byteCount = len(data) + note = "" + + # short frame commands + if (byteCount == 1 and data[0] in ReaderTrafficTypes["SHORTFRAME"]): + note += ReaderTrafficTypes["SHORTFRAME"][data[0]] + + # ANTICOLLISION command + elif (byteCount<9 and byteCount > 1 and data[0] in ReaderTrafficTypes["SEL"] and data[1] & 0x88 == 0 ): + note += "ATCOLI - " + note += ReaderTrafficTypes["SEL"][data[0]] + # note += "UID_CLn:" + binascii.hexlify(data[2:7]).decode() + " " + # note += str((data[1] >> 4) & 0x0f) + "bytes + " + str(data[1] & 0x0f) + "bits " + + # SELECT Command + elif (byteCount== 9 and data[0] in ReaderTrafficTypes["SEL"] and data[1] == 0x70): + note += "SELECT - " + note += ReaderTrafficTypes["SEL"][data[0]] + note += "UID_CLn:" + binascii.hexlify(data[2:7]).decode() + " " + # note += "7bytes + 0bits " + # note += "CRC_A:"+data[8] + # HALT Command + elif (byteCount == 4 and data[0] == 0x50 and data[1] == 0x00): + note += "HALT" + # RATS + elif (byteCount == 4 and data[0] == 0xe0 and ((data[1] & 0x0f) < 15) and ((data[1] & 0xf0 >> 8) in ReaderTrafficTypes["FSDI"])): + note += "RATS - " + note += ReaderTrafficTypes["FSDI"][data[1]>>8] + note += "CID:" + str(data[1]&0x0f) + " " + # PPS Protocol and parameter selection request + # PSS0 only + elif (byteCount == 4 and (data[0]&0xf0 == 0xd0) and (data[1] == 0x01)): + note += "PSS0 - " + note += "CID:"+str(data[1]&0x0f) + " " + # PSS0+1 + elif (byteCount == 5 and (data[0]&0xf0 == 0xd0) and (data[1] == 0x11) and (data[2]&0xf0 == 0x00)): + note += "PSS0+1 - " + note += "CID:"+str(data[1]&0x0f) + " " + note += "DSI:"+str(pow(2,(data[2]>>2)&0x03)) + " " + note += "DRI:"+str(pow(2,data[2]&0x03)) + " " + # BLOCK S-block PCB DESELECT + # Without CID + elif (byteCount == 3 and data[0] == 0xc2): + note += "DESEL" + # With CID + elif (byteCount == 4 and data[0] == 0xca and data[1]&0x30 == 0x00): + note += "DESEL - " + note += "CID:"+ str(data[1]&0x0f) + " " + + return note + +def parseCard(data): + pass \ No newline at end of file diff --git a/Software/Chameleon/Log.py b/Software/Chameleon/Log.py index 168704c..61d2098 100644 --- a/Software/Chameleon/Log.py +++ b/Software/Chameleon/Log.py @@ -3,6 +3,7 @@ import struct import binascii import math +import Chameleon.ISO14443 as iso14443_3 def checkParityBit(data): byteCount = len(data) @@ -96,7 +97,7 @@ eventTypes = { TIMESTAMP_MAX = 65536 eventTypes = { i : ({'name': 'UNKNOWN', 'decoder': binaryDecoder} if i not in eventTypes.keys() else eventTypes[i]) for i in range(256) } -def parseBinary(binaryStream): +def parseBinary(binaryStream, decode=False): log = [] # Completely read file contents and process them byte by byte @@ -135,13 +136,20 @@ def parseBinary(binaryStream): if (deltaTimestamp < 0): deltaTimestamp += TIMESTAMP_MAX; + note = "" + # If we need to decode the data + if (decode): + # Decode the data from Reader + if(event == 0x44 or event == 0x45): + note = iso14443_3.parseReader(binascii.a2b_hex(logData)) # Create log entry as dict and append it to event list logEntry = { 'eventName': eventTypes[event]['name'], 'dataLength': dataLength, 'timestamp': timestamp, 'deltaTimestamp': deltaTimestamp, - 'data': logData + 'data': logData, + 'note': note } log.append(logEntry) diff --git a/Software/chamlog.py b/Software/chamlog.py index f644379..da4281a 100755 --- a/Software/chamlog.py +++ b/Software/chamlog.py @@ -19,10 +19,11 @@ def verboseLog(text): def formatText(log): formatString = '{timestamp:0>5d} ms <{deltaTimestamp:>+6d} ms>:' - formatString += '{eventName:<16} ({dataLength:<3} bytes) [{data}]\n' + formatString += '{eventName:<28} ({dataLength:<3} bytes)\t[{data:<20}]\t{note}\n' text = '' for logEntry in log: + text += formatString.format(**logEntry) return text @@ -46,6 +47,7 @@ def main(): argParser.add_argument("-t", "--type", choices=outputTypes.keys(), default='text', help="specifies output type") + argParser.add_argument("-d", "--decode", dest="decode", action='store_true', default=False) argParser.add_argument("-l", "--live", dest="live", action='store_true', help="Use live logging capabilities of Chameleon") argParser.add_argument("-c", "--clear", dest="clear", action='store_true', help="Clear Chameleon's log memory when using -p") argParser.add_argument("-m", "--mode", dest="mode", metavar="LOGMODE", help="Additionally set Chameleon's log mode after reading it's memory") @@ -69,7 +71,7 @@ def main(): while True: stream = io.BytesIO(chameleon.read()) - log = Chameleon.Log.parseBinary(stream) + log = Chameleon.Log.parseBinary(stream, args.decode) if (len(log) > 0): print(outputTypes[args.type](log)) @@ -95,7 +97,7 @@ def main(): sys.exit(2) # Parse actual logfile - log = Chameleon.Log.parseBinary(handle) + log = Chameleon.Log.parseBinary(handle, args.decode) # Print to console using chosen output type print(outputTypes[args.type](log)) From 7cc3793033f135858926c121b24bee476f26d0e9 Mon Sep 17 00:00:00 2001 From: chenzitai Date: Sun, 22 Jul 2018 00:09:10 +0100 Subject: [PATCH 03/20] Add commands for setting threshold and upgrade (cherry picked from commit d43975e) --- Software/Chameleon/Device.py | 14 +++++++++++++- Software/chamtool.py | 21 ++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Software/Chameleon/Device.py b/Software/Chameleon/Device.py index 57169c7..1852cf0 100644 --- a/Software/Chameleon/Device.py +++ b/Software/Chameleon/Device.py @@ -21,7 +21,10 @@ class Device: COMMAND_RBUTTON = "RBUTTON" COMMAND_GREEN_LED = "LEDGREEN" COMMAND_RED_LED = "LEDRED" - + COMMAND_THRESHOLD = "THRESHOLD" + COMMAND_UPGRADE = "upgrade" + + STATUS_CODE_OK = 100 STATUS_CODE_OK_WITH_TEXT = 101 STATUS_CODE_WAITING_FOR_XMODEM = 110 @@ -240,3 +243,12 @@ class Device: return self.getCmdSuggestions(self.COMMAND_RED_LED) else: return self.getSetCmd(self.COMMAND_RED_LED, newFunction) + + def cmdThreshold(self, value): + if(value == self.SUGGEST_CHAR): + return self.getCmdSuggestions(self.COMMAND_THRESHOLD) + else: + return self.getSetCmd(self.COMMAND_THRESHOLD, value) + + def cmdUpgrade(self): + return self.execCmd(self.COMMAND_UPGRADE) \ No newline at end of file diff --git a/Software/chamtool.py b/Software/chamtool.py index 6f97a96..0e964ee 100755 --- a/Software/chamtool.py +++ b/Software/chamtool.py @@ -119,7 +119,21 @@ def cmdRedLED(chameleon, arg): return "Red LED function has been set to {}".format(chameleon.cmdRedLED()['response']) else: return "Setting red LED function to {} failed: {}".format(arg, result['statusText']) - + +def cmdThreshold(chameleon, arg): + result = chameleon.cmdThreshold(arg) + + if (arg is None): + return "Current threshold is: {}".format(result['response']) + else: + if (result['statusCode'] in chameleon.STATUS_CODES_SUCCESS): + return "Threshold have been set to {}".format(arg) + else: + return "Setting threshold faled: {}".format(arg, result['statusText']) + +def cmdUpgrade(chameleon, arg): + result = chameleon.cmdUpgrade() + return "" # Custom class for argparse class CmdListAction(argparse.Action): def __init__(self, option_strings, dest, default=False, required=False, @@ -154,6 +168,9 @@ def main(): cmdArgGroup.add_argument("-rb", "--rbutton", dest="rbutton", action=CmdListAction, metavar="ACTION", nargs='?', help="retrieve or set the current right button action") cmdArgGroup.add_argument("-gl", "--gled", dest="gled", action=CmdListAction, metavar="FUNCTION", nargs='?', help="retrieve or set the current green led function") cmdArgGroup.add_argument("-rl", "--rled", dest="rled", action=CmdListAction, metavar="FUNCTION", nargs='?', help="retrieve or set the current red led function") + cmdArgGroup.add_argument("-th", "--threshold", dest="threshold", action=CmdListAction, nargs='?', help="retrieve or set the threshold") + cmdArgGroup.add_argument("-ug", "--upgrade", dest="upgrade", action=CmdListAction, nargs=0, help="set the micro Controller to upgrade mode") + args = argParser.parse_args() if (args.verbose): @@ -179,6 +196,8 @@ def main(): "rbutton" : cmdRButton, "gled" : cmdGreenLED, "rled" : cmdRedLED, + "threshold" : cmdThreshold, + "upgrade" : cmdUpgrade, } if hasattr(args, "cmdList"): From c145976f3fcadcd185caeb46e3b938b177f18bcf Mon Sep 17 00:00:00 2001 From: chenzitai Date: Wed, 25 Jul 2018 00:51:40 +0100 Subject: [PATCH 04/20] Add CRC_A check function for Decode Reader SELECT and RATS command (cherry picked from commit 4eec449) --- Software/Chameleon/ISO14443.py | 29 ++++++++++++++++++++++++++++- Software/requirements.txt | 1 + 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Software/Chameleon/ISO14443.py b/Software/Chameleon/ISO14443.py index a04d253..4956232 100644 --- a/Software/Chameleon/ISO14443.py +++ b/Software/Chameleon/ISO14443.py @@ -1,4 +1,10 @@ import binascii +import crcmod + +# Parameters for CRC_A +CRC_INIT = 0x6363 +POLY = 0x11021 +CRC_A_func = crcmod.mkCrcFun(POLY, initCrc=CRC_INIT, xorOut=0) ReaderTrafficTypes = { "SEL":{ @@ -31,6 +37,22 @@ ReaderTrafficTypes = { CardTrafficTypes = { } + +def CRC_A(data): + return CRC_A_func(data) + +def CRC_A_check(data): + datalen = len(data) + # Short frame or no space for CRC skip check + if(datalen < 4 ): + return + + crc = CRC_A(bytearray(data[0:datalen-2])).to_bytes(2,'little') + if (data[datalen-2:datalen] == crc): + return True + else: + return False + def parseReader(data): byteCount = len(data) note = "" @@ -51,8 +73,11 @@ def parseReader(data): note += "SELECT - " note += ReaderTrafficTypes["SEL"][data[0]] note += "UID_CLn:" + binascii.hexlify(data[2:7]).decode() + " " + # Check CRC for SELECT + if(not CRC_A_check(data)): + note+=" WRONG CRC " # note += "7bytes + 0bits " - # note += "CRC_A:"+data[8] + # note += "CRC_A:"+data[7:9] # HALT Command elif (byteCount == 4 and data[0] == 0x50 and data[1] == 0x00): note += "HALT" @@ -61,6 +86,8 @@ def parseReader(data): note += "RATS - " note += ReaderTrafficTypes["FSDI"][data[1]>>8] note += "CID:" + str(data[1]&0x0f) + " " + if(not CRC_A_check(data)): + note+=" WRONG CRC " # PPS Protocol and parameter selection request # PSS0 only elif (byteCount == 4 and (data[0]&0xf0 == 0xd0) and (data[1] == 0x01)): diff --git a/Software/requirements.txt b/Software/requirements.txt index f6c1a1f..5900a94 100644 --- a/Software/requirements.txt +++ b/Software/requirements.txt @@ -1 +1,2 @@ pyserial +crcmod \ No newline at end of file From 686cbf011e8f42f21297f7d9c7b148fe1c03ac8a Mon Sep 17 00:00:00 2001 From: chenzitai Date: Sat, 28 Jul 2018 01:28:55 +0100 Subject: [PATCH 05/20] Add decode functions for card response in anti collision phase (cherry picked from commit b4ca798) --- Software/Chameleon/ISO14443.py | 36 +++++++++++++++++++++++++++++----- Software/Chameleon/Log.py | 2 ++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/Software/Chameleon/ISO14443.py b/Software/Chameleon/ISO14443.py index 4956232..e5242b8 100644 --- a/Software/Chameleon/ISO14443.py +++ b/Software/Chameleon/ISO14443.py @@ -32,10 +32,15 @@ ReaderTrafficTypes = { 0x7: "FSD:128 ", 0x8: "FSD:256 " }, + } CardTrafficTypes = { - + "SAK":{ + 0x04: "UID NOT Complete ", + 0x20: "UID complete, PICC compliant with 14443-4 ", + 0x00: "UID complete, PICC NOT compliant with 14443-4" + } } def CRC_A(data): @@ -43,9 +48,9 @@ def CRC_A(data): def CRC_A_check(data): datalen = len(data) - # Short frame or no space for CRC skip check - if(datalen < 4 ): - return + # Short frame/SAK or no space for CRC skip check + if(datalen < 3 ): + return True crc = CRC_A(bytearray(data[0:datalen-2])).to_bytes(2,'little') if (data[datalen-2:datalen] == crc): @@ -70,9 +75,10 @@ def parseReader(data): # SELECT Command elif (byteCount== 9 and data[0] in ReaderTrafficTypes["SEL"] and data[1] == 0x70): + # TODO: distinguish CT+uid012+BCC and uid0123+BCC note += "SELECT - " note += ReaderTrafficTypes["SEL"][data[0]] - note += "UID_CLn:" + binascii.hexlify(data[2:7]).decode() + " " + note += "UID_CLn:" + binascii.hexlify(data[2:6]).decode() + " " # Check CRC for SELECT if(not CRC_A_check(data)): note+=" WRONG CRC " @@ -111,4 +117,24 @@ def parseReader(data): return note def parseCard(data): + + byteCount = len(data) + note = "" + + # ATQA: RRRR XXXX XXRX XXXX + if(byteCount == 2 and (data[0] & 0x20 == 0x00) and (data[1] & 0xf0 == 0x00)): + note += "ATQA - " + note += binascii.hexlify(data).decode() + # SAK + elif (byteCount == 3 and (data[0] & (~0x24)) == 0x00): + note += "SAK - " + note += CardTrafficTypes["SAK"][(data[2] & 0x24)] + if not CRC_A_check(data): + note += " WRONG CRC " + # UID + elif (byteCount == 5 and (data[0] ^ data[1] ^ data[2] ^ data[3]) == data[4] ): + note += "UID Resp - CLn " + + return note + pass \ No newline at end of file diff --git a/Software/Chameleon/Log.py b/Software/Chameleon/Log.py index 61d2098..aa0d7a9 100644 --- a/Software/Chameleon/Log.py +++ b/Software/Chameleon/Log.py @@ -142,6 +142,8 @@ def parseBinary(binaryStream, decode=False): # Decode the data from Reader if(event == 0x44 or event == 0x45): note = iso14443_3.parseReader(binascii.a2b_hex(logData)) + elif (event == 0x46 or event == 0x47): + note = iso14443_3.parseCard(binascii.a2b_hex(logData)) # Create log entry as dict and append it to event list logEntry = { 'eventName': eventTypes[event]['name'], From 56b17302dff67f1bafd666ddcb26676162b06b32 Mon Sep 17 00:00:00 2001 From: chenzitai Date: Sun, 29 Jul 2018 15:38:07 +0100 Subject: [PATCH 06/20] Add decoding ATS in sniffing mode (cherry picked from commit 1ef1af2) --- Software/Chameleon/ISO14443.py | 133 ++++++++++++++++++++++++++------- Software/Chameleon/Log.py | 5 +- 2 files changed, 110 insertions(+), 28 deletions(-) diff --git a/Software/Chameleon/ISO14443.py b/Software/Chameleon/ISO14443.py index e5242b8..94c60fa 100644 --- a/Software/Chameleon/ISO14443.py +++ b/Software/Chameleon/ISO14443.py @@ -1,11 +1,19 @@ import binascii import crcmod - +from enum import Enum # Parameters for CRC_A CRC_INIT = 0x6363 POLY = 0x11021 CRC_A_func = crcmod.mkCrcFun(POLY, initCrc=CRC_INIT, xorOut=0) + +class ReaderCMD(Enum): + NONE = 0 + RATS = 1 + PPS = 2 + +readerCMD = ReaderCMD.NONE + ReaderTrafficTypes = { "SEL":{ 0x93: "SEL_CL1 ", @@ -38,8 +46,20 @@ ReaderTrafficTypes = { CardTrafficTypes = { "SAK":{ 0x04: "UID NOT Complete ", + 0x24: "UID NOT Complete, PICC compliant with 14443-4", 0x20: "UID complete, PICC compliant with 14443-4 ", 0x00: "UID complete, PICC NOT compliant with 14443-4" + }, + "FSCI": { + 0x0: "FSCC:16 ", + 0x1: "FSC:24 ", + 0x2: "FSC:32 ", + 0x3: "FSC:40 ", + 0x4: "FSC:48 ", + 0x5: "FSC:64 ", + 0x6: "FSC:96 ", + 0x7: "FSC:128 ", + 0x8: "FSC:256 " } } @@ -58,66 +78,77 @@ def CRC_A_check(data): else: return False -def parseReader(data): +def parseReader_3(data): byteCount = len(data) note = "" # short frame commands if (byteCount == 1 and data[0] in ReaderTrafficTypes["SHORTFRAME"]): - note += ReaderTrafficTypes["SHORTFRAME"][data[0]] + note += ReaderTrafficTypes["SHORTFRAME"][data[0]] # ANTICOLLISION command - elif (byteCount<9 and byteCount > 1 and data[0] in ReaderTrafficTypes["SEL"] and data[1] & 0x88 == 0 ): + elif (byteCount < 9 and byteCount > 1 and data[0] in ReaderTrafficTypes["SEL"] and data[1] & 0x88 == 0): note += "ATCOLI - " note += ReaderTrafficTypes["SEL"][data[0]] # note += "UID_CLn:" + binascii.hexlify(data[2:7]).decode() + " " # note += str((data[1] >> 4) & 0x0f) + "bytes + " + str(data[1] & 0x0f) + "bits " # SELECT Command - elif (byteCount== 9 and data[0] in ReaderTrafficTypes["SEL"] and data[1] == 0x70): + elif (byteCount == 9 and data[0] in ReaderTrafficTypes["SEL"] and data[1] == 0x70): # TODO: distinguish CT+uid012+BCC and uid0123+BCC note += "SELECT - " note += ReaderTrafficTypes["SEL"][data[0]] note += "UID_CLn:" + binascii.hexlify(data[2:6]).decode() + " " # Check CRC for SELECT - if(not CRC_A_check(data)): - note+=" WRONG CRC " + if (not CRC_A_check(data)): + note += " WRONG CRC " # note += "7bytes + 0bits " # note += "CRC_A:"+data[7:9] # HALT Command elif (byteCount == 4 and data[0] == 0x50 and data[1] == 0x00): note += "HALT" + + return note + +def parseReader_4(data): + byteCount = len(data) + note = "" + # RATS - elif (byteCount == 4 and data[0] == 0xe0 and ((data[1] & 0x0f) < 15) and ((data[1] & 0xf0 >> 8) in ReaderTrafficTypes["FSDI"])): + if (byteCount == 4 and data[0] == 0xe0 and ((data[1] & 0x0f) < 15) and ( + (data[1] & 0xf0 >> 8) in ReaderTrafficTypes["FSDI"])): note += "RATS - " - note += ReaderTrafficTypes["FSDI"][data[1]>>8] - note += "CID:" + str(data[1]&0x0f) + " " - if(not CRC_A_check(data)): - note+=" WRONG CRC " + note += ReaderTrafficTypes["FSDI"][data[1] >> 8] + note += "CID:" + str(data[1] & 0x0f) + " " + if (not CRC_A_check(data)): + note += " WRONG CRC " + else: + readerCMD = ReaderCMD.RATS + # PPS Protocol and parameter selection request # PSS0 only - elif (byteCount == 4 and (data[0]&0xf0 == 0xd0) and (data[1] == 0x01)): + elif (byteCount == 4 and (data[0] & 0xf0 == 0xd0) and (data[1] == 0x01)): note += "PSS0 - " - note += "CID:"+str(data[1]&0x0f) + " " + note += "CID:" + str(data[1] & 0x0f) + " " + readerCMD = ReaderCMD.PPS # PSS0+1 - elif (byteCount == 5 and (data[0]&0xf0 == 0xd0) and (data[1] == 0x11) and (data[2]&0xf0 == 0x00)): + elif (byteCount == 5 and (data[0] & 0xf0 == 0xd0) and (data[1] == 0x11) and (data[2] & 0xf0 == 0x00)): note += "PSS0+1 - " - note += "CID:"+str(data[1]&0x0f) + " " - note += "DSI:"+str(pow(2,(data[2]>>2)&0x03)) + " " - note += "DRI:"+str(pow(2,data[2]&0x03)) + " " - # BLOCK S-block PCB DESELECT + note += "CID:" + str(data[1] & 0x0f) + " " + note += "DSI:" + str(pow(2, (data[2] >> 2) & 0x03)) + " " + note += "DRI:" + str(pow(2, data[2] & 0x03)) + " " + # BLOCK S-block PCB DESELECT # Without CID elif (byteCount == 3 and data[0] == 0xc2): note += "DESEL" # With CID - elif (byteCount == 4 and data[0] == 0xca and data[1]&0x30 == 0x00): + elif (byteCount == 4 and data[0] == 0xca and data[1] & 0x30 == 0x00): note += "DESEL - " - note += "CID:"+ str(data[1]&0x0f) + " " - + note += "CID:" + str(data[1] & 0x0f) + " " + return note -def parseCard(data): - +def parseCard_3(data): byteCount = len(data) note = "" @@ -126,7 +157,7 @@ def parseCard(data): note += "ATQA - " note += binascii.hexlify(data).decode() # SAK - elif (byteCount == 3 and (data[0] & (~0x24)) == 0x00): + elif (byteCount == 3 and ((data[0] & (0x24)) in CardTrafficTypes["SAK"])): note += "SAK - " note += CardTrafficTypes["SAK"][(data[2] & 0x24)] if not CRC_A_check(data): @@ -137,4 +168,54 @@ def parseCard(data): return note - pass \ No newline at end of file +def parseCard_4(data): + byteCount = len(data) + note = "" + + # ATS + # TL + T0 + TA + TB + TC + T1 ... + CRC + # TL: length without CRC + # ATS without data + if(byteCount == 3 and data[0] == (byteCount-2)): + note += "ATS - NO DATA" + # ATS with data mush have T0, T0 b8=0 + elif (byteCount > 3 and data[0] == (byteCount-2) + and data[1] & 0x80 == 0x00 + and data[1] & 0x0f in CardTrafficTypes["FSCI"]): + # Decode T0 + hasTA = data[1] & 0x10 + hasTB = data[1] & 0x20 + hasTC = data[1] & 0x40 + note += CardTrafficTypes["FSCI"][data[1] & 0x0f] + + # Which byte to decode next + byteNext = 2 # T0 Decoded, next is TA/TB/TC + # TA b4=0 + if (hasTA and data[byteNext] & 0x08 == 0x00): + note += "TA:" + hex(data[byteNext]) + " " + byteNext += 1 + + if (hasTB): + note += "TB:" + hex(data[byteNext]) + " " + byteNext += 1 + + # TC b3-8=0 + if (hasTC and data[byteNext] & 0xFC == 0x00): + note += "TC:" + hex(data[byteNext]) + " " + byteNext += 1 + + # TODO: decode historical bytes + + # Check CRC_A + if not CRC_A_check(data): + note += " WRONG CRC " + + elif (): + pass + return note + +def parseReader(data): + return parseReader_3(data) + parseReader_4(data) + +def parseCard(data): + return parseCard_3(data) + parseCard_4(data) diff --git a/Software/Chameleon/Log.py b/Software/Chameleon/Log.py index aa0d7a9..c10fd5f 100644 --- a/Software/Chameleon/Log.py +++ b/Software/Chameleon/Log.py @@ -137,13 +137,14 @@ def parseBinary(binaryStream, decode=False): deltaTimestamp += TIMESTAMP_MAX; note = "" - # If we need to decode the data - if (decode): + # If we need to decode the data and paritybit check success + if (decode and logData[-1] != '!'): # Decode the data from Reader if(event == 0x44 or event == 0x45): note = iso14443_3.parseReader(binascii.a2b_hex(logData)) elif (event == 0x46 or event == 0x47): note = iso14443_3.parseCard(binascii.a2b_hex(logData)) + # Create log entry as dict and append it to event list logEntry = { 'eventName': eventTypes[event]['name'], From 13e87a0b65e4310ae68dde5e344984e63e91d581 Mon Sep 17 00:00:00 2001 From: chenzitai Date: Mon, 30 Jul 2018 04:40:21 +0100 Subject: [PATCH 07/20] Decode Basic I-Block and MF DESFire Ev1 APP Data in SniffingMode EV1: AuthAES, Select APP, GetAPPIDs, ReadData and decode I-Block (cherry picked from commit 5a14e1b) --- Software/Chameleon/ISO14443.py | 108 ++++++++++++++++++++++++++---- Software/Chameleon/MFDESFire.py | 113 ++++++++++++++++++++++++++++++++ Software/Chameleon/utils.py | 5 ++ 3 files changed, 214 insertions(+), 12 deletions(-) create mode 100644 Software/Chameleon/MFDESFire.py create mode 100644 Software/Chameleon/utils.py diff --git a/Software/Chameleon/ISO14443.py b/Software/Chameleon/ISO14443.py index 94c60fa..04fd6f6 100644 --- a/Software/Chameleon/ISO14443.py +++ b/Software/Chameleon/ISO14443.py @@ -1,6 +1,10 @@ import binascii import crcmod from enum import Enum + +from Chameleon.MFDESFire import MFDESFireDecode +from Chameleon.utils import TrafficSource + # Parameters for CRC_A CRC_INIT = 0x6363 POLY = 0x11021 @@ -12,6 +16,76 @@ class ReaderCMD(Enum): RATS = 1 PPS = 2 + + +class BlockData: + @staticmethod + def isBlockData(byteCount, data): + if(byteCount >= 3 and (data[0]& 0xE6) in ReaderTrafficTypes["PCB"]): + return True + else: + return False + + def __init__(self, byteCount, data, source): + self.byteCount = byteCount + self.data = data + self.PCB = data[0] + self.type = ReaderTrafficTypes["PCB"][self.PCB & 0xE6] + self.CID = None + self.NAD = None + self.INF = None + self.source = source + self.CRCChecked = CRC_A_check(data) + self.CardApplicationDecoder = MFDESFireDecode + + if self.CRCChecked: + + hasCID = self.PCB & 0x08 # PCB b4 indicate CID + hasNAD = 0 + + if (self.type == "IBlock"): + hasNAD = self.PCB & 0x04 # IBlock PCB b2 indicate NAD + + byteNext = 1 + # CID + if (hasCID): + self.CID = self.data[byteNext] + byteNext += 1 + + # NAD + if (hasNAD): + self.NAD = self.data[byteNext] + byteNext += 1 + + # INF field not empty + if(byteNext < byteCount -2): + self.INF = self.data[byteNext: byteCount-2] + + + def decode(self): + note = "" + # Prologue + # PCB + note += self.type + " " + + # CID + if (self.CID != None): + note += "CID:" + hex(self.CID) + " " + + # NAD + if (self.NAD != None): + note += "NAD:" + hex(self.NAD) + " " + + # INF + if (self.INF != None): + note += self.CardApplicationDecoder(self.INF, self.source) + # EDC CRC check + if not self.CRCChecked: + note += " WRONG CRC " + + + return note + readerCMD = ReaderCMD.NONE ReaderTrafficTypes = { @@ -40,7 +114,16 @@ ReaderTrafficTypes = { 0x7: "FSD:128 ", 0x8: "FSD:256 " }, - + "PCB":{ + # IBlock 000X XX1X + 0x02: "IBlock", # 000X X01X + 0x08: "IBlock", # 000X X11X + # RBlock 101X X01X + 0xA2: "RBlock", # 101X X01X + # SBlock 11XX X010 + 0xC2: "SBlock", # 110X X010 + 0xE2: "SBlock" # 111X X010 + } } CardTrafficTypes = { @@ -119,7 +202,7 @@ def parseReader_4(data): (data[1] & 0xf0 >> 8) in ReaderTrafficTypes["FSDI"])): note += "RATS - " note += ReaderTrafficTypes["FSDI"][data[1] >> 8] - note += "CID:" + str(data[1] & 0x0f) + " " + note += "CID:" + hex(data[1] & 0x0f) + " " if (not CRC_A_check(data)): note += " WRONG CRC " else: @@ -137,14 +220,12 @@ def parseReader_4(data): note += "CID:" + str(data[1] & 0x0f) + " " note += "DSI:" + str(pow(2, (data[2] >> 2) & 0x03)) + " " note += "DRI:" + str(pow(2, data[2] & 0x03)) + " " - # BLOCK S-block PCB DESELECT - # Without CID - elif (byteCount == 3 and data[0] == 0xc2): - note += "DESEL" - # With CID - elif (byteCount == 4 and data[0] == 0xca and data[1] & 0x30 == 0x00): - note += "DESEL - " - note += "CID:" + str(data[1] & 0x0f) + " " + + # Half-duplex block transmission + # PCB bit mask: 0b11100110 + elif (BlockData.isBlockData(byteCount, data)): + blockData = BlockData(byteCount,data, TrafficSource.Reader) + note = blockData.decode() return note @@ -209,9 +290,12 @@ def parseCard_4(data): # Check CRC_A if not CRC_A_check(data): note += " WRONG CRC " + # Application Data + elif (BlockData.isBlockData(byteCount, data)): + blockData = BlockData(byteCount,data, TrafficSource.Card) + note = blockData.decode() + - elif (): - pass return note def parseReader(data): diff --git a/Software/Chameleon/MFDESFire.py b/Software/Chameleon/MFDESFire.py new file mode 100644 index 0000000..9e94556 --- /dev/null +++ b/Software/Chameleon/MFDESFire.py @@ -0,0 +1,113 @@ +from Chameleon.utils import TrafficSource +from binascii import hexlify + +lastCMD = 0x00 + +StatusCode = { + 0x00 : "OPERATION_OK", + 0x0C : "NO_CHANGES", + 0x0E : "ERR_OUT_OF_EEPROM", + 0x1C : "ILLEGAL_CMD_CODE", + 0x1E : "ERR_INTEGRITY", + 0x40 : "NO_SUCH_KEY", + 0x7E : "ERR_LENGTH", + 0x9D : "PERMISSION_DENIED", + 0x9E : "ERR_PARAMETER", + 0xA0 : "APP_NOT_FOUND", + 0xA1 : "ERR_APP_INTEGRITY", + 0xAE : "ERR_AUTH", + 0xAF : "ADDITIONAL_FRAME", + 0xBE : "ERR_BOUNDARY", + 0xC1 : "ERR_PICC_INTEGRITY", + 0xCA : "CMD_ABORTED", + 0xCD : "ERR_PICC_DISABLED", + 0xCE : "ERR_COUNT", + 0xDE : "ERR_DUPLICATE", + 0xEE : "ERR_EEPROM", + 0xF0 : "FILE_NOT_FOUND", + 0xF1 : "ERR_FILE_INTEGRITY" +} + + +def decodeSelectAPP(data): + if len(data) == 4: + return "AID: 0x"+ hexlify(data[1:4]).decode() + else: + return "Decode Fail" + +def decodeGetAPPID(data): + if len(data) == 1: + return "" + else: + return "Decode Fail" + +def decodeRespGetAPPID (data): + note = "APPIDs: |" + dataLen = len(data) + + for i in range (0,int((dataLen-1)/3)): + note += "0x"+hexlify(data[3*i: 3*(i+1)]).decode()+"|" + + return note + + +def decodeAuthAES(data): + if len(data) == 2: + return "KeyNo:"+hex(data[1]) + else: + return "Decode Fail" + +def decodeRespAuthAES(data): + return "" + + +def decodeReadData(data): + if len(data) == 8: + fileNo = data[1] + offSet = data[2:5] + length = data[5:8] + return "FileNo:"+hex(fileNo) + " OffSet:0x"+hexlify(offSet).decode() + " len:0x"+hexlify(length).decode() + else: + return "Decode Fail" + +def decodeRespReadData(data): + return "Data:0x"+hexlify(data[1:]).decode() + +def decodeAdiFrame(data): + return "" + +def decodeRespAdiFrame(data): + return "" + +def decodeDummy(data): + return "" + +MFDESFireCMDTypes = { + 0x5A : {"name": "SelectApp ", "CMDdecoder":decodeSelectAPP, "RespDecoder": decodeDummy}, + 0x6A : {"name": "GetAPPID ", "CMDdecoder":decodeGetAPPID, "RespDecoder": decodeRespGetAPPID}, + 0xAA : {"name": "AuthAES ", "CMDdecoder":decodeAuthAES, "RespDecoder": decodeRespAuthAES}, + 0xBD : {"name": "ReadData ", "CMDdecoder":decodeReadData, "RespDecoder": decodeRespReadData}, + 0xAF : {"name": "AdditionalFrame", "CMDdecoder":decodeAdiFrame, "RespDecoder": decodeRespAdiFrame} +} + + +def MFDESFireDecode(data, source): + note = "" + global lastCMD + if (source == TrafficSource.Reader): + cmd = data[0] + if cmd in MFDESFireCMDTypes : + lastCMD = cmd + note += "CMD:" + MFDESFireCMDTypes[cmd]["name"] + note += MFDESFireCMDTypes[cmd]["CMDdecoder"](data) + + elif (source == TrafficSource.Card): + status = data[0] + # Decode status code + if status in StatusCode: + note += StatusCode[status] + " " + # If status Ok, decode data + if status == 0x00 and lastCMD in MFDESFireCMDTypes: + note += MFDESFireCMDTypes[lastCMD]["RespDecoder"](data) + + return note \ No newline at end of file diff --git a/Software/Chameleon/utils.py b/Software/Chameleon/utils.py new file mode 100644 index 0000000..af4a5ab --- /dev/null +++ b/Software/Chameleon/utils.py @@ -0,0 +1,5 @@ +from enum import Enum + +class TrafficSource(Enum): + Reader = 0 + Card = 1 From 9bfcb942dccfcd062ee20537c8981aa53cea543f Mon Sep 17 00:00:00 2001 From: chenzitai Date: Mon, 30 Jul 2018 16:34:53 +0100 Subject: [PATCH 08/20] Add Color to Note (cherry picked from commit 2ef5769) --- Software/chamlog.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Software/chamlog.py b/Software/chamlog.py index da4281a..5bece7a 100755 --- a/Software/chamlog.py +++ b/Software/chamlog.py @@ -19,11 +19,12 @@ def verboseLog(text): def formatText(log): formatString = '{timestamp:0>5d} ms <{deltaTimestamp:>+6d} ms>:' - formatString += '{eventName:<28} ({dataLength:<3} bytes)\t[{data:<20}]\t{note}\n' - text = '' - - for logEntry in log: + formatString += '{eventName:<28} ({dataLength:<3} bytes) [{data:<20}] ' \ + '\033[94m {note} \x1b[0m \n' + text = '' + + for logEntry in log: text += formatString.format(**logEntry) return text From 0e26656eb4fc78dc904911f0420a72cd59173403 Mon Sep 17 00:00:00 2001 From: chenzitai Date: Tue, 31 Jul 2018 04:02:32 +0100 Subject: [PATCH 09/20] Full implement decoding security related CMD in MFDESFire Sniffing (cherry picked from commit 210fd9f) --- Software/Chameleon/MFDESFire.py | 135 +++++++++++++++++++++++++++----- 1 file changed, 117 insertions(+), 18 deletions(-) diff --git a/Software/Chameleon/MFDESFire.py b/Software/Chameleon/MFDESFire.py index 9e94556..6f64ebb 100644 --- a/Software/Chameleon/MFDESFire.py +++ b/Software/Chameleon/MFDESFire.py @@ -2,7 +2,7 @@ from Chameleon.utils import TrafficSource from binascii import hexlify lastCMD = 0x00 - +strFail = "Decode Fail" StatusCode = { 0x00 : "OPERATION_OK", 0x0C : "NO_CHANGES", @@ -33,13 +33,14 @@ def decodeSelectAPP(data): if len(data) == 4: return "AID: 0x"+ hexlify(data[1:4]).decode() else: - return "Decode Fail" + return strFail +# Get APP ID CMD def decodeGetAPPID(data): if len(data) == 1: return "" else: - return "Decode Fail" + return strFail def decodeRespGetAPPID (data): note = "APPIDs: |" @@ -49,18 +50,89 @@ def decodeRespGetAPPID (data): note += "0x"+hexlify(data[3*i: 3*(i+1)]).decode()+"|" return note - - +############################## +# Security Related Commands +############################## +# Authenticate AES CMD def decodeAuthAES(data): if len(data) == 2: return "KeyNo:"+hex(data[1]) else: - return "Decode Fail" + return strFail def decodeRespAuthAES(data): - return "" + if len(data) == 17: + return "ekNo(RndB):0x" + hexlify(data[1:]).decode() + else: + return strFail +def decodeAuthAESAF (data): + if len(data) == 33: + return "ekNo(RndA+RndB'):0x" + hexlify(data[1:]).decode() + else: + return strFail +def decodeCardAuthAESAF (data): + if len(data) == 17: + return "ekNo(RndA'):0x" + hexlify(data[1:]).decode() + else: + return strFail + +# Authenticate 3DES CMD +def decodeAuth3DES(data): + if len(data) == 2: + return "KeyNo:"+hex(data[1]) + else: + return strFail + +def decodeRespAuth3DES(data): + if len(data) == 9: + return "ekNo(RndB):0x" + hexlify(data[1:]).decode() + else: + return strFail + +def decodeAuth3DESAF(data): + if len(data) == 17: + return "dkNo(RndA+RndB'):0x" + hexlify(data[1:]).decode() + else: + return strFail + +def decodeCardAuth3DESAF (data): + if len(data) == 9: + return "ekNo(RndA'):0x" + hexlify(data[1:]).decode() + else: + return strFail + +# ChangeKeySettings CMD +def decodeChangeKeySettings(data): + if len(data) == 9: + return "KeySettings:0x" + hexlify(data[1:]).decode() + else: + return strFail +# GetKeySettings CMD +def decodeRespGetKeySettings(data): + if len(data) == 3: + return "KeySettings:" + hex(data[1]) + " MaxNoKeys:" + hex(data[2]) + else: + return strFail + +# ChangeKey CMD +def decodeChangeKey(data): + if len(data) == 26: + return "KeyNo:" + hex(data[1]) + " decipheredKeyData:0x"+hexlify(data[2:]).decode() + else: + return strFail + +# GetKeyVersion CMD +def decodeGetKeyVersion(data): + if len(data) == 2: + return "KeyNo:" + hex(data[1]) + else: + return strFail +def decodeRespGetKeyVersion(data): + return "KeyVersion" + hex(data[1]) if len(data) == 2 else strFail + +# Read Data def decodeReadData(data): if len(data) == 8: fileNo = data[1] @@ -68,28 +140,43 @@ def decodeReadData(data): length = data[5:8] return "FileNo:"+hex(fileNo) + " OffSet:0x"+hexlify(offSet).decode() + " len:0x"+hexlify(length).decode() else: - return "Decode Fail" + return strFail def decodeRespReadData(data): - return "Data:0x"+hexlify(data[1:]).decode() + return "Data:0x"+hexlify(data[1:]).decode() if len(data) >1 else strFail def decodeAdiFrame(data): - return "" + return "Data:0x"+hexlify(data[1:]).decode() if len(data) >1 else strFail def decodeRespAdiFrame(data): - return "" + return "Data:0x"+hexlify(data[1:]).decode() if len(data) >1 else strFail def decodeDummy(data): return "" + + MFDESFireCMDTypes = { 0x5A : {"name": "SelectApp ", "CMDdecoder":decodeSelectAPP, "RespDecoder": decodeDummy}, 0x6A : {"name": "GetAPPID ", "CMDdecoder":decodeGetAPPID, "RespDecoder": decodeRespGetAPPID}, - 0xAA : {"name": "AuthAES ", "CMDdecoder":decodeAuthAES, "RespDecoder": decodeRespAuthAES}, 0xBD : {"name": "ReadData ", "CMDdecoder":decodeReadData, "RespDecoder": decodeRespReadData}, - 0xAF : {"name": "AdditionalFrame", "CMDdecoder":decodeAdiFrame, "RespDecoder": decodeRespAdiFrame} + 0xAF : {"name": "AdditionalFrame ", "CMDdecoder":decodeAdiFrame, "RespDecoder": decodeRespAdiFrame}, + # Security Related CMD + 0xAA : {"name": "AuthAES ", "CMDdecoder": decodeAuthAES, "RespDecoder": decodeRespAuthAES}, + 0x0A : {"name": "Auth3DES", "CMDdecoder": decodeAuth3DES, "RespDecoder": decodeRespAuth3DES}, + 0x54 : {"name": "ChangeKeySettings ","CMDdecoder":decodeChangeKeySettings,"respDecoder": decodeDummy}, + 0x45 : {"name": "GetKeySettings ", "CMDdecoder":decodeDummy, "RespDecoder": decodeRespGetKeySettings}, + 0xC4 : {"name": "ChangeKey ", "CMDdecoder":decodeChangeKey, "RespDecoder": decodeDummy}, + 0x64 : {"name": "GetKeyVersion ", "CMDdecoder":decodeGetKeyVersion, "RespDecoder": decodeRespGetKeyVersion} + } +# Commands need to use additional frame +MFDESFireAFCMD = { + 0xAA : {"name": "AuthAES", "AFReaderDecoder":decodeAuthAESAF, "AFCardDecoder": decodeCardAuthAESAF}, + 0x0A : {"name": "Auth3DES", "AFReaderDecoder":decodeAuth3DESAF, "AFCardDecoder": decodeCardAuth3DESAF}, + +} def MFDESFireDecode(data, source): note = "" @@ -97,9 +184,17 @@ def MFDESFireDecode(data, source): if (source == TrafficSource.Reader): cmd = data[0] if cmd in MFDESFireCMDTypes : - lastCMD = cmd - note += "CMD:" + MFDESFireCMDTypes[cmd]["name"] - note += MFDESFireCMDTypes[cmd]["CMDdecoder"](data) + # If current frame is Additional Frame + # And the previous cmd need Additional Frame + if lastCMD in MFDESFireAFCMD and cmd == 0xAF: + note += "CMD:AdditionalFrame " + note += MFDESFireAFCMD[lastCMD]["AFReaderDecoder"](data) + # Current command not need Additional Frame + # or last cmd doesn't need AF + else: + lastCMD = cmd + note += "CMD:" + MFDESFireCMDTypes[cmd]["name"] + note += MFDESFireCMDTypes[cmd]["CMDdecoder"](data) elif (source == TrafficSource.Card): status = data[0] @@ -107,7 +202,11 @@ def MFDESFireDecode(data, source): if status in StatusCode: note += StatusCode[status] + " " # If status Ok, decode data - if status == 0x00 and lastCMD in MFDESFireCMDTypes: - note += MFDESFireCMDTypes[lastCMD]["RespDecoder"](data) + if (status == 0x00 or status == 0xAF) and lastCMD in MFDESFireCMDTypes: + # If last cmd need additional frame and this is the last frame + if status == 0x00 and lastCMD in MFDESFireAFCMD: + note += MFDESFireAFCMD[lastCMD]["AFCardDecoder"](data) + else: + note += MFDESFireCMDTypes[lastCMD]["RespDecoder"](data) return note \ No newline at end of file From abff0402f6bc1e89bb690d316e5e4b8d5cfda0cf Mon Sep 17 00:00:00 2001 From: chenzitai Date: Tue, 31 Jul 2018 04:25:52 +0100 Subject: [PATCH 10/20] Full implement decoding half-Duplex Data Trans Protocol in iso14443-4 (Show PCB Block infos) (cherry picked from commit 9f469be) --- Software/Chameleon/ISO14443.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Software/Chameleon/ISO14443.py b/Software/Chameleon/ISO14443.py index 04fd6f6..1ee4596 100644 --- a/Software/Chameleon/ISO14443.py +++ b/Software/Chameleon/ISO14443.py @@ -68,6 +68,28 @@ class BlockData: # PCB note += self.type + " " + # Block number + if ((self.type == "IBlock" or self.type == "RBlock") and self.PCB & 0x01): + note += "BlkNo:1 " + + # Chaining? + if (self.type == "IBlock" and self.PCB & 0x10): + note += "Chaining " + + # ACK/NAK? for R-Block + if (self.type == "RBlock"): + if (self.PCB & 0x10): + note += "NAK " + else: + note += "ACK " + + # DESEL/WTX for SBlock + if (self.type == "SBlock"): + if (self.PCB & 0x30 == 0x00): + note += "DESEL " + elif (self.PCB & 0x30 == 0x30): + note += "WTX" + # CID if (self.CID != None): note += "CID:" + hex(self.CID) + " " From 89b29415aaf969ef4c6f415d70ff755d3f65d5ad Mon Sep 17 00:00:00 2001 From: chenzitai Date: Tue, 31 Jul 2018 16:27:10 +0100 Subject: [PATCH 11/20] Fix bug: parsing SAK and ATS wrong because of non-stateful (cherry picked from commit 32b2e88) --- Software/Chameleon/ISO14443.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/Software/Chameleon/ISO14443.py b/Software/Chameleon/ISO14443.py index 1ee4596..e574819 100644 --- a/Software/Chameleon/ISO14443.py +++ b/Software/Chameleon/ISO14443.py @@ -13,10 +13,13 @@ CRC_A_func = crcmod.mkCrcFun(POLY, initCrc=CRC_INIT, xorOut=0) class ReaderCMD(Enum): NONE = 0 - RATS = 1 - PPS = 2 + SELECT = 1 + RATS = 2 + PPS = 3 +readerCMD = ReaderCMD.NONE + class BlockData: @staticmethod @@ -108,7 +111,6 @@ class BlockData: return note -readerCMD = ReaderCMD.NONE ReaderTrafficTypes = { "SEL":{ @@ -156,7 +158,7 @@ CardTrafficTypes = { 0x00: "UID complete, PICC NOT compliant with 14443-4" }, "FSCI": { - 0x0: "FSCC:16 ", + 0x0: "FSC:16 ", 0x1: "FSC:24 ", 0x2: "FSC:32 ", 0x3: "FSC:40 ", @@ -184,6 +186,7 @@ def CRC_A_check(data): return False def parseReader_3(data): + global readerCMD byteCount = len(data) note = "" @@ -201,6 +204,7 @@ def parseReader_3(data): # SELECT Command elif (byteCount == 9 and data[0] in ReaderTrafficTypes["SEL"] and data[1] == 0x70): # TODO: distinguish CT+uid012+BCC and uid0123+BCC + readerCMD = ReaderCMD.SELECT note += "SELECT - " note += ReaderTrafficTypes["SEL"][data[0]] note += "UID_CLn:" + binascii.hexlify(data[2:6]).decode() + " " @@ -216,6 +220,7 @@ def parseReader_3(data): return note def parseReader_4(data): + global readerCMD byteCount = len(data) note = "" @@ -260,7 +265,7 @@ def parseCard_3(data): note += "ATQA - " note += binascii.hexlify(data).decode() # SAK - elif (byteCount == 3 and ((data[0] & (0x24)) in CardTrafficTypes["SAK"])): + elif (byteCount == 3 and readerCMD == ReaderCMD.SELECT and ((data[0] & (0x24)) in CardTrafficTypes["SAK"])): note += "SAK - " note += CardTrafficTypes["SAK"][(data[2] & 0x24)] if not CRC_A_check(data): @@ -272,6 +277,7 @@ def parseCard_3(data): return note def parseCard_4(data): + global readerCMD byteCount = len(data) note = "" @@ -279,12 +285,13 @@ def parseCard_4(data): # TL + T0 + TA + TB + TC + T1 ... + CRC # TL: length without CRC # ATS without data - if(byteCount == 3 and data[0] == (byteCount-2)): + if(byteCount == 3 and readerCMD == ReaderCMD.RATS and data[0] == (byteCount-2)): note += "ATS - NO DATA" # ATS with data mush have T0, T0 b8=0 - elif (byteCount > 3 and data[0] == (byteCount-2) + elif (byteCount > 3 and readerCMD == ReaderCMD.RATS and data[0] == (byteCount-2) and data[1] & 0x80 == 0x00 and data[1] & 0x0f in CardTrafficTypes["FSCI"]): + note += "ATS - " # Decode T0 hasTA = data[1] & 0x10 hasTB = data[1] & 0x20 @@ -317,6 +324,7 @@ def parseCard_4(data): blockData = BlockData(byteCount,data, TrafficSource.Card) note = blockData.decode() + readerCMD = ReaderCMD.NONE return note From f57207d2f19ab6b86b7288f9c7d34fc43d78e1a4 Mon Sep 17 00:00:00 2001 From: chenzitai Date: Tue, 31 Jul 2018 17:07:03 +0100 Subject: [PATCH 12/20] Finish decoding PICC level commands of MFDESFire (cherry picked from commit 84f636a) --- Software/Chameleon/MFDESFire.py | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/Software/Chameleon/MFDESFire.py b/Software/Chameleon/MFDESFire.py index 6f64ebb..e30e4a2 100644 --- a/Software/Chameleon/MFDESFire.py +++ b/Software/Chameleon/MFDESFire.py @@ -27,11 +27,26 @@ StatusCode = { 0xF0 : "FILE_NOT_FOUND", 0xF1 : "ERR_FILE_INTEGRITY" } +# Create APP CMD +def decodeCreateAPP(data): + note = "" + if len(data) == 6: + note += "AID:0x"+hexlify(data[1:4]).decode() + " " + note += "KeySett:"+hex(data[4]) + " " + note += "NumOfKeys:"+hex(data[5]) + " " + else: + note += strFail + return note +def decodeDelAPP(data): + if len(data) == 4: + return "AID:0x"+hexlify(data[1:]).decode() + else: + return strFail def decodeSelectAPP(data): if len(data) == 4: - return "AID: 0x"+ hexlify(data[1:4]).decode() + return "AID:0x"+ hexlify(data[1:4]).decode() else: return strFail @@ -157,17 +172,25 @@ def decodeDummy(data): MFDESFireCMDTypes = { + # PICC Level Commands, GetVersion not decoded, please refer to datasheet for the meaning of resp + 0xCA : {"name": "CreateApp", "CMDdecoder":decodeCreateAPP, "RespDecoder": decodeDummy}, + 0xDA : {"name": "DelApp", "CMDdecoder":decodeDelAPP, "RespDecoder": decodeDummy}, 0x5A : {"name": "SelectApp ", "CMDdecoder":decodeSelectAPP, "RespDecoder": decodeDummy}, 0x6A : {"name": "GetAPPID ", "CMDdecoder":decodeGetAPPID, "RespDecoder": decodeRespGetAPPID}, + 0xFC : {"name": "FormatPICC", "CMDdecoder":decodeDummy, "RespDecoder": decodeDummy}, + 0x60 : {"name": "GetVersion", "CMDdecoder":decodeDummy, "RespDecoder": decodeDummy}, + # Data Manipulation Commands 0xBD : {"name": "ReadData ", "CMDdecoder":decodeReadData, "RespDecoder": decodeRespReadData}, - 0xAF : {"name": "AdditionalFrame ", "CMDdecoder":decodeAdiFrame, "RespDecoder": decodeRespAdiFrame}, # Security Related CMD 0xAA : {"name": "AuthAES ", "CMDdecoder": decodeAuthAES, "RespDecoder": decodeRespAuthAES}, 0x0A : {"name": "Auth3DES", "CMDdecoder": decodeAuth3DES, "RespDecoder": decodeRespAuth3DES}, 0x54 : {"name": "ChangeKeySettings ","CMDdecoder":decodeChangeKeySettings,"respDecoder": decodeDummy}, 0x45 : {"name": "GetKeySettings ", "CMDdecoder":decodeDummy, "RespDecoder": decodeRespGetKeySettings}, 0xC4 : {"name": "ChangeKey ", "CMDdecoder":decodeChangeKey, "RespDecoder": decodeDummy}, - 0x64 : {"name": "GetKeyVersion ", "CMDdecoder":decodeGetKeyVersion, "RespDecoder": decodeRespGetKeyVersion} + 0x64 : {"name": "GetKeyVersion ", "CMDdecoder":decodeGetKeyVersion, "RespDecoder": decodeRespGetKeyVersion}, + + # Additional Frame + 0xAF: {"name": "AdditionalFrame ", "CMDdecoder": decodeAdiFrame, "RespDecoder": decodeRespAdiFrame}, } From f2d78eeb2572893273978470b69f16292a2e770d Mon Sep 17 00:00:00 2001 From: chenzitai Date: Wed, 1 Aug 2018 18:04:50 +0100 Subject: [PATCH 13/20] Finish decoding Data Manipulation Commands commands of MFDESFire (cherry picked from commit 502d951) --- Software/Chameleon/MFDESFire.py | 71 +++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/Software/Chameleon/MFDESFire.py b/Software/Chameleon/MFDESFire.py index e30e4a2..96307d8 100644 --- a/Software/Chameleon/MFDESFire.py +++ b/Software/Chameleon/MFDESFire.py @@ -27,6 +27,9 @@ StatusCode = { 0xF0 : "FILE_NOT_FOUND", 0xF1 : "ERR_FILE_INTEGRITY" } +######################### +# PICC Level Commands +######################### # Create APP CMD def decodeCreateAPP(data): note = "" @@ -147,8 +150,11 @@ def decodeGetKeyVersion(data): def decodeRespGetKeyVersion(data): return "KeyVersion" + hex(data[1]) if len(data) == 2 else strFail +############################## +# Data Manipulation Commands +############################## # Read Data -def decodeReadData(data): +def decodeFileNoOffsetLen(data): if len(data) == 8: fileNo = data[1] offSet = data[2:5] @@ -156,8 +162,32 @@ def decodeReadData(data): return "FileNo:"+hex(fileNo) + " OffSet:0x"+hexlify(offSet).decode() + " len:0x"+hexlify(length).decode() else: return strFail +# Write Data +def decodeFileNoOffsetLenData(data): + if len(data) > 8: + fileNo = data[1] + offSet = data[2:5] + length = data[5:8] + dataWrite = data[8:] + return "FileNo:"+hex(fileNo) + " OffSet:0x"+hexlify(offSet).decode() + " len:0x"+hexlify(length).decode() + \ + " Data:0x" + hexlify(dataWrite).decode() + else: + return strFail -def decodeRespReadData(data): +# Get Value +def decodeFileNo(data): + if len(data) == 2: + return "FileNo:"+hex(data[1]) + else: + return strFail +# Credit +def decodeFileNoData(data): + if len(data) >2: + return "FileNo:" + hex(data[1]) + " Data:0x" + hexlify(data[2:]).decode() + else: + return strFail + +def decodeData(data): return "Data:0x"+hexlify(data[1:]).decode() if len(data) >1 else strFail def decodeAdiFrame(data): @@ -173,31 +203,46 @@ def decodeDummy(data): MFDESFireCMDTypes = { # PICC Level Commands, GetVersion not decoded, please refer to datasheet for the meaning of resp - 0xCA : {"name": "CreateApp", "CMDdecoder":decodeCreateAPP, "RespDecoder": decodeDummy}, - 0xDA : {"name": "DelApp", "CMDdecoder":decodeDelAPP, "RespDecoder": decodeDummy}, + 0xCA : {"name": "CreateApp ", "CMDdecoder":decodeCreateAPP, "RespDecoder": decodeDummy}, + 0xDA : {"name": "DelApp ", "CMDdecoder":decodeDelAPP, "RespDecoder": decodeDummy}, 0x5A : {"name": "SelectApp ", "CMDdecoder":decodeSelectAPP, "RespDecoder": decodeDummy}, 0x6A : {"name": "GetAPPID ", "CMDdecoder":decodeGetAPPID, "RespDecoder": decodeRespGetAPPID}, - 0xFC : {"name": "FormatPICC", "CMDdecoder":decodeDummy, "RespDecoder": decodeDummy}, - 0x60 : {"name": "GetVersion", "CMDdecoder":decodeDummy, "RespDecoder": decodeDummy}, + 0xFC : {"name": "FormatPICC ", "CMDdecoder":decodeDummy, "RespDecoder": decodeDummy}, + 0x60 : {"name": "GetVersion ", "CMDdecoder":decodeDummy, "RespDecoder": decodeDummy}, # Data Manipulation Commands - 0xBD : {"name": "ReadData ", "CMDdecoder":decodeReadData, "RespDecoder": decodeRespReadData}, + 0xBD : {"name": "ReadData ", "CMDdecoder":decodeFileNoOffsetLen, "RespDecoder": decodeData}, + 0x3D : {"name": "WriteData ", "CMDdecoder":decodeFileNoOffsetLenData, "RespDecoder": decodeDummy}, + 0x6C : {"name": "GetValue ", "CMDdecoder":decodeFileNo, "RespDecoder": decodeData}, + 0x0C : {"name": "Credit ", "CMDdecoder":decodeFileNoData, "RespDecoder": decodeDummy}, + 0xDC : {"name": "Debit ", "CMDdecoder":decodeFileNoData, "RespDecoder": decodeDummy}, + 0x1C : {"name": "LimitedCredit ", "CMDdecoder":decodeFileNoData, "RespDecoder": decodeDummy}, + 0x3B : {"name": "WriteRecord ", "CMDdecoder":decodeFileNoOffsetLenData, "RespDecoder": decodeDummy}, + 0xBB : {"name": "ReadRecord ", "CMDdecoder":decodeFileNoOffsetLen, "RespDecoder": decodeData}, + 0xEB : {"name": "ClearRecordFile ", "CMDdecoder":decodeFileNo, "RespDecoder": decodeDummy}, + 0xC7 : {"name": "CommitTransaction ","CMDdecoder":decodeDummy, "RespDecoder": decodeDummy}, + 0xA7 : {"name": "AbortTransaction ","CMDdecoder":decodeDummy, "RespDecoder": decodeDummy}, # Security Related CMD - 0xAA : {"name": "AuthAES ", "CMDdecoder": decodeAuthAES, "RespDecoder": decodeRespAuthAES}, - 0x0A : {"name": "Auth3DES", "CMDdecoder": decodeAuth3DES, "RespDecoder": decodeRespAuth3DES}, - 0x54 : {"name": "ChangeKeySettings ","CMDdecoder":decodeChangeKeySettings,"respDecoder": decodeDummy}, + 0xAA : {"name": "AuthAES ", "CMDdecoder": decodeAuthAES, "RespDecoder": decodeRespAuthAES}, + 0x0A : {"name": "Auth3DES ", "CMDdecoder": decodeAuth3DES, "RespDecoder": decodeRespAuth3DES}, + 0x54 : {"name": "ChangeKeySettings ","CMDdecoder":decodeChangeKeySettings,"RespDecoder": decodeDummy}, 0x45 : {"name": "GetKeySettings ", "CMDdecoder":decodeDummy, "RespDecoder": decodeRespGetKeySettings}, 0xC4 : {"name": "ChangeKey ", "CMDdecoder":decodeChangeKey, "RespDecoder": decodeDummy}, 0x64 : {"name": "GetKeyVersion ", "CMDdecoder":decodeGetKeyVersion, "RespDecoder": decodeRespGetKeyVersion}, # Additional Frame - 0xAF: {"name": "AdditionalFrame ", "CMDdecoder": decodeAdiFrame, "RespDecoder": decodeRespAdiFrame}, + 0xAF: {"name": "AdditionalFrame ", "CMDdecoder": decodeAdiFrame, "RespDecoder": decodeRespAdiFrame}, } # Commands need to use additional frame MFDESFireAFCMD = { - 0xAA : {"name": "AuthAES", "AFReaderDecoder":decodeAuthAESAF, "AFCardDecoder": decodeCardAuthAESAF}, - 0x0A : {"name": "Auth3DES", "AFReaderDecoder":decodeAuth3DESAF, "AFCardDecoder": decodeCardAuth3DESAF}, + 0xAA : {"name": "AuthAES ", "AFReaderDecoder":decodeAuthAESAF, "AFCardDecoder": decodeCardAuthAESAF}, + 0x0A : {"name": "Auth3DES ", "AFReaderDecoder":decodeAuth3DESAF, "AFCardDecoder": decodeCardAuth3DESAF}, + + 0x3D : {"name": "WriteData ", "AFReaderDecoder":decodeData, "AFCardDecoder": decodeDummy}, + 0xBB : {"name": "ReadRecord ", "AFReaderDecoder":decodeDummy, "AFCardDecoder": decodeData}, + + 0x3B: {"name": "WriteRecord ", "AFReaderDecoder": decodeData, "AFCardDecoder": decodeDummy} } From bc75cb982f843c9c4b85d9a940e66e1483519cd5 Mon Sep 17 00:00:00 2001 From: chenzitai Date: Thu, 2 Aug 2018 01:15:17 +0100 Subject: [PATCH 14/20] Finish decoding # Application Level Commands commands of MFDESFire (cherry picked from commit c0e0607) --- Software/Chameleon/ISO14443.py | 3 -- Software/Chameleon/MFDESFire.py | 95 ++++++++++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 5 deletions(-) diff --git a/Software/Chameleon/ISO14443.py b/Software/Chameleon/ISO14443.py index e574819..3306485 100644 --- a/Software/Chameleon/ISO14443.py +++ b/Software/Chameleon/ISO14443.py @@ -203,7 +203,6 @@ def parseReader_3(data): # SELECT Command elif (byteCount == 9 and data[0] in ReaderTrafficTypes["SEL"] and data[1] == 0x70): - # TODO: distinguish CT+uid012+BCC and uid0123+BCC readerCMD = ReaderCMD.SELECT note += "SELECT - " note += ReaderTrafficTypes["SEL"][data[0]] @@ -314,8 +313,6 @@ def parseCard_4(data): note += "TC:" + hex(data[byteNext]) + " " byteNext += 1 - # TODO: decode historical bytes - # Check CRC_A if not CRC_A_check(data): note += " WRONG CRC " diff --git a/Software/Chameleon/MFDESFire.py b/Software/Chameleon/MFDESFire.py index 96307d8..f66eee6 100644 --- a/Software/Chameleon/MFDESFire.py +++ b/Software/Chameleon/MFDESFire.py @@ -190,6 +190,84 @@ def decodeFileNoData(data): def decodeData(data): return "Data:0x"+hexlify(data[1:]).decode() if len(data) >1 else strFail +########################### +# Application Level Commands +########################### +def decodeRespGetFileIDs(data): + if len(data) == 4: + return "FIDs:0x"+hexlify(data[1:]).decode() + else: + return strFail + +def decodeRespGetFileSettings(data): + lenData = len(data) + note = "" + if lenData >5: + note += "FileType:"+hex(data[1])+" ComSett:"+hex(data[2]) + "AccessRight:0x"+hexlify(data[3:5]).decode() + " " + if lenData == 8: + note += "FileSize:0x"+hexlify(data[5:]).decode() + elif lenData == 18: + note += "LowLimit:0x"+hexlify(data[5:9]).decode() + " " + note += "UpLimit:0x" + hexlify(data[9:13]).decode() + " " + note += "LimitedCreditVal:0x"+hexlify(data[13:17]).decode() + " " + note += "LimitedCreditEn:" + hex(data[17]) + elif lenData == 14: + note += "RecordSize:0x"+ hexlify(data[5:8]).decode() + " " + note += "MaxNumRecords:0x"+hexlify(data[8:11]).decode() + " " + note += "CurrentNumRecords:0x"+hexlify(data[11:]).decode() + return note + else: + return strFail + +def decodeFNComSetAccessRightsFileSize(data): + if len(data) == 8: + note = "FileNo:" + hex(data[1]) + " ComSet:" + hex(data[2]) + "AccessRight:0x" + hexlify( + data[3:5]).decode() + " " + note += "FileSize:0x" + hexlify(data[5:]).decode() + return note + else: + return strFail + +def decodeChangeFileSettings(data): + lenData = len(data) + note ="" + if lenData > 2: + note += "FileNo:"+hex(data[1]) + " " + if lenData == 5: + note += "ComSet:"+hex(data[2]) + " " + note += "AccessRights:0x"+hexlify(data[3:]).decode() + elif lenData == 10: + note += "NewSettings:0x"+hexlify(data[2:]).decode() + return note + else: + return strFail + +def decodeCreateValueFile(data): + if len(data) == 18: + note = "FileNo:"+hex(data[1])+" ComSet:"+hex(data[2]) + "AccessRight:0x"+hexlify(data[3:5]).decode() + " " + note += "LowLimit:0x"+hexlify(data[5:9]).decode() + " " + note += "UpLimit:0x" + hexlify(data[9:13]).decode() + " " + note += "Val:0x"+hexlify(data[13:17]).decode() + " " + note += "LimitedCreditEn:" + hex(data[17]) + return note + else: + return strFail + +def decodeCreateRecordFile(data): + if len(data) == 11: + note = "FileNo:"+hex(data[1])+" ComSet:"+hex(data[2]) + "AccessRight:0x"+hexlify(data[3:5]).decode() + " " + note += "RecordSize:0x"+hexlify(data[5:8]).decode()+" " + note += "MaxNumRecords:0x"+hexlify(data[8:]).decode() + return note + else: + return strFail + +def decodeDelFile(data): + if len(data) == 2: + return "FileNo:"+hex(data[1]) + else: + return strFail + def decodeAdiFrame(data): return "Data:0x"+hexlify(data[1:]).decode() if len(data) >1 else strFail @@ -201,7 +279,20 @@ def decodeDummy(data): + + MFDESFireCMDTypes = { + # Application Level Commands + 0x6F : {"name": "GetFileIDs", "CMDdecoder":decodeDummy, "RespDecoder": decodeRespGetFileIDs}, + 0xF5 : {"name": "GetFileSettings", "CMDdecoder":decodeFileNo, "RespDecoder": decodeRespGetFileSettings}, + 0x5F : {"name": "ChangeFileSettings", "CMDdecoder":decodeChangeFileSettings, "RespDecoder": decodeDummy}, + 0xCD : {"name": "CreateStdDataFile", "CMDdecoder":decodeFNComSetAccessRightsFileSize,"RespDecoder": decodeDummy}, + 0xCB : {"name": "CreateBackupDataFile", "CMDdecoder":decodeFNComSetAccessRightsFileSize,"RespDecoder": decodeDummy}, + 0xCC : {"name": "CreateValueFile", "CMDdecoder":decodeCreateValueFile, "RespDecoder": decodeDummy}, + 0xC1 : {"name": "CreateLinearRecordFile","CMDdecoder":decodeCreateRecordFile, "RespDecoder": decodeDummy}, + 0xC0 : {"name": "CreateCyclicRecordFile","CMDdecoder":decodeCreateRecordFile, "RespDecoder": decodeDummy}, + 0xDF: {"name": "DelFile", "CMDdecoder":decodeDelFile, "RespDecoder": decodeDummy}, + # PICC Level Commands, GetVersion not decoded, please refer to datasheet for the meaning of resp 0xCA : {"name": "CreateApp ", "CMDdecoder":decodeCreateAPP, "RespDecoder": decodeDummy}, 0xDA : {"name": "DelApp ", "CMDdecoder":decodeDelAPP, "RespDecoder": decodeDummy}, @@ -222,8 +313,8 @@ MFDESFireCMDTypes = { 0xC7 : {"name": "CommitTransaction ","CMDdecoder":decodeDummy, "RespDecoder": decodeDummy}, 0xA7 : {"name": "AbortTransaction ","CMDdecoder":decodeDummy, "RespDecoder": decodeDummy}, # Security Related CMD - 0xAA : {"name": "AuthAES ", "CMDdecoder": decodeAuthAES, "RespDecoder": decodeRespAuthAES}, - 0x0A : {"name": "Auth3DES ", "CMDdecoder": decodeAuth3DES, "RespDecoder": decodeRespAuth3DES}, + 0xAA : {"name": "AuthAES ", "CMDdecoder": decodeAuthAES, "RespDecoder": decodeRespAuthAES}, + 0x0A : {"name": "Auth3DES ", "CMDdecoder": decodeAuth3DES, "RespDecoder": decodeRespAuth3DES}, 0x54 : {"name": "ChangeKeySettings ","CMDdecoder":decodeChangeKeySettings,"RespDecoder": decodeDummy}, 0x45 : {"name": "GetKeySettings ", "CMDdecoder":decodeDummy, "RespDecoder": decodeRespGetKeySettings}, 0xC4 : {"name": "ChangeKey ", "CMDdecoder":decodeChangeKey, "RespDecoder": decodeDummy}, From 1d2847cdec3adbddef4ae995fdd831a59021bb72 Mon Sep 17 00:00:00 2001 From: chenzitai Date: Fri, 3 Aug 2018 18:32:01 +0100 Subject: [PATCH 15/20] Add decoder selection parameter -d MFDESFire/None for different cards (cherry picked from commit 93635d9) --- Software/Chameleon/ISO14443.py | 31 +++++++++++++++++++++---------- Software/Chameleon/Log.py | 8 ++++---- Software/chamlog.py | 3 ++- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/Software/Chameleon/ISO14443.py b/Software/Chameleon/ISO14443.py index 3306485..55565e9 100644 --- a/Software/Chameleon/ISO14443.py +++ b/Software/Chameleon/ISO14443.py @@ -20,6 +20,16 @@ class ReaderCMD(Enum): readerCMD = ReaderCMD.NONE +# Map card types string to decoder +def DummyCardDecoder(data, source): + return "" + +CardTypesMap = { + "None": {"ApplicationDecoder": DummyCardDecoder}, + "MFDESFire": {"ApplicationDecoder": MFDESFireDecode}, + +} + class BlockData: @staticmethod @@ -29,7 +39,8 @@ class BlockData: else: return False - def __init__(self, byteCount, data, source): + + def __init__(self, byteCount, data, source, Cardtype): self.byteCount = byteCount self.data = data self.PCB = data[0] @@ -39,7 +50,7 @@ class BlockData: self.INF = None self.source = source self.CRCChecked = CRC_A_check(data) - self.CardApplicationDecoder = MFDESFireDecode + self.CardApplicationDecoder = CardTypesMap[Cardtype]["ApplicationDecoder"] if self.CRCChecked: @@ -218,7 +229,7 @@ def parseReader_3(data): return note -def parseReader_4(data): +def parseReader_4(data, Cardtype): global readerCMD byteCount = len(data) note = "" @@ -250,7 +261,7 @@ def parseReader_4(data): # Half-duplex block transmission # PCB bit mask: 0b11100110 elif (BlockData.isBlockData(byteCount, data)): - blockData = BlockData(byteCount,data, TrafficSource.Reader) + blockData = BlockData(byteCount,data, TrafficSource.Reader, Cardtype) note = blockData.decode() return note @@ -275,7 +286,7 @@ def parseCard_3(data): return note -def parseCard_4(data): +def parseCard_4(data, Cardtype): global readerCMD byteCount = len(data) note = "" @@ -318,15 +329,15 @@ def parseCard_4(data): note += " WRONG CRC " # Application Data elif (BlockData.isBlockData(byteCount, data)): - blockData = BlockData(byteCount,data, TrafficSource.Card) + blockData = BlockData(byteCount,data, TrafficSource.Card, Cardtype) note = blockData.decode() readerCMD = ReaderCMD.NONE return note -def parseReader(data): - return parseReader_3(data) + parseReader_4(data) +def parseReader(data, Cardtype): + return parseReader_3(data) + parseReader_4(data, Cardtype) -def parseCard(data): - return parseCard_3(data) + parseCard_4(data) +def parseCard(data, Cardtype): + return parseCard_3(data) + parseCard_4(data, Cardtype) diff --git a/Software/Chameleon/Log.py b/Software/Chameleon/Log.py index c10fd5f..da32bc8 100644 --- a/Software/Chameleon/Log.py +++ b/Software/Chameleon/Log.py @@ -97,7 +97,7 @@ eventTypes = { TIMESTAMP_MAX = 65536 eventTypes = { i : ({'name': 'UNKNOWN', 'decoder': binaryDecoder} if i not in eventTypes.keys() else eventTypes[i]) for i in range(256) } -def parseBinary(binaryStream, decode=False): +def parseBinary(binaryStream, decoder=None): log = [] # Completely read file contents and process them byte by byte @@ -138,12 +138,12 @@ def parseBinary(binaryStream, decode=False): note = "" # If we need to decode the data and paritybit check success - if (decode and logData[-1] != '!'): + if (decoder!=None and logData[-1] != '!'): # Decode the data from Reader if(event == 0x44 or event == 0x45): - note = iso14443_3.parseReader(binascii.a2b_hex(logData)) + note = iso14443_3.parseReader(binascii.a2b_hex(logData), decoder) elif (event == 0x46 or event == 0x47): - note = iso14443_3.parseCard(binascii.a2b_hex(logData)) + note = iso14443_3.parseCard(binascii.a2b_hex(logData), decoder) # Create log entry as dict and append it to event list logEntry = { diff --git a/Software/chamlog.py b/Software/chamlog.py index 5bece7a..d12e3d3 100755 --- a/Software/chamlog.py +++ b/Software/chamlog.py @@ -11,6 +11,7 @@ import json import Chameleon import io import datetime +from Chameleon.ISO14443 import CardTypesMap def verboseLog(text): formatString = "[{}] {}" @@ -48,7 +49,7 @@ def main(): argParser.add_argument("-t", "--type", choices=outputTypes.keys(), default='text', help="specifies output type") - argParser.add_argument("-d", "--decode", dest="decode", action='store_true', default=False) + argParser.add_argument("-d", "--decode", dest="decode", choices=CardTypesMap.keys(), default=None, help="Decode the sniffed traffic and application data with a decoder") argParser.add_argument("-l", "--live", dest="live", action='store_true', help="Use live logging capabilities of Chameleon") argParser.add_argument("-c", "--clear", dest="clear", action='store_true', help="Clear Chameleon's log memory when using -p") argParser.add_argument("-m", "--mode", dest="mode", metavar="LOGMODE", help="Additionally set Chameleon's log mode after reading it's memory") From 336a32d1e7aa3528497a4ff74d713605f262750f Mon Sep 17 00:00:00 2001 From: chenzitai Date: Fri, 3 Aug 2018 18:42:31 +0100 Subject: [PATCH 16/20] Add a parameter for setting logmode in chamtool (cherry picked from commit 4fb21fa) --- Software/chamtool.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Software/chamtool.py b/Software/chamtool.py index 0e964ee..8d7febc 100755 --- a/Software/chamtool.py +++ b/Software/chamtool.py @@ -68,6 +68,17 @@ def cmdLog(chameleon, arg): bytesReceived = chameleon.cmdDownloadLog(fileHandle) return "{} Bytes successfully written to {}".format(bytesReceived, arg) +def cmdLogMode(chameleon, arg): + result = chameleon.cmdLogMode(arg) + + if (arg is None): + return "Current logmode is: {}".format(result['response']) + else: + if (result['statusCode'] in chameleon.STATUS_CODES_SUCCESS): + return "logmode have been set to {}".format(arg) + else: + return "Setting logmode failed: {}".format(arg, result['statusText']) + def cmdLButton(chameleon, arg): result = chameleon.cmdLButton(arg) @@ -129,7 +140,7 @@ def cmdThreshold(chameleon, arg): if (result['statusCode'] in chameleon.STATUS_CODES_SUCCESS): return "Threshold have been set to {}".format(arg) else: - return "Setting threshold faled: {}".format(arg, result['statusText']) + return "Setting threshold failed: {}".format(arg, result['statusText']) def cmdUpgrade(chameleon, arg): result = chameleon.cmdUpgrade() @@ -164,6 +175,7 @@ def main(): cmdArgGroup.add_argument("-s", "--setting", dest="setting", action=CmdListAction, nargs='?', type=int, choices=Chameleon.VALID_SETTINGS, help="retrieve or set the current setting") cmdArgGroup.add_argument("-U", "--uid", dest="uid", action=CmdListAction, nargs='?', help="retrieve or set the current UID") cmdArgGroup.add_argument("-c", "--config", dest="config", action=CmdListAction, metavar="CFGNAME", nargs='?', help="retrieve or set the current configuration") + cmdArgGroup.add_argument("-lm", "--logmode", dest="logmode", action=CmdListAction, metavar="LOGMODE", nargs='?', help="retrieve or set the current log mode") cmdArgGroup.add_argument("-lb", "--lbutton", dest="lbutton", action=CmdListAction, metavar="ACTION", nargs='?', help="retrieve or set the current left button action") cmdArgGroup.add_argument("-rb", "--rbutton", dest="rbutton", action=CmdListAction, metavar="ACTION", nargs='?', help="retrieve or set the current right button action") cmdArgGroup.add_argument("-gl", "--gled", dest="gled", action=CmdListAction, metavar="FUNCTION", nargs='?', help="retrieve or set the current green led function") @@ -192,6 +204,7 @@ def main(): "upload" : cmdUpload, "download" : cmdDownload, "log" : cmdLog, + "logmode" : cmdLogMode, "lbutton" : cmdLButton, "rbutton" : cmdRButton, "gled" : cmdGreenLED, From 070ed1556b990ff9101c95959ffd5dbb8f0cba3f Mon Sep 17 00:00:00 2001 From: chenzitai Date: Sun, 5 Aug 2018 03:04:45 +0100 Subject: [PATCH 17/20] Fix bug(lengthCheck,decodeAPPIDs) (cherry picked from commit d6ea321) --- Software/Chameleon/Log.py | 2 +- Software/Chameleon/MFDESFire.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Software/Chameleon/Log.py b/Software/Chameleon/Log.py index da32bc8..524ecab 100644 --- a/Software/Chameleon/Log.py +++ b/Software/Chameleon/Log.py @@ -138,7 +138,7 @@ def parseBinary(binaryStream, decoder=None): note = "" # If we need to decode the data and paritybit check success - if (decoder!=None and logData[-1] != '!'): + if (decoder!=None and len(logData) >0 and logData[-1] != '!'): # Decode the data from Reader if(event == 0x44 or event == 0x45): note = iso14443_3.parseReader(binascii.a2b_hex(logData), decoder) diff --git a/Software/Chameleon/MFDESFire.py b/Software/Chameleon/MFDESFire.py index f66eee6..8237970 100644 --- a/Software/Chameleon/MFDESFire.py +++ b/Software/Chameleon/MFDESFire.py @@ -65,7 +65,7 @@ def decodeRespGetAPPID (data): dataLen = len(data) for i in range (0,int((dataLen-1)/3)): - note += "0x"+hexlify(data[3*i: 3*(i+1)]).decode()+"|" + note += "0x"+hexlify(data[1+3*i: 1+3*(i+1)]).decode()+"|" return note ############################## @@ -368,4 +368,4 @@ def MFDESFireDecode(data, source): else: note += MFDESFireCMDTypes[lastCMD]["RespDecoder"](data) - return note \ No newline at end of file + return note From 9e89f1935301c8eb28e20e55acc2bf05f15f414c Mon Sep 17 00:00:00 2001 From: chenzitai Date: Sun, 5 Aug 2018 03:22:26 +0100 Subject: [PATCH 18/20] Fix bug: number of returnd FileIDs (cherry picked from commit e1761af) --- Software/Chameleon/MFDESFire.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/Chameleon/MFDESFire.py b/Software/Chameleon/MFDESFire.py index 8237970..41186c5 100644 --- a/Software/Chameleon/MFDESFire.py +++ b/Software/Chameleon/MFDESFire.py @@ -194,7 +194,7 @@ def decodeData(data): # Application Level Commands ########################### def decodeRespGetFileIDs(data): - if len(data) == 4: + if len(data) > 0: return "FIDs:0x"+hexlify(data[1:]).decode() else: return strFail From 2be9b9e76ea01d357683d86fbd5965cfebb62ab5 Mon Sep 17 00:00:00 2001 From: chenzitai Date: Wed, 8 Aug 2018 23:32:14 +0100 Subject: [PATCH 19/20] Fix chamtool: upgrade command gives exception (cherry picked from commit 592b851) --- Software/Chameleon/Device.py | 5 ++++- Software/chamtool.py | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Software/Chameleon/Device.py b/Software/Chameleon/Device.py index 1852cf0..bfe2267 100644 --- a/Software/Chameleon/Device.py +++ b/Software/Chameleon/Device.py @@ -251,4 +251,7 @@ class Device: return self.getSetCmd(self.COMMAND_THRESHOLD, value) def cmdUpgrade(self): - return self.execCmd(self.COMMAND_UPGRADE) \ No newline at end of file + # Execute command + cmdLine = self.COMMAND_UPGRADE + self.LINE_ENDING + self.serial.write(cmdLine.encode('ascii')) + return 0 diff --git a/Software/chamtool.py b/Software/chamtool.py index 8d7febc..aff659c 100755 --- a/Software/chamtool.py +++ b/Software/chamtool.py @@ -143,8 +143,10 @@ def cmdThreshold(chameleon, arg): return "Setting threshold failed: {}".format(arg, result['statusText']) def cmdUpgrade(chameleon, arg): - result = chameleon.cmdUpgrade() - return "" + if(chameleon.cmdUpgrade() == 0): + print ("Device changed into Upgrade Mode") + exit(0) + # Custom class for argparse class CmdListAction(argparse.Action): def __init__(self, option_strings, dest, default=False, required=False, From 2af62ab8f8ed2c29e8cb2cce029a25dfbaffe3e4 Mon Sep 17 00:00:00 2001 From: chenzitai Date: Sun, 19 Aug 2018 15:32:59 +0100 Subject: [PATCH 20/20] Add decode additional frame for readdata command --- Software/Chameleon/MFDESFire.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Software/Chameleon/MFDESFire.py b/Software/Chameleon/MFDESFire.py index 41186c5..07577ef 100644 --- a/Software/Chameleon/MFDESFire.py +++ b/Software/Chameleon/MFDESFire.py @@ -327,6 +327,7 @@ MFDESFireCMDTypes = { # Commands need to use additional frame MFDESFireAFCMD = { + 0xBD : {"name": "ReadData ", "AFReaderDecoder": decodeDummy, "AFCardDecoder": decodeData}, 0xAA : {"name": "AuthAES ", "AFReaderDecoder":decodeAuthAESAF, "AFCardDecoder": decodeCardAuthAESAF}, 0x0A : {"name": "Auth3DES ", "AFReaderDecoder":decodeAuth3DESAF, "AFCardDecoder": decodeCardAuth3DESAF},