Merge pull request #181 from gypsophlia/DecodeMFDPython-pr

Upgrade chamlog tool, Decode Mifare DESFire and remove parity bit
This commit is contained in:
Georg Land
2018-09-12 13:51:53 +02:00
committed by GitHub
8 changed files with 837 additions and 10 deletions
+16 -1
View File
@@ -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,15 @@ 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):
# Execute command
cmdLine = self.COMMAND_UPGRADE + self.LINE_ENDING
self.serial.write(cmdLine.encode('ascii'))
return 0
+343
View File
@@ -0,0 +1,343 @@
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
CRC_A_func = crcmod.mkCrcFun(POLY, initCrc=CRC_INIT, xorOut=0)
class ReaderCMD(Enum):
NONE = 0
SELECT = 1
RATS = 2
PPS = 3
readerCMD = ReaderCMD.NONE
# Map card types string to decoder
def DummyCardDecoder(data, source):
return ""
CardTypesMap = {
"None": {"ApplicationDecoder": DummyCardDecoder},
"MFDESFire": {"ApplicationDecoder": MFDESFireDecode},
}
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, Cardtype):
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 = CardTypesMap[Cardtype]["ApplicationDecoder"]
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 + " "
# 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) + " "
# 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
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 "
},
"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 = {
"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: "FSC: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 "
}
}
def CRC_A(data):
return CRC_A_func(data)
def CRC_A_check(data):
datalen = len(data)
# 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):
return True
else:
return False
def parseReader_3(data):
global readerCMD
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):
readerCMD = ReaderCMD.SELECT
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 "
# 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, Cardtype):
global readerCMD
byteCount = len(data)
note = ""
# RATS
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:" + hex(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)):
note += "PSS0 - "
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)):
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)) + " "
# Half-duplex block transmission
# PCB bit mask: 0b11100110
elif (BlockData.isBlockData(byteCount, data)):
blockData = BlockData(byteCount,data, TrafficSource.Reader, Cardtype)
note = blockData.decode()
return note
def parseCard_3(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 readerCMD == ReaderCMD.SELECT and ((data[0] & (0x24)) in CardTrafficTypes["SAK"])):
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
def parseCard_4(data, Cardtype):
global readerCMD
byteCount = len(data)
note = ""
# ATS
# TL + T0 + TA + TB + TC + T1 ... + CRC
# TL: length without CRC
# ATS without data
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 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
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
# 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, Cardtype)
note = blockData.decode()
readerCMD = ReaderCMD.NONE
return note
def parseReader(data, Cardtype):
return parseReader_3(data) + parseReader_4(data, Cardtype)
def parseCard(data, Cardtype):
return parseCard_3(data) + parseCard_4(data, Cardtype)
+56 -4
View File
@@ -2,6 +2,41 @@
import struct
import binascii
import math
import Chameleon.ISO14443 as iso14443_3
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 +47,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 },
@@ -26,9 +68,9 @@ eventTypes = {
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': 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': binaryDecoder },
0x47: { 'name': 'CODEC RX SNI CARD W/PARITY', 'decoder': binaryParityDecoder },
0x80: { 'name': 'APP READ', 'decoder': binaryDecoder },
@@ -55,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, decoder=None):
log = []
# Completely read file contents and process them byte by byte
@@ -94,13 +136,23 @@ def parseBinary(binaryStream):
if (deltaTimestamp < 0):
deltaTimestamp += TIMESTAMP_MAX;
note = ""
# If we need to decode the data and paritybit check success
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)
elif (event == 0x46 or event == 0x47):
note = iso14443_3.parseCard(binascii.a2b_hex(logData), decoder)
# 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)
+372
View File
@@ -0,0 +1,372 @@
from Chameleon.utils import TrafficSource
from binascii import hexlify
lastCMD = 0x00
strFail = "Decode Fail"
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"
}
#########################
# PICC Level Commands
#########################
# 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()
else:
return strFail
# Get APP ID CMD
def decodeGetAPPID(data):
if len(data) == 1:
return ""
else:
return strFail
def decodeRespGetAPPID (data):
note = "APPIDs: |"
dataLen = len(data)
for i in range (0,int((dataLen-1)/3)):
note += "0x"+hexlify(data[1+3*i: 1+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 strFail
def decodeRespAuthAES(data):
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
##############################
# Data Manipulation Commands
##############################
# Read Data
def decodeFileNoOffsetLen(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 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
# 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
###########################
# Application Level Commands
###########################
def decodeRespGetFileIDs(data):
if len(data) > 0:
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
def decodeRespAdiFrame(data):
return "Data:0x"+hexlify(data[1:]).decode() if len(data) >1 else strFail
def decodeDummy(data):
return ""
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},
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":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},
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},
}
# 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},
0x3D : {"name": "WriteData ", "AFReaderDecoder":decodeData, "AFCardDecoder": decodeDummy},
0xBB : {"name": "ReadRecord ", "AFReaderDecoder":decodeDummy, "AFCardDecoder": decodeData},
0x3B: {"name": "WriteRecord ", "AFReaderDecoder": decodeData, "AFCardDecoder": decodeDummy}
}
def MFDESFireDecode(data, source):
note = ""
global lastCMD
if (source == TrafficSource.Reader):
cmd = data[0]
if cmd in MFDESFireCMDTypes :
# 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]
# Decode status code
if status in StatusCode:
note += StatusCode[status] + " "
# If status Ok, decode 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
+5
View File
@@ -0,0 +1,5 @@
from enum import Enum
class TrafficSource(Enum):
Reader = 0
Card = 1
+9 -4
View File
@@ -11,6 +11,7 @@ import json
import Chameleon
import io
import datetime
from Chameleon.ISO14443 import CardTypesMap
def verboseLog(text):
formatString = "[{}] {}"
@@ -19,9 +20,11 @@ def verboseLog(text):
def formatText(log):
formatString = '{timestamp:0>5d} ms <{deltaTimestamp:>+6d} ms>:'
formatString += '{eventName:<28} ({dataLength:<3} bytes)\t[{data}]\n'
formatString += '{eventName:<28} ({dataLength:<3} bytes) [{data:<20}] ' \
'\033[94m {note} \x1b[0m \n'
text = ''
for logEntry in log:
text += formatString.format(**logEntry)
@@ -46,6 +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", 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")
@@ -58,6 +62,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):
@@ -68,7 +73,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))
@@ -94,7 +99,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))
+35 -1
View File
@@ -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)
@@ -119,7 +130,23 @@ 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 failed: {}".format(arg, result['statusText'])
def cmdUpgrade(chameleon, arg):
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,
@@ -150,10 +177,14 @@ 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")
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):
@@ -175,10 +206,13 @@ def main():
"upload" : cmdUpload,
"download" : cmdDownload,
"log" : cmdLog,
"logmode" : cmdLogMode,
"lbutton" : cmdLButton,
"rbutton" : cmdRButton,
"gled" : cmdGreenLED,
"rled" : cmdRedLED,
"threshold" : cmdThreshold,
"upgrade" : cmdUpgrade,
}
if hasattr(args, "cmdList"):
+1
View File
@@ -1 +1,2 @@
pyserial
crcmod