Last active
May 25, 2024 21:30
-
-
Save dev-zzo/5c21deca56b7e34e931e18250cf09c83 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import sys | |
import io | |
from pcsc import PcScTransport | |
def h2b(x): | |
return bytes.fromhex(x) | |
# cplc data; also try apdu D088000000? | |
# https://crocs.fi.muni.cz/_media/public/papers/jcalgtest_forensic_certification_profile_secrypt22_preprint.pdf | |
cplc_ic_fabricator = { | |
0x0003: 'Renesas', | |
0x0005: 'Infineon', | |
0x008C: 'Tongxin', | |
0x1290: 'Thales Dis France SAS', | |
0x2050: 'Philips', | |
0x3060: 'Renesas', | |
0x4070: 'NXP', | |
0x4090: 'Infineon', | |
0x4180: 'Atmel', | |
0x4250: 'Samsung', | |
0x4750: 'ST Microelectronics', | |
0x4790: 'NXP', | |
0x4830: 'Infineon', | |
0x8100: 'Infineon', | |
} | |
cplc_ic_type = { | |
# Infineon | |
0x0005: {}, | |
0x4090: { | |
0x3401: 'SLC52GDA804', | |
0x3404: 'SLC52GDA700', | |
0x3405: 'SLC52GDA600', | |
0x00CE: 'SLE66CLX360PE / M1587/M1588', # https://www.commoncriteriaportal.org/files/epfiles/ANSSI-CC_2011-16fr.pdf | |
0x00CF: 'SLE66C(L)X800 / M1580/M1581', # https://www.commoncriteriaportal.org/files/epfiles/ANSSI-CC_2011-16fr.pdf | |
# SLE66CLX1600PEM M1590 A12 | |
# SLE66CLX1600PE M1596 A12 | |
# SLE66CLX1600PES M1597 A12 | |
# SLE66CX1600PE* M1598 A12 | |
# SLE66CLX1440PEM m2090 a13 | |
0x00E9: 'SLE66C(L)X1440 / M2091/M2092/M2093', # https://www.commoncriteriaportal.org/files/epfiles/ANSSI-CC_2011-80fr.pdf | |
# SLE66CLX1280PEM M2094 A12 | |
# SLE66CLX1280PE M2095 A12 | |
# SLE66CLX1280PES M2096 A12 | |
# SLE66CX1280PE* M2097 A12 | |
0x7164: 'SLE78CLX1600P', # ?? | |
0x7879: 'SLE78CLFX4007PHM / M7892 G12', | |
0x7897: 'SLE78CLFX400VPHM / M7892 G12', | |
0x78A0: 'SLE78CLX1440P', | |
0x78A4: 'SLE78CLX1600P', | |
0x78A7: 'SLE78CLX1280P', | |
0x78A9: 'SLE78CLX800P', | |
0x78AF: 'SLE78CLX480P', | |
0x78B1: 'SLE78CLX360P', | |
0x7901: 'SLE78CLFX400VPH', | |
0x79A2: 'SLE78CLX1440PM', | |
0x79A5: 'SLE78CLX1600PM', | |
0x79AB: 'SLE78CLX800PM', | |
0x79B0: 'SLE78CLX480PM', | |
0x79B2: 'SLE78CLX360PM', | |
# E80A: identical to 7229? | |
}, | |
0x4830: { | |
0x7734: 'SLE77CLFX2400P / M7794', | |
0x7767: 'SLE77CLFX2407P / M7794?', | |
}, | |
0x8100: { | |
0x160C: 'SLC52GDL448', | |
0x7352: 'SLE78CLFX5000PHM', | |
0x7353: 'SLE78CLFX5000PH', | |
0x7801: 'SLE78CLFX4000P', | |
0x7802: '? / M7892 B11', | |
0x7805: 'SLE78CLFX4000PM', | |
0x7813: 'SLE78CFX4000P', | |
0x7859: 'SLE78CAFX4000PM', | |
0x7877: 'SLE78CLFX4007PM', | |
0x7878: 'SLE78CLFX4007P', | |
0x7985: 'SLE78CLFX408AP', | |
0x7986: 'SLE78CLFX408APM', | |
}, | |
# Atmel | |
0x4180: { | |
0x010E: 'AT90SC28880RCFV', | |
}, | |
# Samsung | |
0x4250: { | |
0x1611: 'S3FT9MH', | |
}, | |
# STMicro | |
0x4750: { | |
0x001A: 'ST33F1M / K8C0A', | |
0x0025: 'SC33F640', | |
0x0204: 'ST23YR80', | |
0x0205: 'ST23YR48', | |
}, | |
# NXP | |
0x4070: {}, | |
0x4790: { | |
0x0502: 'P60D080', | |
0x0503: 'J3H145', | |
0x5038: 'J2A080 80K', | |
0x5040: 'J3A080', | |
0x5074: 'J2E145G', | |
0x5075: 'J3E145', | |
0x5167: 'J2D081/J2E081/CJ2A081', | |
0xD321: 'J3R110/J3R180', | |
}, | |
} | |
cplc_os_id = { | |
0x0027: 'STM027', | |
0x1291: 'Gemplus', | |
0x1981: 'unknown', # but seen itw | |
0x2391: 'unknown', # but seen itw | |
0x4A4B: 'JK', | |
0x8211: 'Athena OS755', | |
0x8231: 'unknown', # but seen itw | |
0xA201: 'unknown', # but seen itw | |
0xA202: 'unknown', # but seen itw | |
} | |
def cplc_parse(data): | |
if data.startswith(b"\x9f\x7f\x2a"): | |
data = data[3:] | |
return { | |
'ic_fabricator': int.from_bytes(data[0:2], 'big'), | |
'ic_type': int.from_bytes(data[2:4], 'big'), | |
'os_id': int.from_bytes(data[4:6], 'big'), | |
'os_release_date': int.from_bytes(data[6:8], 'big'), | |
'os_release_level': int.from_bytes(data[8:10], 'big'), | |
'ic_fab_date': int.from_bytes(data[10:12], 'big'), | |
'ic_serial_num': int.from_bytes(data[12:16], 'big'), | |
'ic_batch_id': int.from_bytes(data[16:18], 'big'), | |
'ic_module_fabricator': int.from_bytes(data[18:20], 'big'), | |
'ic_module_pkg_date': int.from_bytes(data[20:22], 'big'), | |
'icc_manufacturer': int.from_bytes(data[22:24], 'big'), | |
'ic_embedding_date': int.from_bytes(data[24:26], 'big'), | |
'ic_prepersonalizer': int.from_bytes(data[26:28], 'big'), | |
'ic_prepersonalization_equipment_date': int.from_bytes(data[28:30], 'big'), | |
'ic_prepersonalization_equipment_id': int.from_bytes(data[30:34], 'big'), | |
'ic_personalizer': int.from_bytes(data[34:36], 'big'), | |
'ic_personalization_date': int.from_bytes(data[36:38], 'big'), | |
'ic_personalization_equipment_id': int.from_bytes(data[38:42], 'big'), | |
} | |
def cplc_prettyprint(cplc): | |
print("IC fabricator ...........................: %04X (%s)" % (cplc['ic_fabricator'], cplc_ic_fabricator.get(cplc['ic_fabricator'], "unknown"))) | |
ic_type = "unknown" | |
if cplc_ic_fabricator.get(cplc['ic_fabricator']) is not None: | |
ic_type = cplc_ic_type[cplc['ic_fabricator']].get(cplc['ic_type'], "unknown") | |
print("IC type .................................: %04X (%s)" % (cplc['ic_type'], ic_type)) | |
print("OS identifier ...........................: %04X (%s)" % (cplc['os_id'], cplc_os_id.get(cplc['os_id'], "unknown"))) | |
print("OS release date .........................: %04X" % cplc['os_release_date']) | |
print("OS release level ........................: %04X" % cplc['os_release_level']) | |
print("IC fabrication date .....................: %04X" % cplc['ic_fab_date']) | |
print("IC serial number ........................: %08X" % cplc['ic_serial_num']) | |
print("IC batch identifier .....................: %04X" % cplc['ic_batch_id']) | |
print("IC module fabricator ....................: %04X" % cplc['ic_module_fabricator']) | |
print("IC module packaging date ................: %04X" % cplc['ic_module_pkg_date']) | |
print("ICC manufacturer ........................: %04X" % cplc['icc_manufacturer']) | |
print("IC embedding date .......................: %04X" % cplc['ic_embedding_date']) | |
print("IC prepersonalizer ......................: %04X" % cplc['ic_prepersonalizer']) | |
print("IC prepersonalization equipment date ....: %04X" % cplc['ic_prepersonalization_equipment_date']) | |
print("IC prepersonalization equipment ID ......: %08X" % cplc['ic_prepersonalization_equipment_id']) | |
print("IC personalizer .........................: %04X" % cplc['ic_personalizer']) | |
print("IC personalization date .................: %04X" % cplc['ic_personalization_date']) | |
print("IC personalization equipment ID .........: %08X" % cplc['ic_personalization_equipment_id']) | |
print() | |
# card stuff | |
def send_apdu(sl, cla, ins, p1, p2, data, le): | |
apdu = bytearray([cla, ins, p1, p2]) | |
if data: | |
lc = len(data) // 2 if data else 0 | |
apdu = header + lc + data + le | |
apdu.append(lc) | |
apdu.extend(data) | |
apdu.append(le) | |
else: | |
apdu.append(le) | |
return sl.send_apdu(apdu) | |
def select_app(sl, aid): | |
(res, sw) = send_apdu(sl, 0x00, 0xA4, 0x04, 0x00, aid, 0) | |
return res, sw | |
def get_data_gp(sl, p1, p2): | |
(res, sw) = send_apdu(sl, 0x80, 0xCA, p1, p2, "", 0) | |
print(sw.hex(), res.hex()) | |
if sw != h2b("6a88"): | |
return res | |
def get_data_iso(sl, p1, p2): | |
(res, sw) = send_apdu(sl, 0x00, 0xCA, p1, p2, "", 0) | |
print(sw.hex(), res.hex()) | |
if sw != h2b("6a88"): | |
return res | |
def get_status(sl): | |
(res, sw) = send_apdu(sl, 0x80, 0xF2, 0x80, 0x00, "", 0) | |
print(sw.hex(), res.hex()) | |
def get_funky_data(sl): | |
(res, sw) = send_apdu(sl, 0xD0, 0x88, 0x00, 0x00, "", 0) | |
print(sw.hex(), res.hex()) | |
return res | |
def test_cplc_parsing(): | |
"Test parsing using some observed data" | |
with open("cplcdata.txt", "r") as fp: | |
for line in fp: | |
if not line or line[0] == '#': | |
continue | |
line = line.strip() | |
cplc_prettyprint(cplc_parse(bytes.fromhex(line))), | |
sys.exit(0) | |
if __name__ == '__main__': | |
#test_cplc_parsing() | |
sl = PcScTransport() | |
sl.wait_for_card() | |
atr = sl.get_atr() | |
print("ATR: " + atr.hex() + " " + repr(atr)) | |
aids_to_try = [ | |
"", | |
"a000000003000000", | |
"a000000004000000", | |
"a0000001510000", | |
] | |
for aid in aids_to_try: | |
print("Trying AID %s ..." % (aid,)) | |
try: | |
data, sw = select_app(sl, aid) | |
#print(sw.hex(), data.hex()) | |
if sw == h2b("9000"): | |
break | |
except: | |
print("Resetting ...") | |
sl.reset_card() | |
data = get_data_gp(sl, 0x9f, 0x7f) | |
if data: | |
cplc_prettyprint(cplc_parse(data)) | |
data = get_data_iso(sl, 0x9f, 0x7f) | |
if data: | |
cplc_prettyprint(cplc_parse(data)) | |
#get_status(sl) | |
# I noted in one CC compliance report there was this funky PDU returning interesting data | |
get_funky_data(sl) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
try: | |
from smartcard.CardConnection import CardConnection | |
from smartcard.CardRequest import CardRequest | |
from smartcard.Exceptions import NoCardException, CardRequestTimeoutException, CardConnectionException | |
from smartcard.System import readers | |
from smartcard.ExclusiveConnectCardConnection import ExclusiveConnectCardConnection | |
except ImportError: | |
print("Failed to import from pyscard; make sure you have it -- try pip install pyscard") | |
sys.exit(2) | |
# this is shamelessly stolen from pysim | |
class PcScTransport: | |
def __init__(self, reader_number=0): | |
r = readers() | |
self._reader = readers()[reader_number] | |
self._con = self._reader.createConnection() | |
self._con = ExclusiveConnectCardConnection(self._con) | |
def __del__(self): | |
try: | |
# FIXME: this causes multiple warnings in Python | |
self._con.disconnect() | |
except: | |
pass | |
def wait_for_card(self, timeout=None, newcardonly=False): | |
cr = CardRequest(readers=[self._reader], timeout=timeout, newcardonly=newcardonly) | |
cr.waitforcard() | |
self.connect() | |
def connect(self): | |
# try both T=0 and T=1 | |
try: | |
self.disconnect() | |
# Explicitly select T=0 communication protocol | |
self._con.connect(CardConnection.T0_protocol) | |
except CardConnectionException as exc: | |
self.disconnect() | |
# Explicitly select T=1 communication protocol | |
self._con.connect(CardConnection.T1_protocol) | |
def get_atr(self): | |
return bytes(self._con.getATR()) | |
def disconnect(self): | |
self._con.disconnect() | |
def reset_card(self): | |
self.disconnect() | |
self.connect() | |
def _send_apdu_raw(self, pdu: bytes): | |
data, sw1, sw2 = self._con.transmit(list(pdu)) | |
return bytes(data), bytes([sw1, sw2]) | |
def send_apdu_raw(self, pdu: bytes): | |
# todo: tracing/logging | |
return self._send_apdu_raw(pdu) | |
def send_apdu(self, apdu: bytes): | |
data, sw = self.send_apdu_raw(apdu) | |
while sw[0] in (0x61, 0x9f): | |
data_chunk, sw = self.send_apdu_raw(bytes([apdu[0], 0xc0, 0x00, 0x00, sw[1]])) | |
data += data_chunk | |
if sw[0] == 0x6c: | |
data, sw = self.send_apdu_raw(bytes([apdu[0], apdu[1], apdu[2], apdu[3], sw[1]])) | |
return data, sw | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment