#! /usr/bin/python3 """ # Display PC/SC functions arguments # Copyright (C) 2011-2021 Ludovic Rousseau """ # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os import signal import time try: # for Python3 from queue import Queue except ImportError: # for Python2 from Queue import Queue from threading import Thread from operator import attrgetter def hexdump(data_buffer, width=16): def quotechars(data_buffer): return ''.join(['.', chr(c)][c > 31 and c < 127] for c in data_buffer) result = [] offset = 0 while data_buffer: line = data_buffer[:width] data_buffer = data_buffer[width:] hex_dump = " ".join("%02X" % c for c in line) ascii_dump = quotechars(line) if len(line) < width: hex_dump += " " * (width - len(line)) result.append("%04X %s %s" % (offset, hex_dump, ascii_dump)) offset += width return result def _parse_rv(line): """ parse the return value line """ if line == "": raise Exception("Empty line (application exited?)") (direction, sec, usec, function, code, rv) = line.split('|') if direction != '<': raise Exception("Wrong line:", line) sec = int(sec) usec = int(usec) return (code, rv, sec, usec) class SpyExit(Exception): pass class StatRecord(object): """ Record to store statistics """ def __init__(self, name): self.name = name self.executions = list() self.total_time = 0 self.occurences = 0 def __repr__(self): return self.name + ": " + repr(self.executions) class PCSCspy(object): """ PC/SC spy """ color_red = "\x1b[01;31m" color_green = "\x1b[32m" color_blue = "\x1b[34m" color_magenta = "\x1b[35m" color_normal = "\x1b[0m" def get_line(self): line = self.queue.get() if line == "EXIT": raise SpyExit() return line def _log_rv(self): """ log the return value """ line = self.get_line() (code, rv, sec, usec) = _parse_rv(line) delta_sec = sec - self.sec delta_usec = usec - self.usec if delta_usec < 0: delta_sec -= 1 delta_usec += 1000000 if self.diffable: time = " [??.??]" else: time = " [%d.%06d]" % (delta_sec, delta_usec) self.execution_time = delta_sec + delta_usec / 1000000. rvs = { 0x00000000: "SCARD_S_SUCCESS", 0x80100001: "SCARD_F_INTERNAL_ERROR", 0x80100002: "SCARD_E_CANCELLED", 0x80100003: "SCARD_E_INVALID_HANDLE", 0x80100004: "SCARD_E_INVALID_PARAMETER", 0x80100005: "SCARD_E_INVALID_TARGET", 0x80100006: "SCARD_E_NO_MEMORY", 0x80100007: "SCARD_F_WAITED_TOO_LONG", 0x80100008: "SCARD_E_INSUFFICIENT_BUFFER", 0x80100009: "SCARD_E_UNKNOWN_READER", 0x8010000A: "SCARD_E_TIMEOUT", 0x8010000B: "SCARD_E_SHARING_VIOLATION", 0x8010000C: "SCARD_E_NO_SMARTCARD", 0x8010000D: "SCARD_E_UNKNOWN_CARD", 0x8010000E: "SCARD_E_CANT_DISPOSE", 0x8010000F: "SCARD_E_PROTO_MISMATCH", 0x80100010: "SCARD_E_NOT_READY", 0x80100011: "SCARD_E_INVALID_VALUE", 0x80100012: "SCARD_E_SYSTEM_CANCELLED", 0x80100013: "SCARD_F_COMM_ERROR", 0x80100014: "SCARD_F_UNKNOWN_ERROR", 0x80100015: "SCARD_E_INVALID_ATR", 0x80100016: "SCARD_E_NOT_TRANSACTED", 0x80100017: "SCARD_E_READER_UNAVAILABLE", 0x80100018: "SCARD_P_SHUTDOWN", 0x80100019: "SCARD_E_PCI_TOO_SMALL", 0x8010001A: "SCARD_E_READER_UNSUPPORTED", 0x8010001B: "SCARD_E_DUPLICATE_READER", 0x8010001C: "SCARD_E_CARD_UNSUPPORTED", 0x8010001D: "SCARD_E_NO_SERVICE", 0x8010001E: "SCARD_E_SERVICE_STOPPED", 0x8010001F: "SCARD_E_UNSUPPORTED_FEATURE", 0x80100020: "SCARD_E_ICC_INSTALLATION", 0x80100021: "SCARD_E_ICC_CREATEORDER", 0x80100023: "SCARD_E_DIR_NOT_FOUND", 0x80100024: "SCARD_E_FILE_NOT_FOUND", 0x80100025: "SCARD_E_NO_DIR", 0x80100026: "SCARD_E_NO_FILE", 0x80100027: "SCARD_E_NO_ACCESS", 0x80100028: "SCARD_E_WRITE_TOO_MANY", 0x80100029: "SCARD_E_BAD_SEEK", 0x8010002A: "SCARD_E_INVALID_CHV", 0x8010002B: "SCARD_E_UNKNOWN_RES_MNG", 0x8010002C: "SCARD_E_NO_SUCH_CERTIFICATE", 0x8010002D: "SCARD_E_CERTIFICATE_UNAVAILABLE", 0x8010002E: "SCARD_E_NO_READERS_AVAILABLE", 0x8010002F: "SCARD_E_COMM_DATA_LOST", 0x80100030: "SCARD_E_NO_KEY_CONTAINER", 0x80100031: "SCARD_E_SERVER_TOO_BUSY", 0x80100065: "SCARD_W_UNSUPPORTED_CARD", 0x80100066: "SCARD_W_UNRESPONSIVE_CARD", 0x80100067: "SCARD_W_UNPOWERED_CARD", 0x80100068: "SCARD_W_RESET_CARD", 0x80100069: "SCARD_W_REMOVED_CARD", 0x8010006A: "SCARD_W_SECURITY_VIOLATION", 0x8010006B: "SCARD_W_WRONG_CHV", 0x8010006C: "SCARD_W_CHV_BLOCKED", 0x8010006D: "SCARD_W_EOF", 0x8010006E: "SCARD_W_CANCELLED_BY_USER", 0x8010006F: "SCARD_W_CARD_NOT_AUTHENTICATED", } rv_text = rvs[int(rv, 16)] data = " => " + code + " (" + rv_text + " [" + rv + "]) " if "0x00000000" != rv: if self.color: print(self.indent + PCSCspy.color_red + data + PCSCspy.color_normal + time) else: print(self.indent + data + time) else: print(self.indent + data + time) return rv_text def log_in(self, line): """ generic log for IN line """ if self.color: print(self.indent + PCSCspy.color_green + " i " + line + PCSCspy.color_normal) else: print(self.indent + " i " + line) def log_out(self, line): """ generic log for OUT line """ if self.color: print(self.indent + PCSCspy.color_magenta + " o " + line + PCSCspy.color_normal) else: print(self.indent + " o " + line) def log_in_multi(self, lines, padding=""): """ generic log for IN lines """ for line in lines: self.log_in(padding + line) def log_out_multi(self, lines, padding=""): """ generic log for OUT lines """ for line in lines: self.log_out(padding + line) def log_in_hCard(self): """ log hCard IN parameter """ hCard = self.get_line() if self.diffable: self.log_in("hCard: 0x????") else: self.log_in("hCard: %s" % hCard) def log_in_scard_io_request(self): """ log SCARD_IO_REQUEST IN parameter """ dwProtocol = self.get_line() if self.diffable: self.log_in("pioSendPci.dwProtocol: 0x????") else: self.log_in("pioSendPci.dwProtocol: %s" % dwProtocol) cbPciLength = self.get_line() if self.diffable: self.log_in("pioSendPci.cbPciLength: 0x????") else: self.log_in("pioSendPci.cbPciLength: %s" % cbPciLength) def log_in_hContext(self): """ log hContext IN parameter """ hContext = self.get_line() if self.diffable: self.log_in("hContext: 0x????") else: self.log_in("hContext: %s" % hContext) def log_in_disposition(self): """ log dwDisposition IN parameter """ dwDisposition = self.get_line() dispositions = {0: 'SCARD_LEAVE_CARD', 1: 'SCARD_RESET_CARD', 2: 'SCARD_UNPOWER_CARD', 3: 'SCARD_EJECT_CARD'} try: disposition = dispositions[int(dwDisposition, 16)] except KeyError: disposition = "UNKNOWN" self.log_in("dwDisposition: %s (%s)" % (disposition, dwDisposition)) def log_in_attrid(self): """ log dwAttrId IN parameter """ dwAttrId = self.get_line() attrids = {0x00010100: 'SCARD_ATTR_VENDOR_NAME', 0x00010102: 'SCARD_ATTR_VENDOR_IFD_VERSION', 0x00010103: 'SCARD_ATTR_VENDOR_IFD_SERIAL_NO', 0x0007A007: 'SCARD_ATTR_MAXINPUT', 0x00090300: 'SCARD_ATTR_ICC_PRESENCE', 0x00090301: 'SCARD_ATTR_ICC_INTERFACE_STATUS', 0x00090303: 'SCARD_ATTR_ATR_STRING', 0x7FFF0003: 'SCARD_ATTR_DEVICE_FRIENDLY_NAME_A', 0x7FFF0004: 'SCARD_ATTR_DEVICE_SYSTEM_NAME_A', 0x7FFF0005: 'SCARD_ATTR_DEVICE_FRIENDLY_NAME_W', 0x7FFF0006: 'SCARD_ATTR_DEVICE_SYSTEM_NAME_W'} try: attrid = attrids[int(dwAttrId, 16)] except KeyError: attrid = "UNKNOWN" self.log_in("dwAttrId: %s (%s)" % (attrid, dwAttrId)) def log_in_dwShareMode(self): """ log dwShareMode IN parameter """ dwShareMode = self.get_line() sharemodes = {1: 'SCARD_SHARE_EXCLUSIVE', 2: 'SCARD_SHARE_SHARED', 3: 'SCARD_SHARE_DIRECT'} try: sharemode = sharemodes[int(dwShareMode, 16)] except KeyError: sharemode = "UNKNOWN" self.log_in("dwShareMode: %s (%s)" % (sharemode, dwShareMode)) def log_in_dwPreferredProtocols(self): """ log dwPreferredProtocols IN parameter """ dwPreferredProtocols = self.get_line() PreferredProtocols = list() protocol = int(dwPreferredProtocols, 16) if protocol & 1: PreferredProtocols.append("T=0") if protocol & 2: PreferredProtocols.append("T=1") if protocol & 4: PreferredProtocols.append("RAW") if protocol & 8: PreferredProtocols.append("T=15") self.log_in("dwPreferredProtocols: %s (%s)" % (dwPreferredProtocols, ", ".join(PreferredProtocols))) def log_out_scard_io_request(self): """ log SCARD_IO_REQUEST OUT parameter """ dwProtocol = self.get_line() if self.diffable: self.log_out("pioRecvPci.dwProtocol: 0x????") else: self.log_out("pioRecvPci.dwProtocol: %s" % dwProtocol) cbPciLength = self.get_line() if self.diffable: self.log_out("pioRecvPci.cbPciLength: 0x????") else: self.log_out("pioRecvPci.cbPciLength: %s" % cbPciLength) def log_out_dwActiveProtocol(self): """ log dwActiveProtocol OUT parameter """ dwActiveProtocol = self.get_line() protocol = int(dwActiveProtocol, 16) if protocol & 1: protocol = "T=0" elif protocol & 2: protocol = "T=1" elif protocol & 4: protocol = "RAW" elif protocol & 8: protocol = "T=15" else: protocol = "UNKNOWN" self.log_out("dwActiveProtocol: %s (%s)" % (protocol, dwActiveProtocol)) def log_out_hContext(self): """ log hContext OUT parameter """ hContext = self.get_line() if self.diffable: self.log_out("hContext: 0x????") else: self.log_out("hContext: %s" % hContext) def _get_state(self, dwState): """ parse dwCurrentState and dwEventState """ SCardStates = {0: 'SCARD_STATE_UNAWARE', 1: 'SCARD_STATE_IGNORE', 2: 'SCARD_STATE_CHANGED', 4: 'SCARD_STATE_UNKNOWN', 8: 'SCARD_STATE_UNAVAILABLE', 16: 'SCARD_STATE_EMPTY', 32: 'SCARD_STATE_PRESENT', 64: 'SCARD_STATE_ATRMATCH', 128: 'SCARD_STATE_EXCLUSIVE', 256: 'SCARD_STATE_INUSE', 512: 'SCARD_STATE_MUTE', 1024: 'SCARD_STATE_UNPOWERED'} state = list() for bit in SCardStates.keys(): if dwState & bit: state.append(SCardStates[bit]) return ", ".join(state) def log_dwCurrentState(self, log): """ log dwCurrentState IN/OUT parameter """ dwCurrentState = self.get_line() state = self._get_state(int(dwCurrentState, 16)) log(" dwCurrentState: %s (%s)" % (state, dwCurrentState)) def log_dwEventState(self, log): """ log dwEventState IN/OUT parameter """ dwEventState = self.get_line() state = self._get_state(int(dwEventState, 16)) log(" dwEventState: %s (%s)" % (state, dwEventState)) def log_dwControlCode(self): """ log SCardControl() dwControlCode """ dwControlCode = self.get_line() try: code = self.ControlCodes[int(dwControlCode, 16)] except KeyError: code = "UNKNOWN" self.log_in("dwControlCode: %s (%s)" % (code, dwControlCode)) return int(dwControlCode, 16) def log_in2(self, header): """ generic log IN parameter """ data = self.get_line() if data.startswith("0x"): decimal = int(data, 16) self.log_in("%s %s (%d)" % (header, data, decimal)) else: self.log_in("%s %s" % (header, data)) return data def log_out2(self, header): """ generic log OUT parameter """ data = self.get_line() if data.startswith("0x"): decimal = int(data, 16) self.log_out("%s %s (%d)" % (header, data, decimal)) else: self.log_out("%s %s" % (header, data)) return data def log_out_n_str(self, size_name, field_name): """ log multi-lines entries """ data = self.get_line() self.log_out("%s %s" % (size_name, data)) size = int(data, 16) data_read = 0 if 0 == size: data = self.get_line() self.log_out("%s %s" % (field_name, data)) else: while data_read < size: data = self.get_line() self.log_out("%s %s" % (field_name, data)) if data == 'NULL': break data_read += len(data) + 1 def log_name(self, name): """ log function name """ if self.color: print(self.indent + PCSCspy.color_blue + name + PCSCspy.color_normal) else: print(self.indent + name) def _log_readers(self, readers, direction): """ log SCARD_READERSTATE structure """ log = self.log_in2 raw_log = self.log_in if (direction == "out"): log = self.log_out2 raw_log = self.log_out for index in range(readers): log("szReader:") self.log_dwCurrentState(raw_log) self.log_dwEventState(raw_log) log(" Atr length:") log(" Atr:") def log_buffer(self, field, direction): log = self.log_in log_multi = self.log_in_multi if direction == "out": log = self.log_out log_multi = self.log_out_multi hex_buffer = self.get_line() log(field) if hex_buffer == "NULL": log(" NULL") elif hex_buffer != "": int_buffer = [int(x, 16) for x in hex_buffer.split(" ")] formated_buffer = hexdump(int_buffer) log_multi(formated_buffer, " ") return hex_buffer def _SCardEstablishContext(self): """ SCardEstablishContext """ self.log_name("SCardEstablishContext") dwScope = self.get_line() scopes = {0: 'SCARD_SCOPE_USER', 1: 'SCARD_SCOPE_TERMINAL', 2: 'SCARD_SCOPE_SYSTEM'} self.log_in("dwScope: %s (%s)" % (scopes[int(dwScope, 16)], dwScope)) self.log_out_hContext() self._log_rv() def _SCardIsValidContext(self): """ SCardIsValidContext """ self.log_name("SCardIsValidContext") self.log_in_hContext() self._log_rv() def _SCardReleaseContext(self): """ SCardReleaseContext """ self.log_name("SCardReleaseContext") self.log_in_hContext() self._log_rv() def _SCardListReaders(self): """ SCardListReaders """ self.log_name("SCardListReaders") self.log_in_hContext() self.log_in2("mszGroups:") self.log_out_n_str("pcchReaders:", "mszReaders:") self._log_rv() def _SCardListReaderGroups(self): """ SCardListReaderGroups """ self.log_name("SCardListReaderGroups") self.log_in_hContext() self.log_in2("pcchGroups:") self.log_out_n_str("pcchGroups:", "mszGroups:") self._log_rv() def _SCardGetStatusChange(self): """ SCardGetStatusChange """ self.log_name("SCardGetStatusChange") self.log_in_hContext() self.log_in2("dwTimeout:") readers = int(self.get_line(), 16) self.log_in("cReaders: %d" % readers) self._log_readers(readers, direction="in") self._log_readers(readers, direction="out") self._log_rv() def _SCardFreeMemory(self): """ SCardFreeMemory """ self.log_name("SCardFreeMemory") self.log_in_hContext() self.log_in2("pvMem:") self._log_rv() def _SCardConnect(self): """ SCardConnect """ self.log_name("SCardConnect") self.log_in_hContext() self.log_in2("szReader") self.log_in_dwShareMode() self.log_in_dwPreferredProtocols() self.log_in2("phCard") self.log_in2("pdwActiveProtocol") self.log_out2("phCard") self.log_out_dwActiveProtocol() self._log_rv() def _SCardTransmit(self): """ SCardTransmit """ self.log_name("SCardTransmit") self.log_in_hCard() self.log_in_scard_io_request() self.log_in2("bSendLength") self.log_buffer("bSendBuffer", "in") self.log_out_scard_io_request() self.log_out2("bRecvLength") self.log_buffer("bRecvBuffer", "out") self._log_rv() def _SCardControl(self): """ SCardControl """ self.log_name("SCarControl") self.log_in_hCard() dwControlCode = self.log_dwControlCode() bSendLength = self.log_in2("bSendLength") bSendBuffer = self.log_buffer("bSendBuffer", "in") bRecvLength = self.log_out2("bRecvLength") bRecvBuffer = self.log_buffer("bRecvBuffer", "out") rv_text = self._log_rv() # do not parse the received buffer in case of error if rv_text != "SCARD_S_SUCCESS": return def hex2int(data, lengh): return [int(x, 16) for x in data.split(" ")] if dwControlCode == self.CM_IOCTL_GET_FEATURE_REQUEST: print(" parsing CM_IOCTL_GET_FEATURE_REQUEST results:") bRecvLength = int(bRecvLength, 16) bRecvBuffer = hex2int(bRecvBuffer, bRecvLength) # parse GET_FEATURE_REQUEST results while bRecvBuffer: tag = bRecvBuffer[0] length = bRecvBuffer[1] value = bRecvBuffer[2:2 + length] value_int = value[3] + 256 * (value[2] + 256 * (value[1] + 256 * value[0])) try: self.ControlCodes[value_int] = self.features[tag] self.__dict__[self.features[tag]] = value_int except KeyError: self.ControlCodes[value_int] = "UNKNOWN" print(" Tag %s is 0x%X" % (self.ControlCodes[value_int], value_int)) bRecvBuffer = bRecvBuffer[2 + length:] elif dwControlCode == self.FEATURE_GET_TLV_PROPERTIES: print(" parsing FEATURE_GET_TLV_PROPERTIES results:") bRecvLength = int(bRecvLength, 16) bRecvBuffer = hex2int(bRecvBuffer, bRecvLength) tlv_properties = { 1: "PCSCv2_PART10_PROPERTY_wLcdLayout", 2: "PCSCv2_PART10_PROPERTY_bEntryValidationCondition", 3: "PCSCv2_PART10_PROPERTY_bTimeOut2", 4: "PCSCv2_PART10_PROPERTY_wLcdMaxCharacters", 5: "PCSCv2_PART10_PROPERTY_wLcdMaxLines", 6: "PCSCv2_PART10_PROPERTY_bMinPINSize", 7: "PCSCv2_PART10_PROPERTY_bMaxPINSize", 8: "PCSCv2_PART10_PROPERTY_sFirmwareID", 9: "PCSCv2_PART10_PROPERTY_bPPDUSupport"} # parse GET_TLV_PROPERTIES results while bRecvBuffer: tag = bRecvBuffer[0] length = bRecvBuffer[1] value = bRecvBuffer[2:2 + length] try: tag_text = tlv_properties[tag] except KeyError: tag_text = "UNKNOWN" print(" Tag:", tag_text) print(" Length: ", length) print(" Value:", value) bRecvBuffer = bRecvBuffer[2 + length:] elif dwControlCode == self.FEATURE_IFD_PIN_PROPERTIES: print(" parsing FEATURE_IFD_PIN_PROPERTIES results:") bRecvBuffer = hex2int(bRecvBuffer, int(bRecvLength, 16)) print(" wLcdLayout:", bRecvBuffer[0], bRecvBuffer[1]) print(" bEntryValidationCondition:", bRecvBuffer[2]) print(" bTimeOut2:", bRecvBuffer[3]) elif dwControlCode == self.FEATURE_VERIFY_PIN_DIRECT: print(" parsing FEATURE_VERIFY_PIN_DIRECT:") bSendBuffer = hex2int(bSendBuffer, int(bSendLength, 16)) print(" bTimerOut:", bSendBuffer[0]) print(" bTimerOut2:", bSendBuffer[1]) print(" bmFormatString:", bSendBuffer[2]) print(" bmPINBlockString:", bSendBuffer[3]) print(" bmPINLengthFormat:", bSendBuffer[4]) print(" wPINMaxExtraDigit: 0x%02X%02X" % (bSendBuffer[6], bSendBuffer[5])) print(" Min:", bSendBuffer[6]) print(" Max:", bSendBuffer[5]) print(" bEntryValidationCondition:", bSendBuffer[7]) print(" bNumberMessage:", bSendBuffer[8]) print(" wLangId: 0x%02X%02X" % (bSendBuffer[10], bSendBuffer[9])) print(" bMsgIndex:", bSendBuffer[11]) print(" bTeoPrologue:", bSendBuffer[12], bSendBuffer[13], \ bSendBuffer[14]) print(" ulDataLength:", bSendBuffer[15] + \ bSendBuffer[16] * 256 + bSendBuffer[17] * 2 ** 16 + \ bSendBuffer[18] * 2 ** 24) print(" APDU:") result = hexdump(bSendBuffer[19:]) for line in result: print(" ", line) def _SCardGetAttrib(self): """ SCardGetAttrib """ self.log_name("SCardGetAttrib") self.log_in_hCard() self.log_in_attrid() self.log_out2("bAttrLen") self.log_buffer("bAttr", "out") self._log_rv() def _SCardSetAttrib(self): """ SCardSetAttrib """ self.log_name("SCardSetAttrib") self.log_in_hCard() self.log_in_attrid() self.log_in2("bAttrLen") self.log_buffer("bAttr", "in") self._log_rv() def _SCardStatus(self): """ SCardStatus """ self.log_name("SCardStatus") self.log_in_hCard() self.log_in2("pcchReaderLen") self.log_in2("pcbAtrLen") self.log_out2("cchReaderLen") self.log_out2("mszReaderName") self.log_out2("dwState") self.log_out2("dwProtocol") data = self.log_out2("bAtrLen") if not data == "NULL": self.log_out2("bAtr") else: self.log_out("bAtr") self._log_rv() def _SCardReconnect(self): """ SCardReconnect """ self.log_name("SCardReconnect") self.log_in_hCard() self.log_in_dwShareMode() self.log_in_dwPreferredProtocols() self.log_in2("dwInitialization") self.log_out_dwActiveProtocol() self._log_rv() def _SCardDisconnect(self): """" SCardDisconnect """ self.log_name("SCardDisconnect") self.log_in_hCard() self.log_in_disposition() self._log_rv() def _SCardBeginTransaction(self): """ SCardBeginTransaction """ self.log_name("SCardBeginTransaction") self.log_in_hCard() self._log_rv() def _SCardEndTransaction(self): """ SCardEndTransaction """ self.log_name("SCardEndTransaction") self.log_in_hCard() self.log_in_disposition() self._log_rv() def _SCardCancel(self): """ SCardCancel """ self.log_name("SCardCancel") self.log_in_hCard() self._log_rv() def __init__(self, queue, stats, indent=0, color=True, diffable=False): """ constructor """ # communication queue self.queue = queue self.color = color self.diffable = diffable self.stats = stats self.indent = " " * (indent * 4) if display_thread: self.indent += " (t%d) " % indent self.features = {0x01: "FEATURE_VERIFY_PIN_START", 0x02: "FEATURE_VERIFY_PIN_FINISH", 0x03: "FEATURE_MODIFY_PIN_START", 0x04: "FEATURE_MODIFY_PIN_FINISH", 0x05: "FEATURE_GET_KEY_PRESSED", 0x06: "FEATURE_VERIFY_PIN_DIRECT", 0x07: "FEATURE_MODIFY_PIN_DIRECT", 0x08: "FEATURE_MCT_READER_DIRECT", 0x09: "FEATURE_MCT_UNIVERSAL", 0x0A: "FEATURE_IFD_PIN_PROPERTIES", 0x0B: "FEATURE_ABORT", 0x0C: "FEATURE_SET_SPE_MESSAGE", 0x0D: "FEATURE_VERIFY_PIN_DIRECT_APP_ID", 0x0E: "FEATURE_MODIFY_PIN_DIRECT_APP_ID", 0x0F: "FEATURE_WRITE_DISPLAY", 0x10: "FEATURE_GET_KEY", 0x11: "FEATURE_IFD_DISPLAY_PROPERTIES", 0x12: "FEATURE_GET_TLV_PROPERTIES", 0x13: "FEATURE_CCID_ESC_COMMAND"} def SCARD_CTL_CODE(code): return 0x42000000 + code self.CM_IOCTL_GET_FEATURE_REQUEST = SCARD_CTL_CODE(3400) self.ControlCodes = { SCARD_CTL_CODE(1): "IOCTL_SMARTCARD_VENDOR_IFD_EXCHANGE", SCARD_CTL_CODE(3400): "CM_IOCTL_GET_FEATURE_REQUEST" } # dwControlCode not yet known for key in self.features.keys(): self.__dict__[self.features[key]] = -1 def worker(self, *args): line = self.queue.get() while line != '': # Enter function? if line[0] != '>': if line == 'EXIT': return else: print("Garbage: ", line) else: try: # dispatch (direction, sec, usec, fct) = line.strip().split('|') self.sec = int(sec) self.usec = int(usec) if fct == 'SCardEstablishContext': self._SCardEstablishContext() elif fct == 'SCardReleaseContext': self._SCardReleaseContext() elif fct == 'SCardIsValidContext': self._SCardIsValidContext() elif fct == 'SCardListReaderGroups': self._SCardListReaderGroups() elif fct == 'SCardFreeMemory': self._SCardFreeMemory() elif fct == 'SCardListReaders': self._SCardListReaders() elif fct == 'SCardGetStatusChange': self._SCardGetStatusChange() elif fct == 'SCardConnect': self._SCardConnect() elif fct == 'SCardTransmit': self._SCardTransmit() elif fct == 'SCardControl' or fct == 'SCardControl132': self._SCardControl() elif fct == 'SCardGetAttrib': self._SCardGetAttrib() elif fct == 'SCardSetAttrib': self._SCardSetAttrib() elif fct == 'SCardStatus': self._SCardStatus() elif fct == 'SCardReconnect': self._SCardReconnect() elif fct == 'SCardDisconnect': self._SCardDisconnect() elif fct == 'SCardBeginTransaction': self._SCardBeginTransaction() elif fct == 'SCardEndTransaction': self._SCardEndTransaction() elif fct == 'SCardCancel': self._SCardCancel() else: print("Unknown function:", fct) except SpyExit: return try: record = self.stats[fct] except KeyError: record = self.stats[fct] = StatRecord(fct) record.executions.append(self.execution_time) line = self.queue.get() class PCSCdemultiplexer(object): def __init__(self, logfile=None, color=True, diffable=False): """ constructor """ # use default fifo file? if logfile is None: logfile = os.path.expanduser('~/pcsc-spy') # create the FIFO file try: os.mkfifo(logfile) except OSError: print("fifo %s already present. Reusing it." % logfile) self.sec = self.usec = 0 self.fifo = logfile self.filedesc2 = open(self.fifo, 'r') self.queues = dict() self.color = color self.diffable = diffable def __del__(self): """ cleanup """ from stat import S_ISFIFO file_stat = os.stat(self.fifo) # remove the log fifo only if it is a FIFO and not a log file if S_ISFIFO(file_stat.st_mode): os.unlink(self.fifo) def loop(self): """ loop reading logs """ # for statistics stats = dict() threads = dict() indent = 0 # dispatch line = self.filedesc2.readline().strip() (thread, tail) = line.split('@') res = tail.strip().split('|') # check the first line format if res[0] != ">": print("Wrong format!") print("First line Should start with a '>' but got:") print(tail) return (direction, sec, usec, fct) = res start_time = int(sec) + int(usec) / 1000000. lastest_result = "" while line != '': previous_thread = thread (thread, tail) = line.split('@') if "<" in tail: lastest_result = tail # in case the thread changes if previous_thread != thread: # schedule the other thread so it has time to empty its # queue time.sleep(.001) try: queue = self.queues[thread] except KeyError: queue = self.queues[thread] = Queue() stats[thread] = dict() # new worker spy = PCSCspy(queue, stats[thread], indent=indent, color=self.color, diffable=self.diffable) threads[thread] = Thread(target=spy.worker) threads[thread].start() indent += 1 queue.put(tail) line = self.filedesc2.readline().strip() # tell the workers to exit for thread in self.queues.keys(): self.queues[thread].put('EXIT') # wait for all the workers to finish for thread in threads: threads[thread].join() (code, rv, sec, usec) = _parse_rv(lastest_result) end_time = sec + usec / 1000000. total_time = end_time - start_time # compute some statistics thread_n = 1 for thread in stats: stat = stats[thread] for fct in stat: record = stat[fct] record.occurences = len(record.executions) record.total_time = sum(record.executions) records = [stat[fct] for fct in stat] # display statistics sorted by total_time print() print("Thread %d/%d" % (thread_n, len(stats))) print("Results sorted by total execution time") print("total time: %f sec" % total_time) for record in sorted(records, key=attrgetter('total_time'), reverse=True): print("%f sec (%3d calls) %5.2f%% %s" % (record.total_time, record.occurences, record.total_time / total_time * 100., record.name)) thread_n += 1 def main(logfile=None, color=True, diffable=False): """ main """ spy = PCSCdemultiplexer(logfile, color, diffable) spy.loop() def signal_handler(sig, frame): print('Ctrl-C, exiting.') os.kill(os.getpid(), signal.SIGQUIT) def print_usage(): print("Usage: pcsc-spy [-n|--nocolor] [-d|--diffable] [-h|--help] [-v|--version] [-t|--thread]") if __name__ == "__main__": import sys import getopt logfile = None try: opts, args = getopt.getopt(sys.argv[1:], "ndhvt", ["nocolor", "diffable", "help", "version", "thread"]) except getopt.GetoptError: print_usage() sys.exit(1) color = True diffable = False display_thread = False for o, a in opts: if o == "-n" or o == "--nocolor": color = False if o == "-d" or o == "--diffable": diffable = True if o == "-h" or o == "--help": print_usage() sys.exit(1) if o == "-v" or o == "--version": print("pcsc-spy version 1.1") print("Copyright (c) 2011-2021, Ludovic Rousseau ") print() sys.exit(1) if o == "-t" or o == "--thread": display_thread = True if len(args) > 0: logfile = args[0] signal.signal(signal.SIGINT, signal_handler) main(logfile, color=color, diffable=diffable)