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