├── .gitignore ├── LICENSE ├── README.md ├── TODO ├── mim ├── __init__.py ├── mim_live.py ├── mim_live_live.py ├── mim_live_live_soft_sat.py ├── mim_live_soft_sat.py ├── mim_soft.py ├── mim_soft_live_auth.py └── mim_soft_live_reg.py ├── res ├── live.gif ├── live.ico ├── live_live.gif ├── live_live_soft.gif ├── live_soft.gif ├── simLAB-logo.png ├── soft.gif └── soft_live.gif ├── sim ├── __init__.py ├── file_parser.py ├── pyscard_rpc_ctrl.py ├── sim_backup.py ├── sim_card.py ├── sim_codes.py ├── sim_ctrl_2g.py ├── sim_ctrl_3g.py ├── sim_files.py ├── sim_files_2g.xml ├── sim_files_3g.xml ├── sim_reader.py ├── sim_router.py └── sim_shell.py ├── sim_soft ├── __init__.py ├── sat_ctrl.py ├── sat_types.py ├── sim_auth.py ├── sim_backup.xml.bak ├── sim_soft.py ├── sim_soft_ctrl.py └── sim_xml.py ├── simlab.py ├── tests ├── __init__.py ├── html_test_runner.py ├── live │ ├── __init__.py │ ├── test_debug.py │ ├── test_shell.py │ ├── test_sim.py │ └── test_sim_2g.py ├── runner.py └── soft │ ├── __init__.py │ ├── test_authentication.py │ ├── test_ext_control_dbus.py │ ├── test_ext_control_telnet.py │ ├── test_sat.py │ ├── test_shell.py │ ├── test_sim.py │ └── test_sim_2g.py └── util ├── __init__.py ├── coder.py ├── dbus_ctrl.py ├── gsmtap.py ├── hextools.py ├── terminal_profile.py ├── types.py └── types_g.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | mim_tmp.py 3 | sim_backup*.xml 4 | pyscard_rpc.log 5 | sim_soft.log 6 | apdu.log 7 | *.pyproj 8 | /tests/result 9 | \#* 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | simLAB 2 | ================= 3 | 4 | **simLAB** is a pure python tool for editing and simulating SIM card. Together with simTrace HW, it allows modification of APDU exchanged between Terminal (Mobile Equipment) and SIM. 5 | 6 | Want to learn more? Detailed information can be found in the [simLAB wiki](https://github.com/kamwar/simLAB/wiki) 7 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | >> SIM SOFT << 2 | # Implement SFI. Now as a workaround the FILE_NOT_FOUND status is returned 3 | # Implement security attributes (when EF_ARR is not set as access rule) for USIM 4 | # Implement millenge authentication for 3G 5 | # Implement sqn range check and EF_SQN update during dummy xor authentication 6 | # Add ADM4 tag inside fs xml file 7 | # Add support for 2G cards. Now only a few commands support this mode. 8 | # Move function related to logical channel inside LogicalChannel class 9 | # Implement opening logical channel with select command 10 | # Implement file linking (ADF0/EF_IMSI shall be linked with /7F20/EF_IMSI) 11 | # Implement INCREASE and DECREASE command 12 | 13 | >> SIM SOFT SAT << 14 | # Display post action handler response without showing displayText in the meantime. 15 | Check for delayed proactive response 16 | 17 | >> COMMON << 18 | # Number ADFs staring from 1 (e.g ADF1, ADF2 etc.) 19 | # Handle currentFile path for selecting files by path from MF 20 | (e.g. 00A40804067F105F3A4F3A) 21 | 22 | >> KNOWN ISSUE << 23 | # ADF creation doesn't work for Gemalto v2.1 and Anritsu ISIM cards. 24 | INCORRECT_PARAMETER_IN_DATA_FIELD or INCORRECT_PARAMETERS_P1_P2 is returned. 25 | Many different combinations have been checked using: 26 | - Security Attribute Tag (8Bh) 27 | - PIN Status Template DO Tag (C6h) 28 | - DF_AID tag (84h) included in 'A5' tag 29 | # Gemalto card doesn't support RESIZE_FILE command. 30 | It supports EXTEND command instead. Removing a file record is impossible. 31 | -------------------------------------------------------------------------------- /mim/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/mim/__init__.py -------------------------------------------------------------------------------- /mim/mim_live.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2013 Tom Schouten 4 | # (c) 2014 Kamil Wartanowicz 5 | import sys,os.path 6 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 7 | import logging 8 | 9 | from sim import sim_router 10 | from sim import sim_card 11 | from sim import sim_reader 12 | from util import types 13 | 14 | logging.basicConfig(level=logging.INFO, format='%(message)s') 15 | simType=types.TYPE_USIM 16 | 17 | simCard = sim_card.SimCard() 18 | simCard.removeAllReaders() 19 | simCard.connect(sim_reader.READER_ID_0) 20 | 21 | simRouter = sim_router.SimRouter(cards=[simCard], 22 | atr=None, 23 | type=simType) 24 | simRouter.run(mode=sim_router.ROUTER_MODE_INTERACTIVE) 25 | -------------------------------------------------------------------------------- /mim/mim_live_live.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2014 Kamil Wartanowicz 4 | 5 | import sys,os.path 6 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 7 | import logging 8 | 9 | from sim import sim_router 10 | from sim import sim_card 11 | from sim import sim_reader 12 | from util import hextools 13 | from util import types 14 | 15 | logging.basicConfig(level=logging.INFO, format='%(message)s') 16 | 17 | readers = sim_card.SimCard().listReaders() 18 | if "Dell" in readers[0]: 19 | readerFirst = sim_reader.READER_ID_0 20 | else: 21 | readerFirst = sim_reader.READER_ID_1 22 | 23 | simType=types.TYPE_USIM 24 | 25 | sim1 = sim_card.SimCard() 26 | sim1.removeAllReaders() 27 | sim1.connect(readerFirst) 28 | atr1 = sim1.getATR() 29 | 30 | sim2 = sim_card.SimCard() 31 | sim2.connect(not readerFirst) 32 | atr2 = sim2.getATR() 33 | 34 | simRouter = sim_router.SimRouter(cards=[sim1, sim2], 35 | atr=atr2, type=simType) 36 | 37 | sim2.routingAttr.filesReplaced = sim_card.FILES_REG 38 | sim2.routingAttr.insReplaced = ['INTERNAL_AUTHENTICATE'] 39 | # Uncomment to forward SAT to sim2. 40 | #sim2.routingAttr.insReplaced.append(sim_card.SAT_INS) 41 | simRouter.run(mode=sim_router.ROUTER_MODE_INTERACTIVE) -------------------------------------------------------------------------------- /mim/mim_live_live_soft_sat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2014 Kamil Wartanowicz 4 | 5 | import sys,os.path 6 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 7 | import logging 8 | 9 | from sim import sim_router 10 | from sim import sim_card 11 | from sim import sim_reader 12 | from util import types 13 | 14 | logging.basicConfig(level=logging.INFO, format='%(message)s') 15 | 16 | simType=types.TYPE_USIM 17 | 18 | sim1 = sim_card.SimCard() 19 | sim1.connect(sim_reader.READER_ID_0) 20 | atr1 = sim1.getATR() 21 | 22 | sim2 = sim_card.SimCard() 23 | sim2.connect(sim_reader.READER_ID_1) 24 | atr2 = sim2.getATR() 25 | 26 | sim3 = sim_card.SimCard(mode=sim_reader.MODE_SIM_SOFT) 27 | sim3.connect(sim_reader.READER_ID_0) 28 | atr3 = sim3.getATR() 29 | 30 | simRouter = sim_router.SimRouter(cards=[sim1, sim2, sim3], 31 | atr=atr1, type=simType) 32 | 33 | #sim2.routingAttr.filesReplaced = sim_card.FILES_REG 34 | #sim2.routingAttr.insReplaced = ['INTERNAL_AUTHENTICATE'] 35 | 36 | # Forward SAT to sim3. 37 | sim3.routingAttr.insReplaced = sim_card.SAT_INS 38 | 39 | ''' 40 | When SIM is invalidated, uncomment below. 41 | # Files which need to be the same on all sim cards. 42 | simRouter.getMainCard(0).routingAttr.filesReplaced += ['EF_ADN', 'EF_SDN', 'EF_SMS'] 43 | # Uncomment when card resets after quering status. UE migth expect. 44 | # Different AID after SIM card switching if UE doesn't read EF_DIR on the basic channel. 45 | sim1.routingAttr.filesReplaced += ['EF_DIR'] 46 | sim1.routingAttr.filesReplaced = ['STATUS'] 47 | # Other SIM files which might cause problem if not the same on all cards. 48 | sim1.routingAttr.filesReplaced = +[ 49 | 'EF_SMS', 'EF_MBDN', 'EF_EPSNSC', 'EF_MBI', 'EF_SMSS', 'EF_MWIS', 'EF_CFIS', 'EF_LI', 'EF_ELP', 50 | 'EF_UST', 'EF_EST', 'EF_AD', 'EF_NETPAR', 'EF_ACC', 'EF_HPPLMN', 'EF_HPLMNWACT', 'EF_PLMNWACT', 51 | 'EF_START_HFN', 'EF_THRESHOLD', 'EF_IMG', 'EF_ACM', 'EF_ACMMAX', 'EF_HIDDENKEY', 'EF_ICCID', 52 | 'EF_CBMI', 'EF_SMSP', 'EF_FDN', 'EF_ARR', 'EF_ANR', 'EF_MSISDN', 'EF_BDN', 'EF_ADN', 'EF_SDN', 53 | 'EF_OCI', 'EF_ICI', 'EF_MBDN', 'EF_EXT1', 'EF_EXT2', 'EF_EXT3' ] 54 | ''' 55 | simRouter.run(mode=sim_router.ROUTER_MODE_INTERACTIVE) -------------------------------------------------------------------------------- /mim/mim_live_soft_sat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2014 Kamil Wartanowicz 4 | 5 | import sys,os.path 6 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 7 | import logging 8 | 9 | from sim import sim_router 10 | from sim import sim_card 11 | from sim import sim_reader 12 | from util import types 13 | 14 | logging.basicConfig(level=logging.INFO, format='%(message)s') 15 | 16 | simType=types.TYPE_USIM 17 | 18 | sim1 = sim_card.SimCard() 19 | sim1.connect(sim_reader.READER_ID_0) 20 | atr1 = sim1.getATR() 21 | 22 | sim2 = sim_card.SimCard(mode=sim_reader.MODE_SIM_SOFT) 23 | sim2.connect(sim_reader.READER_ID_0) 24 | atr2 = sim2.getATR() 25 | 26 | simRouter = sim_router.SimRouter(cards=[sim1, sim2], 27 | atr=atr1, type=simType) 28 | 29 | # Forward SAT to sim2. 30 | sim2.routingAttr.insReplaced = sim_card.SAT_INS 31 | simRouter.run(mode=sim_router.ROUTER_MODE_INTERACTIVE) -------------------------------------------------------------------------------- /mim/mim_soft.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2014 Kamil Wartanowicz 4 | 5 | import sys,os.path 6 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 7 | import logging 8 | 9 | from sim import sim_router 10 | from sim import sim_card 11 | from sim import sim_reader 12 | from util import types 13 | 14 | logging.basicConfig(level=logging.INFO, format='%(message)s') 15 | 16 | simType=types.TYPE_USIM 17 | 18 | simCard = sim_card.SimCard(mode=sim_reader.MODE_SIM_SOFT, type=simType) 19 | simCard.removeAllReaders() 20 | simCard.connect(sim_reader.READER_ID_0) 21 | 22 | simRouter = sim_router.SimRouter(cards=[simCard], 23 | atr=None, 24 | type=simType) 25 | simRouter.run(mode=sim_router.ROUTER_MODE_INTERACTIVE) 26 | -------------------------------------------------------------------------------- /mim/mim_soft_live_auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2014 Kamil Wartanowicz 4 | 5 | import sys,os.path 6 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 7 | import logging 8 | 9 | from sim import sim_router 10 | from sim import sim_card 11 | from sim import sim_reader 12 | from sim import sim_shell 13 | from sim import sim_ctrl_3g 14 | from util import types 15 | 16 | simType=types.TYPE_USIM 17 | CONNECT_EXTERNAL_SERVER = False 18 | SERVER_IP = "10.28.27.200" 19 | 20 | if CONNECT_EXTERNAL_SERVER: 21 | # Connect to external SIM server/reader. 22 | sim_reader.PYSCARD_RPC_IP_ADDRESS = SERVER_IP 23 | sim_reader.LOCAL_PYSCARD_SERVER = False 24 | else: 25 | sim_reader.LOCAL_PYSCARD_SERVER = True # Use local reader 26 | logging.basicConfig(level=logging.INFO, format='%(message)s') 27 | 28 | sim1 = sim_card.SimCard(mode=sim_reader.MODE_SIM_SOFT, type=simType) 29 | sim1.connect(sim_reader.READER_ID_0) 30 | atr1 = sim1.getATR() 31 | 32 | sim2 = sim_card.SimCard(type=simType) 33 | sim2.connect(sim_reader.READER_ID_0) 34 | atr2 = sim2.getATR() 35 | 36 | simRouter = sim_router.SimRouter(cards=[sim1, sim2], 37 | atr=atr1, type=simType) 38 | 39 | simRouter.run(mode=sim_router.ROUTER_MODE_DISABLED) 40 | shell = simRouter.shell 41 | 42 | #simRouter.copyFiles(sim2, sim1, sim_card.FILES_REG) 43 | simRouter.copyFiles(sim2, sim1, ['EF_IMSI', 'EF_LOCI', 'EF_SMSP']) 44 | 45 | # Select ADF_USIM on the main channel of SIM1. 46 | # It's needed for authenticate instruction. 47 | shell.select_sim_card("1") 48 | shell.set_active_channel("0") 49 | shell.cd("/ADF_USIM") 50 | shell.set_active_channel("1") 51 | shell.select_sim_card("0") 52 | 53 | sim1.routingAttr.insReplaced = sim_card.SAT_INS 54 | # Only INTERNAL_AUTHENTICATE instruction is forwarded to sim2. 55 | sim2.routingAttr.insCommon = [] 56 | sim2.routingAttr.filesCommon = [] 57 | sim2.routingAttr.filesReplaced = [] 58 | sim2.routingAttr.insReplaced = ['INTERNAL_AUTHENTICATE'] 59 | #simRouter.run(mode=sim_router.ROUTER_MODE_INTERACTIVE) 60 | simRouter.run(mode=sim_router.ROUTER_MODE_TELNET) 61 | -------------------------------------------------------------------------------- /mim/mim_soft_live_reg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2014 Kamil Wartanowicz 4 | 5 | import sys,os.path 6 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 7 | import logging 8 | 9 | from sim import sim_router 10 | from sim import sim_card 11 | from sim import sim_reader 12 | from util import types 13 | 14 | logging.basicConfig(level=logging.INFO, format='%(message)s') 15 | 16 | simType=types.TYPE_USIM 17 | 18 | sim1 = sim_card.SimCard(mode=sim_reader.MODE_SIM_SOFT, type=simType) 19 | sim1.connect(sim_reader.READER_ID_0) 20 | atr1 = sim1.getATR() 21 | 22 | sim2 = sim_card.SimCard() 23 | sim2.connect(sim_reader.READER_ID_0) 24 | atr2 = sim2.getATR() 25 | 26 | simRouter = sim_router.SimRouter(cards=[sim1, sim2], 27 | atr=atr1, type=simType) 28 | 29 | sim1.routingAttr.insReplaced = sim_card.SAT_INS 30 | sim2.routingAttr.filesReplaced = sim_card.FILES_REG 31 | sim2.routingAttr.insReplaced = ['INTERNAL_AUTHENTICATE'] 32 | simRouter.run(mode=sim_router.ROUTER_MODE_INTERACTIVE) -------------------------------------------------------------------------------- /res/live.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/res/live.gif -------------------------------------------------------------------------------- /res/live.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/res/live.ico -------------------------------------------------------------------------------- /res/live_live.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/res/live_live.gif -------------------------------------------------------------------------------- /res/live_live_soft.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/res/live_live_soft.gif -------------------------------------------------------------------------------- /res/live_soft.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/res/live_soft.gif -------------------------------------------------------------------------------- /res/simLAB-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/res/simLAB-logo.png -------------------------------------------------------------------------------- /res/soft.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/res/soft.gif -------------------------------------------------------------------------------- /res/soft_live.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/res/soft_live.gif -------------------------------------------------------------------------------- /sim/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/sim/__init__.py -------------------------------------------------------------------------------- /sim/file_parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2015, Kamil Wartanowicz 4 | from util import hextools 5 | from util import types 6 | from util import coder 7 | 8 | MODE_GET = 0 9 | MODE_SET = 1 10 | 11 | class FileParser(object): 12 | def __init__(self): 13 | pass 14 | 15 | def getEfImsi(self, data): 16 | data = hextools.hex2bytes(data) 17 | imsi = hextools.decode_BCD(data)[3:] 18 | return imsi 19 | 20 | def setEfImsi(self, imsi): 21 | if len(imsi) % 2: 22 | dataLow = 9 23 | else: 24 | dataLow = 1 25 | firstByte = dataLow<<4 | int(imsi[0], 16) 26 | imsi = "%02X%s" %(firstByte, imsi[1:]) 27 | imsi = hextools.encode_BCD(imsi) 28 | return "08%s" %hextools.bytes2hex(imsi) 29 | 30 | def getEfUst(self, data): 31 | data = hextools.hex2bytes(data) 32 | ustTable = [] 33 | for byte in data: 34 | for bitId in range(8): 35 | value = int(byte & (1 << bitId) != 0) 36 | ustTable.append(value) 37 | return ustTable 38 | 39 | def setEfUst(self, data): 40 | ustTable = data 41 | ustRaw = [] 42 | for i, bitValue in enumerate(ustTable): 43 | byteId = i / 8 44 | bitId = i % 8 45 | if len(ustRaw) <= byteId: 46 | ustRaw.append(0x00) 47 | if bitValue: 48 | ustRaw[byteId] |= (1 << bitId) 49 | else: 50 | ustRaw[byteId] &= ~(1 << bitId) 51 | return hextools.bytes2hex(ustRaw) 52 | 53 | def getEfEcc(self, data): 54 | eccStr = '' 55 | data = data.split(';') 56 | for record in data: 57 | recordRaw = hextools.hex2bytes(record) 58 | number = hextools.decode_BCD(recordRaw[0:3]) 59 | number = number.replace('F','') 60 | if not number: 61 | continue 62 | #number = type.removeTrailingBytes(hextools.hex2bytes(number), 0xFF) 63 | aplha = None 64 | category = recordRaw[-1] 65 | eccStr += "number=%s,alpha=%s,cat=%d;" %(number, aplha, category) 66 | eccStr = eccStr.rstrip(";") 67 | if not eccStr: 68 | eccStr = "EMPTY" 69 | return eccStr 70 | 71 | def setEfEcc(self, data): 72 | data = data.split(';') 73 | dataNew = '' 74 | for record in data: 75 | number = types.getParamValue(record, "number") 76 | if not number: 77 | return False 78 | category = types.getParamValue(record, "cat") 79 | number = number.replace('F','') 80 | if number: 81 | numberBcd = hextools.encode_BCD(number) 82 | numberBcd = hextools.bytes2hex(numberBcd) 83 | numberBcd = types.addTrailingBytes(numberBcd, 0xFF, 3) 84 | else: 85 | numberBcd = "FFFFFF" 86 | dataTmp = numberBcd 87 | if category: 88 | raise Exception("Not implemented") 89 | #TODO: move it to the last byte 90 | dataTmp += "%02X" %int(category) 91 | if dataNew: 92 | dataNew = "%s;%s" %(dataNew, dataTmp) 93 | else: 94 | dataNew = dataTmp 95 | return dataNew 96 | 97 | def getEfSpn(self, data): 98 | spnRaw = types.removeTrailingBytes(hextools.hex2bytes(data), 0xFF) 99 | if spnRaw: 100 | displayByte = spnRaw[0] 101 | name = hextools.bytes2hex(spnRaw[1:]).decode("hex") 102 | else: 103 | return '' 104 | return "name=%s,display=%02X" %(name, displayByte) 105 | 106 | def setEfSpn(self, value): 107 | name = types.getParamValue(value, "name") 108 | if not name: 109 | raise Exception("Name not provided") 110 | displayByte = types.getParamValue(value, "display") 111 | if displayByte: 112 | displayByte = int(displayByte) 113 | else: 114 | #display is not provided, set default value 115 | displayByte = 0x00 116 | value = "%02X%s" %(displayByte, name.encode('hex')) 117 | return value 118 | 119 | def getEfCphs_onstr(self, data): 120 | nameHex = types.removeTrailingBytes(hextools.hex2bytes(data), 0xFF) 121 | #fullName = coder.decodeGsm7(hextools.bytes2hex(nameHex)) 122 | #return fullName 123 | return hextools.bytes2hex(nameHex.decode("hex")) 124 | 125 | def setEfCphs_onstr(self, name): 126 | #return coder.encodeGsm7(data) 127 | return name.encode('hex') 128 | 129 | def getEfImpu(self, data): 130 | impuStr = '' 131 | data = data.split(';') 132 | for record in data: 133 | value = types.removeTrailingBytes(hextools.hex2bytes(record), 0xFF) 134 | if len(value) < 3: 135 | continue 136 | if impuStr: 137 | impuStr += ";" 138 | impuStr += hextools.bytes2hex(value[2:]).decode("hex") 139 | return impuStr 140 | 141 | def setEfImpu(self, data): 142 | data = data.split(';') 143 | dataNew = '' 144 | for record in data: 145 | tag = 0x80 146 | length = len(record) 147 | value = "%02X%02X%s" %(tag, length, record.encode('hex')) 148 | if dataNew: 149 | dataNew += ";" 150 | dataNew += value 151 | return dataNew 152 | 153 | def getEfImpi(self, data): 154 | valueRaw = types.removeTrailingBytes(hextools.hex2bytes(data), 0xFF) 155 | if len(valueRaw) < 3: 156 | return '' 157 | value = hextools.bytes2hex(valueRaw[2:]).decode("hex") 158 | return "impi=%s" %value 159 | 160 | def setEfImpi(self, data): 161 | impi = types.getParamValue(data, "impi") 162 | if not impi: 163 | raise Exception("impi not provided") 164 | tag = 0x80 165 | length = len(impi) 166 | value = "%02X%02X%s" %(tag, length, impi.encode('hex')) 167 | return value 168 | 169 | def getEfPcscf(self, data): 170 | #TODO: handle many records 171 | firstRecord = data.split(';')[0] 172 | value = types.removeTrailingBytes(hextools.hex2bytes(firstRecord), 0xFF) 173 | if len(value) < 3: 174 | return '' 175 | cscf = hextools.bytes2hex(value[3:]).decode("hex") 176 | return "cscf=%s" %cscf 177 | 178 | def setEfPcscf(self, data): 179 | data = data.split(';') 180 | dataNew = '' 181 | for record in data: 182 | cscf = types.getParamValue(record, "cscf") 183 | if not cscf: 184 | raise Exception("cscf not provided") 185 | tag = 0x80 186 | length = len(cscf) + 1 187 | ''' 188 | Value | Name 189 | ============ 190 | '00' | FQDN 191 | '01' | IPv4 192 | '02' | IPv6 193 | ''' 194 | addrType = 0x00 195 | value = "%02X%02X%02X%s" %(tag, length, addrType, cscf.encode('hex')) 196 | if dataNew: 197 | dataNew += ";" 198 | dataNew += value 199 | return dataNew 200 | 201 | def getEfLoci(self, data): 202 | valueRaw = hextools.hex2bytes(data) 203 | tmsi = hextools.bytes2hex(valueRaw[0:4]) 204 | lai = hextools.bytes2hex(valueRaw[4:9]) 205 | #TODO: check for mnc containing 3digits 206 | mcc_mnc = hextools.decode_BCD(hextools.hex2bytes(lai)[0:3]) 207 | lac = lai[6:10] 208 | rfu = hextools.bytes2hex([valueRaw[9]]) 209 | loction_status = hextools.bytes2hex([valueRaw[10]]) 210 | ''' 211 | loction_status 212 | Bits: b3 b2 b1 213 | 0 0 0 : updated. 214 | 0 0 1 : not updated. 215 | 0 1 0 : PLMN not allowed. 216 | 0 1 1 : Location Area not allowed. 217 | 1 1 1 : reserved 218 | ''' 219 | return "tmsi=%s,mcc_mnc=%s,lac=%s,loc_status=%s"\ 220 | %(tmsi, mcc_mnc, lac, loction_status) 221 | 222 | def setEfLoci(self, data): 223 | param = "tmsi" 224 | tmsi = types.getParamValue(data, param) 225 | if not tmsi: 226 | raise Exception("%s not provided" %param) 227 | 228 | param = "lai" 229 | lai = types.getParamValue(data, param) 230 | if not lai: 231 | #if lai is not provided, check mcc_mnc and lac 232 | param = "mcc_mnc" 233 | mccMnc = types.getParamValue(data, param) 234 | if not mccMnc: 235 | raise Exception("%s not provided" %param) 236 | if len(mccMnc) != 6: 237 | mnc3 = 'F' 238 | else: 239 | mnc3 = mccMnc[5] 240 | mccMnc = "%s%s%s" %(mccMnc[0:3], mnc3, mccMnc[3:5]) 241 | 242 | param = "lac" 243 | lac = types.getParamValue(data, param) 244 | if not lac: 245 | raise Exception("%s not provided" %param) 246 | lai = "%s%04X" %(hextools.bytes2hex(hextools.encode_BCD(mccMnc)), int(lac, 16)) 247 | param = "rfu" 248 | rfu = types.getParamValue(data, param) 249 | if not rfu: 250 | rfu = "FF" 251 | 252 | param = "loc_status" 253 | locStatus = types.getParamValue(data, param) 254 | if not locStatus: 255 | locStatus = "00" 256 | 257 | loci = "%s%s%s%s" %(tmsi, lai, rfu, locStatus) 258 | return loci 259 | 260 | def getEfPnn(self, data): 261 | data = data.split(';') 262 | dataNew = '' 263 | for record in data: 264 | if not record: 265 | continue 266 | binRecord = hextools.hex2bytes(record) 267 | if binRecord.count(0xFF) == len(binRecord): 268 | continue 269 | fullNameRaw = types.parseTlv(binRecord, types.FULL_NW_NAME_TAG) 270 | shortNameRaw = types.parseTlv(binRecord, types.SHORT_NW_NAME_TAG) 271 | additionalInfo = types.parseTlv(binRecord, types.ADDITIONAL_INFORMATION_PLMN_TAG) 272 | if fullNameRaw: 273 | infoByte = fullNameRaw[0] 274 | spareBits = infoByte & 0b00000111 275 | ci = infoByte >> 3 & 0b00000001 276 | dcs = infoByte >> 4 & 0b00000111 277 | if dcs != 0x00: 278 | raise Exception("Only GSM coding is supported in DCS, current coding: %02X" %dcs) 279 | fullName = coder.decodeGsm7(hextools.bytes2hex(fullNameRaw[1:])) 280 | else: 281 | fullName = None 282 | if shortNameRaw: 283 | infoByte = shortNameRaw[0] 284 | spareBits = infoByte & 0b00000111 285 | ci = infoByte >> 3 & 0b00000001 286 | dcs = infoByte >> 4 & 0b00000111 287 | if dcs != 0x00: 288 | raise Exception("Only GSM coding is supported in DCS, current coding: %02X" %dcs) 289 | shortName = coder.decodeGsm7(hextools.bytes2hex(shortNameRaw[1:])) 290 | else: 291 | shortName = None 292 | dataNew += "full_name=%s,short_name=%s"\ 293 | %(fullName, shortName) 294 | if additionalInfo: 295 | dataNew += ",additional_info=%s" %additionalInfo 296 | dataNew += ";" 297 | return dataNew 298 | 299 | def setEfPnn(self, data): 300 | data = data.split(';') 301 | dataNew = '' 302 | for record in data: 303 | if not record: 304 | continue 305 | tmpData = [] 306 | infoFull = types.getParamValue(record, "info_full") 307 | fullName = types.getParamValue(record, "full_name") 308 | infoShort = types.getParamValue(record, "info_short") 309 | shortName = types.getParamValue(record, "short_name") 310 | additionalInfo = types.getParamValue(record, "additional_info") 311 | if fullName: 312 | fullNameGsm7 = coder.encodeGsm7(fullName) 313 | if infoFull: 314 | infoByte = int(infoFull, 16) 315 | spareBits = infoByte & 0b00000111 316 | ci = infoByte >> 3 & 0b00000001 317 | dcs = infoByte >> 4 & 0b00000111 318 | if dcs != 0x00: 319 | raise Exception("Only GSM coding is supported in DCS") 320 | ext = infoByte >> 7 & 0b00000001 321 | else: 322 | spareBits = len(fullName) % 8 323 | ci = 0 #don't add the letters for the Country's Initials 324 | dcs = 0 #GSM 325 | ext = 1 #? 326 | infoByte = ext << 7 | dcs << 3 | ci << 2 | spareBits 327 | fullNameGsm7 = "%02X%s" %(infoByte, fullNameGsm7) 328 | types.addTlv(tmpData, types.FULL_NW_NAME_TAG, hextools.hex2bytes(fullNameGsm7)) 329 | if shortName: 330 | shortNameGsm7 = coder.encodeGsm7(shortName) 331 | if infoShort: 332 | infoByte = int(infoShort, 16) 333 | spareBits = infoByte & 0b00000111 334 | ci = infoByte >> 3 & 0b00000001 335 | dcs = infoByte >> 4 & 0b00000111 336 | if dcs != 0x00: 337 | raise Exception("Only GSM coding is supported in DCS") 338 | ext = infoByte >> 7 & 0b00000001 339 | else: 340 | spareBits = len(shortName) % 8 341 | ci = 0 #don't add the letters for the Country's Initials 342 | dcs = 0 #GSM 343 | ext = 1 #? 344 | infoByte = ext << 7 | dcs << 3 | ci << 2 | spareBits 345 | shortNameGsm7 = "%02X%s" %(infoByte, shortNameGsm7) 346 | types.addTlv(tmpData, types.SHORT_NW_NAME_TAG, hextools.hex2bytes(shortNameGsm7)) 347 | if additionalInfo: 348 | types.addTlv(tmpData, types.ADDITIONAL_INFORMATION_PLMN_TAG, hextools.hex2bytes(additionalInfo)) 349 | dataNew += "%s;" %hextools.bytes2hex(tmpData) 350 | return dataNew 351 | 352 | def getEfOpl(self, data): 353 | data = data.split(';') 354 | dataNew = '' 355 | for record in data: 356 | if not record: 357 | continue 358 | binRecord = hextools.hex2bytes(record) 359 | if binRecord.count(0xFF) == len(binRecord): 360 | continue 361 | lai = hextools.bytes2hex(binRecord[0:7]) 362 | mccMnc = hextools.decode_BCD(hextools.hex2bytes(lai)[0:3]) 363 | lacStart = lai[6:10] 364 | lacEnd = lai[10:14] 365 | lacRange = "%s-%s" %(lacStart, lacEnd) 366 | pnnId = binRecord[7] 367 | dataNew += "mcc_mnc=%s,lac=%s,pnnId=%d;" %(mccMnc, lacRange, pnnId) 368 | return dataNew 369 | 370 | def setEfOpl(self, data): 371 | data = data.split(';') 372 | dataNew = '' 373 | for record in data: 374 | if not record: 375 | continue 376 | mccMnc = types.getParamValue(record, "mcc_mnc") 377 | if not mccMnc: 378 | raise Exception("mcc_mnc not provided") 379 | if len(mccMnc) != 6: 380 | mnc3 = 'F' 381 | else: 382 | mnc3 = mccMnc[5] 383 | mccMnc = "%s%s%s" %(mccMnc[0:3], mnc3, mccMnc[3:5]) 384 | lacRange = types.getParamValue(record, "lac") 385 | if not lacRange: 386 | lacRange = '0000-FFFE' 387 | lacStart = lacRange.split("-")[0] 388 | lacEnd = lacRange.split("-")[1] 389 | pnnId = int(types.getParamValue(record, "pnnId"), 16) 390 | lai = "%s%04X%04X" %(hextools.bytes2hex(hextools.encode_BCD(mccMnc)), int(lacStart, 16), int(lacEnd, 16)) 391 | dataNew += "%s%02X;" %(lai, pnnId) 392 | return dataNew 393 | 394 | def fileHandler(self, mode, fileName, data): 395 | fileName = fileName.replace("EF_", '') 396 | #remove forbidden characters in function name 397 | fileName = fileName.replace("-", '') 398 | fileName = fileName.lower() 399 | fileName = "%s%s" %(fileName[0].upper(), fileName[1:]) 400 | if mode == MODE_GET: 401 | prefix = "get" 402 | else: 403 | prefix = "set" 404 | functionName = "%sEf%s" %(prefix, fileName) 405 | try: 406 | handler = getattr(self, functionName) 407 | except: 408 | raise Exception("Add method '%s' in file %s" %(functionName, __file__)) 409 | return handler(data) 410 | 411 | def getFileValue(self, fileName, data): 412 | return self.fileHandler(MODE_GET, fileName, data) 413 | 414 | def setFileValue(self, fileName, data): 415 | return self.fileHandler(MODE_SET, fileName, data) 416 | -------------------------------------------------------------------------------- /sim/pyscard_rpc_ctrl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2014 Kamil Wartanowicz 4 | import sys,os.path 5 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 6 | import logging 7 | import inspect 8 | from optparse import OptionParser 9 | import os 10 | import shlex 11 | import smartcard 12 | from smartcard.Exceptions import CardConnectionException 13 | import subprocess 14 | import threading 15 | import time 16 | import zerorpc 17 | import zmq 18 | from util import hextools 19 | 20 | class PyscardRPC(object): 21 | #################### 22 | # local methods 23 | #################### 24 | def __init__(self, logLevel=logging.WARNING): 25 | self.setupLogger(logLevel) 26 | self.readers = [] 27 | self.pollSim = {} 28 | 29 | def setupLogger(self, logLevel): 30 | logger = logging.getLogger() 31 | logger.setLevel(logging.INFO) 32 | consoleHndl = logging.StreamHandler() 33 | FORMATTER = logging.Formatter(fmt='%(asctime)s %(message)s', datefmt='%H:%M:%S') 34 | consoleHndl.setFormatter(FORMATTER) 35 | consoleHndl.setLevel(logLevel) 36 | logger.addHandler(consoleHndl) 37 | 38 | def runPollSim(self, index): 39 | if index in self.pollSim.keys(): 40 | # Poll already started. 41 | return 42 | pollSim = {index : PollSimThread(self, index)} 43 | pollSim[index].setDaemon(True) 44 | pollSim[index].update() 45 | pollSim[index].start() 46 | self.pollSim.update(pollSim) 47 | 48 | def stopPollSim(self, index): 49 | if index not in self.pollSim.keys(): 50 | # Poll not started. 51 | return 52 | self.pollSim[index].stop() 53 | 54 | def updatePoll(self, index): 55 | if index in self.pollSim.keys(): 56 | self.pollSim[index].update() 57 | 58 | # TODO: remove, already implemented in PyscardRpcServerThread. 59 | # Endpoint is e.g. "tcp://0.0.0.0:4242" 60 | def runServer(self, endpoint): 61 | self.s = zerorpc.Server(self) 62 | #self.s.bind(endpoint) 63 | try: 64 | self.s.bind(endpoint) 65 | except zmq.ZMQError as e: 66 | if e.errno == zmq.EADDRINUSE: 67 | logging.info("Pyscard RPC server already running\n") 68 | else: 69 | logging.warning("Pyscard RPC server " + endpoint + " could not be started") 70 | self.s.close() 71 | sys.exit(0) 72 | 73 | #create result file only when server is started 74 | dir = os.path.dirname(__file__) 75 | resultFile = dir + "/../pyscard_rpc.log" 76 | FORMATTER = logging.Formatter(fmt='%(asctime)s %(message)s', datefmt='%H:%M:%S') 77 | fileHndl = logging.FileHandler(resultFile, mode='w') 78 | fileHndl.setFormatter(FORMATTER) 79 | fileHndl.setLevel(logging.DEBUG) 80 | 81 | logger = logging.getLogger() 82 | logger.addHandler(fileHndl) 83 | 84 | logging.info("Pyscard RPC Server " + endpoint + " started\n") 85 | 86 | self.s.run() 87 | return self 88 | 89 | def getReader(self, index): 90 | for reader in self.readers: 91 | if reader.index == index: 92 | return reader 93 | return None 94 | 95 | def getReaderName(self, index): 96 | reader = self.getReader(index) 97 | if not reader: 98 | return None 99 | return reader.reader.name 100 | 101 | def getCard(self, index): 102 | self.checkReader(index) 103 | return self.getReader(index).card 104 | 105 | def checkReader(self, index): 106 | if not self.getReader(index): 107 | raise Exception("Reader with index=%d not created" %index) 108 | 109 | def checkCard(self, index): 110 | if not self.getCard(index): 111 | raise Exception("Card for reader with index=%d not created" %index) 112 | 113 | #################### 114 | # remote methods 115 | #################### 116 | def listReaders(self): 117 | logFunctionAndArgs() 118 | readers = smartcard.System.readers() 119 | readersStr = [] 120 | for reader in readers: 121 | readersStr.append(reader.name) 122 | logReturnVal(readersStr=readersStr) 123 | return readersStr 124 | 125 | def addReader(self, index): 126 | logFunctionAndArgs() 127 | readersConnected = smartcard.System.readers() 128 | if not len(readersConnected): 129 | raise Exception("No reader connected") 130 | if index >= len(readersConnected): 131 | raise Exception("Reader id:%d not connected, number of connected readers:%d" 132 | %(index, len(readersConnected))) 133 | newReader = False 134 | if not self.getReader(index): 135 | newReader = True 136 | self.readers.append(Reader()) 137 | self.readers[-1].index = index 138 | self.readers[-1].reader = readersConnected[index] 139 | logReturnVal(newReader=newReader) 140 | return newReader 141 | 142 | def removeReader(self, index): 143 | logFunctionAndArgs() 144 | self.checkReader(index) 145 | for reader in self.readers: 146 | if reader.index == index: 147 | del reader 148 | logReturnVal() 149 | return None 150 | 151 | def removeAllReaders(self): 152 | logFunctionAndArgs() 153 | for reader in self.readers: 154 | del reader 155 | logReturnVal() 156 | return None 157 | 158 | def r_createConnection(self, index): 159 | logFunctionAndArgs() 160 | newConnection = False 161 | self.checkReader(index) 162 | if not self.getReader(index).card: 163 | self.getReader(index).card = self.getReader(index).reader.createConnection() 164 | newConnection = True 165 | logReturnVal(newConnection=newConnection) 166 | return newConnection 167 | 168 | def c_connect(self, index): 169 | logFunctionAndArgs() 170 | self.checkCard(index) 171 | self.getCard(index).connect() 172 | # TODO: get simType from sim_router. 173 | self.runPollSim(index) 174 | logReturnVal() 175 | return None 176 | 177 | def c_disconnect(self, index): 178 | logFunctionAndArgs() 179 | self.checkCard(index) 180 | self.stopPollSim(index) 181 | self.getCard(index).disconnect() 182 | logReturnVal() 183 | return None 184 | 185 | def c_control(self, controlCode, inBuffer, index): 186 | #logFunctionAndArgs() 187 | self.checkCard(index) 188 | self.getCard(index).control(controlCode, inBuffer) 189 | #logReturnVal() 190 | return None 191 | 192 | def c_getATR(self, index): 193 | logFunctionAndArgs() 194 | self.checkCard(index) 195 | self.updatePoll(index) 196 | try: 197 | atr = self.getCard(index).getATR() 198 | except CardConnectionException: 199 | time.sleep(0.1) 200 | atr = self.getCard(index).getATR() 201 | logReturnVal(atr=atr) 202 | return atr 203 | 204 | def c_transmit(self, apdu, index): 205 | if apdu not in POLL_STATUS_PATTERN_BIN: 206 | logFunctionAndArgs() 207 | self.checkCard(index) 208 | self.updatePoll(index) 209 | data, sw1, sw2 = self.getCard(index).transmit(apdu) 210 | if apdu not in POLL_STATUS_PATTERN_BIN: 211 | logReturnVal(data=data, sw1=sw1, sw2=sw2) 212 | return data, sw1, sw2 213 | 214 | class Reader(object): 215 | def __init__(self): 216 | self.index = None 217 | self.reader = None 218 | self.card = None 219 | 220 | POLL_STATUS_PATTERN_BIN = [ 221 | [0x80, 0xF2, 0x00, 0x0C, 0x00], #3G SIM 222 | [0xA0, 0xF2, 0x00, 0x00, 0x01], #2G SIM 223 | ] 224 | 225 | def logFunctionAndArgs(): 226 | frame = inspect.getouterframes(inspect.currentframe())[1][0] 227 | args, _, _, values = inspect.getargvalues(frame) 228 | frameinfo = inspect.getframeinfo(frame) 229 | functionName=inspect.getframeinfo(frame)[2] 230 | output = "" 231 | for arg in args[1:]: #[1:] skip the first argument 'self' 232 | value = values[arg] 233 | if isinstance(value, str): 234 | #add apostrophes for string values 235 | value = "\'"+value+"\'" 236 | elif isinstance(value, int): 237 | value = ''.join('%02X' % value) 238 | else: 239 | newValue = "" 240 | for i in value: 241 | if isinstance(i, int): 242 | newValue += '%02X' % i 243 | else: 244 | newValue += str(i) 245 | value = newValue 246 | output += arg + '=' + value 247 | if arg != args[-1]: 248 | #add comma if not the last element 249 | output +=',' 250 | #do not print "\n' as a new line 251 | output = output.replace("\n","\\n") 252 | logging.info("--> "+functionName+'('+output+')') 253 | 254 | def logReturnVal(**kwargs): 255 | output = "" 256 | for key, value in kwargs.iteritems(): 257 | if isinstance(value, str): 258 | #add apostrophes for string values 259 | value = "\'"+value+"\'" 260 | elif isinstance(value, int): 261 | value = ''.join('%02X' % value) 262 | else: 263 | newValue = "" 264 | for i in value: 265 | if isinstance(i, int): 266 | newValue += '%02X' % i 267 | else: 268 | newValue += str(i) 269 | value = newValue 270 | output += key + ':' + value + ', ' 271 | output = output.rstrip(', ') #remove last comma and space 272 | logging.info("<-- "+output+'\n') 273 | 274 | ###################### 275 | #Server configuration# 276 | ###################### 277 | class PyscardRpcServerThread(threading.Thread): 278 | def __init__(self, port, logLevel=logging.WARNING): 279 | threading.Thread.__init__(self) 280 | self.port = port 281 | self.logLevel = logLevel 282 | threading.Thread.setName(self, 'PyscardRpcServerThread') 283 | self.proc = None 284 | self.__lock = threading.Lock() 285 | 286 | def run(self): 287 | self.__lock.acquire(); 288 | #start locally pyscard RPC server 289 | rpcServerScript = os.path.abspath(__file__).replace("\\", "/") 290 | scriptCmd = "python %s --port=%d --logLevel=%d" %(rpcServerScript, self.port, self.logLevel) 291 | self.proc = subprocess.Popen(shlex.split(scriptCmd)) 292 | self.__lock.release(); 293 | 294 | def close(self): 295 | if self.proc: 296 | subprocess.Popen.terminate(self.proc) 297 | 298 | class PollSimThread(threading.Thread): 299 | def __init__(self, pyscardRpc, index, pollRate=400): 300 | threading.Thread.__init__(self) 301 | self.pyscardRpc = pyscardRpc 302 | self.index = index 303 | self.pollRate = pollRate 304 | self.poll = False 305 | self.startTime = 0 306 | self.lastUpdate = 0 307 | self.pattern = 0 308 | threading.Thread.setName(self, 'PollSimThread') 309 | self.__lock = threading.Lock() 310 | 311 | def run(self): 312 | self.__lock.acquire() 313 | self.poll = True 314 | 315 | while (self.poll): 316 | self.startTime = time.time() 317 | if self.startTime - self.lastUpdate > (self.pollRate / 1000.0 - 0.1): 318 | try: 319 | sw1 = self.pyscardRpc.c_transmit(hextools.hex2bytes(POLL_STATUS_PATTERN[self.pattern]), 320 | self.index)[1] 321 | if sw1 != 0x90 and self.pattern < (len(POLL_STATUS_PATTERN) - 1): 322 | # Use different pattern e.g. 2G status. 323 | self.pattern += 1 324 | except: 325 | logging.error("Stop polling") 326 | self.stop() 327 | self.lastUpdate = time.time() 328 | time.sleep(self.pollRate / 1000.0) 329 | self.__lock.release() 330 | 331 | def update(self): 332 | self.lastUpdate = time.time() - 0.1 333 | 334 | def stop(self): 335 | self.poll = False 336 | try: 337 | self.join() 338 | except: pass 339 | 340 | POLL_STATUS_PATTERN = [ 341 | "80F2000C00", #3G SIM 342 | "A0F2000001", #2G SIM 343 | ] 344 | 345 | if __name__ == '__main__': 346 | parser = OptionParser() 347 | parser.add_option("-p", "--port", dest="port", help="Port number") 348 | parser.add_option("-v", "--logLevel", dest="logLevel", help="Log level") 349 | (options, args) = parser.parse_args() 350 | if options.port: 351 | try: 352 | pyscardRpcPort = int(options.port) 353 | except: 354 | raise Exception("Expecting --port argument to be integer, got %r instead" %options.port) 355 | pyscardRpcPort = options.port 356 | else: 357 | pyscardRpcPort = 4148 358 | if options.logLevel: 359 | logLevel = int(options.logLevel) 360 | else: 361 | logLevel = logging.INFO 362 | PyscardRPC(logLevel).runServer("tcp://0.0.0.0:"+str(pyscardRpcPort)) 363 | -------------------------------------------------------------------------------- /sim/sim_backup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2016 Kamil Wartanowicz 4 | 5 | import sys,os.path 6 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 7 | from util import types 8 | from sim_soft import sim_xml 9 | 10 | class SimBackup(object): 11 | def __init__(self, simCtrl, imsi, atr): 12 | self.simCtrl = simCtrl 13 | self.imsi = imsi 14 | self.root = sim_xml.xmlInit(atr) 15 | 16 | def setMf(self, data): 17 | return self.addMf(self.root, data) 18 | 19 | def addMf(self, node, data): 20 | arrFile, arrRecord = types.getArrFileFromData(data) 21 | if not arrFile: 22 | #TODO: handle security attr 23 | arrFile = "2F06" 24 | arrRecord = "2" 25 | return sim_xml.addMfNode(node, str(arrFile), str(arrRecord)) 26 | 27 | def addDf(self, node, id, data): 28 | arrFile, arrRecord = types.getArrFileFromData(data) 29 | if not arrFile: 30 | #TODO: handle security attr 31 | arrFile = "2F06" 32 | arrRecord = "2" 33 | return sim_xml.addDfNode(node, id, str(arrFile), str(arrRecord)) 34 | 35 | def addAdf(self, node, id, aid, data): 36 | arrFile, arrRecord = types.getArrFileFromData(data) 37 | if not arrFile: 38 | #TODO: handle security attr 39 | arrFile = "2F06" 40 | arrRecord = "2" 41 | return sim_xml.addAdfNode(node, id, aid, str(arrFile), str(arrRecord)) 42 | 43 | def addEf(self, node, id, value, data): 44 | struct = self.simCtrl.getFileStructure(data) 45 | if self.simCtrl.getFileStructure(data) in [types.FILE_STRUCTURE_LINEAR_FIXED, 46 | types.FILE_STRUCTURE_CYCLIC]: 47 | recordLength, nbrOfRecords = self.simCtrl.getRecordInfo(data) 48 | size = recordLength * nbrOfRecords 49 | else: 50 | recordLength = 0 51 | size = types.getFileLength(data) 52 | #TODO: for older 3G cards security attr might be used instead of arr 53 | arrFile, arrRecord = types.getArrFileFromData(data) 54 | if not arrFile: 55 | #TODO: handle security attr 56 | arrFile = "6F06" 57 | arrRecord = "2" 58 | sfi = "%02X" %types.getSfiFromData(data) 59 | #TODO: get from data 60 | invalidated = 0 61 | rwInvalidated = 0 62 | sim_xml.addEfNode(node, 63 | id, 64 | sfi, 65 | str(struct), 66 | str(size), 67 | str(recordLength), 68 | value, 69 | arrFile, 70 | str(arrRecord), 71 | str(invalidated), 72 | str(rwInvalidated)) 73 | 74 | def saveXml(self): 75 | xmlPath = os.path.dirname(__file__) + "/../sim_soft/sim_backup_" + self.imsi + ".xml" 76 | sim_xml.writeXml(xmlPath, self.root) 77 | return xmlPath -------------------------------------------------------------------------------- /sim/sim_card.py: -------------------------------------------------------------------------------- 1 | # LICENSE: GPL2 2 | # (c) 2013 Tom Schouten 3 | # (c) 2014, Kamil Wartanowicz 4 | import logging 5 | from util import hextools 6 | import sim_reader 7 | from util import types 8 | from util import types_g 9 | 10 | class SimCard(object): 11 | def __init__(self, index=0, mode=sim_reader.MODE_PYSCARD, type=types.TYPE_USIM): 12 | self.simReader = sim_reader.SimReader(mode=mode, type=type) 13 | self.index = None 14 | self.mode = mode 15 | self.imsi = None 16 | self.atr = None 17 | self.currentAidId = None # TODO: consider logical channels 18 | self.currentFile = CurrentFile() # TODO: consider logical channels 19 | self.routingAttr = RoutingAttr() 20 | self.swNoError = 0x9000 21 | self.type = type 22 | self.activeChannel = 0 23 | self.logicalChannelClosed = False 24 | 25 | def removeRoutingAttr(self): 26 | self.routingAttr.insCommon = [] 27 | self.routingAttr.filesCommon = [] 28 | self.routingAttr.filesReplaced = [] 29 | self.routingAttr.insReplaced = [] 30 | 31 | def listReaders(self): 32 | return self.simReader.listReaders() 33 | 34 | def removeAllReaders(self): 35 | return self.simReader.removeAllReaders() 36 | 37 | def connect(self, index=0): 38 | self.index = index 39 | readers = self.simReader.listReaders() 40 | self.simReader.addReader(index) 41 | self.simReader.r_createConnection(index) 42 | self.simReader.c_connect(index) 43 | self.clearLogicalChannels() 44 | self.getATR() 45 | 46 | def clearLogicalChannels(self): 47 | self.logicalChannelClosed = True 48 | self.activeChannel = 0 49 | 50 | def setActiveChannel(self, channel): 51 | self.activeChannel = channel 52 | 53 | def getActiveChannel(self): 54 | return self.activeChannel 55 | 56 | def stop(self): 57 | self.simReader.close() 58 | 59 | def disconnect(self): 60 | try: 61 | self.simReader.c_disconnect(self.index) 62 | except: 63 | logging.debug("Reader not connected") 64 | self.simReader.removeReader(self.index) 65 | self.atr = None 66 | 67 | def reset(self): 68 | #TODO: implement different solution, takes too much time on live SIM 69 | self.disconnect() 70 | self.connect(self.index) 71 | 72 | def getATR(self): 73 | self.atr = self.simReader.c_getATR(self.index) 74 | self.clearLogicalChannels() 75 | return self.atr 76 | 77 | def getCachedAtr(self): 78 | return self.atr 79 | 80 | def setImsi(self, imsi): 81 | self.imsi = imsi 82 | 83 | def getCurrentAidId(self): 84 | return self.currentAidId 85 | 86 | def setCurrentAidId(self, aidId): 87 | self.currentAidId = aidId 88 | 89 | # Perform APDU request on card 90 | def apdu(self, c_apdu): 91 | c_apdu = hextools.bytes(c_apdu) 92 | # Delegate 93 | try: 94 | (data,sw1,sw2) = self.simReader.c_transmit(list(c_apdu), self.index) 95 | except Exception as e: 96 | #TODO: Remove printing the error. Check why exception is not printed in tes_sat 97 | logging.error(str(e) + "\n\n") 98 | raise Exception("Failed to transmit C_APDU: " + hextools.bytes2hex(c_apdu) + "\n" + str(e)) 99 | self.updateSwNoError(sw1, sw2) 100 | return pack(data,sw1,sw2) 101 | 102 | def updateSwNoError(self, sw1, sw2): 103 | "cache last success" 104 | sw = types.packSw(sw1, sw2) 105 | if sw == types_g.sw.NO_ERROR or sw1 == types_g.sw1.NO_ERROR_PROACTIVE_DATA: 106 | self.swNoError = sw 107 | 108 | def getCurrentFile(self): 109 | return self.currentFile 110 | 111 | def getCurrentFilePath(self): 112 | return self.currentFile.path 113 | 114 | def getCurrentFileType(self): 115 | return self.currentFile.type 116 | 117 | def getCurrentDirPath(self): 118 | if types.cmpBitwise(self.currentFile.type, types_g.fileDescriptor.DF_OR_ADF): 119 | path = self.currentFile.path 120 | else: 121 | path = types.parentDirFromPath(self.currentFile.path) 122 | return path 123 | 124 | def setCurrentFile(self, path, type): 125 | self.setCurrentFilePath(path) 126 | self.setCurrentFileType(type) 127 | 128 | def setCurrentFilePath(self, path): 129 | self.currentFile.path = path 130 | 131 | def setCurrentFileType(self, type): 132 | self.currentFile.type = type 133 | 134 | def appendCurrentDir(self, name, type=types_g.fileDescriptor.NO_INFORMATION_GIVEN): 135 | if name == "3F00": 136 | self.resetCurrentDir() 137 | return 138 | if name == "7FFF": 139 | aidId = self.getCurrentAidId() 140 | if aidId == None: 141 | aidId = 0 142 | self.resetCurrentDir() 143 | name = "ADF%d" %aidId 144 | type = types_g.fileDescriptor.DF_OR_ADF 145 | if not types.cmpBitwise(self.currentFile.type, types_g.fileDescriptor.DF_OR_ADF): 146 | path = types.parentDirFromPath(self.currentFile.path) 147 | self.setCurrentFile(path, types_g.fileDescriptor.DF_OR_ADF) 148 | if types.fidFromPath(self.currentFile.path) == name: 149 | #parrent directory name must be different than currently selected file 150 | return 151 | currentFilePath = self.currentFile.path 152 | if self.currentFile.path[-1] != "/": 153 | currentFilePath += "/" 154 | currentFilePath += name 155 | self.setCurrentFile(currentFilePath, type) 156 | 157 | def decrementCurrentDir(self): 158 | if not self.currentFile.path: 159 | return 160 | #path = types.parentDirFromPath(self.currentFile.path) 161 | path = types.parentDirFromPath(self.getCurrentDirPath()) 162 | self.setCurrentFile(path, types_g.fileDescriptor.DF_OR_ADF) 163 | 164 | def resetCurrentDir(self): 165 | self.setCurrentFile("/", types_g.fileDescriptor.DF_OR_ADF) 166 | 167 | FILE_INS = [ 168 | 'READ_BINARY', 169 | 'READ_RECORD', 170 | 'SEARCH_RECORD', 171 | 'UPDATE_BINARY', 172 | 'WRITE_BINARY', 173 | 'WRITE_RECORD', 174 | 'UPDATE_RECORD' 175 | ] 176 | 177 | SAT_INS = [ 178 | 'TERMINAL_PROFILE', 179 | 'TERMINAL_RESPONSE', 180 | 'FETCH', 181 | 'ENVELOPE', 182 | ] 183 | 184 | FILES_AID = [ 185 | 'EF_DIR', 186 | 'A000', 187 | ] 188 | 189 | FILES_REG = [ 190 | 'EF_IMSI', 191 | 'EF_LOCI', #rplmn 192 | 'EF_SMSP', #sms center number 193 | 'EF_LRPLMNSI', #last RPLMN Selection Indication 194 | 'EF_PLMNSEL', #plmn selector 195 | 'EF_FPLMN', #forbiden plmn 196 | 197 | 'EF_PSLOCI', 198 | 'EF_EPSLOCI', 199 | 'EF_EPSNSC', 200 | 'EF_LOCIGPRS', 201 | 202 | 'EF_KEYS', 203 | 'EF_KEYSPS', 204 | 'EF_KCGPRS', 205 | 'EF_KC', 206 | 207 | 'EF_EHPLMN', 208 | 'EF_EHPLMNPI', 209 | 'EF_LRPLMNSI', 210 | ] 211 | 212 | ''' 213 | #migth be also considered in FILES_REG 214 | 'EF_RPLMN_ACT', 215 | 'EF_PLMNWACT', 216 | 'EF_HIDDENKEY', 217 | 'EF_OPLMNWACT', 218 | 'EF_HPLMNWACT', 219 | 'RPLMN_ACT', 220 | ''' 221 | 222 | FILES_REPLACED = [] 223 | 224 | INS_REPLACED = [] 225 | 226 | INS_COMMON = [ 227 | 'GET_RESPONSE', 228 | 'SELECT_FILE', 229 | 'MANAGE_CHANNEL', 230 | #'INTERNAL_AUTHENTICATE', 231 | ] 232 | 233 | def pack(reply,sw1,sw2): 234 | p = list(reply) 235 | p.append(sw1) 236 | p.append(sw2) 237 | return p 238 | 239 | class RoutingAttr(object): 240 | def __init__(self): 241 | self.insCommon = list(INS_COMMON) 242 | self.filesCommon = list(FILES_AID) 243 | self.filesReplaced = list(FILES_REPLACED) 244 | self.insReplaced = list(INS_REPLACED) 245 | 246 | self.getResponse = None 247 | self.fileSelected = [] 248 | self.aidToSelect = None 249 | self.recordEfDirLength = None 250 | 251 | def getFileSelected(self, channel): 252 | for file in self.fileSelected: 253 | if not channel or file[1] == channel: 254 | return file[0] 255 | return None 256 | 257 | def setFileSelected(self, file, channel): 258 | for i,fileDict in enumerate(self.fileSelected): 259 | if fileDict[1] == channel: 260 | self.fileSelected[i] = (file, channel) 261 | return 262 | self.fileSelected.append((file, channel)) 263 | 264 | class CurrentFile(object): 265 | def __init__(self): 266 | self.path = "/" 267 | self.type = types_g.fileDescriptor.DF_OR_ADF 268 | -------------------------------------------------------------------------------- /sim/sim_codes.py: -------------------------------------------------------------------------------- 1 | PIN_1 = 0 2 | PIN_1_UNBLOCK = 1 3 | PIN_2 = 3 4 | PIN_2_UNBLOCK = 4 5 | ADM_1 = 5 6 | ADM_2 = 6 7 | ADM_3 = 7 8 | ADM_4 = 8 9 | PIN_1_ENABLE = 10 10 | PIN_1_DISABLE = 11 11 | AUTH_KEY = 12 12 | AUTH_SQN = 13 13 | AUTH_AMF = 14 14 | 15 | defaultCard = { 16 | PIN_1 : "1111", 17 | PIN_1_UNBLOCK : "11111111", 18 | PIN_2 : "2222", 19 | PIN_2_UNBLOCK : "22222222", 20 | ADM_1 : "ADM11111", 21 | ADM_2 : "ADM22222", 22 | ADM_4 : "ADM44444", 23 | AUTH_KEY : "00112233445566778899AABBCCDDEEFF", 24 | AUTH_SQN : "000000000000", 25 | #AUTH_SQN : "FFFFFFFFFFFF", 26 | AUTH_AMF : "8000", 27 | } -------------------------------------------------------------------------------- /sim/sim_files_2g.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | DF_GRAPHICS 7 | 8 | EF_IMG 9 | 10 | 11 | EF_ARR 12 | 13 | 14 | 15 | 16 | EF_ARR 17 | 18 | 19 | 20 | EF_CCP 21 | 22 | 23 | EF_ADN 24 | 25 | 26 | EF_SMS 27 | 28 | 29 | EF_FDN 30 | 31 | DF_TELECOM 32 | 33 | EF_BDN 34 | 35 | 36 | EF_EXT4 37 | 38 | 39 | EF_ECCP 40 | 41 | 42 | EF_EXT1 43 | 44 | 45 | EF_EXT2 46 | 47 | 48 | EF_EXT3 49 | 50 | 51 | EF_SDN 52 | 53 | 54 | EF_LND 55 | 56 | 57 | EF_SMSR 58 | 59 | 60 | EF_MSISDN 61 | 62 | 63 | EF_SMSP 64 | 65 | 66 | EF_SMSS 67 | 68 | 69 | EF_CMI 70 | 71 | 72 | EF_ARR 73 | 74 | 75 | 76 | 77 | EF_CPHS_ONSHF 78 | 79 | 80 | EF_CPHS_INFN 81 | 82 | 83 | DF_GLOBALSTAR 84 | 85 | 86 | EF_FPLMN 87 | 88 | 89 | EF_CPHS_VMW 90 | 91 | 92 | EF_CPHS_SST 93 | 94 | 95 | EF_CPHS_CFF 96 | 97 | 98 | EF_CPHS_ONSTR 99 | 100 | 101 | EF_CPHS_CSP 102 | 103 | 104 | EF_CPHS 105 | 106 | 107 | EF_CPHS_MBXN 108 | 109 | 110 | EF_ACC 111 | 112 | 113 | EF_BCCH 114 | 115 | 116 | DF_SOLSA 117 | 118 | EF_SAI 119 | 120 | 121 | EF_SLL 122 | 123 | 124 | 125 | DF_IRIDIUM 126 | 127 | 128 | EF_IMSI 129 | 130 | 131 | EF_ARR 132 | 133 | 134 | EF_LP 135 | 136 | 137 | EF_OPLMNWACT 138 | 139 | 140 | EF_PLMNWACT 141 | 142 | 143 | EF_CPBCCH 144 | 145 | 146 | EF_HPLMNWACT 147 | 148 | 149 | EF_RPLMN_ACT 150 | 151 | 152 | EF_INVSCAN 153 | 154 | 155 | EF_MMSUCP 156 | 157 | 158 | EF_MMSUP 159 | 160 | 161 | EF_MMSICP 162 | 163 | 164 | EF_EXT8 165 | 166 | 167 | EF_SPDI 168 | 169 | 170 | EF_MMSN 171 | 172 | 173 | EF_EXT7 174 | 175 | 176 | EF_MWIS 177 | 178 | 179 | DF_ACES 180 | 181 | 182 | EF_SUME 183 | 184 | 185 | DF_CTS 186 | 187 | 188 | EF_CBMIR 189 | 190 | 191 | EF_NIA 192 | 193 | 194 | EF_KCGPRS 195 | 196 | 197 | EF_LOCIGPRS 198 | 199 | 200 | EF_OPL 201 | 202 | 203 | EF_MBDN 204 | 205 | 206 | EF_PNN 207 | 208 | 209 | EF_EXT6 210 | 211 | 212 | EF_MBI 213 | 214 | 215 | DF_MEXE 216 | 217 | EF_MEXE-ST 218 | 219 | 220 | EF_ORPK 221 | 222 | 223 | EF_ARPK 224 | 225 | 226 | EF_TPRK 227 | 228 | 229 | 230 | DF_ICO 231 | 232 | 233 | EF_PUCT 234 | 235 | 236 | EF_SMSR 237 | 238 | 239 | EF_SPN 240 | 241 | 242 | EF_CBMI 243 | 244 | 245 | EF_CBMID 246 | 247 | 248 | EF_EMLPP 249 | 250 | 251 | EF_VBSS 252 | 253 | 254 | EF_ECC 255 | 256 | 257 | EF_AAEM 258 | 259 | 260 | EF_VGCS 261 | 262 | 263 | EF_VBS 264 | 265 | 266 | EF_VGCSS 267 | 268 | 269 | EF_GID2 270 | 271 | 272 | EF_GID1 273 | 274 | 275 | EF_AD 276 | 277 | 278 | EF_PHASE 279 | 280 | 281 | EF_ACMMAX 282 | 283 | 284 | EF_CNL 285 | 286 | 287 | EF_PLMNSEL 288 | 289 | 290 | EF_HPLMN 291 | 292 | 293 | EF_SST 294 | 295 | 296 | EF_ACM 297 | 298 | DF_GSM 299 | 300 | EF_LOCI 301 | 302 | 303 | EF_KC 304 | 305 | 306 | EF_CFIS 307 | 308 | 309 | EF_DCK 310 | 311 | 312 | DF_PCS_1900 313 | 314 | 315 | MF 316 | 317 | DF_IS_41 318 | 319 | 320 | DF_FP_CTS 321 | 322 | 323 | EF_ICCID 324 | 325 | 326 | EF_ELP 327 | 328 | 329 | 330 | -------------------------------------------------------------------------------- /sim/sim_reader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Wrapper to pyscard 3 | # LICENSE: GPL2 4 | # (c) 2014 Kamil Wartanowicz 5 | 6 | from util import types 7 | import logging 8 | import threading 9 | import zerorpc 10 | from zerorpc.exceptions import * 11 | from sim_soft import sim_soft_ctrl 12 | from sim import pyscard_rpc_ctrl 13 | 14 | PYSCARD_RPC_IP_ADDRESS = "10.28.27.200" 15 | PYSCARD_RPC_PORT = 4148 16 | 17 | MODE_PYSCARD = 0 18 | MODE_SIM_SOFT = 1 19 | 20 | LOCAL_PYSCARD_SERVER = True 21 | MESSAGE_TIMEOUT = 60#sec 22 | 23 | class SimReader(object): 24 | def __init__(self, mode=MODE_PYSCARD, type=types.TYPE_USIM): 25 | self.mode = mode 26 | self.type = type 27 | self.server = None 28 | address=PYSCARD_RPC_IP_ADDRESS 29 | if mode == MODE_PYSCARD and LOCAL_PYSCARD_SERVER: 30 | logging.debug("Pyscard local server") 31 | self.server = pyscard_rpc_ctrl.PyscardRpcServerThread(PYSCARD_RPC_PORT) 32 | self.server.setDaemon(True) 33 | self.server.start() 34 | #set locall IP addr 35 | address = "127.0.0.1" 36 | self.address = address 37 | self.handlers = {} 38 | 39 | def close(self): 40 | threadId = threading.current_thread().ident 41 | if threadId in self.handlers.keys(): 42 | try: 43 | self.handlers[threadId].close() 44 | except: 45 | pass 46 | del self.handlers[threadId] 47 | if self.server: 48 | self.server.close() 49 | 50 | def runClient(self, endpoint): 51 | # TODO: check that client might be connected otherwise kill server 52 | client = zerorpc.Client(heartbeat=None, timeout=MESSAGE_TIMEOUT) 53 | logging.debug("Pyscard RPC client: %r" %endpoint) 54 | client.connect(endpoint) 55 | return client 56 | 57 | def getHandler(self): 58 | if self.mode == MODE_SIM_SOFT: 59 | if len(self.handlers): 60 | # TODO: remove type from SimPyscard. 61 | # Return first element in dict. 62 | return self.handlers.itervalues().next() 63 | threadId = threading.current_thread().ident 64 | if threadId not in self.handlers.keys(): 65 | if self.mode != MODE_SIM_SOFT: 66 | self.handlers.update({threadId : self.runClient("tcp://"+self.address+":"+str(PYSCARD_RPC_PORT))}) 67 | else: 68 | self.handlers.update({threadId : sim_soft_ctrl.SimSoftCtrl(type=self.type)}) 69 | return self.handlers[threadId] 70 | 71 | def __getattr__(self,attr): 72 | #__getattr__ is called when undefined method of class is called 73 | handler = self.getHandler() 74 | orig_attr = getattr(handler, attr) 75 | 76 | if not callable(orig_attr): 77 | #means the objct is: function, method or callable class 78 | #if orig_attr is not callable (not False) return attribute (e.g. class variable) 79 | return orig_attr 80 | 81 | def hooked(*args, **kwargs): 82 | result = 0 83 | try: 84 | result = orig_attr(*args, **kwargs) 85 | #if result == handler: 86 | # prevent wrapped_class from becoming unwrapped 87 | # return self 88 | except TimeoutExpired as exceptionObj: 89 | logging.warning("Timeout, repeat the last command") 90 | try: 91 | result = orig_attr(*args, **kwargs) 92 | except TimeoutExpired as exceptionObj: 93 | raise Exception(attr + "() failed!\n" + exceptionObj.message) 94 | except Exception as e: 95 | #catch every exception except TimeoutExpired(caught above) 96 | e = "".join(str(e)).rstrip("\n\n") 97 | e = e.split("\n") 98 | if len(e) >= 6: 99 | #limit stack 100 | e = e[-6:] 101 | raise Exception("%s() failed!\n%s" %(attr, "\n".join(e))) 102 | return result 103 | return hooked 104 | 105 | READER_ID_0 = 0 106 | READER_ID_1 = 1 107 | READER_ID_2 = 2 108 | READER_ID_3 = 3 -------------------------------------------------------------------------------- /sim_soft/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/sim_soft/__init__.py -------------------------------------------------------------------------------- /sim_soft/sat_types.py: -------------------------------------------------------------------------------- 1 | # LICENSE: GPL2 2 | # (c) 2014 Szymon Mielczarek 3 | 4 | # Fixed lengths of some COMPREHENSION-TLV data objects 5 | TLV_COMMAND_DETAILS_LENGTH = 0x03 6 | TLV_DEVICE_IDENTITIES_LENGTH = 0x02 7 | TLV_DURATION_LENGTH = 0x02 8 | TLV_ITEM_IDENTIFIER_LENGTH = 0x01 9 | TLV_RESPONSE_LENGTH = 0x02 10 | 11 | class data_coding(object): 12 | #SMS class 13 | CODING_MESSAGE_CLASS_0 = 0b00000000 14 | CODING_MESSAGE_CLASS_1 = 0b00000001 15 | CODING_MESSAGE_CLASS_2 = 0b00000010 16 | CODING_MESSAGE_CLASS_3 = 0b00000011 17 | 18 | #character set 19 | CODING_GSM7 = 0b00000000 20 | CODING_8_BIT_DATA = 0b00000100 21 | CODING_UCS2 = 0b00001000 22 | CODING_RESERVED = 0b00001100 23 | 24 | class gsm_charset(object): 25 | CHARSET_UTF8 = 1 26 | CHARSET_UCS2 = 2 27 | CHARSET_GSM = 3 28 | 29 | class duration_time_unit(object): 30 | MINUTES = 0x00 31 | SECONDS = 0x01 32 | TENTHS_OF_SECONDS = 0x02 33 | 34 | # REFRESH modes (command qualifiers) 35 | class refresh_mode(object): 36 | # NAA Initialization and Full File Change Notification 37 | SIM_INIT_AND_FULL_FILE_CHANGE_NOTIFICATION = 0x00 38 | # File Change Notification 39 | FILE_CHANGE_NOTIFICATION = 0x01 40 | # NAA Initialization and File Change Notification 41 | SIM_INIT_AND_FILE_CHANGE_NOTIFICATION = 0x02 42 | # NAA Initialization 43 | SIM_INIT = 0x03 44 | # UICC Reset 45 | SIM_RESET = 0x04 46 | # NAA Application Reset, only applicable for a 3G platform 47 | USIM_INIT = 0x05 48 | # NAA Session Reset, only applicable for a 3G platform 49 | USIM_RESET = 0x06 50 | 51 | # LOCAL INFORMATION (command qualifiers) 52 | class local_info(object): 53 | # Location Information according to current NAA 54 | LOCATION_INFO_PER_NAA = 0x00 55 | # IMEI of the terminal 56 | IMEI_OF_THE_TERMINAL = 0x01 57 | # Network Measurement results according to current NAA 58 | NETWORK_MEASUREMENT_RESULTS_PER_NAA = 0x02 59 | # Date, time and time zone 60 | DATETIME = 0x03 61 | # Language setting 62 | LANGUAGE = 0x04 63 | # Reserved for GSM 64 | RESERVED_FOR_GSM = 0x05 65 | # Access Technology (single access technology) 66 | ACCESS_TECHNOLOGY = 0x06 67 | # ESN of the terminal 68 | ESN_OF_THE_TERMINAL = 0x07 69 | # IMEISV of the terminal 70 | IMEISV_OF_THE_TERMINAL = 0x08 71 | # Search Mode 72 | SEARCH_MODE = 0x09 73 | # Charge State of the Battery (if class "g" is supported) 74 | BATTERY_CHARGE_STATE = 0x0A 75 | # MEID of the terminal 76 | MEID_OF_THE_TERMINAL = 0x0B 77 | # reserved for 3GPP (current WSID) 78 | RESERVED_FOR_3GPP = 0x0C 79 | # Broadcast Network information 80 | # according to current Broadcast Network Technology used 81 | BROADCAST_NETWORK_INFO = 0x0D 82 | # Multiple Access Technologies 83 | MULTIPLE_ACCESS_TECHNOLOGY = 0x0E 84 | # Location Information for multiple access technologies 85 | LOCATION_INFO_FOR_MULTITECH = 0x0F 86 | # Network Measurement results for multiple access technologies 87 | NETWORK_MEASUREMENT_RESULTS_FOR_MULTITECH = 0x10 88 | # '11' to 'FF' = Reserved. 89 | 90 | # Tag values used to identify the BER-TLV and COMPREHENSION-TLV data objects 91 | # TS 102.223 v12.1.0 (sections 9.1-9.3) 92 | # TS 101 220 V8.4.0 (page 14) 93 | # BER (Basing encoding rules) 94 | class ber_tag(object): 95 | PROACTIVE = 0xD0 96 | SMS_PP_DOWNLOAD = 0xD1 97 | CELL_BROADCAST_DOWNLOAD = 0xD2 98 | MENU_SELECTION = 0xD3 99 | CALL_CONTROL = 0xD4 100 | MO_SHORT_MSG_CONTROL = 0xD5 # GSM/3G 101 | EVENT_DOWNLOAD = 0xD6 102 | TIMER_EXPIRATION = 0xD7 103 | USSD_DOWNLOAD = 0xD9 # 3G 104 | MMS_TRANSFER_STATUS = 0xDA 105 | MMS_NOTIFICATION_DOWNLOAD = 0xDB 106 | TERMINAL_APPLICATION = 0xDC 107 | # and some more 108 | 109 | class comprehension_tag(object): 110 | COMPREHENSION_REQUIRED = 0x80 111 | COMMAND_DETAILS = 0x01 112 | DEVICE_IDENTITIES = 0x02 113 | RESULT = 0x03 114 | DURATION = 0x04 115 | ALPHA_IDENTIFIER = 0x05 116 | ADDRESS = 0x06 117 | CAPABILITY_CONFIGURATION_PARAMETERS = 0x07 118 | CALLED_PARTY_SUBADDRESS = 0x08 119 | SS_STRING = 0x09 # Reserved for GSM/3G 120 | USSD_STRING = 0x0A # Reserved for GSM/3G 121 | SMS_TPDU = 0x0B # Reserved for GSM/3G 122 | CELL_BROADCAST_PAGE = 0x0C # Reserved for GSM/3G 123 | TEXT_STRING = 0x0D 124 | TONE = 0x0E 125 | ECAT_CLIENT_PROFILE = 0x0E 126 | ITEM = 0x0F 127 | ECAT_CLIENT_IDENTITY = 0x0F 128 | ITEM_IDENTIFIER = 0x10 129 | ENCAPSULATED_ENVELOPE = 0x10 130 | RESPONSE_LENGTH = 0x11 131 | FILE_LIST = 0x12 132 | LOCATION_INFORMATION = 0x13 133 | IMEI = 0x14 134 | HELP_REQUEST = 0x15 135 | ICON_IDENTIFIER = 0x1E 136 | # and many more (up to 0x7B) 137 | 138 | # TS 102.223 v12.1.0 (section 9.4) 139 | class cmd_type(object): 140 | REFRESH = 0x01 141 | MORE_TIME = 0x02 142 | POLL_INTERVAL = 0x03 143 | POLLING_OFF = 0x04 144 | SETUP_EVENT_LIST = 0x05 145 | SET_UP_CALL = 0x10 146 | SEND_SS = 0x11 147 | SEND_USSD = 0x12 148 | SEND_SHORT_MESSAGE = 0x13 149 | SEND_DTMF = 0x14 150 | LAUNCH_BROWSER = 0x15 151 | GEOGRAPHICAL_LOCATION_REQUEST = 0x16 152 | PLAY_TONE = 0x20 153 | DISPLAY_TEXT = 0x21 154 | GET_INKEY = 0x22 155 | GET_INPUT = 0x23 156 | SELECT_ITEM = 0x24 157 | SET_UP_MENU = 0x25 158 | PROVIDE_LOCAL_INFO = 0x26 159 | TIMER_MANAGEMENT = 0x27 160 | SETUP_IDLE_MODE_TEXT = 0x28 161 | CARD_APDU = 0x30 162 | POWER_ON_CARD = 0x31 163 | POWER_OFF_CARD = 0x32 164 | GET_READER_STATUS = 0x33 165 | RUN_AT_COMMAND = 0x34 166 | LANG_NOTIFICATION = 0x35 167 | OPEN_CHANNEL = 0x40 168 | CLOSE_CHANNEL = 0x41 169 | RECEIVE_DATA = 0x42 170 | SEND_DATA = 0x43 171 | GET_CHANNEL_STATUS = 0x44 172 | SERVICE_SEARCH = 0x45 173 | GET_SERVICE_INFORMATION = 0x46 174 | DECLARE_SERVICE = 0x47 175 | SET_FRAMES = 0x50 176 | GET_FRAMES_STATUS = 0x51 177 | RETRIEVE_MM = 0x60 178 | SUBMIT_MM = 0x61 179 | DISPLAY_MM = 0x62 180 | ACTIVATE = 0x70 181 | CONTACTLESS_STATE_CHANGED = 0x71 182 | COMMAND_CONTAINER = 0x72 183 | ENCAPSULATED_SESSION_CONTROL = 0x73 184 | END_OF_PROACTIVE_SESSION = 0x81 185 | 186 | # TS 102.223 v12.1.0 (section 8.7) 187 | class device_identity(object): 188 | DEV_KEYPAD = 0x01 189 | DEV_DISPLAY = 0x02 190 | DEV_EARPIECE = 0x03 191 | DEV_ADDT_CARD_READER = 0x10 192 | # card reader id (0 to 7) 193 | DEV_CHANNEL_IDENTIFIER = 0x20 194 | # channel id (1 to 7) 195 | DEV_ECAT_CLIENT_IDENTIFIER = 0x30 196 | # client id (1 to 15) 197 | DEV_UICC = 0x81 198 | DEV_TERMINAL = 0x82 199 | DEV_NETWORK = 0x83 200 | 201 | # 8.12 Result: General result 202 | general_result = { 203 | 0x00 : 'Command performed successfully', 204 | 0x01 : 'Command performed with partial comprehension', 205 | 0x02 : 'Command performed, with missing information', 206 | 0x03 : 'REFRESH performed with additional Efs read', 207 | 0x04 : 'Command performed successfully, but requested icon could not be displayed', 208 | 0x05 : 'Command performed, but modified by call control by NAA', 209 | 0x06 : 'Command performed successfully, limited service', 210 | 0x07 : 'Command performed with modification; ETSI', 211 | # Release 12 151 ETSI TS 102 223 V12.1.0 (2014-09) 212 | 0x08 : 'REFRESH performed but indicated NAA was not active', 213 | 0x09 : 'Command performed successfully, tone not played', 214 | 0x10 : 'Proactive UICC session terminated by the user', 215 | 0x11 : 'Backward move in the proactive UICC session requested by the user', 216 | 0x12 : 'No response from user', 217 | 0x13 : 'Help information required by the user', 218 | 0x14 : 'USSD or SS transaction terminated by the user', 219 | # Results '0X' and '1X' indicate that the command has been performed: 220 | 0x20 : 'terminal currently unable to process command', 221 | 0x21 : 'Network currently unable to process command', 222 | 0x22 : 'User did not accept the proactive command', 223 | 0x23 : 'User cleared down call before connection or network release', 224 | 0x24 : 'Action in contradiction with the current timer state', 225 | 0x25 : 'Interaction with call control by NAA, temporary problem', 226 | 0x26 : 'Launch browser generic error code', 227 | 0x27 : 'MMS temporary problem', 228 | # Results '2X' indicate to the UICC that 229 | # it may be worth re-trying the command at a later opportunity: 230 | 0x30 : 'Command beyond terminal\'s capabilities', 231 | 0x31 : 'Command type not understood by terminal', 232 | 0x32 : 'Command data not understood by terminal', 233 | 0x33 : 'Command number not known by terminal', 234 | 0x34 : 'SS Return Error', 235 | 0x35 : 'SMS RP-ERROR', 236 | 0x36 : 'Error, required values are missing', 237 | 0x37 : 'USSD Return Error', 238 | 0x38 : 'MultipleCard commands error', 239 | 0x39 : 'Interaction with call control by NAA, permanent problem', 240 | 0x3A : 'Bearer Independent Protocol error', 241 | 0x3B : 'Access Technology unable to process command', 242 | 0x3C : 'Frames error', 243 | 0x3D : 'MMS Error', 244 | # Results '3X' indicate that it is not worth 245 | # the UICC re-trying with an identical command, as it will only get the 246 | #same response. However, the decision to retry lies with the application. 247 | } 248 | -------------------------------------------------------------------------------- /sim_soft/sim_auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2015 Mikolaj Bartnicki 4 | # (c) 2015 Kamil Wartanowicz 5 | import logging 6 | """ 7 | USIM authentication algorithm. 8 | Names of Dummy XOR variables 9 | 10 | Variable | bits | name 11 | ====================================== 12 | key | 128 | Key Value (K) 13 | rand 128 Random Value (RAND) 14 | amf | 16 | AMF 15 | sqn | 48 | SQN 16 | 17 | ak | 48 | AK 18 | mac | 64 | MAC 19 | autn | 128 | AUTN 20 | 21 | res | 32-128| RES (XDOUT RES) 22 | ck | 128 | CK 23 | ik | 128 | IK 24 | kc | 128 | KC 25 | """ 26 | 27 | # masks used to cut lengths of values 28 | MASK_48_BIT = 0xFFFFFFFFFFFF 29 | MASK_64_BIT = 0xFFFFFFFFFFFFFFFF 30 | MASK_128_BIT = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 31 | 32 | AUTH_RESULT_OK = 0xDB 33 | AUTH_RESULT_SYNCHORNISATION_FAILURE = 0xDC 34 | 35 | def rotateLeft128bit(i, n): 36 | """Rotate left i by n bytes. 37 | The result value lenght is set to 128 bits.""" 38 | mask = (i >> (128 - n)) & MASK_128_BIT 39 | i = ((i << n) | mask) & MASK_128_BIT 40 | return i 41 | 42 | def dummyXor(key, rand, sqn, amf, autn): 43 | """Perform a Dummy XOR authentication algorithm.""" 44 | #http://cosconor.fr/GSM/Divers/Others/Cours/SIM%20Cards/GemXplore3G%20V2.pdf 45 | xdout = key ^ rand 46 | ck = rotateLeft128bit(xdout, 8) 47 | ik = rotateLeft128bit(xdout, 16) 48 | ak = (xdout >> 56) & MASK_48_BIT 49 | 50 | if autn: 51 | sqnFromAutn = (autn >> 80) ^ ak 52 | if sqnFromAutn != sqn: 53 | #TODO: implement sqn range check and EF_SQN update 54 | logging.info("Received sqn: %s, expected: %s" %(keyHex(sqnFromAutn), keyHex(sqn))) 55 | sqn = sqnFromAutn 56 | cdout = (sqn << 16) | amf 57 | xmac = (xdout >> 64) ^ cdout 58 | outAutn = ((sqn ^ ak) << 80) | (amf << 64) | xmac 59 | kc = (ik & MASK_64_BIT) ^ (ik >> 64) ^ (ck & MASK_64_BIT) ^ (ck >> 64) 60 | return {'res': xdout, 'autn' : outAutn, 'ck': ck, 'ik': ik, 'kc' : kc} 61 | 62 | def dummyXorHex(key, rand, sqn, amf, autn): 63 | key = int(key, 16) 64 | rand = int(rand, 16) 65 | sqn = int(sqn, 16) 66 | amf = int(amf, 16) 67 | if autn: 68 | autn = int(autn, 16) 69 | result = dummyXor(key, rand, sqn, amf, autn) 70 | for key in result: 71 | if key != 'kc': 72 | result[key] = keyHex(result[key]) 73 | else: 74 | result[key] = keyHex(result[key], length=8) 75 | return result 76 | 77 | def keyHex(key, length=16): 78 | length = 2 * length 79 | data = "%02X" %(key) 80 | currentlength = len(data) 81 | if currentlength < length: 82 | data = "%s%s" %("0" * (length-currentlength), data) 83 | return data 84 | 85 | def dummyXorData(rand, key, sqn, amf, autn=None): 86 | result = dummyXorHex(key, rand, sqn, amf, autn) 87 | if result['autn'] != autn: 88 | return [] 89 | data = "%02X" %AUTH_RESULT_OK 90 | data += "%02X%s" %(len(result['res'])/2, result['res']) 91 | data += "%02X%s" %(len(result['ck'])/2, result['ck']) 92 | data += "%02X%s" %(len(result['ik'])/2, result['ik']) 93 | #service 27 available in EF_UST 94 | data += "%02X%s" %(len(result['kc'])/2, result['kc']) 95 | return data 96 | 97 | def authenticateDummyXor(rand, key, sqn, amf, autn=None): 98 | return dummyXorData(rand, key, sqn, amf, autn) -------------------------------------------------------------------------------- /sim_soft/sim_soft_ctrl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2014 Kamil Wartanowicz 4 | 5 | import inspect 6 | import logging 7 | import os 8 | import sys 9 | import traceback 10 | 11 | import sim_xml 12 | import sim_soft 13 | import sat_ctrl 14 | 15 | from util import types_g 16 | from util import types 17 | 18 | class SoftCard(object): 19 | def __init__(self, simType=types.TYPE_USIM, file=None): 20 | self.simType = simType 21 | if not file: 22 | file = os.path.dirname(__file__) + "/sim_backup.xml" 23 | self.file = file 24 | 25 | def connect(self): 26 | self.init() 27 | 28 | def init(self): 29 | #TODO: check if already opened, then close might be needed 30 | self.simXml = sim_xml.SimXml(self.file) 31 | self.satCtrl = sat_ctrl.SatCtrl(types.TYPE_SIM, self.simXml) 32 | self.simHandler = sim_soft.SimHandler(self.simXml, self.satCtrl, self.simType) 33 | 34 | def getATR(self): 35 | self.init() 36 | return self.simXml.getAtr() 37 | 38 | def transmit(self, apdu): 39 | try: 40 | return self._transmit(apdu) 41 | except: 42 | exc_type, exc_value, exc_traceback = sys.exc_info() 43 | stackTrace = "".join(traceback.format_tb(exc_traceback)) 44 | error = exc_value 45 | raise Exception("%s\nError: %s" %(stackTrace, error)) 46 | 47 | def _transmit(self, apdu): 48 | sw = 0x9000 49 | data = [] 50 | 51 | channel = types.channel(apdu) 52 | if channel > types.MAX_LOGICAL_CHANNELS - 1: 53 | sw = types_g.sw.CLASS_NOT_SUPPORTED 54 | return data, sw 55 | 56 | # Check if the channel is open 57 | if self.simHandler.isChannelOpen(channel) == False: 58 | sw = types_g.sw.LOGICAL_CHANNEL_NOT_SUPPORTED 59 | return data, sw 60 | # TODO: open channel through SELECT command 61 | self.simHandler.setChannel(channel) 62 | 63 | ins = types.insName(apdu) 64 | if ins == "SELECT_FILE": 65 | data, sw = self.simHandler.select(apdu) 66 | elif ins == "GET_RESPONSE": 67 | data, sw = self.simHandler.getResponse(apdu) 68 | elif ins == "READ_BINARY": 69 | data, sw = self.simHandler.readBinary(apdu) 70 | elif ins == "VERIFY_PIN": 71 | data, sw = self.simHandler.verifyPin(apdu) 72 | elif ins == "UNBLOCK_PIN": 73 | data, sw = self.simHandler.unblockPin(apdu) 74 | elif ins == "CHANGE_PIN": 75 | data, sw = self.simHandler.changePin(apdu) 76 | elif ins == "ENABLE_PIN": 77 | data, sw = self.simHandler.enablePin(apdu) 78 | elif ins == "DISABLE_PIN": 79 | data, sw = self.simHandler.disablePin(apdu) 80 | elif ins == "STATUS": 81 | data, sw = self.simHandler.status(apdu) 82 | elif ins == "SEARCH_RECORD": 83 | data, sw = self.simHandler.searchRecord(apdu) 84 | elif ins == "READ_RECORD": 85 | data, sw = self.simHandler.readRecord(apdu) 86 | elif ins == "UPDATE_BINARY": 87 | data, sw = self.simHandler.updateBinary(apdu) 88 | elif ins == "UPDATE_RECORD": 89 | data, sw = self.simHandler.updateRecord(apdu) 90 | elif ins == "DEACTIVATE_FILE": 91 | data, sw = self.simHandler.deactivateFile(apdu) 92 | elif ins == "ACTIVATE_FILE": 93 | data, sw = self.simHandler.activateFile(apdu) 94 | elif ins == "TERMINAL_PROFILE": 95 | data, sw = self.satCtrl.terminalProfile(apdu) 96 | elif ins == "TERMINAL_RESPONSE": 97 | data, sw = self.satCtrl.terminalResponse(apdu) 98 | elif ins == "FETCH": 99 | data, sw = self.satCtrl.fetch(apdu) 100 | elif ins == "ENVELOPE": 101 | data, sw = self.satCtrl.envelope(apdu) 102 | elif ins == "MANAGE_CHANNEL": 103 | data, sw = self.simHandler.manageChannel(apdu) 104 | elif ins == 'INTERNAL_AUTHENTICATE': 105 | data, sw = self.simHandler.authenticate(apdu) 106 | elif ins == "CREATE_FILE": 107 | data, sw = self.simHandler.createFile(apdu) 108 | elif ins == "DELETE_FILE": 109 | data, sw = self.simHandler.deleteFile(apdu) 110 | elif ins == "RESIZE_FILE": 111 | data, sw = self.simHandler.resizeFile(apdu) 112 | elif ins == 'DE': 113 | data, sw = self.simHandler.unknownInstruction() 114 | else: 115 | raise Exception("Invalid instruction: %s" %ins) 116 | 117 | return data, sw 118 | 119 | class SoftReader(object): 120 | def __init__(self, name): 121 | self.name = name 122 | self.card = None 123 | 124 | def createConnection(self, type=types.TYPE_SIM): 125 | self.card = SoftCard(simType=type) 126 | return self.card 127 | 128 | reader1 = SoftReader('Soft SIM reader 0') 129 | reader2 = SoftReader('Soft SIM reader 1') 130 | 131 | READERS = [reader1, reader2] 132 | 133 | class Reader(object): 134 | def __init__(self): 135 | self.index = None 136 | self.reader = None 137 | self.card = None 138 | 139 | class SimSoftCtrl(object): 140 | def __init__(self, type=types.TYPE_USIM, logLevel=logging.INFO): 141 | dir = os.path.dirname(__file__) 142 | resultFile = dir + "/../sim_soft.log" 143 | FORMATTER = logging.Formatter(fmt='%(asctime)s %(message)s', datefmt='%H:%M:%S') 144 | fileHndl = logging.FileHandler(resultFile, mode='w') 145 | fileHndl.setFormatter(FORMATTER) 146 | fileHndl.setLevel(logLevel) 147 | logger = logging.getLogger("sim_soft") 148 | #dont't propagate to root logger 149 | logger.propagate=False 150 | logger.handlers = [] 151 | logger.setLevel(logLevel) 152 | logger.addHandler(fileHndl) 153 | self.logging = logger 154 | self.readers = [] 155 | self.simType = type 156 | 157 | def close(self): 158 | pass 159 | 160 | def getReader(self, index): 161 | for reader in self.readers: 162 | if reader.index == index: 163 | return reader 164 | return None 165 | 166 | def getReaderName(self, index): 167 | reader = self.getReader(index) 168 | if not reader: 169 | return None 170 | return reader.reader.name 171 | 172 | def getCard(self, index): 173 | self.checkReader(index) 174 | return self.getReader(index).card 175 | 176 | def checkReader(self, index): 177 | if not self.getReader(index): 178 | raise Exception("Reader with index=%d not created" %index) 179 | 180 | def checkCard(self, index): 181 | if not self.getCard(index): 182 | raise Exception("Card for reader with index=%d not created" %index) 183 | 184 | #exported methods 185 | def listReaders(self): 186 | self.logFunctionAndArgs() 187 | readers = READERS 188 | readersStr = [] 189 | for reader in readers: 190 | readersStr.append(reader.name) 191 | self.logReturnVal(readersStr=readersStr) 192 | return readersStr 193 | 194 | def addReader(self, index): 195 | self.logFunctionAndArgs() 196 | self.readers.append(Reader()) 197 | readersConnected = READERS 198 | if not len(readersConnected): 199 | raise Exception("No reader connected") 200 | if index >= len(readersConnected): 201 | raise Exception("Reader id:%d not connected, number of connected readers:%d" 202 | %(index, len(readersConnected))) 203 | self.readers[-1].index = index 204 | self.readers[-1].reader = readersConnected[index] 205 | self.logReturnVal() 206 | return None 207 | 208 | def removeReader(self, index): 209 | self.logFunctionAndArgs() 210 | self.checkReader(index) 211 | for reader in self.readers: 212 | if reader.index == index: 213 | del reader 214 | self.logReturnVal() 215 | return None 216 | 217 | def removeAllReaders(self): 218 | self.logFunctionAndArgs() 219 | for reader in self.readers: 220 | del reader 221 | self.logReturnVal() 222 | return None 223 | 224 | def r_createConnection(self, index): 225 | self.logFunctionAndArgs() 226 | self.checkReader(index) 227 | self.getReader(index).card = self.getReader(index).reader.createConnection(type=self.simType) 228 | return None 229 | 230 | def c_connect(self, index): 231 | self.logFunctionAndArgs() 232 | self.checkCard(index) 233 | self.getCard(index).connect() 234 | self.logReturnVal() 235 | return None 236 | 237 | def c_disconnect(self, index): 238 | self.logFunctionAndArgs() 239 | self.checkCard(index) 240 | self.getCard(index).disconnect() 241 | self.logReturnVal() 242 | return None 243 | 244 | def c_getATR(self, index): 245 | self.logFunctionAndArgs() 246 | self.checkCard(index) 247 | atr = self.getCard(index).getATR() 248 | self.logReturnVal(atr=atr) 249 | return atr 250 | 251 | def c_transmit(self, apdu, index): 252 | self.logFunctionAndArgs() 253 | self.checkCard(index) 254 | data, sw = self.getCard(index).transmit(apdu) 255 | sw1 = sw>>8 256 | sw2 = sw & 0x00FF 257 | self.logReturnVal(data=data, sw1=sw1, sw2=sw2) 258 | return data, sw1, sw2 259 | 260 | def logFunctionAndArgs(self): 261 | frame = inspect.getouterframes(inspect.currentframe())[1][0] 262 | args, _, _, values = inspect.getargvalues(frame) 263 | frameinfo = inspect.getframeinfo(frame) 264 | functionName=inspect.getframeinfo(frame)[2] 265 | output = "" 266 | for arg in args[1:]: #[1:] skip the first argument 'self' 267 | value = values[arg] 268 | if isinstance(value, str): 269 | #add apostrophes for string values 270 | value = "\'"+value+"\'" 271 | elif isinstance(value, int): 272 | value = ''.join('%02X' % value) 273 | else: 274 | newValue = "" 275 | for i in value: 276 | if isinstance(i, int): 277 | newValue += '%02X' % i 278 | else: 279 | newValue += str(i) 280 | value = newValue 281 | output += arg + '=' + value 282 | if arg != args[-1]: 283 | #add comma if not the last element 284 | output +=',' 285 | #do not print "\n' as a new line 286 | output = output.replace("\n","\\n") 287 | self.logging.info("--> "+functionName+'('+output+')') 288 | 289 | def logReturnVal(self, **kwargs): 290 | output = "" 291 | for key, value in kwargs.iteritems(): 292 | if isinstance(value, str): 293 | #add apostrophes for string values 294 | value = "\'"+value+"\'" 295 | elif isinstance(value, int): 296 | value = ''.join('%02X' % value) 297 | else: 298 | newValue = "" 299 | for i in value: 300 | if isinstance(i, int): 301 | newValue += '%02X' % i 302 | else: 303 | newValue += str(i) 304 | value = newValue 305 | output += key + ':' + value + ', ' 306 | output = output.rstrip(', ') #remove last comma and space 307 | self.logging.info("<-- "+output+'\n') 308 | -------------------------------------------------------------------------------- /simlab.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2015 Kamil Wartanowicz 4 | # (c) 2015 Szymon Mielczarek 5 | 6 | import os, sys 7 | import Tkinter as tk 8 | import subprocess as sub 9 | 10 | # Note: python-tk package needs to be installed on Linux machine 11 | 12 | RESOURCE_DIR = 'res' 13 | MIM_DIR = 'mim' 14 | FGCOLOR = '#FFFFFF' 15 | BGCOLOR = '#1F67B1' 16 | HIGHLIGHT_BGCOLOR = '#3E7BBC' 17 | TITLE_FONT = "Verdana 16" 18 | LABEL1_FONT = "Verdana 10 bold" 19 | LABEL2_FONT = "Verdana 7" 20 | 21 | MIM_OPTIONS = [ 22 | {'script': 'mim_live.py', 23 | 'text1': 'Live', 'text2': 'USIM Live', 'icon': "live.gif"}, 24 | {'script': 'mim_soft.py', 25 | 'text1': 'Soft', 'text2': 'USIM Soft with SAT', 'icon': "soft.gif"}, 26 | {'script': 'mim_live_live.py', 27 | 'text1': 'Live, Live', 'text2': 'USIM Live, USIM Live with AUTH', 'icon': "live_live.gif"}, 28 | {'script': 'mim_live_soft_sat.py', 29 | 'text1': 'Live, Soft SAT', 'text2': 'USIM Live, SAT Soft', 'icon': "live_soft.gif"}, 30 | {'script': 'mim_soft_live_reg.py', 31 | 'text1': 'Soft, Live', 'text2': 'USIM Soft with SAT, USIM Live with AUTH and REG', 'icon': "soft_live.gif"}, 32 | {'script': 'mim_soft_live_auth.py', 33 | 'text1': 'Soft, Live', 'text2': 'USIM Soft with SAT, USIM Live with AUTH ins', 'icon': "soft_live.gif"}, 34 | {'script': 'mim_live_live_soft_sat.py', 35 | 'text1': 'Live, Live, Soft SAT','text2': 'USIM Live, USIM Live, SAT Soft', 'icon': "live_live_soft.gif"} 36 | ] 37 | 38 | def runScript(event, root, scriptName): 39 | print scriptName 40 | root.destroy() # close Tk window and mainloop 41 | absPath = os.path.abspath(scriptName) 42 | sub.call(['python', absPath], shell=False) 43 | 44 | def retag(tag, *args): 45 | '''Add the given tag as the first bindtag for every widget passed in''' 46 | for widget in args: 47 | widget.bindtags((tag,) + widget.bindtags()) 48 | 49 | def on_enter(event, widgets, color): 50 | event.widget.configure(bg=color) 51 | for w in widgets: 52 | w.configure(bg=color) 53 | 54 | def on_leave(event, widgets, color): 55 | event.widget.configure(bg=color) 56 | for w in widgets: 57 | w.configure(bg=color) 58 | 59 | def main(): 60 | root = tk.Tk() 61 | 62 | lbl = tk.Label(root, text="Choose an option", fg=FGCOLOR, bg=BGCOLOR, font=TITLE_FONT) 63 | lbl.pack(pady=(10,0)) 64 | 65 | # Main frame 66 | fraMain = tk.Frame(root) 67 | fraMain.pack(padx=10, pady=10) 68 | 69 | optionsNum = len(MIM_OPTIONS) 70 | imgIcons = [None] * optionsNum 71 | lblImages = [None] * optionsNum 72 | lblText1 = [None] * optionsNum 73 | lblText2 = [None] * optionsNum 74 | fraFields = [None] * optionsNum 75 | i = 0 76 | for r in MIM_OPTIONS: 77 | fraFields[i] = tk.Frame(fraMain, bg=BGCOLOR) 78 | # Images 79 | imgIcons[i] = tk.PhotoImage(file=(os.path.join(RESOURCE_DIR, r['icon']))) 80 | lblImages[i] = tk.Label(fraFields[i], image=imgIcons[i], bg=BGCOLOR) 81 | lblImages[i].pack(side=tk.LEFT, padx=5, pady=5) 82 | # Labels 83 | lblText1[i] = tk.Label(fraFields[i], text=r['text1'], fg=FGCOLOR, bg=BGCOLOR, font=LABEL1_FONT) 84 | lblText1[i].pack(pady=(10,0), anchor=tk.W) #side=tk.LEFT, 85 | lblText2[i] = tk.Label(fraFields[i], text=r['text2'], fg=FGCOLOR, bg=BGCOLOR, font=LABEL2_FONT) 86 | lblText2[i].pack(anchor=tk.W, padx=(0,3)) #side=tk.BOTTOM, 87 | # Frames 88 | fraFields[i].bind("", lambda event, i=i: on_enter(event, [lblImages[i], lblText1[i], lblText2[i]], HIGHLIGHT_BGCOLOR)) 89 | fraFields[i].bind("", lambda event, i=i: on_leave(event, [lblImages[i], lblText1[i], lblText2[i]], BGCOLOR)) 90 | fraFields[i].pack(fill=tk.BOTH) 91 | 92 | name = r['script'] 93 | tag = "special" + str(i) 94 | retag(tag, fraFields[i], lblImages[i], lblText1[i], lblText2[i]) 95 | root.bind_class(tag, "", lambda event, name=name: runScript(event, root, os.path.join(MIM_DIR, name))) 96 | i = i + 1 97 | 98 | root.configure(bg=BGCOLOR) 99 | root.resizable(width=tk.FALSE, height=tk.FALSE) 100 | if os.name != 'posix': 101 | #workaround, do not load icon on unix 102 | try: 103 | root.iconbitmap(os.path.join(RESOURCE_DIR, 'live.ico')) 104 | except tk.TclError: 105 | print 'No ico file found' 106 | root.title("simLAB") 107 | root.mainloop() 108 | 109 | if __name__ == '__main__': 110 | main() 111 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/tests/__init__.py -------------------------------------------------------------------------------- /tests/live/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/tests/live/__init__.py -------------------------------------------------------------------------------- /tests/live/test_debug.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2014 Kamil Wartanowicz 4 | 5 | import sys,os.path 6 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) 7 | 8 | from sim import sim_router 9 | from sim import sim_card 10 | from util import hextools 11 | from util import types_g 12 | from util import types 13 | import unittest 14 | import logging 15 | import time 16 | 17 | from sim import sim_reader 18 | from sim import sim_ctrl_2g 19 | from sim import sim_ctrl_3g 20 | from sim import sim_shell 21 | 22 | MODE_SIM = sim_reader.MODE_PYSCARD 23 | SIM_TYPE = types.TYPE_USIM 24 | 25 | PIN_1 = "1111" 26 | PUK_1 = "11111111" 27 | 28 | PIN_1_FALSE = "1112" 29 | PUK_1_FALSE = "11111112" 30 | 31 | class TestDebug(unittest.TestCase): 32 | @classmethod 33 | def setUpClass(cls): 34 | cls.sim1 = sim_card.SimCard() 35 | cls.sim1.removeAllReaders() 36 | cls.sim1.connect(0) 37 | cls.sim2 = sim_card.SimCard(mode=sim_reader.MODE_SIM_SOFT, type=SIM_TYPE) 38 | try: 39 | cls.sim2.connect(0) 40 | except Exception as e: 41 | if "no card in reader" in str(e): 42 | cls.sim2.stop() 43 | raise Exception("No card in reader") 44 | cls.simRouter = sim_router.SimRouter(cards=[cls.sim1, cls.sim2], type=types.TYPE_USIM, mode=sim_router.SIMTRACE_OFFLINE) 45 | cls.simRouter.run(mode=sim_router.ROUTER_MODE_DISABLED) 46 | #update soft SIM for SAT handling 47 | cls.simRouter.getMainCard(1).routingAttr.insReplaced = sim_card.SAT_INS 48 | cls.sendApdu = cls.simRouter.simCtrl.sendApdu 49 | cls.shell = cls.simRouter.shell 50 | 51 | def test_1_send_apdu(self): 52 | #self.simCtrl.selectAid() 53 | sw1, sw2, data = self.sendApdu("801000001EFFFFFFFF7F9F00DFFF00001FE2000000C3" 54 | "FB000700016800710000000018", 55 | mode=sim_router.INJECT_WITH_FORWARD) 56 | sw1, sw2, data = self.sendApdu("8012000076", mode=sim_router.INJECT_WITH_FORWARD) 57 | sw1, sw2, data = self.sendApdu("801400000C810301250002028281830100", 58 | mode=sim_router.INJECT_WITH_FORWARD) 59 | sw1, sw2, data = self.sendApdu("80C2000009D30782020181900181", 60 | mode=sim_router.INJECT_WITH_FORWARD) 61 | sw1, sw2, data = self.sendApdu("8012000026", mode=sim_router.INJECT_WITH_FORWARD) 62 | sw1, sw2, data = self.sendApdu("80140000108103022300020282818301008D020430", 63 | mode=sim_router.INJECT_WITH_FORWARD) 64 | sw1, sw2, data = self.sendApdu("801200002A", mode=sim_router.INJECT_WITH_FORWARD) 65 | sw1, sw2, data = self.sendApdu("80140000148103022300020282818301008D060430303" 66 | "13031", mode=sim_router.INJECT_WITH_FORWARD) 67 | sw1, sw2, data = self.sendApdu("801200001F", mode=sim_router.INJECT_WITH_FORWARD) 68 | #wait for post action finish in sat_ctrl 69 | time.sleep(3) 70 | 71 | def test_2_send_apdu(self): 72 | #self.simCtrl.selectAid() 73 | sw1, sw2, data = self.sendApdu("801000001EFFFFFFFF7F9F00DFFF00001FE2000000C3FB" 74 | "000700016800710000000018", 75 | mode=sim_router.INJECT_WITH_FORWARD) 76 | sw1, sw2, data = self.sendApdu("8012000076", mode=sim_router.INJECT_WITH_FORWARD) 77 | sw1, sw2, data = self.sendApdu("80C2000009D30782020181900185", 78 | mode=sim_router.INJECT_WITH_FORWARD) 79 | sw1, sw2, data = self.sendApdu("801200000B", mode=sim_router.INJECT_WITH_FORWARD) 80 | sw1, sw2, data = self.sendApdu("8014000017810305260002028281030106130962F060002" 81 | "093E00022", mode=sim_router.INJECT_WITH_FORWARD) 82 | sw1, sw2, data = self.sendApdu("8014000017810305260002028281030106130962F0600020" 83 | "93E00022", mode=sim_router.INJECT_WITH_FORWARD) 84 | #wait for post action finish in sat_ctrl 85 | time.sleep(3) 86 | #uncomment when fixed 87 | #self.shell.cd(".") 88 | 89 | @classmethod 90 | def tearDownClass(cls): 91 | cls.sim1.stop() 92 | cls.sim2.stop() 93 | 94 | if __name__ == "__main__": 95 | logging.basicConfig(level=logging.INFO, format='%(message)s') 96 | unittest.main() 97 | -------------------------------------------------------------------------------- /tests/live/test_shell.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2015 Kamil Wartanowicz 4 | 5 | import sys,os.path 6 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) 7 | 8 | from sim import sim_router 9 | from sim import sim_card 10 | from sim import sim_shell 11 | from util import hextools 12 | from util import types_g 13 | from util import types 14 | import unittest 15 | import logging 16 | 17 | from sim import sim_reader 18 | from sim import sim_ctrl_2g 19 | from sim import sim_ctrl_3g 20 | 21 | MODE_SIM = sim_reader.MODE_PYSCARD 22 | SIM_TYPE = types.TYPE_USIM 23 | 24 | class TestSimShell(unittest.TestCase): 25 | @classmethod 26 | def setUpClass(cls): 27 | cls.simCard = sim_card.SimCard(mode=MODE_SIM) 28 | cls.simCard.removeAllReaders() 29 | try: 30 | cls.simCard.connect(0) 31 | except Exception as e: 32 | if "no card in reader" in str(e): 33 | cls.simCard.stop() 34 | raise Exception("No card in reader") 35 | cls.simRouter = sim_router.SimRouter(cards=[cls.simCard], type=SIM_TYPE, mode=sim_router.SIMTRACE_OFFLINE) 36 | cls.simRouter.run(mode=sim_router.ROUTER_MODE_DISABLED) 37 | cls.shell = cls.simRouter.shell 38 | 39 | def test_01_read_iccid(self): 40 | status, data = self.shell.read("/2FE2") 41 | self.shell.assertOk(status, data) 42 | value = types.getDataValue(data) 43 | valueLength = len(value) 44 | self.assertGreater(valueLength, 9, "Invalid ICCID length") 45 | 46 | def test_02_read_imsi(self): 47 | path = "/ADF0/EF_IMSI" 48 | logging.info("test path: %s" %path) 49 | status, data1 = self.shell.readi(path) 50 | self.shell.assertOk(status, data1) 51 | path = "/7F20/EF_IMSI" 52 | logging.info("test path: %s" %path) 53 | status, data2 = self.shell.readi(path) 54 | self.shell.assertOk(status, data2) 55 | status, data = self.shell.cd("/") 56 | self.shell.assertOk(status, data) 57 | path = "EF_IMSI" 58 | logging.info("test path: %s" %path) 59 | status, data3 = self.shell.readi(path) 60 | self.shell.assertOk(status, data3) 61 | path = "/ADF_USIM" 62 | logging.info("test path: %s" %path) 63 | status, data = self.shell.cd(path) 64 | self.shell.assertOk(status, data) 65 | path = "./6F07" 66 | logging.info("test path: %s" %path) 67 | status, data4 = self.shell.readi(path) 68 | self.shell.assertOk(status, data4) 69 | self.assertEqual(data1, data2) 70 | self.assertEqual(data1, data3) 71 | self.assertEqual(data1, data4) 72 | 73 | def test_03_read_imsi_raw(self): 74 | status, data1 = self.shell.read("/ADF0/EF_IMSI") 75 | self.shell.assertOk(status, data1) 76 | status, data = self.shell.cd("/") 77 | self.shell.assertOk(status, data) 78 | status, data2 = self.shell.read("EF_IMSI") 79 | self.shell.assertOk(status, data2) 80 | imsi1 = types.getDataValue(data1) 81 | imsi2 = types.getDataValue(data2) 82 | self.assertEqual(imsi1, imsi2) 83 | imsiRawLength = len(imsi1) 84 | self.assertGreater(imsiRawLength, 14+3, "Invalid imsi raw length") 85 | 86 | def test_04_get_plmn(self): 87 | status, data1 = self.shell.get_plmn() 88 | self.shell.assertOk(status, data1) 89 | self.assertGreaterEqual(len(data1), 5*2) 90 | self.assertLessEqual(len(data1), 6*2) 91 | status, data2 = self.shell.readi("EF_IMSI") 92 | self.shell.assertOk(status, data2) 93 | self.assertTrue(data1 in data2) 94 | 95 | def test_05_read_arr(self): 96 | status, data = self.shell.read("/2F06") 97 | self.shell.assertOk(status, data) 98 | 99 | def test_06_create_file(self): 100 | # Create DF and EF using absolute paths 101 | dirPath = "/DEAD/" 102 | try: 103 | self.shell.delete(dirPath) 104 | except: 105 | pass 106 | status, out = self.shell.create(dirPath) 107 | self.shell.assertOk(status, out) 108 | status, out = self.shell.delete(dirPath) 109 | self.shell.assertOk(status, out) 110 | 111 | dirPath = "/ADF0/DEAD/" 112 | filePath = "/ADF0/DEAD/BEEF" 113 | try: 114 | self.shell.delete(dirPath) 115 | except: 116 | pass 117 | status, out = self.shell.create(dirPath) 118 | self.shell.assertOk(status, out) 119 | status, out = self.shell.create(filePath) 120 | self.shell.assertOk(status, out) 121 | status, out = self.shell.delete(filePath) 122 | self.shell.assertOk(status, out) 123 | status, out = self.shell.delete(dirPath) 124 | self.shell.assertOk(status, out) 125 | 126 | def test_07_create_file_relative(self): 127 | # Create DF and EF using relative paths 128 | dirPath = "./DEAD/" 129 | filePath = "./BEEF" 130 | dirPath2 = "./DEAF/" 131 | try: 132 | self.shell.delete(dirPath) 133 | except: 134 | pass 135 | status, out = self.shell.create(dirPath) 136 | self.shell.assertOk(status, out) 137 | status, out = self.shell.create(filePath) 138 | self.shell.assertOk(status, out) 139 | status, out = self.shell.create(dirPath2) 140 | self.shell.assertOk(status, out) 141 | status, out = self.shell.create(filePath) 142 | self.shell.assertOk(status, out) 143 | 144 | status, out = self.shell.delete(dirPath2) 145 | self.shell.assertOk(status, out) 146 | status, out = self.shell.delete(dirPath) 147 | self.shell.assertOk(status, out) 148 | 149 | def test_08_create_adf(self): 150 | # Get number of EF_DIR records 151 | status, data = self.shell.read("/2F00") 152 | self.shell.assertOk(status, data) 153 | numOfRecords = len(data.split(';')) - 1 154 | # Use the next free Id 155 | dirPath = "/ADF%d/" % numOfRecords 156 | try: 157 | self.shell.delete(dirPath) 158 | except: 159 | pass 160 | status, out = self.shell.create(dirPath) 161 | if status == "status NOK": 162 | raise unittest.SkipTest( 163 | """Known issue: ADF creation doesn't work for some SIM cards 164 | (INCORRECT_PARAMETER_IN_DATA_FIELD is returned)""") 165 | #self.shell.assertOk(status, out) 166 | status, out = self.shell.delete(dirPath) 167 | self.shell.assertOk(status, out) 168 | 169 | def test_09_pwd(self): 170 | dirPath = "/7F10/5F3A" 171 | name = "DF_PHONEBOOK" 172 | status, out = self.shell.cd(dirPath) 173 | self.shell.assertOk(status, out) 174 | status, out = self.shell.pwd() 175 | self.shell.assertOk(status, out) 176 | path = types.getDataValue(out) 177 | #compare to directory 178 | self.assertEqual(path, "path=%s/,name=%s,simId=0" %(dirPath, name)) 179 | 180 | def test_10_ls(self): 181 | dirPath = "/" 182 | status, out = self.shell.cd(dirPath) 183 | self.shell.assertOk(status, out) 184 | status, out = self.shell.ls() 185 | self.shell.assertOk(status, out) 186 | files = types.getDataValue(out) 187 | self.assertTrue(files) 188 | dirPath = "/7F10" 189 | status, out = self.shell.cd(dirPath) 190 | self.shell.assertOk(status, out) 191 | status, out = self.shell.ls() 192 | self.shell.assertOk(status, out) 193 | files = types.getDataValue(out) 194 | self.assertTrue("5F3A/" in files, "Files: %s" %files) 195 | 196 | def test_11_resize(self): 197 | filePath = "/ADF0/DEAD/BEEF" 198 | parentPath = types.parentDirFromPath(filePath) + '/' 199 | # Cleanup 200 | try: 201 | self.shell.delete(parentPath) 202 | except: 203 | pass 204 | # Create temporary dir and file (linear) 205 | fileType = types_g.fileDescriptor.LINEAR_FIXED_STRUCTURE 206 | fileSize = 0x30 207 | recordLength = 0x10 208 | status, out = self.shell.create(filePath, 209 | "fileType=%X,fileSize=%X,recordLength=%X" \ 210 | % (fileType, fileSize, recordLength)) 211 | self.shell.assertOk(status, out) 212 | # Increase the size of the file (by 2 new records) with a pattern 213 | newFileSize = fileSize + recordLength * 2 214 | pattern = types.addTrailingBytes('', 0xA5, recordLength-4) # not the whole record length 215 | status, out = self.shell.resize(filePath, hex(newFileSize), pattern) 216 | self.shell.assertOk(status, out) 217 | # Check the data after resize 218 | status, data = self.shell.read(filePath) 219 | self.shell.assertOk(status, data) 220 | value = types.getDataValue(data).replace(';', '') 221 | self.assertEqual(len(value)/2, newFileSize) 222 | # Decrease the size of the file to one record 223 | status, out = self.shell.resize(filePath, hex(recordLength)) 224 | self.shell.assertOk(status, out) 225 | 226 | status, out = self.shell.delete(parentPath) 227 | self.shell.assertOk(status, out) 228 | 229 | @unittest.skip("The EXTEND command is probably only supported by Gemalto") 230 | def test_12_extend(self): 231 | filePath = "/ADF0/DEAD/BEEF" 232 | parentPath = types.parentDirFromPath(filePath) + '/' 233 | # Cleanup 234 | try: 235 | self.shell.delete(parentPath) 236 | except: 237 | pass 238 | # Create temporary dir and file (linear) 239 | fileType = types_g.fileDescriptor.LINEAR_FIXED_STRUCTURE 240 | fileSize = 0x30 241 | recordLength = 0x10 242 | status, out = self.shell.create(filePath, 243 | "fileType=%X,fileSize=%X,recordLength=%X" \ 244 | % (fileType, fileSize, recordLength)) 245 | self.shell.assertOk(status, out) 246 | # Increase the size of the file (by 2 new records) with a pattern 247 | numOfRecordsToExtend = 2 248 | status, out = self.shell.extend(filePath, numOfRecordsToExtend) 249 | self.shell.assertOk(status, out) 250 | # Check the data after extension 251 | status, data = self.shell.read(filePath) 252 | self.shell.assertOk(status, data) 253 | value = types.getDataValue(data).replace(';', '') 254 | self.assertEqual(len(value)/2, fileSize + numOfRecordsToExtend * recordLength) 255 | 256 | status, out = self.shell.delete(parentPath) 257 | self.shell.assertOk(status, out) 258 | 259 | @classmethod 260 | def tearDownClass(cls): 261 | cls.simCard.stop() 262 | 263 | if __name__ == "__main__": 264 | logging.basicConfig(level=logging.INFO, format='%(message)s') 265 | unittest.main() -------------------------------------------------------------------------------- /tests/live/test_sim.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2014 Kamil Wartanowicz 4 | 5 | import sys,os.path 6 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) 7 | 8 | from sim import sim_router 9 | from sim import sim_card 10 | from util import hextools 11 | from util import types_g 12 | from util import types 13 | import unittest 14 | import logging 15 | 16 | from sim import sim_reader 17 | from sim import sim_ctrl_2g 18 | from sim import sim_ctrl_3g 19 | 20 | MODE_SIM = sim_reader.MODE_PYSCARD 21 | 22 | PIN_1 = "1111" 23 | PUK_1 = "11111111" 24 | 25 | PIN_1_FALSE = "1112" 26 | PUK_1_FALSE = "11111112" 27 | 28 | class TestSim(unittest.TestCase): 29 | @classmethod 30 | def setUpClass(cls): 31 | cls.simCard = sim_card.SimCard(mode=MODE_SIM, type=types.TYPE_USIM) 32 | cls.simCard.removeAllReaders() 33 | try: 34 | cls.simCard.connect(0) 35 | except Exception as e: 36 | if "no card in reader" in str(e): 37 | cls.simCard.stop() 38 | raise Exception("No card in reader") 39 | cls.simRouter = sim_router.SimRouter(cards=[cls.simCard], type=types.TYPE_USIM, mode=sim_router.SIMTRACE_OFFLINE) 40 | cls.simRouter.run(mode=sim_router.ROUTER_MODE_DISABLED) 41 | cls.simCtrl = cls.simRouter.simCtrl 42 | cls.sendApdu = cls.simRouter.simCtrl.sendApdu 43 | 44 | def test_1_getAtr(self): 45 | atr = self.simCard.getATR() 46 | logging.info("ATR: %s" %hextools.bytes2hex(atr)) 47 | self.assertGreater(len(atr), 4) 48 | 49 | def test_2_select_file(self): 50 | #SELECT_AID 51 | self.simCtrl.selectAid() 52 | #SELECT_FILE MF 53 | sw1, sw2, data = self.sendApdu("00A40004023F00") 54 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_3G', raiseException=True) 55 | self.assertGreater(sw2, 2) 56 | length = sw2 57 | #GET_RESPONSE 58 | sw1, sw2, data = self.sendApdu("00C00000%02X" %length) 59 | 60 | #SELECT_FILE EF_ICCID 61 | sw1, sw2, data = self.sendApdu("00A40004022FE2") 62 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_3G', raiseException=True) 63 | length = sw2 64 | #GET_RESPONSE 65 | sw1, sw2, data = self.sendApdu("00C00000%02X" %length) 66 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 67 | 68 | #SELECT_FILE EF_ICCID by path from MF 69 | sw1, sw2, data = self.sendApdu("00A40804022FE2") 70 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_3G', raiseException=True) 71 | length = sw2 72 | #GET_RESPONSE 73 | sw1, sw2, data = self.sendApdu("00C00000%02X" %length) 74 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 75 | 76 | #SELECT_FILE EF_IMSI by path from MF 77 | sw1, sw2, data = self.sendApdu("00A40804047FFF6F07") 78 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_3G', raiseException=True) 79 | length = sw2 80 | #GET_RESPONSE 81 | sw1, sw2, data = self.sendApdu("00C00000%02X" %length) 82 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 83 | 84 | def test_3_enable_pin(self): 85 | enabled = self.simCtrl.pin1Enabled() 86 | attemptsLeft = self.simCtrl.pin1Status() 87 | self.assertTrue(attemptsLeft, "PUK needed") 88 | 89 | if enabled: 90 | #disable pin 91 | sw1, sw2, data = self.sendApdu("0026000108%sFFFFFFFF" %PIN_1.encode("hex")) 92 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 93 | 94 | enabled = self.simCtrl.pin1Enabled() 95 | attemptsLeft = self.simCtrl.pin1Status() 96 | self.assertFalse(enabled) 97 | self.assertTrue(attemptsLeft, "No attempts left. PUK needed") 98 | 99 | #enable pin 100 | sw1, sw2, data = self.sendApdu("0028000108%sFFFFFFFF" %PIN_1.encode("hex")) 101 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 102 | enabled = self.simCtrl.pin1Enabled() 103 | attemptsLeft = self.simCtrl.pin1Status() 104 | self.assertTrue(enabled) 105 | self.assertTrue(attemptsLeft, "No attempts left. PUK needed") 106 | 107 | @unittest.skip("It's risky to block PIN1 on live SIM") 108 | def test_4_unblock_pin(self): 109 | enabled = self.simCtrl.pin1Enabled() 110 | attemptsLeft = self.simCtrl.pin1Status() 111 | self.assertTrue(enabled) 112 | self.assertTrue(attemptsLeft, "No attempts left. PUK needed") 113 | 114 | #VERIFY 115 | sw1, sw2, data = self.sendApdu("0020000108%sFFFFFFFF" %PIN_1.encode("hex")) 116 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 117 | enabled = self.simCtrl.pin1Enabled() 118 | attemptsLeft = self.simCtrl.pin1Status() 119 | self.assertTrue(enabled) 120 | 121 | #block PIN 122 | for i in range(attemptsLeft): 123 | sw1, sw2, data = self.sendApdu("0020000108%sFFFFFFFF" %PIN_1_FALSE.encode("hex")) 124 | _attemptsLeft = attemptsLeft - i - 1 125 | types.assertSw(sw1, sw2, checkSw1='CODE_ATTEMPTS_LEFT', raiseException=True) 126 | left = self.simCtrl.pin1Status() 127 | self.assertEqual(left, _attemptsLeft) 128 | 129 | #unblock PIN 130 | enabled = self.simCtrl.pin1Enabled() 131 | attemptsLeft = self.simCtrl.pin1Status() 132 | self.assertFalse(attemptsLeft, "PIN is not blocked") 133 | 134 | attemptsLeft = self.simCtrl.pin1UnblockStatus() 135 | self.assertTrue(attemptsLeft>=10, "PUK1 attempts left: %d" %attemptsLeft) 136 | sw1, sw2, data = self.sendApdu("002C000110%s%sFFFFFFFF" %(PUK_1.encode("hex"), PIN_1.encode("hex"))) 137 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 138 | 139 | def test_5_read_imsi(self): 140 | enabled = self.simCtrl.pin1Enabled() 141 | attemptsLeft = self.simCtrl.pin1Status() 142 | self.assertTrue(enabled) 143 | self.assertTrue(attemptsLeft, "PUK needed") 144 | 145 | self.simCtrl.selectAid() 146 | #SELECT_FILE IMSI 147 | sw1, sw2, data = self.sendApdu("00A40004026F07") 148 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_3G', raiseException=True) 149 | length = sw2 150 | #GET_RESPONSE 151 | sw1, sw2, data = self.sendApdu("00C00000%02X" %length) 152 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 153 | 154 | #READ BINARY 155 | tagData = types.parseFcpTlv(data, types.FILE_LENGTH_EXCLUDING_SI_TAG) 156 | if tagData == None: 157 | raise Exception("BINARY_LENGTH_TAG not found in FCI") 158 | imsiLength = tagData[1] 159 | sw1, sw2, data = self.sendApdu("00B00000%02X" %imsiLength) 160 | if types_g.sw[(sw1<<8) + sw2] == 'SECURITY_STATUS_NOT_SATISFIED': 161 | #VERIFY 162 | sw1, sw2, data = self.sendApdu("0020000108%sFFFFFFFF" %PIN_1.encode("hex")) 163 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 164 | #repeat READ BINARY 165 | sw1, sw2, data = self.sendApdu("00B00000%02X" %imsiLength) 166 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 167 | imsi = hextools.decode_BCD(data)[3:] 168 | logging.info("IMSI: %s" %imsi) 169 | 170 | def test_6_manage_channel(self): 171 | originChannel = 0 172 | 173 | """ Open first free channel """ 174 | # MANAGE CHANNEL: OPEN (first free) 175 | sw1, sw2, data = self.sendApdu("0%01X7000%02X01" %(originChannel, 0)) 176 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 177 | targetChannel = data[0] # 2 178 | # MANAGE CHANNEL: CLOSE 179 | sw1, sw2, data = self.sendApdu("0%01X7080%02X00" %(originChannel, targetChannel)) 180 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 181 | 182 | """ Select on not open channel (when targetChannel = 0) """ 183 | #SELECT_FILE MF 184 | sw1, sw2, data = self.sendApdu("0%01XA40004023F00" %targetChannel) 185 | # Two possible responses 186 | if types.assertSw(sw1, sw2, checkSw1='WRONG_INSTRUCTION_CLASS', raiseException=False): 187 | types.assertSw(sw1, sw2, checkSw='LOGICAL_CHANNEL_NOT_SUPPORTED', raiseException=True) 188 | 189 | """ Open, Select and Close channel """ 190 | # MANAGE CHANNEL: OPEN (chosen) 191 | sw1, sw2, data = self.sendApdu("0%01X7000%02X00" %(originChannel, targetChannel)) 192 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 193 | 194 | #SELECT_AID (ADF_USIM) 195 | # Select some AID (applet) on a specific channel. 196 | aid = self.simCtrl.getAid() 197 | apdu = "0%01XA40404%02X%s" %(targetChannel, len(aid)/2, aid) 198 | sw1, sw2, data = self.sendApdu(apdu) 199 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_3G', raiseException=True) 200 | #SELECT_FILE MF 201 | sw1, sw2, data = self.sendApdu("0%01XA40004023F00" %targetChannel) 202 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_3G', raiseException=True) 203 | self.assertGreater(sw2, 2) 204 | # MANAGE CHANNEL: CLOSE 205 | sw1, sw2, data = self.sendApdu("0%01X7080%02X00" %(originChannel, targetChannel)) 206 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 207 | 208 | """ Open first free channel (when targetChannel = 0) """ 209 | # MANAGE CHANNEL: OPEN (first free) 210 | sw1, sw2, data = self.sendApdu("0%01X7000%02X01" %(originChannel, 0)) 211 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 212 | self.assertEqual(data[0], targetChannel) 213 | 214 | originChannel = data[0] # 1 215 | # MANAGE CHANNEL: OPEN (non-basic origin channel) 216 | sw1, sw2, data = self.sendApdu("0%01X7000%02X01" %(originChannel, 0)) 217 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 218 | self.assertEqual(data[0], originChannel + 1) 219 | targetChannel = data[0] # 2 220 | 221 | """ Select MF on Non-Basic channel """ 222 | #SELECT_AID (ADF_USIM) 223 | apdu = "0%01XA40404%02X%s" %(targetChannel, len(aid)/2, aid) 224 | sw1, sw2, data = self.sendApdu(apdu) 225 | #SELECT_FILE MF 226 | sw1, sw2, data = self.sendApdu("0%01XA40004023F00" %targetChannel) 227 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_3G', raiseException=True) 228 | self.assertGreater(sw2, 2) 229 | """ Close Non-Basic channel """ 230 | # MANAGE CHANNEL: CLOSE 231 | sw1, sw2, data = self.sendApdu("0%01X7080%02X00" %(originChannel, targetChannel)) 232 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 233 | 234 | """ Select IMSI file on Basic channel """ 235 | #SELECT_AID (ADF_USIM) 236 | #self.simCtrl.selectAid() 237 | apdu = "00A40404%02X%s" %(len(aid)/2, aid) 238 | sw1, sw2, data = self.sendApdu(apdu) 239 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_3G', raiseException=True) 240 | #SELECT_FILE IMSI 241 | sw1, sw2, data = self.sendApdu("00A40004026F07") 242 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_3G', raiseException=True) 243 | length = sw2 244 | #GET_RESPONSE 245 | sw1, sw2, data = self.sendApdu("00C00000%02X" %length) 246 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 247 | tagData = types.parseFcpTlv(data, types.FILE_LENGTH_EXCLUDING_SI_TAG) 248 | if tagData == None: 249 | raise Exception("BINARY_LENGTH_TAG not found in FCI") 250 | imsiLength = tagData[1] 251 | 252 | """ Read IMSI file on Basic channel """ 253 | #READ BINARY 254 | sw1, sw2, data = self.sendApdu("00B00000%02X" %imsiLength) 255 | if types_g.sw[(sw1<<8) + sw2] == 'SECURITY_STATUS_NOT_SATISFIED': 256 | #VERIFY 257 | sw1, sw2, data = self.sendApdu("0020000108%sFFFFFFFF" %PIN_1.encode("hex")) 258 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 259 | #repeat READ BINARY 260 | sw1, sw2, data = self.sendApdu("00B00000%02X" %imsiLength) 261 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 262 | imsi = hextools.decode_BCD(data)[3:] 263 | logging.info("IMSI: %s" %imsi) 264 | 265 | def tearDown(self): 266 | self.simCard.reset() 267 | 268 | @classmethod 269 | def tearDownClass(cls): 270 | cls.simCard.stop() 271 | 272 | if __name__ == "__main__": 273 | logging.basicConfig(level=logging.INFO, format='%(message)s') 274 | unittest.main() -------------------------------------------------------------------------------- /tests/live/test_sim_2g.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2014 Kamil Wartanowicz 4 | 5 | import sys,os.path 6 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) 7 | 8 | from sim import sim_router 9 | from sim import sim_card 10 | from util import hextools 11 | from util import types_g 12 | from util import types 13 | import unittest 14 | import logging 15 | 16 | from sim import sim_reader 17 | from sim import sim_ctrl_2g 18 | from sim import sim_ctrl_3g 19 | 20 | MODE_SIM = sim_reader.MODE_PYSCARD 21 | SIM_TYPE = types.TYPE_SIM 22 | 23 | PIN_1 = "1111" 24 | PUK_1 = "11111111" 25 | 26 | PIN_1_FALSE = "1112" 27 | PUK_1_FALSE = "11111112" 28 | 29 | class TestSim(unittest.TestCase): 30 | @classmethod 31 | def setUpClass(cls): 32 | cls.simCard = sim_card.SimCard(mode=MODE_SIM, type=SIM_TYPE) 33 | cls.simCard.removeAllReaders() 34 | try: 35 | cls.simCard.connect(0) 36 | except Exception as e: 37 | if "no card in reader" in str(e): 38 | cls.simCard.stop() 39 | raise Exception("No card in reader") 40 | cls.simRouter = sim_router.SimRouter(cards=[cls.simCard], type=SIM_TYPE, mode=sim_router.SIMTRACE_OFFLINE) 41 | cls.simRouter.run(mode=sim_router.ROUTER_MODE_DISABLED) 42 | cls.simCtrl = cls.simRouter.simCtrl 43 | cls.sendApdu = cls.simRouter.simCtrl.sendApdu 44 | 45 | def test_1_getAtr(self): 46 | atr = self.simCard.getATR() 47 | logging.info("ATR: %s" %hextools.bytes2hex(atr)) 48 | self.assertGreater(len(atr), 4) 49 | 50 | def test_2_select_file(self): 51 | #SELECT_FILE DF_GSM 52 | sw1, sw2, data = self.sendApdu("A0A40000027F20") 53 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True) 54 | length = sw2 55 | #GET_RESPONSE 56 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length) 57 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 58 | 59 | #SELECT_FILE MF 60 | sw1, sw2, data = self.sendApdu("A0A40000023F00") 61 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True) 62 | self.assertGreater(sw2, 2) 63 | length = sw2 64 | #GET_RESPONSE 65 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length) 66 | 67 | #SELECT_FILE EF_ICCID 68 | sw1, sw2, data = self.sendApdu("A0A40000022FE2") 69 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True) 70 | length = sw2 71 | #GET_RESPONSE 72 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length) 73 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 74 | 75 | def test_3_enable_pin(self): 76 | enabled = self.simCtrl.pin1Enabled() 77 | attemptsLeft = self.simCtrl.pin1Status() 78 | self.assertTrue(attemptsLeft, "PUK needed") 79 | 80 | if enabled: 81 | #disable pin 82 | sw1, sw2, data = self.sendApdu("A026000108%sFFFFFFFF" %PIN_1.encode("hex")) 83 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 84 | 85 | enabled = self.simCtrl.pin1Enabled() 86 | attemptsLeft = self.simCtrl.pin1Status() 87 | self.assertFalse(enabled) 88 | self.assertTrue(attemptsLeft, "No attempts left. PUK needed") 89 | 90 | #enable pin 91 | sw1, sw2, data = self.sendApdu("A028000108%sFFFFFFFF" %PIN_1.encode("hex")) 92 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 93 | enabled = self.simCtrl.pin1Enabled() 94 | attemptsLeft = self.simCtrl.pin1Status() 95 | self.assertTrue(enabled) 96 | self.assertTrue(attemptsLeft, "No attempts left. PUK needed") 97 | 98 | @unittest.skip("It's risky to block PIN1 on live SIM") 99 | def test_4_unblock_pin(self): 100 | enabled = self.simCtrl.pin1Enabled() 101 | attemptsLeft = self.simCtrl.pin1Status() 102 | self.assertTrue(enabled) 103 | self.assertTrue(attemptsLeft, "No attempts left. PUK needed") 104 | 105 | #VERIFY 106 | sw1, sw2, data = self.sendApdu("A020000108%sFFFFFFFF" %PIN_1.encode("hex")) 107 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 108 | enabled = self.simCtrl.pin1Enabled() 109 | attemptsLeft = self.simCtrl.pin1Status() 110 | self.assertTrue(enabled) 111 | 112 | #block PIN 113 | for i in range(attemptsLeft): 114 | sw1, sw2, data = self.sendApdu("A020000108%sFFFFFFFF" %PIN_1_FALSE.encode("hex")) 115 | 116 | _attemptsLeft = attemptsLeft - i - 1 117 | if _attemptsLeft: 118 | types.assertSw(sw1, sw2, checkSw='GSM_ACCESS_CONDITION_NOT_FULFILLED', raiseException=True) 119 | else: 120 | types.assertSw(sw1, sw2, checkSw='GSM_UNSUCCESSFUL_USER_PIN_VERIFICATION', raiseException=True) 121 | left = self.simCtrl.pin1Status() 122 | self.assertEqual(left, _attemptsLeft) 123 | 124 | #unblock PIN 125 | enabled = self.simCtrl.pin1Enabled() 126 | attemptsLeft = self.simCtrl.pin1Status() 127 | self.assertFalse(attemptsLeft, "PIN is not blocked") 128 | 129 | attemptsLeft = self.simCtrl.pin1UnblockStatus() 130 | self.assertTrue(attemptsLeft>=10, "PUK1 attempts left: %d" %attemptsLeft) 131 | sw1, sw2, data = self.sendApdu("A02C000010%s%sFFFFFFFF" %(PUK_1.encode("hex"), PIN_1.encode("hex"))) 132 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 133 | 134 | def test_5_read_imsi(self): 135 | enabled = self.simCtrl.pin1Enabled() 136 | attemptsLeft = self.simCtrl.pin1Status() 137 | self.assertTrue(enabled) 138 | self.assertTrue(attemptsLeft, "PUK needed") 139 | 140 | #SELECT_FILE DF_GSM 141 | sw1, sw2, data = self.sendApdu("A0A40000027F20") 142 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True) 143 | length = sw2 144 | #GET_RESPONSE 145 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length) 146 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 147 | 148 | #SELECT_FILE IMSI 149 | sw1, sw2, data = self.sendApdu("A0A40000026F07") 150 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True) 151 | length = sw2 152 | #GET_RESPONSE 153 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length) 154 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 155 | 156 | #READ BINARY 157 | length = data[3] 158 | sw1, sw2, data = self.sendApdu("A0B00000%02X" %length) 159 | if types_g.sw[(sw1<<8) + sw2] == 'GSM_ACCESS_CONDITION_NOT_FULFILLED': 160 | #VERIFY 161 | sw1, sw2, data = self.sendApdu("A020000108%sFFFFFFFF" %PIN_1.encode("hex")) 162 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 163 | #repeat READ BINARY 164 | sw1, sw2, data = self.sendApdu("A0B00000%02X" %length) 165 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 166 | imsi = hextools.decode_BCD(data)[3:] 167 | logging.info("IMSI: %s" %imsi) 168 | 169 | @unittest.skip("Might be not supported on 2G SIM") 170 | def test_6_manage_channel(self): 171 | originChannel = 0 172 | targetChannel = 2 173 | 174 | """ Select on not open channel """ 175 | #SELECT_FILE MF 176 | sw1, sw2, data = self.sendApdu("A%01XA40000023F00" %targetChannel) 177 | types.assertSw(sw1, sw2, checkSw='LOGICAL_CHANNEL_NOT_SUPPORTED', raiseException=True) 178 | 179 | """ Open, Select and Close channel """ 180 | # MANAGE CHANNEL: OPEN (chosen) 181 | sw1, sw2, data = self.sendApdu("A%01X7000%02X00" %(originChannel, targetChannel)) 182 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 183 | #SELECT_FILE MF 184 | sw1, sw2, data = self.sendApdu("A%01XA40000023F00" %targetChannel) 185 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True) 186 | self.assertGreater(sw2, 2) 187 | # MANAGE CHANNEL: CLOSE 188 | sw1, sw2, data = self.sendApdu("A%01X7080%02X00" %(originChannel, targetChannel)) 189 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 190 | 191 | """ Open first free channel (when targetChannel = 0) """ 192 | # MANAGE CHANNEL: OPEN (first free) 193 | sw1, sw2, data = self.sendApdu("A%01X7000%02X01" %(originChannel, 0)) 194 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 195 | self.assertEqual(data[0], 1) 196 | 197 | originChannel = data[0] # 1 198 | # MANAGE CHANNEL: OPEN (non-basic origin channel) 199 | sw1, sw2, data = self.sendApdu("A%01X7000%02X01" %(originChannel, 0)) 200 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 201 | self.assertEqual(data[0], originChannel + 1) 202 | targetChannel = data[0] # 2 203 | 204 | """ Select IMSI file on Basic channel """ 205 | #SELECT_FILE DF_GSM 206 | sw1, sw2, data = self.sendApdu("A0A40000027F20") 207 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True) 208 | #SELECT_FILE IMSI 209 | sw1, sw2, data = self.sendApdu("A0A40000026F07") 210 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True) 211 | length = sw2 212 | #GET_RESPONSE 213 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length) 214 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 215 | imsiLen = data[3] 216 | """ Select MF on Non-Basic channel """ 217 | #SELECT_FILE MF 218 | sw1, sw2, data = self.sendApdu("A%01XA40000023F00" %targetChannel) 219 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True) 220 | self.assertGreater(sw2, 2) 221 | """ Read IMSI file on Basic channel """ 222 | #READ BINARY 223 | sw1, sw2, data = self.sendApdu("A0B00000%02X" %imsiLen) 224 | if types_g.sw[(sw1<<8) + sw2] == 'GSM_ACCESS_CONDITION_NOT_FULFILLED': 225 | #VERIFY 226 | sw1, sw2, data = self.sendApdu("A020000108%sFFFFFFFF" %PIN_1.encode("hex")) 227 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 228 | #repeat READ BINARY 229 | sw1, sw2, data = self.sendApdu("A0B00000%02X" %length) 230 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 231 | imsi = hextools.decode_BCD(data)[3:] 232 | logging.info("IMSI: %s" %imsi) 233 | 234 | """ Close Non-Basic channel """ 235 | # MANAGE CHANNEL: CLOSE 236 | sw1, sw2, data = self.sendApdu("A%01X7080%02X00" %(originChannel, targetChannel)) 237 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 238 | 239 | def tearDown(self): 240 | self.simCard.reset() 241 | 242 | @classmethod 243 | def tearDownClass(cls): 244 | cls.simCard.stop() 245 | 246 | if __name__ == "__main__": 247 | logging.basicConfig(level=logging.INFO, format='%(message)s') 248 | unittest.main() -------------------------------------------------------------------------------- /tests/runner.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2015 Kamil Wartanowicz 4 | 5 | import os 6 | import sys 7 | sys.path.append(os.path.join(os.path.dirname(__file__), '../')) 8 | import unittest 9 | import logging 10 | import traceback 11 | from optparse import OptionParser 12 | 13 | import html_test_runner 14 | from sim import sim_router 15 | 16 | dir = os.path.dirname(__file__) 17 | resultDir = os.path.join(dir, "result") 18 | startDir = dir 19 | 20 | def setupLogger(loggingLevel): 21 | if not os.path.exists(resultDir): 22 | os.makedirs(resultDir) 23 | resultFile = os.path.join(resultDir, "result.txt") 24 | # create logger with 'root' 25 | logger = logging.getLogger() 26 | logger.handlers = [] 27 | logger.setLevel(loggingLevel) 28 | # create file handler which logs even debug messages 29 | fileHandler = logging.FileHandler(resultFile) 30 | fileHandler.setLevel(loggingLevel) 31 | # create console handler with a higher log level 32 | consoleHandler = logging.StreamHandler() 33 | consoleHandler.setLevel(loggingLevel) 34 | # create ext handler with a higher log level 35 | # use this handler to redirect streaming in html_test_runner 36 | extHandler = logging.StreamHandler(stream=html_test_runner.stdout_redirector) 37 | extHandler.setLevel(loggingLevel) 38 | # create formatter and add it to the handlers 39 | formatterConsole = logging.Formatter(fmt='%(message)s') 40 | formatter = logging.Formatter(fmt='%(asctime)s %(message)s', datefmt='%H:%M:%S') 41 | consoleHandler.setFormatter(formatterConsole) 42 | fileHandler.setFormatter(formatter) 43 | extHandler.setFormatter(formatter) 44 | # add the handlers to the logger 45 | logger.addHandler(fileHandler) 46 | logger.addHandler(consoleHandler) 47 | logger.addHandler(extHandler) 48 | sim_router.setLoggerExtHandler(extHandler) 49 | 50 | def startFile(outfile): 51 | sysName = sys.platform 52 | if sysName.startswith('java'): 53 | import java.lang.System 54 | sysName = java.lang.System.getProperty('os.name').lower() 55 | if sysName.startswith('linux'): 56 | osName = "linux" 57 | elif sysName.startswith('win'): 58 | osName = "windows" 59 | if osName == "linux": 60 | cmd = "/usr/bin/xdg-open " 61 | elif osName == "windows": 62 | cmd = "cmd /c START " 63 | os.system(cmd + outfile.name) 64 | 65 | if __name__ == '__main__': 66 | parser = OptionParser() 67 | parser.add_option("-l", "--logging-level", dest="logging_level", 68 | help="Logging level, DEBUG=10, INFO=20, WARNING=30, ERROR=40") 69 | parser.add_option("-s", "--start-directory", dest="start_dir", 70 | help="Directory to start discovery") 71 | parser.add_option("-p", "--pattern", dest="pattern", 72 | help="Pattern to match test files (test*.py default)") 73 | parser.add_option("-m", "--module-class-test", dest="module_class_test", 74 | help="Pattern to match specific test/class: module.class.test") 75 | parser.add_option("-t", "--top-level-directory", dest="top_level_dir", 76 | help="Top level directory of project (defaults to start directory)") 77 | parser.add_option("-i", "--iterations", dest="iterations", 78 | help="Number of iterations") 79 | (options, args) = parser.parse_args() 80 | 81 | dictArgs = {} 82 | if options.logging_level: 83 | loggingLevel = int(options.logging_level) 84 | else: 85 | loggingLevel = logging.INFO 86 | if options.start_dir: 87 | startDir = options.start_dir 88 | dictArgs.update({'start_dir':startDir}) 89 | if options.pattern: 90 | dictArgs.update({'pattern':options.pattern}) 91 | if options.top_level_dir: 92 | dictArgs.update({'top_level_dir':options.top_level_dir}) 93 | 94 | if options.iterations: 95 | nbrOfiterations = int(options.iterations) 96 | else: 97 | nbrOfiterations = 1 98 | 99 | moduleClassTests = [] 100 | if options.module_class_test: 101 | moduleClassTests = options.module_class_test.split() 102 | #prevent creating *.pyc files 103 | sys.dont_write_bytecode = True 104 | 105 | while True: 106 | setupLogger(loggingLevel) 107 | infoText = "simLAB test runner: %s" %(" ".join(sys.argv[1:])) 108 | logging.info(infoText) 109 | loader = unittest.TestLoader() 110 | suites = loader.discover(**dictArgs) 111 | newSuite = unittest.TestSuite() 112 | 113 | for suite in suites._tests: 114 | new_suite = unittest.TestSuite() 115 | for _test_group in suite._tests: 116 | test_group = [] 117 | try: 118 | iter(_test_group) 119 | test_group = _test_group 120 | except TypeError: 121 | # not iterable, probably module import failure 122 | logging.error("Import Failure? class="+_test_group.__class__.__name__+" test="+_test_group._testMethodName) 123 | test_group.append(_test_group) 124 | for test in test_group: 125 | if options.module_class_test: 126 | # check if any test matches expresion included with '-m' option 127 | moduleName = test.__class__.__module__ 128 | className = test.__class__.__name__ 129 | testMethodName = test._testMethodName 130 | nbrOfModules = len(moduleName.split(".")) 131 | 132 | for moduleClassTest in moduleClassTests: 133 | classNameTmp = None 134 | testMethodNameTmp = None 135 | nbrOfPartsTmp = len(moduleClassTest.split(".")) 136 | moduleNameTmp = ".".join(moduleClassTest.split(".")[0:nbrOfModules]) 137 | if nbrOfPartsTmp > nbrOfModules: 138 | classNameTmp = moduleClassTest.split(".")[nbrOfModules] 139 | else: 140 | className = None 141 | if nbrOfPartsTmp > nbrOfModules + 1: 142 | testMethodNameTmp = moduleClassTest.split(".")[nbrOfModules+1] 143 | else: 144 | testMethodName = None 145 | if (moduleNameTmp == moduleName and 146 | classNameTmp == className and 147 | testMethodNameTmp == testMethodName): 148 | newSuite.addTest(test) 149 | else: 150 | #add test to suite in case of import error 151 | if className == 'ModuleImportFailure' and moduleClassTest[0] == test._testMethodName: 152 | newSuite.addTest(test) 153 | continue 154 | newSuite.addTest(test) 155 | 156 | outfile = open(os.path.join(resultDir, "report.html"), "w") 157 | testRunner = html_test_runner.HTMLTestRunner( 158 | stream=outfile, 159 | verbosity=2, 160 | title='Test Report', 161 | description=infoText, 162 | ) 163 | testRunner.run(newSuite) 164 | outfile.close() 165 | startFile(outfile) 166 | 167 | nbrOfiterations -= 1 168 | if nbrOfiterations > 0: 169 | removeModuleSuite(suites) 170 | continue 171 | break 172 | -------------------------------------------------------------------------------- /tests/soft/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/tests/soft/__init__.py -------------------------------------------------------------------------------- /tests/soft/test_authentication.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2015 Kamil Wartanowicz 4 | # (c) 2015 Mikolaj Bartnicki 5 | 6 | import sys 7 | import os.path 8 | import logging 9 | import unittest 10 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) 11 | from sim_soft import sim_auth 12 | 13 | class TestAuthentication(unittest.TestCase): 14 | def test_1_dummy_xor(self): 15 | # input: 16 | key = "00112233445566778899AABBCCDDEEFF" 17 | rand = "31323131353836343132313135383634" 18 | sqn = "000000000000" 19 | amf = "0000" 20 | 21 | # expected output 22 | res = "31231302716D5043B9AB9B8AF9E5D8CB" 23 | ck = "231302716D5043B9AB9B8AF9E5D8CB31" 24 | ik = "1302716D5043B9AB9B8AF9E5D8CB3123" 25 | kc = "0000000000000000" 26 | autn = "02716D5043B9000031231302716D5043" 27 | 28 | logging.info("Codes to check:\n" 29 | "key=%s\n" 30 | "rand=%s\n" 31 | "sqn=%s\n" 32 | "amf=%s\n" 33 | %(key, rand, sqn, amf)) 34 | result = sim_auth.dummyXorHex(key, rand, sqn, amf, None) 35 | 36 | self.assertEqual(result['res'], res) 37 | self.assertEqual(result['ck'], ck) 38 | self.assertEqual(result['ik'], ik) 39 | self.assertEqual(result['kc'], kc) 40 | self.assertEqual(result['autn'], autn) 41 | 42 | def test_2_dummy_xor(self): 43 | # input: 44 | key = "00112233445566778899AABBCCDDEEFF" 45 | rand = "41ec50d284b5284fb9a317e9f089f247" 46 | sqn = "FFFFFFFFFFFF" 47 | amf = "8000" 48 | 49 | # expected output 50 | autn = "1E3F1FB1C7CE8000BE028D1E3F1FCE38" 51 | res = "41FD72E1C0E04E38313ABD523C541CB8" 52 | ck = "FD72E1C0E04E38313ABD523C541CB841" 53 | ik = "72E1C0E04E38313ABD523C541CB841FD" 54 | kc = "087C4F48E6D2F0B7" 55 | 56 | logging.info("Codes to check:\n" 57 | "key=%s\n" 58 | "rand=%s\n" 59 | "sqn=%s\n" 60 | "amf=%s\n" 61 | %(key, rand, sqn, amf)) 62 | result = sim_auth.dummyXorHex(key, rand, sqn, amf, None) 63 | 64 | self.assertEqual(result['res'], res) 65 | self.assertEqual(result['ck'], ck) 66 | self.assertEqual(result['ik'], ik) 67 | self.assertEqual(result['kc'], kc) 68 | self.assertEqual(result['autn'], autn) 69 | 70 | def test_3_dummy_xor_data(self): 71 | # input: 72 | key = "00112233445566778899AABBCCDDEEFF" 73 | rand = "0123456789ABCDEF0123456789ABCDEF" 74 | sqn = "000000000000" 75 | amf = "0000" 76 | autn = "54CDFEAB9889000001326754CDFEAB98" 77 | 78 | logging.info("Codes to check:\n" 79 | "key=%s\n" 80 | "rand=%s\n" 81 | "sqn=%s\n" 82 | "amf=%s\n" 83 | %(key, rand, sqn, amf)) 84 | 85 | result = sim_auth.dummyXorData(key, rand, sqn, amf, autn) 86 | self.assertEqual(result, 87 | "DB" #OK 88 | "1001326754CDFEAB9889BAEFDC45762310" #RES 89 | "10326754CDFEAB9889BAEFDC4576231001" #CK 90 | "106754CDFEAB9889BAEFDC457623100132" #IK 91 | "080000000000000000") #KC 92 | 93 | def test_4_dummy_xor_data(self): 94 | '''TODO: implement sqn range check''' 95 | #apdu 00880081221041EC50D284B5284FB9A317E9F089F24710E1C0E04E3822800041FD72E1C0F3CE38 96 | # input: 97 | key = "00112233445566778899AABBCCDDEEFF" 98 | rand = "41ec50d284b5284fb9a317e9f089f247" 99 | sqn = "000000000000" 100 | amf = "8000" 101 | autn = "1E3F1FB1C7CE8000BE028D1E3F1FCE38" 102 | 103 | logging.info("Codes to check:\n" 104 | "key=%s\n" 105 | "rand=%s\n" 106 | "sqn=%s\n" 107 | "amf=%s\n" 108 | %(key, rand, sqn, amf)) 109 | 110 | result = sim_auth.dummyXorData(key, rand, sqn, amf, autn) 111 | self.assertEqual(result, 112 | "DB" #OK 113 | "1041FD72E1C0E04E38313ABD523C541CB8" #RES 114 | "10FD72E1C0E04E38313ABD523C541CB841" #CK 115 | "1072E1C0E04E38313ABD523C541CB841FD" #IK 116 | "08087C4F48E6D2F0B7") #KC 117 | 118 | if __name__ == "__main__": 119 | logging.basicConfig(level=logging.INFO, format='%(message)s') 120 | unittest.main() -------------------------------------------------------------------------------- /tests/soft/test_ext_control_dbus.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2016 Janusz Kuszczynski 4 | 5 | import unittest 6 | import logging 7 | import os 8 | import shlex 9 | import signal 10 | import subprocess 11 | import threading 12 | import time 13 | import sys 14 | import os.path 15 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) 16 | from util import types 17 | 18 | DBUS_ENABLE = True 19 | if os.name != 'posix': 20 | DBUS_ENABLE = False 21 | if DBUS_ENABLE: 22 | import dbus 23 | 24 | 25 | @unittest.skipUnless(DBUS_ENABLE, "Windows doesn't support DBUS") 26 | class TestDBus(unittest.TestCase): 27 | @classmethod 28 | def setUpClass(cls): 29 | cls.server = SimlabServerThread() 30 | cls.server.setDaemon(True) 31 | cls.server.start() 32 | time.sleep(1) 33 | cls.bus = dbus.SessionBus() 34 | cls.session = cls.bus.get_object("org.sim.simlab", "/org/sim/simlab") 35 | cls.interface = dbus.Interface(cls.session, "org.sim.simlab") 36 | 37 | def test_1_pwd(self): 38 | result = self.interface.pwd() 39 | self.assertStatus(result) 40 | 41 | def test_2_long_command(self): 42 | self.interface.cd('/') 43 | result = self.interface.ls() 44 | self.assertStatus(result) 45 | 46 | def test_3_get_plmn(self): 47 | result = self.interface.get_plmn() 48 | self.assertStatus(result) 49 | 50 | def assertStatus(self, status): 51 | if 'status OK' in status: 52 | logging.info('Test OK') 53 | else: 54 | logging.warning("".join(status)) 55 | self.fail(status) 56 | 57 | @classmethod 58 | def tearDownClass(cls): 59 | cls.server.close() 60 | 61 | class SimlabServerThread(threading.Thread): 62 | def __init__(self): 63 | threading.Thread.__init__(self) 64 | threading.Thread.setName(self, 'SimlabServerThread') 65 | self.proc = None 66 | self.__lock = threading.Lock() 67 | 68 | def run(self): 69 | self.__lock.acquire() 70 | # Can't run plac in thread thus script is loaded from external script 71 | scriptPath = os.path.abspath(os.path.dirname(__file__)) 72 | scriptPath = os.path.join(scriptPath, "../../mim/mim_tmp.py").replace("\\", "/") 73 | scriptFile = open(scriptPath, "w") 74 | scriptFile.write(script) 75 | scriptFile.close() 76 | scriptCmd = "python " + scriptPath 77 | self.proc = subprocess.Popen(shlex.split(scriptCmd)) 78 | self.__lock.release() 79 | 80 | def close(self): 81 | types.killProcess("mim_tmp.py") 82 | 83 | script = ''' 84 | import sys,os.path 85 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 86 | import logging 87 | from sim import sim_router 88 | from sim import sim_card 89 | from sim import sim_reader 90 | from util import types 91 | logging.basicConfig(level=logging.INFO, format='%(message)s') 92 | simType=types.TYPE_USIM 93 | simCard = sim_card.SimCard(mode=sim_reader.MODE_SIM_SOFT, type=simType) 94 | simCard.removeAllReaders() 95 | simCard.connect(sim_reader.READER_ID_0) 96 | simRouter = sim_router.SimRouter(cards=[simCard], 97 | atr=None, 98 | type=simType, 99 | mode=sim_router.SIMTRACE_OFFLINE) 100 | simRouter.run(mode=sim_router.ROUTER_MODE_DBUS) 101 | ''' 102 | 103 | if __name__ == "__main__": 104 | logging.basicConfig(level=logging.INFO, format='%(message)s') 105 | unittest.main() 106 | 107 | -------------------------------------------------------------------------------- /tests/soft/test_ext_control_telnet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2016 Kamil Wartanowicz 4 | import unittest 5 | import logging 6 | import os 7 | import re 8 | import shlex 9 | import signal 10 | import subprocess 11 | import sys 12 | import telnetlib 13 | import threading 14 | import time 15 | import os.path 16 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) 17 | from util import types 18 | 19 | HOST = "localhost" 20 | prompt = "i> " 21 | 22 | 23 | class TestTelnet(unittest.TestCase): 24 | @classmethod 25 | def setUpClass(cls): 26 | cls.server = SimlabServerThread() 27 | cls.server.setDaemon(True) 28 | cls.server.start() 29 | time.sleep(3) 30 | cls.telnet = TelnetSimlab() 31 | cls.simLab = cls.telnet.send 32 | 33 | def test_1_pwd(self): 34 | status, data = self.simLab("pwd") 35 | self.assertTrue(status) 36 | logging.info(data) 37 | self.assertTrue(data) 38 | 39 | def test_2_long_command(self): 40 | self.simLab("cd ADF0") 41 | status, data = self.simLab("ls") 42 | self.assertTrue(status) 43 | logging.info(data) 44 | self.assertTrue(data) 45 | 46 | @classmethod 47 | def tearDownClass(cls): 48 | cls.telnet.close() 49 | cls.server.close() 50 | 51 | class SimlabServerThread(threading.Thread): 52 | def __init__(self): 53 | threading.Thread.__init__(self) 54 | threading.Thread.setName(self, 'SimlabServerThread') 55 | self.proc = None 56 | self.__lock = threading.Lock() 57 | 58 | def run(self): 59 | self.__lock.acquire() 60 | # Can't run plac in thread thus script is loaded from external script 61 | scriptPath = os.path.abspath(os.path.dirname(__file__)) 62 | scriptPath = os.path.join(scriptPath, "../../mim/mim_tmp.py").replace("\\", "/") 63 | scriptFile = open(scriptPath, "w") 64 | scriptFile.write(script) 65 | scriptFile.close() 66 | scriptCmd = "python " + scriptPath 67 | self.proc = subprocess.Popen(shlex.split(scriptCmd)) 68 | self.__lock.release() 69 | 70 | def close(self): 71 | types.killProcess("mim_tmp.py") 72 | 73 | class TelnetSimlab(object): 74 | def __init__(self, port=2199): 75 | self.tn = telnetlib.Telnet(HOST, port) 76 | # Read prompt 77 | self.tn.read_until(prompt, timeout=1) 78 | 79 | def clearReadBuffer(self): 80 | # Clear buffer 81 | self.tn.read_very_eager() 82 | 83 | def send(self, cmd): 84 | self.clearReadBuffer() 85 | self.tn.write(cmd + "\r\n") 86 | return self.getResponse() 87 | 88 | def validateResponse(self, out): 89 | if len(out.split("\n")) > 2: 90 | raise Exception("Response data not expected: " + out) 91 | 92 | def statusOk(self, out): 93 | self.validateResponse(out) 94 | statusRe = re.search("status (.*)", out) 95 | if not statusRe: 96 | raise Exception("Status data not expected: " + out) 97 | status = statusRe.group(1) 98 | if status == "OK": 99 | return True 100 | else: 101 | return False 102 | 103 | def getData(self, out): 104 | self.validateResponse(out) 105 | dataRe = re.search("data (.*)", out) 106 | if not dataRe: 107 | return None 108 | data = dataRe.group(1) 109 | return data 110 | 111 | def getResponse(self): 112 | out = self.tn.read_until(prompt, timeout=30) 113 | out = out.rstrip(prompt) 114 | out = out.replace("\r", "") 115 | out = out.rstrip("\n") 116 | status = self.statusOk(out) 117 | data = self.getData(out) 118 | return status, data 119 | 120 | def close(self): 121 | self.tn.close() 122 | 123 | script = ''' 124 | import sys,os.path 125 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 126 | import logging 127 | from sim import sim_router 128 | from sim import sim_card 129 | from sim import sim_reader 130 | from util import types 131 | logging.basicConfig(level=logging.INFO, format='%(message)s') 132 | simType=types.TYPE_USIM 133 | simCard = sim_card.SimCard(mode=sim_reader.MODE_SIM_SOFT, type=simType) 134 | simCard.removeAllReaders() 135 | simCard.connect(sim_reader.READER_ID_0) 136 | simRouter = sim_router.SimRouter(cards=[simCard], 137 | atr=None, 138 | type=simType, 139 | mode=sim_router.SIMTRACE_OFFLINE) 140 | simRouter.run(mode=sim_router.ROUTER_MODE_TELNET) 141 | ''' 142 | 143 | if __name__ == "__main__": 144 | logging.basicConfig(level=logging.INFO, format='%(message)s') 145 | unittest.main() 146 | -------------------------------------------------------------------------------- /tests/soft/test_sat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2014 Kamil Wartanowicz 4 | # (c) 2014 Szymon Mielczarek 5 | 6 | import sys 7 | import os.path 8 | import time 9 | 10 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) 11 | from sim import sim_router 12 | from sim import sim_card 13 | from util import types 14 | import unittest 15 | import logging 16 | 17 | from sim import sim_reader 18 | 19 | MODE_SIM = sim_reader.MODE_SIM_SOFT 20 | 21 | PIN_1 = "1111" 22 | PUK_1 = "11111111" 23 | 24 | PIN_1_FALSE = "1112" 25 | PUK_1_FALSE = "11111112" 26 | 27 | class TestSimRouter(unittest.TestCase): 28 | @classmethod 29 | def setUpClass(cls): 30 | cls.simCard = sim_card.SimCard(mode=MODE_SIM, type=types.TYPE_SIM) 31 | cls.simCard.removeAllReaders() 32 | try: 33 | cls.simCard.connect(0) 34 | except Exception as e: 35 | if "no card in reader" in str(e): 36 | cls.simCard.stop() 37 | raise Exception("No card in reader") 38 | cls.simRouter = sim_router.SimRouter(cards=[cls.simCard], type=types.TYPE_SIM, mode=sim_router.SIMTRACE_OFFLINE) 39 | cls.simRouter.run(mode=sim_router.ROUTER_MODE_DISABLED) 40 | cls.simCtrl = cls.simRouter.simCtrl 41 | cls.sendApdu = cls.simRouter.simCtrl.sendApdu 42 | 43 | def test_1_create_setUpMenu(self): 44 | # create Set Up Menu 45 | sw1, sw2, data = self.sendApdu( 46 | "A01000001FFFFFFFFF7F0F00DF7F03071FE2080C0003000E000000000000000000000000") 47 | types.assertSw(sw1, sw2, checkSw1='NO_ERROR_PROACTIVE_DATA', raiseException=True) 48 | self.assertGreater(sw2, 2) 49 | 50 | def test_2_select_menu_item1(self): 51 | # envelope: MENU_SELECTION - Item1 52 | item = 0 53 | sw1, sw2, data = self.sendApdu("A0C2000009D3078202018190018" + str(item)) 54 | types.assertSw(sw1, sw2, checkSw1='NO_ERROR_PROACTIVE_DATA', raiseException=True) 55 | length = sw2 56 | # fetch: provideLocalInformation(local_info.IMEI_OF_THE_TERMINAL) 57 | sw1, sw2, data = self.sendApdu("A0120000%02X" %length) 58 | # send terminal response with IMEI: 00440245243851-2 59 | sw1, sw2, data = self.sendApdu( 60 | "A01400001681030226010202828103010014080A40044225345801") 61 | 62 | def test_3_select_menu_item2(self): 63 | # envelope: MENU_SELECTION - Item2 64 | item = 1 65 | sw1, sw2, data = self.sendApdu("A0C2000009D3078202018190018" + str(item)) 66 | types.assertSw(sw1, sw2, checkSw1='NO_ERROR_PROACTIVE_DATA', raiseException=True) 67 | length = sw2 68 | # fetch: GET_INPUT 69 | sw1, sw2, data = self.sendApdu("A0120000%02X" %length) 70 | # terminal response: input text 71 | sw1, sw2, data = self.sendApdu( 72 | "A0140000148103052301020282818301008D06043030313031") 73 | time.sleep(2) 74 | 75 | def test_4_select_menu_item3(self): 76 | # envelope: MENU_SELECTION - Item3 77 | item = 2 78 | sw1, sw2, data = self.sendApdu("A0C2000009D3078202018190018" + str(item)) 79 | types.assertSw(sw1, sw2, checkSw1='NO_ERROR_PROACTIVE_DATA', raiseException=True) 80 | length = sw2 81 | # fetch - PLAY_TONE 82 | sw1, sw2, data = self.sendApdu("A0120000%02X" %length) 83 | # terminal response 84 | sw1, sw2, data = self.sendApdu( 85 | "A01400000C810304200002028281830100") 86 | 87 | def test_5_select_menu_item4(self): 88 | # envelope: MENU_SELECTION - Item4 89 | item = 3 90 | sw1, sw2, data = self.sendApdu("A0C2000009D3078202018190018" + str(item)) 91 | types.assertSw(sw1, sw2, checkSw1='NO_ERROR_PROACTIVE_DATA', raiseException=True) 92 | length = sw2 93 | # fetch 94 | sw1, sw2, data = self.sendApdu("A0120000%02X" % length) 95 | 96 | # envelope: SELECT_ITEM - Item 1 97 | # terminal response 98 | sw1, sw2, data = self.sendApdu("A01400000F810302240002028281830100900180") 99 | 100 | def test_6_select_menu_item5(self): 101 | # envelope: MENU_SELECTION - Item5 102 | item = 4 103 | sw1, sw2, data = self.sendApdu("A0C2000009D3078202018190018" + str(item)) 104 | types.assertSw(sw1, sw2, checkSw1='NO_ERROR_PROACTIVE_DATA', raiseException=True) 105 | length = sw2 106 | # fetch 107 | sw1, sw2, data = self.sendApdu("A0120000%02X" %length) 108 | 109 | @classmethod 110 | def tearDownClass(cls): 111 | time.sleep(1) 112 | cls.simCard.stop() 113 | 114 | if __name__ == "__main__": 115 | logging.basicConfig(level=logging.INFO, format='%(message)s') 116 | unittest.main() -------------------------------------------------------------------------------- /tests/soft/test_shell.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2015 Kamil Wartanowicz 4 | 5 | import sys,os.path 6 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) 7 | 8 | from sim import sim_router 9 | from sim import sim_card 10 | from util import types_g 11 | from util import types 12 | import unittest 13 | import logging 14 | 15 | from sim import sim_reader 16 | 17 | MODE_SIM = sim_reader.MODE_SIM_SOFT 18 | SIM_TYPE = types.TYPE_USIM 19 | 20 | 21 | class TestSimShell(unittest.TestCase): 22 | @classmethod 23 | def setUpClass(cls): 24 | cls.simCard = sim_card.SimCard(mode=MODE_SIM, type=SIM_TYPE) 25 | cls.simCard.removeAllReaders() 26 | try: 27 | cls.simCard.connect(0) 28 | except Exception as e: 29 | if "no card in reader" in str(e): 30 | cls.simCard.stop() 31 | raise Exception("No card in reader") 32 | cls.simRouter = sim_router.SimRouter(cards=[cls.simCard], type=SIM_TYPE, mode=sim_router.SIMTRACE_OFFLINE) 33 | cls.simRouter.run(mode=sim_router.ROUTER_MODE_DISABLED) 34 | cls.shell = cls.simRouter.shell 35 | 36 | def test_01_read_iccid(self): 37 | status, data = self.shell.read("/2FE2") 38 | self.assertEqual(status, "status OK") 39 | value = types.getDataValue(data) 40 | self.assertTrue(status, "read imsi failed") 41 | valueLength = len(value) 42 | self.assertGreater(valueLength, 9, "Invalid ICCID length") 43 | 44 | def test_02_read_imsi(self): 45 | status, data1 = self.shell.readi("/ADF0/EF_IMSI") 46 | self.shell.assertOk(status, data1) 47 | status, data2 = self.shell.readi("/7F20/EF_IMSI") 48 | self.shell.assertOk(status, data2) 49 | status, data = self.shell.cd("/") 50 | self.shell.assertOk(status, data) 51 | status, data3 = self.shell.readi("EF_IMSI") 52 | self.shell.assertOk(status, data3) 53 | status, data = self.shell.cd("/ADF_USIM") 54 | self.shell.assertOk(status, data) 55 | status, data4 = self.shell.readi("./6F07") 56 | self.shell.assertOk(status, data4) 57 | self.assertEqual(data1, data2) 58 | self.assertEqual(data1, data3) 59 | self.assertEqual(data1, data4) 60 | 61 | def test_03_read_imsi_raw(self): 62 | status, data1 = self.shell.read("/ADF0/EF_IMSI") 63 | self.shell.assertOk(status, data1) 64 | status, data = self.shell.cd("/") 65 | self.shell.assertOk(status, data) 66 | status, data2 = self.shell.read("EF_IMSI") 67 | self.shell.assertOk(status, data2) 68 | imsi1 = types.getDataValue(data1) 69 | imsi2 = types.getDataValue(data2) 70 | self.assertEqual(imsi1, imsi2) 71 | imsiRawLength = len(imsi1) 72 | self.assertGreater(imsiRawLength, 14+3, "Invalid imsi raw length") 73 | 74 | def test_04_get_plmn(self): 75 | status, data1 = self.shell.get_plmn() 76 | self.shell.assertOk(status, data1) 77 | self.assertGreaterEqual(len(data1), 5*2) 78 | self.assertLessEqual(len(data1), 6*2) 79 | status, data2 = self.shell.readi("EF_IMSI") 80 | self.shell.assertOk(status, data2) 81 | self.assertTrue(data1 in data2) 82 | 83 | def test_05_read_arr(self): 84 | status, data = self.shell.read("/2F06") 85 | self.assertTrue(status, "read arr failed") 86 | 87 | def test_06_create_file(self): 88 | # Create DF and EF using absolute paths. 89 | dirPath = "/DEAD/" 90 | try: 91 | self.shell.delete(dirPath) 92 | except: 93 | pass 94 | status, out = self.shell.create(dirPath) 95 | self.shell.assertOk(status, out) 96 | status, out = self.shell.delete(dirPath) 97 | self.shell.assertOk(status, out) 98 | 99 | dirPath = "/ADF0/DEAD/" 100 | filePath = "/ADF0/DEAD/BEEF" 101 | try: 102 | self.shell.delete(dirPath) 103 | except: 104 | pass 105 | status, out = self.shell.create(dirPath) 106 | self.shell.assertOk(status, out) 107 | status, out = self.shell.create(filePath) 108 | self.shell.assertOk(status, out) 109 | status, out = self.shell.delete(filePath) 110 | self.shell.assertOk(status, out) 111 | status, out = self.shell.delete(dirPath) 112 | self.shell.assertOk(status, out) 113 | 114 | def test_07_create_file_relative(self): 115 | # Create DF and EF using relative paths. 116 | dirPath = "./DEAD/" 117 | filePath = "./BEEF" 118 | dirPath2 = "./DEAF/" 119 | try: 120 | self.shell.delete(dirPath) 121 | except: 122 | pass 123 | status, out = self.shell.create(dirPath) 124 | self.shell.assertOk(status, out) 125 | status, out = self.shell.create(filePath) 126 | self.shell.assertOk(status, out) 127 | status, out = self.shell.create(dirPath2) 128 | self.shell.assertOk(status, out) 129 | status, out = self.shell.create(filePath) 130 | self.shell.assertOk(status, out) 131 | 132 | status, out = self.shell.delete(dirPath2) 133 | self.shell.assertOk(status, out) 134 | status, out = self.shell.delete(dirPath) 135 | self.shell.assertOk(status, out) 136 | 137 | def test_08_create_adf(self): 138 | # Get number of EF_DIR records 139 | status, data = self.shell.read("/2F00") 140 | self.shell.assertOk(status, data) 141 | numOfRecords = len(data.split(';')) - 1 142 | # Use the next free Id 143 | dirPath = "/ADF%d/" % numOfRecords 144 | try: 145 | self.shell.delete(dirPath) 146 | except: 147 | pass 148 | status, out = self.shell.create(dirPath) 149 | self.shell.assertOk(status, out) 150 | status, out = self.shell.delete(dirPath) 151 | self.shell.assertOk(status, out) 152 | 153 | def test_09_pwd(self): 154 | dirPath = "/7F10/5F3A" 155 | name = "DF_PHONEBOOK" 156 | status, out = self.shell.cd(dirPath) 157 | self.shell.assertOk(status, out) 158 | status, out = self.shell.pwd() 159 | self.shell.assertOk(status, out) 160 | path = types.getDataValue(out) 161 | #compare to directory 162 | self.assertEqual(path, "path=%s/,name=%s,simId=0" %(dirPath, name)) 163 | 164 | def test_10_ls(self): 165 | dirPath = "/" 166 | status, out = self.shell.cd(dirPath) 167 | self.shell.assertOk(status, out) 168 | status, out = self.shell.ls() 169 | self.shell.assertOk(status, out) 170 | files = types.getDataValue(out) 171 | self.assertTrue(files) 172 | dirPath = "/7F10" 173 | status, out = self.shell.cd(dirPath) 174 | self.shell.assertOk(status, out) 175 | status, out = self.shell.ls() 176 | self.shell.assertOk(status, out) 177 | files = types.getDataValue(out) 178 | self.assertTrue("5F3A/" in files, "Files: %s" %files) 179 | 180 | def test_11_resize(self): 181 | filePath = "/ADF0/DEAD/BEEF" 182 | parentPath = types.parentDirFromPath(filePath) + '/' 183 | # Cleanup 184 | try: 185 | self.shell.delete(parentPath) 186 | except: 187 | pass 188 | # Create temporary dir and file (linear) 189 | fileType = types_g.fileDescriptor.LINEAR_FIXED_STRUCTURE 190 | fileSize = 0x30 191 | recordLength = 0x10 192 | status, out = self.shell.create(filePath, 193 | "fileType=%X,fileSize=%X,recordLength=%X" \ 194 | % (fileType, fileSize, recordLength)) 195 | self.shell.assertOk(status, out) 196 | # Increase the size of the file (by 2 new records) with a pattern 197 | newFileSize = fileSize + recordLength * 2 198 | pattern = types.addTrailingBytes('', 0xA5, recordLength-4) # not the whole record length 199 | status, out = self.shell.resize(filePath, hex(newFileSize), pattern) 200 | self.shell.assertOk(status, out) 201 | # Check the data after resize 202 | status, data = self.shell.read(filePath) 203 | self.shell.assertOk(status, data) 204 | value = types.getDataValue(data).replace(';', '') 205 | self.assertEqual(len(value)/2, newFileSize) 206 | # Decrease the size of the file to one record 207 | status, out = self.shell.resize(filePath, hex(recordLength)) 208 | self.shell.assertOk(status, out) 209 | 210 | status, out = self.shell.delete(parentPath) 211 | self.shell.assertOk(status, out) 212 | 213 | if __name__ == "__main__": 214 | logging.basicConfig(level=logging.INFO, format='%(message)s') 215 | unittest.main() 216 | -------------------------------------------------------------------------------- /tests/soft/test_sim_2g.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2014 Kamil Wartanowicz 4 | 5 | import sys,os.path 6 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) 7 | 8 | from sim import sim_router 9 | from sim import sim_card 10 | from util import hextools 11 | from util import types_g 12 | from util import types 13 | import unittest 14 | import logging 15 | 16 | from sim import sim_reader 17 | from sim import sim_ctrl_2g 18 | from sim import sim_ctrl_3g 19 | 20 | MODE_SIM = sim_reader.MODE_SIM_SOFT 21 | SIM_TYPE = types.TYPE_SIM 22 | 23 | PIN_1 = "1111" 24 | PUK_1 = "11111111" 25 | 26 | PIN_1_FALSE = "1112" 27 | PUK_1_FALSE = "11111112" 28 | 29 | class TestSim(unittest.TestCase): 30 | @classmethod 31 | def setUpClass(cls): 32 | cls.simCard = sim_card.SimCard(mode=MODE_SIM, type=SIM_TYPE) 33 | cls.simCard.removeAllReaders() 34 | try: 35 | cls.simCard.connect(0) 36 | except Exception as e: 37 | if "no card in reader" in str(e): 38 | cls.simCard.stop() 39 | raise Exception("No card in reader") 40 | cls.simRouter = sim_router.SimRouter(cards=[cls.simCard], type=types.TYPE_SIM, mode=sim_router.SIMTRACE_OFFLINE) 41 | cls.simRouter.run(mode=sim_router.ROUTER_MODE_DISABLED) 42 | cls.simCtrl = cls.simRouter.simCtrl 43 | cls.sendApdu = cls.simCtrl.sendApdu 44 | 45 | def test_1_getAtr(self): 46 | atr = self.simCard.getATR() 47 | logging.info("ATR: %s" %hextools.bytes2hex(atr)) 48 | self.assertGreater(len(atr), 4) 49 | 50 | def test_2_select_file(self): 51 | #SELECT_FILE DF_GSM 52 | sw1, sw2, data = self.sendApdu("A0A40000027F20") 53 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True) 54 | length = sw2 55 | #GET_RESPONSE 56 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length) 57 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 58 | 59 | #SELECT_FILE MF 60 | sw1, sw2, data = self.sendApdu("A0A40000023F00") 61 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True) 62 | self.assertGreater(sw2, 2) 63 | length = sw2 64 | #GET_RESPONSE 65 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length) 66 | 67 | #SELECT_FILE EF_ICCID 68 | sw1, sw2, data = self.sendApdu("A0A40000022FE2") 69 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True) 70 | length = sw2 71 | #GET_RESPONSE 72 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length) 73 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 74 | 75 | def test_3_enable_pin(self): 76 | enabled = self.simCtrl.pin1Enabled() 77 | attemptsLeft = self.simCtrl.pin1Status() 78 | self.assertTrue(attemptsLeft, "PUK needed") 79 | 80 | if enabled: 81 | #disable pin 82 | sw1, sw2, data = self.sendApdu("A026000108%sFFFFFFFF" %PIN_1.encode("hex")) 83 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 84 | 85 | enabled = self.simCtrl.pin1Enabled() 86 | attemptsLeft = self.simCtrl.pin1Status() 87 | self.assertFalse(enabled) 88 | self.assertTrue(attemptsLeft, "No attempts left. PUK needed") 89 | 90 | #enable pin 91 | sw1, sw2, data = self.sendApdu("A028000108%sFFFFFFFF" %PIN_1.encode("hex")) 92 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 93 | enabled = self.simCtrl.pin1Enabled() 94 | attemptsLeft = self.simCtrl.pin1Status() 95 | self.assertTrue(enabled) 96 | self.assertTrue(attemptsLeft, "No attempts left. PUK needed") 97 | 98 | def test_4_unblock_pin(self): 99 | enabled = self.simCtrl.pin1Enabled() 100 | attemptsLeft = self.simCtrl.pin1Status() 101 | self.assertTrue(enabled) 102 | self.assertTrue(attemptsLeft, "No attempts left. PUK needed") 103 | 104 | #VERIFY 105 | sw1, sw2, data = self.sendApdu("A020000108%sFFFFFFFF" %PIN_1.encode("hex")) 106 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 107 | enabled = self.simCtrl.pin1Enabled() 108 | attemptsLeft = self.simCtrl.pin1Status() 109 | self.assertTrue(enabled) 110 | 111 | #block PIN 112 | for i in range(attemptsLeft): 113 | sw1, sw2, data = self.sendApdu("A020000108%sFFFFFFFF" %PIN_1_FALSE.encode("hex")) 114 | 115 | _attemptsLeft = attemptsLeft - i - 1 116 | if _attemptsLeft: 117 | types.assertSw(sw1, sw2, checkSw='GSM_ACCESS_CONDITION_NOT_FULFILLED', raiseException=True) 118 | else: 119 | types.assertSw(sw1, sw2, checkSw='GSM_UNSUCCESSFUL_USER_PIN_VERIFICATION', raiseException=True) 120 | left = self.simCtrl.pin1Status() 121 | self.assertEqual(left, _attemptsLeft) 122 | 123 | #unblock PIN 124 | enabled = self.simCtrl.pin1Enabled() 125 | attemptsLeft = self.simCtrl.pin1Status() 126 | self.assertFalse(attemptsLeft, "PIN is not blocked") 127 | 128 | attemptsLeft = self.simCtrl.pin1UnblockStatus() 129 | self.assertTrue(attemptsLeft, "SIM is permanently blocked") 130 | 131 | for i in range(attemptsLeft - 2): 132 | sw1, sw2, data = self.sendApdu("A02C000010%s%sFFFFFFFF" %(PUK_1_FALSE.encode("hex"), PIN_1.encode("hex"))) 133 | 134 | _attemptsLeft = attemptsLeft - i - 1 135 | if _attemptsLeft: 136 | types.assertSw(sw1, sw2, checkSw='GSM_ACCESS_CONDITION_NOT_FULFILLED', raiseException=True) 137 | else: 138 | types.assertSw(sw1, sw2, checkSw='GSM_UNSUCCESSFUL_USER_PIN_VERIFICATION', raiseException=True) 139 | left = self.simCtrl.pin1UnblockStatus() 140 | self.assertEqual(left, _attemptsLeft) 141 | 142 | attemptsLeft = self.simCtrl.pin1UnblockStatus() 143 | self.assertGreater(attemptsLeft, 1, "Prevent permanent blocking of SIM") 144 | 145 | sw1, sw2, data = self.sendApdu("A02C000010%s%sFFFFFFFF" %(PUK_1.encode("hex"), PIN_1.encode("hex"))) 146 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 147 | 148 | def test_5_read_imsi(self): 149 | enabled = self.simCtrl.pin1Enabled() 150 | attemptsLeft = self.simCtrl.pin1Status() 151 | self.assertTrue(enabled) 152 | self.assertTrue(attemptsLeft, "PUK needed") 153 | 154 | #SELECT_FILE DF_GSM 155 | sw1, sw2, data = self.sendApdu("A0A40000027F20") 156 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True) 157 | length = sw2 158 | #GET_RESPONSE 159 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length) 160 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 161 | 162 | #SELECT_FILE IMSI 163 | sw1, sw2, data = self.sendApdu("A0A40000026F07") 164 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True) 165 | length = sw2 166 | #GET_RESPONSE 167 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length) 168 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 169 | 170 | #READ BINARY 171 | length = data[3] 172 | sw1, sw2, data = self.sendApdu("A0B00000%02X" %length) 173 | if types_g.sw[(sw1<<8) + sw2] == 'GSM_ACCESS_CONDITION_NOT_FULFILLED': 174 | #VERIFY 175 | sw1, sw2, data = self.sendApdu("A020000108%sFFFFFFFF" %PIN_1.encode("hex")) 176 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 177 | #repeat READ BINARY 178 | sw1, sw2, data = self.sendApdu("A0B00000%02X" %length) 179 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 180 | imsi = hextools.decode_BCD(data)[3:] 181 | logging.info("IMSI: %s" %imsi) 182 | 183 | @unittest.skip("Might be not supported on 2G SIM") 184 | def test_6_manage_channel(self): 185 | originChannel = 0 186 | targetChannel = 2 187 | 188 | """ Select on not open channel """ 189 | #SELECT_FILE MF 190 | sw1, sw2, data = self.sendApdu("A%01XA40000023F00" %targetChannel) 191 | types.assertSw(sw1, sw2, checkSw='LOGICAL_CHANNEL_NOT_SUPPORTED', raiseException=True) 192 | 193 | """ Open, Select and Close channel """ 194 | # MANAGE CHANNEL: OPEN (chosen) 195 | sw1, sw2, data = self.sendApdu("A%01X7000%02X00" %(originChannel, targetChannel)) 196 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 197 | #SELECT_FILE MF 198 | sw1, sw2, data = self.sendApdu("A%01XA40000023F00" %targetChannel) 199 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True) 200 | self.assertGreater(sw2, 2) 201 | # MANAGE CHANNEL: CLOSE 202 | sw1, sw2, data = self.sendApdu("A%01X7080%02X00" %(originChannel, targetChannel)) 203 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 204 | 205 | """ Open first free channel (when targetChannel = 0) """ 206 | # MANAGE CHANNEL: OPEN (first free) 207 | sw1, sw2, data = self.sendApdu("A%01X7000%02X01" %(originChannel, 0)) 208 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 209 | self.assertEqual(data[0], 1) 210 | 211 | originChannel = data[0] # 1 212 | # MANAGE CHANNEL: OPEN (non-basic origin channel) 213 | sw1, sw2, data = self.sendApdu("A%01X7000%02X01" %(originChannel, 0)) 214 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 215 | self.assertEqual(data[0], originChannel + 1) 216 | targetChannel = data[0] # 2 217 | 218 | """ Select IMSI file on Basic channel """ 219 | #SELECT_FILE DF_GSM 220 | sw1, sw2, data = self.sendApdu("A0A40000027F20") 221 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True) 222 | #SELECT_FILE IMSI 223 | sw1, sw2, data = self.sendApdu("A0A40000026F07") 224 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True) 225 | length = sw2 226 | #GET_RESPONSE 227 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length) 228 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 229 | imsiLen = data[3] 230 | """ Select MF on Non-Basic channel """ 231 | #SELECT_FILE MF 232 | sw1, sw2, data = self.sendApdu("A%01XA40000023F00" %targetChannel) 233 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True) 234 | self.assertGreater(sw2, 2) 235 | """ Read IMSI file on Basic channel """ 236 | #READ BINARY 237 | sw1, sw2, data = self.sendApdu("A0B00000%02X" %imsiLen) 238 | if types_g.sw[(sw1<<8) + sw2] == 'GSM_ACCESS_CONDITION_NOT_FULFILLED': 239 | #VERIFY 240 | sw1, sw2, data = self.sendApdu("A020000108%sFFFFFFFF" %PIN_1.encode("hex")) 241 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 242 | #repeat READ BINARY 243 | sw1, sw2, data = self.sendApdu("A0B00000%02X" %length) 244 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 245 | imsi = hextools.decode_BCD(data)[3:] 246 | logging.info("IMSI: %s" %imsi) 247 | 248 | """ Close Non-Basic channel """ 249 | # MANAGE CHANNEL: CLOSE 250 | sw1, sw2, data = self.sendApdu("A%01X7080%02X00" %(originChannel, targetChannel)) 251 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True) 252 | 253 | def tearDown(self): 254 | self.simCard.reset() 255 | 256 | @classmethod 257 | def tearDownClass(cls): 258 | cls.simCard.stop() 259 | 260 | if __name__ == "__main__": 261 | logging.basicConfig(level=logging.INFO, format='%(message)s') 262 | unittest.main() -------------------------------------------------------------------------------- /util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/util/__init__.py -------------------------------------------------------------------------------- /util/coder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf8 3 | 4 | import logging 5 | 6 | GSM7_BASIC = (u"@£$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞ\x1bÆæßÉ !\"#¤%&'()*+,-./0123456789:;<=>" 7 | u"?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑÜ`¿abcdefghijklmnopqrstuvwxyzäöñüà") 8 | GSM7_EXT = (u"````````````````````^```````````````````{}`````\\````````````[~]`" 9 | u"|````````````````````````````````````€``````````````````````````") 10 | 11 | def encodeGsm7(plaintext): 12 | res = "" 13 | f = -1 14 | t = 0 15 | bytes = getBytes(plaintext) 16 | bytesLength = len(bytes) 17 | for b in bytes: 18 | f = f+1 19 | t = (f%8)+1 20 | res += getEncode(b, t, t-1, f, f+1, 8-t, bytesLength, bytes) 21 | return res 22 | 23 | def decodeGsm7(codedtext): 24 | if not codedtext: 25 | raise Exception("Empy data to decode") 26 | hexparts = chunks(codedtext, 2) 27 | number = 0 28 | bitcount = 0 29 | output = '' 30 | found_external = False 31 | for byte in hexparts: 32 | byte = int(byte, 16); 33 | # add data on to the end 34 | number = number + (byte << bitcount) 35 | # increase the counter 36 | bitcount = bitcount + 1 37 | # output the first 7 bits 38 | if number % 128 == 27: 39 | '''skip''' 40 | found_external = True 41 | else: 42 | if found_external == True: 43 | character = GSM7_EXT[number % 128] 44 | found_external = False 45 | else: 46 | character = GSM7_BASIC[number % 128] 47 | output = output + character 48 | 49 | # then throw them away 50 | number = number >> 7 51 | # every 7th letter you have an extra one in the buffer 52 | if bitcount == 7: 53 | if number % 128 == 27: 54 | '''skip''' 55 | found_external = True 56 | else: 57 | if found_external == True: 58 | character = GSM7_EXT[number % 128] 59 | found_external = False 60 | else: 61 | character = GSM7_BASIC[number % 128] 62 | output = output + character 63 | 64 | bitcount = 0 65 | number = 0 66 | return output 67 | 68 | def getEncode(currentByte, index, bitRightCount, position, nextPosition, leftShiftCount, bytesLength, bytes): 69 | if index < 8: 70 | byte = currentByte >> bitRightCount 71 | if nextPosition < bytesLength: 72 | idx2 = bytes[nextPosition] 73 | byte = byte | ((idx2) << leftShiftCount) 74 | byte = byte & 0x000000FF 75 | else: 76 | byte = byte & 0x000000FF 77 | return chr(byte).encode('hex').upper() 78 | return '' 79 | 80 | def getBytes(plaintext): 81 | if type(plaintext) != str: 82 | plaintext = str(plaintext) 83 | bytes = [] 84 | for c in plaintext.decode('utf-8'): 85 | idx = GSM7_BASIC.find(c) 86 | if idx != -1: 87 | bytes.append(idx) 88 | else: 89 | idx = GSM7_EXT.find(c) 90 | if idx != -1: 91 | bytes.append(27) 92 | bytes.append(idx) 93 | return bytes 94 | 95 | def chunks(l, n): 96 | if n < 1: 97 | n = 1 98 | return [l[i:i + n] for i in range(0, len(l), n)] 99 | -------------------------------------------------------------------------------- /util/dbus_ctrl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # LICENSE: GPL2 3 | # (c) 2016 Janusz Kuszczynski 4 | """ 5 | DBus service made as separate process, as GLib.MainLoop() is a blocking loop, interfering with gevent module. 6 | """ 7 | 8 | import logging 9 | import time 10 | import multiprocessing 11 | import Queue 12 | import dbus 13 | import dbus.service 14 | from dbus.mainloop.glib import DBusGMainLoop 15 | from gi.repository import GLib 16 | import gevent 17 | from gevent import select as geventSelect 18 | import sys 19 | 20 | from sim import sim_shell 21 | 22 | def startDbusProcess(self): 23 | gevent.spawn(privateInterpreter, self) # plac.interpreter.interact is blocking, overriding 24 | taskQueue = multiprocessing.Queue() 25 | resultQueue = multiprocessing.Queue() 26 | dbusProcess = DbusProcess(taskQueue, resultQueue) 27 | dbusProcess.daemon = True 28 | dbusProcess.start() 29 | dbusReceiveTask(self, taskQueue, resultQueue) # acts as mainloop 30 | gevent.sleep(0.01) # freeze main loop for given amount of time 31 | dbusProcess.join(0.5) # try gentle 32 | dbusProcess.terminate() 33 | 34 | def getChar(): 35 | if geventSelect.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []): # non blocking raw_input, unix only 36 | return sys.stdin.read(1) 37 | 38 | def privateInterpreter(self): 39 | """Trivial interpreter implementation, sends command to plac interpreter""" 40 | logging.info("Starting plain interpreter") 41 | char = line = '' 42 | try: 43 | while char != '\x1b': # \x1b = escape character 44 | char = getChar() 45 | if char: 46 | line += char 47 | line += sys.stdin.readline() 48 | if '\n' in line: 49 | self.interpreter.execute([line[:-1]], verbose=True) # '[:-1]' to omit '\n' char 50 | line = '' 51 | sys.stdout.write(">") 52 | sys.stdout.flush() 53 | gevent.sleep(0.1) 54 | except KeyboardInterrupt: 55 | pass 56 | 57 | def dbusReceiveTask(self, taskQueue, resultQueue): 58 | """Loop to execute tasks from queue""" 59 | try: 60 | while True: 61 | try: 62 | task = taskQueue.get(timeout=0.1) 63 | logging.info("Received task from dbus: " + str(task)) 64 | with self.interpreter: 65 | result = self.interpreter.send(task) 66 | resultQueue.put(result.str) 67 | except Queue.Empty: 68 | pass # nothing in receive task queue 69 | gevent.sleep(1) 70 | except KeyboardInterrupt: 71 | pass 72 | 73 | class DbusProcess(multiprocessing.Process): 74 | def __init__(self, taskQueue, resultQueue): 75 | multiprocessing.Process.__init__(self) 76 | self.taskQueue = taskQueue 77 | self.resultQueue = resultQueue 78 | 79 | @staticmethod 80 | def _idleQueueSync(): 81 | time.sleep(0.01) # just to synchronize queue, otherwise task can be dropped 82 | return True 83 | 84 | def run(self): 85 | logging.info('D-Bus process started') 86 | GLib.threads_init() # allow threads in GLib 87 | GLib.idle_add(self._idleQueueSync) 88 | 89 | DBusGMainLoop(set_as_default=True) 90 | dbusService = SessionDBus(self.taskQueue, self.resultQueue) 91 | 92 | try: 93 | GLib.MainLoop().run() 94 | except KeyboardInterrupt: 95 | logging.debug("\nThe MainLoop will close...") 96 | GLib.MainLoop().quit() 97 | return 98 | 99 | 100 | class SessionDBus(dbus.service.Object, sim_shell.SimShell): 101 | def __init__(self, taskQueue, resultQueue): 102 | self.taskQueue = taskQueue 103 | self.resultQueue = resultQueue 104 | bus_name = dbus.service.BusName('org.sim.simlab', bus=dbus.SessionBus()) 105 | dbus.service.Object.__init__(self, bus_name, '/org/sim/simlab') 106 | 107 | """ 108 | Dynamically create functions, based on sim_shell.SimShell.commands. They are exported to D-Bus. 109 | """ 110 | functionTemplate = "" \ 111 | "def command_name(self,args=None): \n" \ 112 | " self.taskQueue.put('command_name ' + args) \n" \ 113 | " return self.resultQueue.get() \n" \ 114 | "command_name = dbus.service.method('org.sim.simlab')(command_name) " 115 | for command in sim_shell.SimShell.commands: 116 | functionCode = functionTemplate.replace('command_name', command) 117 | # check how many parameters takes original function 118 | argumentCount = getattr(sim_shell.SimShell, command).func_code.co_argcount 119 | if argumentCount == 1: 120 | functionCode = functionCode.replace(',args=None', '') 121 | functionCode = functionCode.replace('+ args', '') 122 | exec functionCode 123 | 124 | 125 | -------------------------------------------------------------------------------- /util/gsmtap.py: -------------------------------------------------------------------------------- 1 | # LICENSE: GPL2 2 | # (c) 2013 Tom Schouten 3 | 4 | import socket 5 | import struct 6 | 7 | # log APDUs to gsmtap 8 | # http://wiki.wireshark.org/GSMTAP 9 | # http://bb.osmocom.org/trac/wiki/GSMTAP 10 | 11 | ### to test: setup wireshark to listen on "lo" 12 | # from util import gsmtap 13 | # gsmtap.loghex("A0A40000023f00", "9000") 14 | 15 | 16 | gsmtap_hdr = [2, # GSMTAP_VERSION, 17 | 4, # nb of u32 in header 18 | 4, # GSMTAP_TYPE_SIM, 19 | 0,0,0,0,0,0,0,0,0,0,0,0,0] 20 | 21 | # gsmtap_addr = ("127.0.0.1", 4729) 22 | gsmtap_addr = ("", 4729) 23 | gsmtap_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 24 | gsmtap_sock.bind(('127.0.0.1', 0)) 25 | # broadcast avoids ICMP port unreachable 26 | gsmtap_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) 27 | 28 | 29 | def bytes2str(bytes): 30 | return struct.pack('B'*len(bytes), *bytes) 31 | def hex2bytes(hex): 32 | return map(ord,hex.decode("hex")) 33 | 34 | # input = bytes 35 | def log(c_apdu, r_apdu=[]): 36 | msg = list(gsmtap_hdr) + list(c_apdu) + list(r_apdu) 37 | gsmtap_sock.sendto(bytes2str(msg), gsmtap_addr) 38 | 39 | # input = hex string 40 | def loghex(c, r=""): 41 | log(hex2bytes(c), hex2bytes(r)) 42 | -------------------------------------------------------------------------------- /util/hextools.py: -------------------------------------------------------------------------------- 1 | # LICENSE: GPL2 2 | # (c) 2013 Tom Schouten 3 | 4 | import array 5 | 6 | def bytes2hex(bytes, separator=""): 7 | hexStr = "".join(map(lambda v: "%02X%s"%(v, separator), bytes)) 8 | if hexStr and hexStr[-1] == separator: 9 | #remove unwanted char at the end 10 | hexStr = hexStr[0:-1] 11 | return hexStr 12 | 13 | def hex2bytes(hex): 14 | return map(ord,hex.decode("hex")) 15 | 16 | def bytes2string(bytes): 17 | return array.array('B',bytes).tostring() 18 | 19 | def bytes(x): 20 | if type(x) == str: 21 | x = hex2bytes(x) 22 | return x 23 | 24 | def hex(x): 25 | if type(x) == str: 26 | return x 27 | if type(x) == int: 28 | return be_int_bytes(x) 29 | if type(x) == list: 30 | return bytes2hex(x) 31 | if type(x) == tuple: 32 | return bytes2hex(list(x)) 33 | if type(x) == bytearray: 34 | return bytes2hex(x) 35 | raise Exception(x) 36 | 37 | def string(x): 38 | return bytes2string(bytes(x)) 39 | 40 | def le_int_bytes(i): 41 | lst = [] 42 | while (i != 0): 43 | lst.append(i & 0xFF) 44 | i = i >> 8 45 | return lst 46 | def be_int_bytes(i): 47 | lst = le_int_bytes(i) 48 | lst.reverse() 49 | return lst 50 | 51 | # Remove spaces 52 | def strip(str): 53 | return str.replace(" ", "") 54 | 55 | 56 | class chunks: 57 | def __init__(self, lst, nb): 58 | self.lst = lst 59 | self.nb = nb 60 | def __iter__(self): 61 | return self 62 | def next(self): 63 | if not len(self.lst): 64 | raise StopIteration 65 | rv = self.lst[0:self.nb] 66 | self.lst = self.lst[self.nb:] 67 | return rv 68 | 69 | # see iso7816_slave.h 70 | # AT91 is little endian. 71 | def le_u32(val): 72 | return [val & 0xFF, 73 | (val >> 8) & 0xFF, 74 | (val >> 16) & 0xFF, 75 | (val >> 24) & 0xFF]; 76 | def u32(val): 77 | return le_u32(val) 78 | 79 | def be_u16(val): 80 | return [(val >> 8) & 0xFF, val & 0xFF] 81 | 82 | 83 | # big endian byte list to integer 84 | def le_int(lst): 85 | shift = 0 86 | acc = 0 87 | for b in lst: 88 | acc += b << shift 89 | shift += 8 90 | return acc 91 | 92 | def be_int(lst): 93 | r_lst = list(lst) 94 | r_lst.reverse() 95 | return le_int(r_lst) 96 | 97 | def all_FF(msg): 98 | for b in msg: 99 | if b != 0xFF: 100 | return False 101 | return True 102 | 103 | def pathstring(path): 104 | return "".join(map((lambda p: "/%s" % hex(p)), path)) 105 | 106 | 107 | def decode_BCD(data=[]): 108 | string = '' 109 | for B in data: 110 | string += "%01X" %(B & 0x0F) 111 | hidig = B >> 4 112 | if (hidig < 10): 113 | string += str( hidig ) 114 | return string 115 | 116 | def encode_BCD(data=[]): 117 | data = map(lambda x: int(x, 16), data) 118 | acc = [] 119 | while len(data): 120 | head = data[0:2] 121 | data = data[2:] 122 | if (len(head) == 1): 123 | head.append(0xF) 124 | byte = head[0] + 0x10 * head[1] 125 | acc.append(byte) 126 | return acc 127 | 128 | def test(): 129 | assert "00010203" == hex([0,1,2,3]) 130 | assert "ABCD" == hex("ABCD") 131 | assert [0x7F, 0] == hex(0x7F00) 132 | assert 0x00010203 == be_int([0,1,2,3]) 133 | assert 0x03020100 == le_int([0,1,2,3]) 134 | assert "/A/B" == pathstring(["A","B"]) 135 | 136 | assert [0x21,0x43] == encode_BCD("1234") 137 | assert "1234" == decode_BCD([0x21,0x43]) 138 | assert [0x21,0xF3] == encode_BCD("123") 139 | assert "123" == decode_BCD([0x21,0xF3]) 140 | 141 | assert [[1,2],[3,4]] == [x for x in chunks([1,2,3,4], 2)] 142 | 143 | logging.info("hextools: OK") 144 | 145 | 146 | 147 | 148 | if __name__ == '__main__': 149 | test() 150 | -------------------------------------------------------------------------------- /util/terminal_profile.py: -------------------------------------------------------------------------------- 1 | # LICENSE: GPL2 2 | # (c) 2012 Kevin Redon 3 | # (c) 2013 Tom Schouten 4 | 5 | # how to decode the terminal profile 6 | # from ETSI TS 102 223 V11.0.0 - 5.2 7 | # Table Pythonified by Tom from from https://terminal-profile.osmocom.org/terminal_profile.rb 8 | template = [ 9 | ( "Download", [ 10 | (1, "Profile download"), 11 | (1, "3GPP: SMS-PP data download"), 12 | (1, "3GPP: Cell Broadcast data download"), 13 | (1, "Menu selection"), 14 | (1, "3GPP: SMS-PP data download"), 15 | (1, "Timer expiration"), 16 | (1, "3GPP: USSD string data object support in Call Control by USIM"), 17 | (1, "Call Control by NAA") 18 | ]), 19 | ( "Other", [ 20 | (1, "Command result"), 21 | (1, "Call Control by NAA"), 22 | (1, "Call Control by NAA"), 23 | (1, "MO short message control support"), 24 | (1, "Call Control by NAA"), 25 | (1, "UCS2 Entry supported"), 26 | (1, "UCS2 Display supported"), 27 | (1, "Display Text") 28 | ]), 29 | ( "Proactive UICC", [ 30 | (1, "DISPLAY TEXT"), 31 | (1, "GET INKEY"), 32 | (1, "GET INPUT"), 33 | (1, "MORE TIME"), 34 | (1, "PLAY TONE"), 35 | (1, "POLL INTERVAL"), 36 | (1, "POLLING OFF"), 37 | (1, "REFRESH") 38 | ]), 39 | ( "Proactive UICC", [ 40 | (1, "SELECT ITEM"), 41 | (1, "3GPP: SEND SHORT MESSAGE with 3GPP-SMS-TPDU"), 42 | (1, "3GPP: SEND SS"), 43 | (1, "3GPP: SEND USSD"), 44 | (1, "SET UP CALL"), 45 | (1, "SET UP MENU"), 46 | (1, "PROVIDE LOCAL INFORMATION (MCC, MNC, LAC, Cell ID & IMEI)"), 47 | (1, "PROVIDE LOCAL INFORMATION (NMR)") 48 | ]), 49 | ( "Event driven information", [ 50 | (1, "Proactive UICC: SET UP EVENT LIST"), 51 | (1, "MT call"), 52 | (1, "Call connected"), 53 | (1, "Call disconnected"), 54 | (1, "Location status"), 55 | (1, "User activity"), 56 | (1, "Idle screen available"), 57 | (1, "Card reader status") 58 | ]), 59 | ( "Event driven information extensions (for class a)", [ 60 | (1, "Language selection"), 61 | (1, "Browser Termination"), 62 | (1, "Data available"), 63 | (1, "Channel status"), 64 | (1, "Access Technology Change"), 65 | (1, "Display parameters changed"), 66 | (1, "Local Connection"), 67 | (1, "Network Search Mode Change") 68 | ]), 69 | ( "Multiple card proactive commands (for class a) (Proactive UICC)", [ 70 | (1, "POWER ON CARD"), 71 | (1, "POWER OFF CARD"), 72 | (1, "PERFORM CARD APDU"), 73 | (1, "GET READER STATUS (Card reader status)"), 74 | (1, "GET READER STATUS (Card reader identifier)") 75 | ]), 76 | ( "Proactive UICC", [ 77 | (1, "TIMER MANAGEMENT (start, stop)"), 78 | (1, "TIMER MANAGEMENT (get current value)"), 79 | (1, "PROVIDE LOCAL INFORMATION (date, time and time zone)"), 80 | (1, "GET INKEY"), 81 | (1, "SET UP IDLE MODE TEXT"), 82 | (1, "RUN AT COMMAND"), 83 | (1, "SETUP CALL"), 84 | (1, "Call Control by NAA") 85 | ]), 86 | ( "Proactive UICC", [ 87 | (1, "DISPLAY TEXT"), 88 | (1, "SEND DTMF command"), 89 | (1, "PROVIDE LOCAL INFORMATION (NMR)"), 90 | (1, "PROVIDE LOCAL INFORMATION (language)"), 91 | (1, "3GPP: PROVIDE LOCAL INFORMATION, Timing Advance"), 92 | (1, "LANGUAGE NOTIFICATION"), 93 | (1, "LAUNCH BROWSER"), 94 | (1, "PROVIDE LOCAL INFORMATION (Access Technology)") 95 | ]), 96 | ( "Soft keys support (for class d)", [ 97 | (1, "Soft keys support for SELECT ITEM"), 98 | (1, "Soft keys support for SET UP MENU") 99 | ]), 100 | ( "Soft keys information", [ 101 | (8, "Maximum number of soft keys available") 102 | ]), 103 | ( "Bearer Independent protocol proactive commands (for class e) (Proactive UICC)", [ 104 | (1, "OPEN CHANNEL"), 105 | (1, "CLOSE CHANNEL"), 106 | (1, "RECEIVE DATA"), 107 | (1, "SEND DATA"), 108 | (1, "GET CHANNEL STATUS"), 109 | (1, "SERVICE SEARCH"), 110 | (1, "GET SERVICE INFORMATION"), 111 | (1, "DECLARE SERVICE") 112 | ]), 113 | ( "Bearer Independent protocol proactive commands (for class e)", [ 114 | (1, "CSD"), 115 | (1, "GPRS"), 116 | (1, "Bluetooth"), 117 | (1, "IrDA"), 118 | (1, "RS232"), 119 | (3, "Number of channels supported by terminal") 120 | ]), 121 | ( "Screen height", [ 122 | (5, "Number of characters supported down the terminal display"), 123 | (1, "No display capability"), 124 | (1, "No keypad available"), 125 | (1, "Screen Sizing Parameters supported") 126 | ]), 127 | ( "Screen width", [ 128 | (7, "Number of characters supported across the terminal display"), 129 | (1, "Variable size fonts") 130 | ]), 131 | ( "Screen effects", [ 132 | (1, "Display can be resized"), 133 | (1, "Text Wrapping supported"), 134 | (1, "Text Scrolling supported"), 135 | (1, "Text Attributes supported"), 136 | (1, "RFU"), 137 | (3, "Width reduction when in a menu") 138 | ]), 139 | ( "Bearer independent protocol supported transport interface/bearers (for class e)", [ 140 | (1, "TCP, UICC in client mode, remote connection"), 141 | (1, "UDP, UICC in client mode, remote connection"), 142 | (1, "TCP, UICC in server mode"), 143 | (1, "TCP, UICC in client mode, local connection (i.e. class k is supported)"), 144 | (1, "UDP, UICC in client mode, local connection (i.e. class k is supported)"), 145 | (1, "Direct communication channel (i.e. class k is supported)"), 146 | (1, "3GPP: E-UTRAN"), 147 | (1, "3GPP: HSDPA") 148 | ]), 149 | ( "Proactive UICC", [ 150 | (1, "DISPLAY TEXT (Variable Time out)"), 151 | (1, "GET INKEY (help is supported while waiting for immediate response or variable timeout)"), 152 | (1, "USB (Bearer Independent protocol supported bearers, class e)"), 153 | (1, "GET INKEY (Variable Timeout)"), 154 | (1, "PROVIDE LOCAL INFORMATION (ESN)"), 155 | (1, "Call control on GPRS"), 156 | (1, "PROVIDE LOCAL INFORMATION (IMEISV)"), 157 | (1, "PROVIDE LOCAL INFORMATION (Search Mode change)") 158 | ]), 159 | ( "reserved for TIA/EIA-136-C facilities", [ 160 | (4, "Protocol Version support") 161 | ]), 162 | ( "reserved for TIA/EIA/IS-820-A facilities", [] ), 163 | ( "Extended Launch Browser Capability (for class c)", [ 164 | (1, "WML"), 165 | (1, "XHTML"), 166 | (1, "HTML"), 167 | (1, "CHTML") 168 | ]), 169 | ( "", [ 170 | (1, "3GPP: Support of UTRAN PS with extended parameters"), 171 | (1, "Proactive UICC: PROVIDE LOCAL INFORMATION (battery state), (i.e. class g is supported)"), 172 | (1, "Proactive UICC: PLAY TONE (Melody tones and Themed tones supported)"), 173 | (1, "Multi-media Calls in SET UP CALL (if class h supported)"), 174 | (1, "3GPP: Toolkit-initiated GBA"), 175 | (1, "Proactive UICC: RETRIEVE MULTIMEDIA MESSAGE (if class j is supported)"), 176 | (1, "Proactive UICC: SUBMIT MULTIMEDIA MESSAGE (if class j is supported)"), 177 | (1, "Proactive UICC: DISPLAY MULTIMEDIA MESSAGE (if class j is supported)") 178 | ]), 179 | ( "", [ 180 | (1, "Proactive UICC: SET FRAMES (i.e. class i is supported)"), 181 | (1, "Proactive UICC: GET FRAMES STATUS (i.e. class i is supported)"), 182 | (1, "MMS notification download (if class j is supported)"), 183 | (1, "Alpha Identifier in REFRESH command supported by terminal"), 184 | (1, "3GPP: Geographical Location Reporting (if class n is supported)"), 185 | (1, "Proactive UICC: PROVIDE LOCAL INFORMATION (MEID)"), 186 | (1, "Proactive UICC: PROVIDE LOCAL INFORMATION (NMR(UTRAN/E-UTRAN))"), 187 | (1, "3GPP: USSD Data download and application mode") 188 | ]), 189 | ( "(for class i)", [ 190 | (4, "Maximum number of frames supported (including frames created in existing frames)") 191 | ]), 192 | ( "Event driven information extensions", [ 193 | (1, "Event: Browsing status"), 194 | (1, "Event: MMS Transfer status (if class j is supported)"), 195 | (1, "Event: Frame Information changed (i.e. class i is supported)"), 196 | (1, "3GPP: Event: I-WLAN Access status (if class e is supported)"), 197 | (1, "3GPP: Event Network Rejection"), 198 | (1, "Event: HCI connectivity event (i.e. class m is supported)"), 199 | (1, "3GPP: E-UTRAN support in Event Network Rejection)"), 200 | (1, "Multiple access technologies supported in Event Access Technology Change and PROVIDE LOCAL INFORMATION") 201 | ]), 202 | ( "Event driven information extensions", [ 203 | (1, "Event : CSG Cell Selection (if class q is supported"), 204 | (1, "Event: Contactless state request (if class r is supported)"), 205 | (1, "Multiple access technologies supported in Event Access Technology Change and PROVIDE LOCAL INFORMATION") 206 | ]), 207 | ( "Event driven information extensions", [] ), 208 | ( "Text attributes", [ 209 | (1, "Alignment left supported by Terminal"), 210 | (1, "Alignment centre supported by Terminal"), 211 | (1, "Alignment right supported by Terminal"), 212 | (1, "Font size normal supported by Terminal"), 213 | (1, "Font size large supported by Terminal"), 214 | (1, "Font size small supported by Terminal") 215 | ]), 216 | ( "Text attributes", [ 217 | (1, "Style normal supported by Terminal"), 218 | (1, "Style bold supported by Terminal"), 219 | (1, "Style italic supported by Terminal"), 220 | (1, "Style underlined supported by Terminal"), 221 | (1, "Style strikethrough supported by Terminal"), 222 | (1, "Style text foreground colour supported by Terminal"), 223 | (1, "Style text background colour supported by Terminal") 224 | ]), 225 | ( "", [ 226 | (1, "3GPP: I-WLAN bearer support (if class e is supported)"), 227 | (1, "3GPP: Proactive UICC: PROVIDE LOCAL INFORMATION (WSID of the current I-WLAN connection)"), 228 | (1, "TERMINAL APPLICATIONS (i.e. class k is supported)"), 229 | (1, "3GPP: Steering of Roaming REFRESH support"), 230 | (1, "Proactive UICC: ACTIVATE (i.e. class l is supported)"), 231 | (1, "3GPP: Proactive UICC: GEOGRAPHICAL LOCATION REQUEST (if class n is supported)"), 232 | (1, "Proactive UICC: PROVIDE LOCAL INFORMATION (Broadcast Network Information) (i.e. class o is supported)"), 233 | (1, "3GPP: Steering of Roaming for I-WLAN REFRESH support") 234 | ]), 235 | ( "", [ 236 | (1, "Proactive UICC: Contactless State Changed (if class r is supported)"), 237 | (1, "3GPP: Support of CSG cell discovery (if class q is supported)"), 238 | (1, "Confirmation parameters supported for OPEN CHANNEL in Terminal Server Mode"), 239 | (1, "3GPP: Communication Control for IMS"), 240 | (1, "Support of CAT over the modem interface (if class s is supported)"), 241 | (1, "3GPP Support for Incoming IMS Data event (if classes e and t are supported)"), 242 | (1, "3GPP Support for IMS Registration event (if classes e and t are supported)"), 243 | (1, "Proactive UICC: Profile Container, Envelope Container, COMMAND CONTAINER and ENCAPSULATED SESSION CONTROL (if class u is supported)") 244 | ]), 245 | ( "", [ 246 | (1, "3GPP: Support of IMS as a bearer for BIP (if classes e and t are supported)") 247 | ]) 248 | ] 249 | 250 | 251 | def parse(bytes): 252 | for (title, fields), byte in zip(template, bytes): 253 | print title 254 | for nb_bits,name in fields: 255 | f = byte & nb_bits 256 | logging.info(" %d %s" % (f, name)) 257 | byte = byte >> nb_bits 258 | 259 | 260 | 261 | 262 | def test(): 263 | from util import hextools 264 | parse(hextools.bytes("FFFFFFFF1F0000DFD7030A000000000600000000")) # N1 265 | 266 | if __name__ == '__main__': 267 | test() 268 | -------------------------------------------------------------------------------- /util/types_g.py: -------------------------------------------------------------------------------- 1 | # LICENSE: GPL2 2 | # (c) 2013 Tom Schouten 3 | # (c) 2014 Kamil Wartanowicz 4 | 5 | # Central place to store all symbolic<->numeric tag conversions. 6 | 7 | # Build bi-directional lookup with some automatic conversion 8 | 9 | # Sentinel 10 | class empty_table: 11 | def __getattr__(self, key): 12 | raise Exception("symbolic key not found: 0x%02X" % key) 13 | def __getitem__(self, key): 14 | raise Exception("numeric key not found: 0x%02X" % key) 15 | 16 | class sym_table: 17 | def __init__(self, num_to_sym, parent = empty_table()): 18 | self.parent = parent 19 | self.n2s = num_to_sym 20 | self.s2n = dict([(s,n) for n,s in num_to_sym.items()]) 21 | # table.key 22 | def __getattr__(self, key): 23 | try: 24 | return self.s2n[key] 25 | except: 26 | return self.parent.__getattr__(key) 27 | # table[key] 28 | def __getitem__(self, key): 29 | try: 30 | return self.n2s[key] 31 | except: 32 | return self.parent.__getitem__(key) 33 | 34 | # ETSI TS 102 221 - 10.2.1 Status conditions returned by the UICC 35 | sw1 = sym_table({ 36 | 0x61 : 'RESPONSE_DATA_AVAILABLE_3G', 37 | 0x63 : 'CODE_ATTEMPTS_LEFT', 38 | 0x67 : 'INCORRECT_PARAMETER_P3', 39 | 0x6C : 'REPEAT_COMMAND_WITH_LE', 40 | 0x6D : 'UNKNOWN_INSTRUCTION_CODE', 41 | 0x6E : 'WRONG_INSTRUCTION_CLASS', 42 | 0x6F : 'TECHNICAL_PROBLEM', 43 | 0x91 : 'NO_ERROR_PROACTIVE_DATA', 44 | 0x92 : 'NO_ERROR_DATA_TRANSFER', 45 | 0x9F : 'RESPONSE_DATA_AVAILABLE_2G', 46 | }) 47 | 48 | sw = sym_table({ 49 | 0x9000 : 'NO_ERROR', 50 | 0x9300 : 'TOOLKIT_BUSY', 51 | # Warnings (state of non-volatile memory unchanged). 52 | 0x6200 : 'WARNING_CARD_STATE_UNCHANGED', # (No information given) 53 | 0x6282 : 'END_OF_FILE_REACHED', 54 | 0x6283 : 'SELECTED_FILE_INVALIDATED', 55 | 0x6285 : 'SELECTED_FILE_TERMINATED', 56 | 0x62F1 : 'MORE_DATA_AVAILABLE', 57 | 0x62F2 : 'MORE_DATA_AVAILABLE_AND_PROACTIVE_COMMAND_PENDING', 58 | 0x62F3 : 'RESPONSE_DATA_AVAILABLE', 59 | # Warnings (state of non-volatile memory changed). 60 | 0x63F1 : 'MORE_DATA_EXPECTED', 61 | 0x63F2 : 'MORE_DATA_EXPECTED_AND_PROACTIVE_COMMAND_PENDING', 62 | 0x63CF : 'VERIFICATION_FAILED', 63 | # Execution errors (state of non-volatile memory unchanged). 64 | 0x6400 : 'ERROR_CARD_STATE_UNCHANGED', # (No information given) 65 | # Execution errors (state of non-volatile memory changed). 66 | 0x6500 : 'ERROR_CARD_STATE_CHANGED', # (No information given) 67 | 0x6581 : 'MEMORY_PROBLEM', 68 | # Checking errors. 69 | 0x6700 : 'WRONG_LENGTH', 70 | # Functions in CLA not supported. 71 | 0x6800 : 'FUNCTION_IN_CLA_NOT_SUPPORTED', # (No information given) 72 | 0x6881 : 'LOGICAL_CHANNEL_NOT_SUPPORTED', 73 | 0x6882 : 'SECURE_MESSAGING_NOT_SUPPORTED', 74 | # Command not allowed. 75 | 0x6900 : 'COMMAND_NOT_ALLOWED', # (No information given) 76 | 0x6981 : 'COMMAND_INCOPATIBLE_WITH_FILE_STRUCTURE', 77 | 0x6982 : 'SECURITY_STATUS_NOT_SATISFIED', 78 | 0x6983 : 'AUTHENTICATION_METHOD_BLOCKED', 79 | 0x6984 : 'REFERNCE_DATA_INVALIDATE', 80 | 0x6985 : 'CONDITIONS_OF_USE_NOT_SATISFIED', 81 | 0x6986 : 'COMMAND_NOT_ALLOWED_NO_EF_SELECTED', 82 | 0x6989 : 'COMMAND_NOT_ALLOWED_SECURE_CHANNEL_SECURITY_NOT_SATISFIED', 83 | 0x6999 : 'APPLET_SELECT_FAILED', 84 | # Wrong parameter(s) P1-P2. 85 | 0x6A80 : 'INCORRECT_PARAMETER_IN_DATA_FIELD', 86 | 0x6A81 : 'INVALID_INSTRUCTION', 87 | 0x6A82 : 'FILE_NOT_FOUND', 88 | 0x6A83 : 'INVALID_DATA_ADDRESS', 89 | 0x6A84 : 'NOT_ENOUGH_MEMORY_SPACE', 90 | 0x6A86 : 'INCORRECT_PARAMETERS_P1_P2', # for SFI mode (out of range) 91 | 0x6A87 : 'INCORRECT_PARAMETER_P3', # Lc 92 | 0x6A88 : 'REFERENCE_DATA_NOT_FOUND', 93 | 0x6A89 : 'FILE_ID_ALREADY_EXISTS', 94 | 0x6A8A : 'DF_NAME_ALREADY_EXISTS', 95 | # Checking errors. 96 | 0x6B00 : 'WRONG_PARAMETERS_P1_P2', 97 | 0x6D00 : 'INVALID_INSTRUCTION_OR_NOT_SUPPORTED', 98 | 0x6E00 : 'CLASS_NOT_SUPPORTED', 99 | 0x6F00 : 'TECHNICAL_PROBLEM', 100 | # 2G/GSM codes. 101 | 0x9400 : 'GSM_COMMAND_NOT_ALLOWED_NO_EF_SELECTED', 102 | 0x9402 : 'GSM_INVALID_DATA_ADDRESS', #SC 103 | 0x9404 : 'GSM_FILE_NOT_FOUND', 104 | 0x9408 : 'GSM_COMMAND_INCOPATIBLE_WITH_FILE_STRUCTURE', 105 | 0x9802 : 'GSM_CHV_NOT_ACTIVE', 106 | 0x9802 : 'GSM_CHV_IS_INVALIDATED', 107 | 0x9804 : 'GSM_ACCESS_CONDITION_NOT_FULFILLED', 108 | 0x9808 : 'GSM_CHV_ALREADY_VALIDATED', 109 | 0x9810 : 'GSM_SELECTED_FILE_INVALIDATED', 110 | 0x9840 : 'GSM_UNSUCCESSFUL_USER_PIN_VERIFICATION', 111 | # Applications errors. 112 | 0x9850 : 'INCREASE_CANNOT_BE_PERFORMED_MAX_REACHED', 113 | 0x9862 : 'AUTHENTICATION_ERROR_APPLICATION_SPECIFIC', 114 | 0x9863 : 'SECURITY_SESSION_OR_ASSOCIATION_EXPIRED', 115 | }) 116 | 117 | iso7816 = sym_table({ 118 | 0x04 : 'DEACTIVATE_FILE', 119 | 0x0E : 'ERASE_BINARY', 120 | 0x10 : 'TERMINAL_PROFILE', 121 | 0x12 : 'FETCH', 122 | 0x14 : 'TERMINAL_RESPONSE', 123 | 0x20 : 'VERIFY_PIN', 124 | 0x24 : 'CHANGE_PIN', 125 | 0x26 : 'DISABLE_PIN', 126 | 0x28 : 'ENABLE_PIN', 127 | 0x2C : 'UNBLOCK_PIN', 128 | 0x32 : 'INCREASE', 129 | 0x44 : 'ACTIVATE_FILE', 130 | 0x70 : 'MANAGE_CHANNEL', 131 | 0x73 : 'MANAGE_SECURE_CHANNEL', 132 | 0x75 : 'TRANSACT_DATA', 133 | 0x82 : 'EXTERNAL_AUTHENTICATE', 134 | 0x84 : 'GET_CHALLENGE', 135 | 0x88 : 'INTERNAL_AUTHENTICATE', 136 | 0x89 : 'INTERNAL_AUTHENTICATE2', 137 | 0xA2 : 'SEARCH_RECORD', 138 | 0xA4 : 'SELECT_FILE', 139 | 0xAA : 'TERMINAL_CAPABILITY', 140 | 0xB0 : 'READ_BINARY', 141 | 0xB2 : 'READ_RECORD', 142 | 0xC0 : 'GET_RESPONSE', 143 | 0xC2 : 'ENVELOPE', 144 | 0xCB : 'RETRIEVE_DATA', 145 | 0xCA : 'GET_DATA', 146 | 0xD0 : 'WRITE_BINARY', 147 | 0xD2 : 'WRITE_RECORD', 148 | 0xD4 : 'RESIZE_FILE', 149 | 0xD6 : 'UPDATE_BINARY', 150 | 0xDA : 'PUT_DATA', 151 | 0xDB : 'SET_DATA', 152 | 0xDC : 'UPDATE_RECORD', 153 | 0xE0 : 'CREATE_FILE', 154 | 0xE2 : 'APPEND_RECORD', 155 | 0xE4 : 'DELETE_FILE', 156 | 0xF2 : 'STATUS', 157 | }) 158 | 159 | # ETSI TS 102 223 - 9.4 160 | proactive_command = sym_table({ 161 | 0x03 : 'POLL_INTERVAL', 162 | 0x13 : 'SEND_SHORT_MESSAGE', 163 | 0x16 : 'GEOGRAPHICAL_LOCATION_REQUEST', 164 | }) 165 | 166 | # ETSI TS 101 220 - 7.2 Assigned TLV tag values 167 | # Card application toolkit templates 168 | cat = sym_table({ 169 | 0xD0 : 'PROACTIVE_COMMAND', 170 | 0xD1 : 'SMS_PP_DOWNLOAD', 171 | }) 172 | 173 | # ETSI TS 101 220 - 7.2 Assigned TLV tag values 174 | # Card application toolkit data objects 175 | cat_data = sym_table({ 176 | 0x81 : 'COMMAND_DETAILS', 177 | 0x82 : 'DEVICE_IDENTITY', 178 | 0x0B : 'SMS_PDU', 179 | }) 180 | 181 | # ETSI TS 101 220 - 7.2 Assigned TLV tag values 182 | # Proprietary information ('A5') 183 | properietaryTag = sym_table({ 184 | 0x80 : 'UICC_CHARACTERISTICS', 185 | 0x81 : 'APP_POWER_CONSUMPTION', 186 | 0x82 : 'MIN_APP_CLOCK_FREQ', 187 | 0x83 : 'AMOUNT_OF_AVAIL_MEMORY', 188 | 0x84 : 'FILE_DETAILS', 189 | 0x85 : 'RESERVED_FILE_SIZE', 190 | 0x86 : 'MAXIMUM_FILE_SIZE', 191 | 0x87 : 'SUPPORTED_SYSTEM_COMMANDS', 192 | 0x88 : 'SPECIFIC_UICC_ENV_CONDITIONS' 193 | }) 194 | 195 | # ETSI TS 101 220 - 7.2 Assigned TLV tag values 196 | # PIN Status data objects ('C6') 197 | pinStatusTag = sym_table({ 198 | 0x83 : 'KEY_REFERENCE', 199 | 0x90 : 'PIN_STATUS', 200 | 0x95 : 'USAGE_QUALIFIER', 201 | }) 202 | 203 | #ts_102221v080200p 11.1.1.3 Response Data 204 | selectTag = sym_table({ 205 | 0x62 : "FCP", 206 | 0x80 : "FILE_SIZE", 207 | 0x81 : "TOTAL_FILE_SIZE", 208 | 0x82 : "FILE_DESCRIPTOR", 209 | 0x83 : "FILE_IDENTIFIER", 210 | 0x84 : "DF_NAME", 211 | 0x88 : "SHORT_FILE_IDENTIFIER", 212 | 0x8A : "LIFE_CYCLE_STATUS", 213 | 0x8B : "SECURITY_ATRIBUTES_COMPACT", 214 | 0x8C : "SECURITY_ATRIBUTES_REF_EXPANDED", 215 | 0xAB : "SECURITY_ATRIBUTES_EXPANDED", 216 | 0xA5 : "PROPRIETARY_INF", 217 | 0xC6 : "PIN_STATUS_TEMPLATE", 218 | }) 219 | 220 | #ts_102221v080200p Table 11.5: File descriptor byte 221 | fileDecriptorMask = sym_table({ 222 | 0b01000000 : "FILE_ACCESSIBILITY", 223 | 0b00111000 : "FILE_TYPE", 224 | 0b00000111 : "EF_STRUCTURE" 225 | }) 226 | 227 | #ts_102221v080200p Table 11.5: File descriptor byte 228 | fileDescriptor = sym_table({ 229 | 0b00000000 : "NOT_SHAREABLE", 230 | 0b01000000 : "SHAREABLE", 231 | 0b00000000 : "WORKING_EF", 232 | 0b00010000 : "INTERNAL_EF", 233 | 0b00111000 : "DF_OR_ADF", 234 | 0b00000000 : "NO_INFORMATION_GIVEN", #b4-b6 not all set to 1 235 | 0b00000001 : "TRANSPARENT_STRUCTURE", 236 | 0b00000010 : "LINEAR_FIXED_STRUCTURE", 237 | 0b00000110 : "CYCLIC_STRUCTURE", 238 | #refactor, cannot be the same value as TRANSPARENT_STRUCTURE 239 | #0b00000001 : "BER_TLV_STRUCTURE", #b4-b6 all set to 1 240 | }) 241 | 242 | # 3G specific 243 | fileSearchType = sym_table({ 244 | 0b00000100 : "FORWARD_SEARCH_FROM_P1", 245 | 0b00000101 : "BACKWARD_SEARCH_FROM_P1", 246 | 0b00000110 : "ENHENCED_SEARCH", 247 | 0b00000111 : "PROPERIETARY_SEARCH" 248 | }) 249 | 250 | fileSearchIndication = sym_table({ 251 | 0b00000100 : "FORWARD_SEARCH_FROM_P1", 252 | 0b00000101 : "BACKWARD_SEARCH_FROM_P1", 253 | 0b00000110 : "FORWARD_SEARCH_FROM_NEXT_RECORD", 254 | 0b00000111 : "BACKWARD_SEARCH_FROM_PREVIOUS_RECORD" 255 | }) 256 | 257 | searchStartMode = sym_table({ 258 | 0b00000000 : "START_FROM_OFFSET", 259 | 0b00001000 : "START_FROM_VALUE", 260 | }) 261 | 262 | # 2G specific 263 | fileSeekType = sym_table({ 264 | 0x00 : "TYPE_1", 265 | 0x10 : "TYPE_2" 266 | }) 267 | 268 | # with x='0' specifies type 1 and x='1' specifies type 2 of the SEEK command 269 | fileSeekMode = sym_table({ 270 | 0x00 : "FROM_THE_BEGINNING_FORWARD", 271 | 0x01 : "FROM_THE_END_BACKWARD", 272 | 0x02 : "FROM_THE_NEXT_LOCATION_FORWARD", 273 | 0x03 : "FROM_THE_PREVIOUS_LOCATION_BACKWARD", 274 | }) 275 | 276 | verifyChvP2_3g = sym_table({ 277 | 0x01 : "chv1", # PIN1 278 | 0x81 : "chv2", # PIN2 279 | 0x0A : "adm1", 280 | 0x0B : "adm2", 281 | 0x0C : "adm3", 282 | 0x0D : "adm4", 283 | }) 284 | 285 | # 2G specific 286 | verifyChvP2_2g = sym_table({ 287 | 0x01 : "chv1", 288 | 0x02 : "chv2", 289 | 0x0B : "adm1", 290 | }) 291 | 292 | # 2G specific 293 | verifyChvUnblockP2 = sym_table({ 294 | 0x00 : "chv1", 295 | 0x02 : "chv2" 296 | }) 297 | 298 | readRecordSelect = sym_table({ 299 | 0b00000000 : "CURRENT_EF", 300 | 0b11111000 : "SFI", 301 | }) 302 | 303 | readRecordMode = sym_table({ 304 | 0b00000010 : "NEXT_RECORD", 305 | 0b00000011 : "PREVIOUS_RECORD", 306 | 0b00000100 : "ABSOLUTE_OR_CURRENT", 307 | }) 308 | 309 | binaryCmdP1 = sym_table({ 310 | 0b10000000 : "SFI_MODE", 311 | 0b00000111 : "SFI_MASK", 312 | }) 313 | 314 | adfName = sym_table({ 315 | 0x02 : 'ADF_USIM', 316 | 0x04 : 'ADF_ISIM' 317 | }) 318 | --------------------------------------------------------------------------------