# -*- coding: UTF-8 -*- """ card: Library adapted to request (U)SIM cards and other types of telco cards. Copyright (C) 2010 Benoit Michau This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ ################################# # Python library to work on # SIM card # communication based on ISO7816 card # # needs pyscard from: # http://pyscard.sourceforge.net/ ################################# from card.ICC import ISO7816 from card.FS import SIM_FS, MF_FS from card.utils import * SIM_service_table = { 1 : "CHV1 disable function", 2 : "Abbreviated Dialling Numbers (ADN)", 3 : "Fixed Dialling Numbers (FDN)", 4 : "Short Message Storage (SMS)", 5 : "Advice of Charge (AoC)", 6 : "Capability Configuration Parameters (CCP)", 7 : "PLMN selector", 8 : "RFU", 9 : "MSISDN", 10 : "Extension1", 11 : "Extension2", 12 : "SMS Parameters", 13 : "Last Number Dialled (LND)", 14 : "Cell Broadcast Message Identifier", 15 : "Group Identifier Level 1", 16 : "Group Identifier Level 2", 17 : "Service Provider Name", 18 : "Service Dialling Numbers (SDN)", 19 : "Extension3", 20 : "RFU", 21 : "VGCS Group Identifier List (EFVGCS and EFVGCSS)", 22 : "VBS Group Identifier List (EFVBS and EFVBSS)", 23 : "enhanced Multi-Level Precedence and Pre-emption Service", 24 : "Automatic Answer for eMLPP", 25 : "Data download via SMS-CB", 26 : "Data download via SMS-PP", 27 : "Menu selection", 28 : "Call control", 29 : "Proactive SIM", 30 : "Cell Broadcast Message Identifier Ranges", 31 : "Barred Dialling Numbers (BDN)", 32 : "Extension4", 33 : "De-personalization Control Keys", 34 : "Co-operative Network List", 35 : "Short Message Status Reports", 36 : "Network's indication of alerting in the MS ", 37 : "Mobile Originated Short Message control by SIM ", 38 : "GPRS", 39 : "Image (IMG)", 40 : "SoLSA (Support of Local Service Area)", 41 : "USSD string data object supported in Call Control", 42 : "RUN AT COMMAND command", 43 : "User controlled PLMN Selector with Access Technology", 44 : "Operator controlled PLMN Selector with Access Technology", 45 : "HPLMN Selector with Access Technology", 46 : "CPBCCH Information", 47 : "Investigation Scan", 48 : "Extended Capability Configuration Parameters", 49 : "MExE", 50 : "RPLMN last used Access Technology", 51 : "PLMN Network Name", 52 : "Operator PLMN List", 53 : "Mailbox Dialling Numbers ", 54 : "Message Waiting Indication Status", 55 : "Call Forwarding Indication Status", 56 : "Service Provider Display Information", } class SIM(ISO7816): """ define attributes, methods and facilities for ETSI / 3GPP SIM card check SIM specifications in ETSI TS 102.221 and 3GPP TS 51.011 inherit methods and objects from ISO7816 class use self.dbg = 1 or more to print live debugging information """ def __init__(self, atr = None): """ initialize like an ISO7816-4 card with CLA=0xA0 can also be used for USIM working in SIM mode, """ ISO7816.__init__(self, atr, CLA=0xA0) if self.dbg >= 2: log(3, '(SIM.__init__) type definition: %s' % type(self)) log(3, '(SIM.__init__) CLA definition: %s' % hex(self.CLA)) @staticmethod def sw_status(sw1, sw2): """ sw_status(sw1=int, sw2=int) -> string extends SW status bytes interpretation from ISO7816 with ETSI / 3GPP SW codes helps to speak with the smartcard! """ status = ISO7816.sw_status(sw1, sw2) if sw1 == 0x91: status = 'normal processing, with extra info ' \ 'containing a command for the terminal: length of the ' \ 'response data %d' % sw2 elif sw1 == 0x9E: status = 'normal processing, SIM data download ' \ 'error: length of the response data %d' % sw2 elif sw1 == 0x9F: status = 'normal processing: length of the ' \ 'response data %d' % sw2 elif (sw1, sw2) == (0x93, 0x00): status = 'SIM application toolkit ' \ 'busy, command cannot be executed at present' elif sw1 == 0x92 : status = 'memory management' if sw2 < 16: status += ': command successful but after %d '\ 'retry routine' % sw2 elif sw2 == 0x40: status += ': memory problem' elif sw1 == 0x94: status = 'referencing management' if sw2 == 0x00: status += ': no EF selected' elif sw2 == 0x02: status += ': out of range (invalid address)' elif sw2 == 0x04: status += ': file ID or pattern not found' elif sw2 == 0x08: status += ': file inconsistent with the command' elif sw1 == 0x98: status = 'security management' if sw2 == 0x02: status += ': no CHV initialized' elif sw2 == 0x04: status += ': access condition not fulfilled, ' \ 'at least 1 attempt left' elif sw2 == 0x08: status += ': in contradiction with CHV status' elif sw2 == 0x10: status += ': in contradiction with ' \ 'invalidation status' elif sw2 == 0x40: status += ': unsuccessful CHV verification, ' \ 'no attempt left' elif sw2 == 0x50: status += ': increase cannot be performed, ' \ 'max value reached' elif sw2 == 0x62: status += ': authentication error, ' \ 'application specific' elif sw2 == 0x63: status += ': security session expired' return status def verify_pin(self, pin='', pin_type=1): """ verify CHV1 (PIN code) or CHV2 with VERIFY APDU command call ISO7816 VERIFY method """ if pin_type in [1, 2] and type(pin) is str and \ len(pin) == 4 and 0 <= int(pin) < 10000: PIN = [ord(i) for i in pin] + [0xFF, 0xFF, 0xFF, 0xFF] self.coms.push( self.VERIFY(P2=pin_type, Data=PIN) ) else: if self.dbg: log(2, '(verify_pin) bad input parameters') def disable_pin(self, pin='', pin_type=1): """ disable CHV1 (PIN code) or CHV2 with DISABLE_CHV APDU command TIP: do it as soon as you can when you are working with a SIM / USIM card for which you know the PIN! call ISO7816 DISABLE method """ if pin_type in [1, 2] and type(pin) is str and \ len(pin) == 4 and 0 <= int(pin) < 10000: PIN = [ord(i) for i in pin] + [0xFF, 0xFF, 0xFF, 0xFF] self.coms.push( self.DISABLE_CHV(P2=pin_type, Data=PIN) ) else: if self.dbg: log(2, '(disable_pin) bad input parameters') def enable_pin(self, pin='', pin_type=1): """ enable CHV1 (PIN code) or CHV2 with ENABLE_CHV APDU command call ISO7816 ENABLE method """ if pin_type in [1, 2] and type(pin) is str and \ len(pin) == 4 and 0 <= int(pin) < 10000: PIN = [ord(i) for i in pin] + [0xFF, 0xFF, 0xFF, 0xFF] self.coms.push( self.ENABLE_CHV(P2=pin_type, Data=PIN) ) else: if self.dbg: log(2, '(enable_pin) bad input parameters') def unblock_pin(self, pin_type=1, unblock_pin=''): """ WARNING: not correctly implemented!!! and PUK are in general 8 nums... TODO: make it correctly! APDU Tx de-activated unblock CHV1 (PIN code) or CHV2 with UNBLOCK_CHV APDU command and set 0000 value for new PIN call ISO7816 UNBLOCK_CHV method """ log(1, '(unblock_pin) not implemented: aborting') return #if pin_type == 1: # pin_type = 0 if pin_type in [0, 2] and type(unblock_pin) is str and \ len(unblock_pin) == 4 and 0 <= int(unblock_pin) < 10000: UNBL_PIN = [ord(i) for i in unblock_pin] + [0xFF, 0xFF, 0xFF, 0xFF] #self.coms.push( self.UNBLOCK_CHV(P2=pin_type, Lc=0x10, \ # Data=UNBL_PIN + \ # [0x30, 0x30, 0x30, 0x30, 0xFF, 0xFF, 0xFF, 0xFF]) ) else: if self.dbg: log(2, '(unblock_pin) bad input parameters') #return self.UNBLOCK_CHV(P2=pin_type) def parse_file(self, Data=[]): """ parse_file(Data=[0x12, 0x34, 0x56, 0x89]) -> dict(file) parses a list of bytes returned when selecting a file interprets the content of some informative bytes for right accesses, type / format of file... see TS 51.011 works over the SIM file structure """ fil = {} fil['Size'] = Data[2]*0x100 + Data[3] fil['File Identifier'] = Data[4:6] fil['Type'] = ('RFU', 'MF', 'DF', '', 'EF')[Data[6]] fil['Length'] = Data[12] if fil['Type'] == 'MF' or fil['Type'] == 'DF': fil['DF_num'] = Data[14] fil['EF_num'] = Data[15] fil['codes_num'] = Data[16] fil['CHV1'] = ('not initialized','initialized')\ [Data[18] >> 7]\ + ': %d attempts remain' % (Data[18] & 0x0F) fil['unblock_CHV1'] = ('not initialized','initialized')\ [Data[19] >> 7]\ + ': %d attempts remain' % (Data[19] & 0x0F) fil['CHV2'] = ('not initialized','initialized')\ [Data[20] >> 7]\ + ': %d attempts remain' % (Data[20] & 0x0F) fil['unblock_CHV2'] = ('not initialized','initialized')\ [Data[21] >> 7]\ + ': %d attempts remain' % (Data[21] & 0x0F) if len(Data) > 23: fil['Adm'] = Data[23:] elif fil['Type'] == 'EF': cond = ('ALW', 'CHV1', 'CHV2', 'RFU', 'ADM_4', 'ADM_5', 'ADM_6', 'ADM_7', 'ADM_8', 'ADM_9', 'ADM_A', 'ADM_B', 'ADM_C', 'ADM_D', 'ADM_E', 'NEW') fil['UPDATE'] = cond[Data[8] & 0x0F] fil['READ'] = cond[Data[8] >> 4] fil['INCREASE'] = cond[Data[9] >> 4] fil['INVALIDATE'] = cond[Data[10] & 0x0F] fil['REHABILITATE'] = cond[Data[10] >> 4] fil['Status'] = ('not read/updatable when invalidated', 'read/updatable when invalidated')\ [byteToBit(Data[11])[5]] \ + (': invalidated',': not invalidated')\ [byteToBit(Data[11])[7]] fil['Structure'] = ('transparent', 'linear fixed', '', 'cyclic')\ [Data[13]] if fil['Structure'] == 'cyclic': fil['INCREASE'] = byteToBit(Data[7])[1] if len(Data) > 14: fil['Record Length'] = Data[14] return fil def run_gsm_alg(self, RAND=16*[0x00]): """ self.run_gsm_alg( RAND ) -> ( SRES, Kc ) RAND : list of bytes, length 16 SRES : list of bytes, length 4 Kc : list of bytes, length 8 runs GSM authentication algorithm: accepts any kind of RAND (old GSM fashion) feed with RAND 16 bytes value returns a list with SRES and Kc, or None on error """ if len(RAND) != 16: if self.dbg: log(1, '(run_gsm_alg) bad RAND value: aborting') return None # select DF_GSM directory self.select([0x7F, 0x20]) if self.coms()[2] != (0x90, 0x00): if self.dbg >= 2: log(3, '(run_gsm_alg) %s' % self.coms()) return None # run authentication self.coms.push(self.INTERNAL_AUTHENTICATE(P1=0x00, P2=0x00, Data=RAND)) if self.coms()[2][0] != 0x9F: if self.dbg >= 2: log(3, '(run_gsm_alg) %s' % self.coms()) return None # get authentication response self.coms.push(self.GET_RESPONSE(Le=self.coms()[2][1])) if self.coms()[2] != (0x90, 0x00): if self.dbg >= 2: log(3, '(run_gsm_alg) %s' % self.coms()) return None SRES, Kc = self.coms()[3][0:4], self.coms()[3][4:] return [ SRES, Kc ] def get_imsi(self): """ self.get_imsi() -> string(IMSI) reads IMSI value at address [0x6F, 0x07] returns IMSI string on success or None on error """ # select DF_GSM for SIM card self.select([0x7F, 0x20]) if self.coms()[2] != (0x90, 0x00): if self.dbg >= 2: log(3, '(get_imsi) %s' % self.coms()) return None # select IMSI file imsi = self.select([0x6F, 0x07]) if self.coms()[2] != (0x90, 0x00): if self.dbg >= 2: log(3, '(get_imsi) %s' % self.coms()) return None # and parse the received data into the IMSI structure if 'Data' in imsi.keys() and len(imsi['Data']) == 9: return decode_BCD(imsi['Data'])[3:] # if issue with the content of the DF_IMSI file if self.dbg >= 2: log(3, '(get_imsi) %s' % self.coms()) return None def get_services(self): """ self.get_services() -> None reads SIM Service Table at address [0x6F, 0x38] returns list of services allowed / activated """ # select DF_GSM for SIM card self.select([0x7F, 0x20]) if self.coms()[2] != (0x90, 0x00): if self.dbg >= 2: log(3, '(get_services) %s' % self.coms()) return None # select SST file sst = self.select([0x6F, 0x38]) if self.coms()[2] != (0x90, 0x00): if self.dbg >= 2: log(3, '(get_services) %s' % self.coms()) return None # parse data and prints corresponding services if 'Data' in sst.keys() and len(sst['Data']) >= 2: return self.get_services_from_sst(sst['Data']) def read_services(self): """ self.read_services() -> None reads SIM Service Table at address [0x6F, 0x38] prints services allowed / activated returns None """ serv = self.get_services() for s in serv: print(s) def get_services_from_sst(self, sst=[0, 0]): services = [] cnt = 0 for B in sst: # 2 bits per service -> 4 services per byte for i in range(0, 7, 2): cnt += 1 if B & 2**i: info = 'allocated' if B & (2**i+1): info += ' | activated' if cnt in SIM_service_table: services.append('%i : %s : %s' \ % (cnt, SIM_service_table[cnt], info)) else: services.append('%i : %s' % (cnt, info)) return services def explore_fs(self, filename='sim_fs', depth=True, emul=False): """ self.explore_fs(self, filename='sim_fs') -> void filename: file to write in information found depth: depth in recursivity, True=infinite brute force all file addresses from MF recursively (until no more DF are found) write information on existing DF and file in the output file """ simfs_entries = MF_FS.keys() if not emul: self.explore_DF([], None, depth) fd = open(filename, 'w') fd.write('\n### MF ###\n') f = self.select() write_dict(f, fd) fd.write('\n') # for f in self.FS: path = tuple(f['Absolut Path']) if path in simfs_entries: f['Name'] = MF_FS[path] write_dict(f, fd) fd.write('\n') fd.close() def get_ICCID(self): # select MF self.select([0x3F, 0x0]) if self.coms()[2] != (0x90, 0x00): if self.dbg >= 2: log(3, '(get_ICCID) %s' % self.coms()) return None # select IMSI file iccid = self.select([0x2F, 0xE2]) if self.coms()[2] != (0x90, 0x00): if self.dbg >= 2: log(3, '(get_ICCID) %s' % self.coms()) return None # and parse the received data into the IMSI structure if 'Data' in iccid.keys() and len(iccid['Data']) >= 10: return decode_BCD(iccid['Data']) # if issue with the content of the ICCID file if self.dbg >= 2: log(3, '(get_ICCID) %s' % self.coms()) return None