diff options
Diffstat (limited to 'card/USIM.py')
-rw-r--r-- | card/USIM.py | 402 |
1 files changed, 402 insertions, 0 deletions
diff --git a/card/USIM.py b/card/USIM.py new file mode 100644 index 0000000..d8cbb85 --- /dev/null +++ b/card/USIM.py @@ -0,0 +1,402 @@ +""" +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 +# USIM card +# communication based on ISO7816 card +# and commands and formats based on UICC card +# +# needs pyscard from: +# http://pyscard.sourceforge.net/ +################################# + +from card.ICC import UICC, ISO7816 +from card.FS import * +from card.utils import * + + +class USIM(UICC): + ''' + defines attributes, methods and facilities for ETSI / 3GPP USIM card + check USIM specifications in 3GPP TS 31.102 + + inherits (eventually overrides) methods and objects from UICC class + use self.dbg = 1 or more to print live debugging information + ''' + + def __init__(self): + ''' + initializes like an ISO7816-4 card with CLA=0x00 + and checks available AID (Application ID) read from EF_DIR + + initializes on the MF + ''' + # initialize like a UICC + ISO7816.__init__(self, CLA=0x00) + self.AID = [] + if self.dbg: + print '[DBG] type definition: %s' % type(self) + print '[DBG] CLA definition: %s' % hex(self.CLA) + + # USIM selection from AID + print '[+] UICC AID found:' + self.get_AID() + for aid in self.AID: + if tuple(aid[0:5]) == (0xA0, 0x00, 0x00, 0x00, 0x87) \ + and tuple(aid[5:7]) == (0x10, 0x02) : + usim = self.select( Data=aid, typ='aid') + if usim is None: + print '[+] USIM AID selection failed' + else: + print '[+] USIM AID selection succeeded\n' + + def get_imsi(self): + ''' + get_imsi() -> string(IMSI) + + reads IMSI value at address [0x6F, 0x07] + returns IMSI string on success or None on error + ''' + # select IMSI file + imsi = self.select([0x6F, 0x07]) + if imsi is None: + 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: + print '[DBG] %s' % self.coms() + return None + + def get_CS_keys(self): + ''' + get_CS_keys() -> [KSI, CK, IK] + + reads CS UMTS keys at address [0x6F, 0x08] + returns list of 3 keys, each are list of bytes, on success + (or eventually the whole file dict if the format is strange) + or None on error + ''' + EF_KEYS = self.select( [0x6F, 0x08] ) + if self.coms()[2] == (0x90, 0x00): + if len(EF_KEYS['Data']) == 33: + KSI, CK, IK = ( EF_KEYS['Data'][0:1], + EF_KEYS['Data'][1:17], + EF_KEYS['Data'][17:33]) + print '[+] Successful CS keys selection: Get [KSI, CK, IK]' + return [KSI, CK, IK] + else: + return EF_KEYS + return None + + def get_PS_keys(self): + ''' + get_PS_keys() -> [KSI, CK_PS, IK_PS] + + reads PS UMTS keys at address [0x6F, 0x09] + returns list of 3 keys, each are list of bytes, on success + (or eventually the whole file dict if the format is strange) + or None on error + ''' + EF_KEYSPS = self.select( [0x6F, 0x09] ) + if self.coms()[2] == (0x90, 0x00): + if len(EF_KEYSPS['Data']) == 33: + KSI, CK, IK = ( EF_KEYSPS['Data'][0:1], + EF_KEYSPS['Data'][1:17], + EF_KEYSPS['Data'][17:33] ) + print '[+] Successful PS keys selection: Get [KSI, CK, IK]' + return [KSI, CK, IK] + else: + return EF_KEYSPS + return None + + def get_GBA_BP(self): + ''' + get_GBA_BP() -> [[RAND, B-TID, KeyLifetime], ...], + Length-Value parsing style + + reads EF_GBABP file at address [0x6F, 0xD6], + containing RAND and associated B-TID and KeyLifetime + returns list of list of bytes on success + (or eventually the whole file dict if the format is strange) + or None on error + ''' + EF_GBABP = self.select( [0x6F, 0xD6] ) + if self.coms()[2] == (0x90, 0x00): + if len(EF_GBABP['Data']) > 2: + #RAND, B_TID, Lifetime = LV_parser( EF_GBABP['Data'] ) + print '[+] Successful GBA_BP selection: Get list of ' \ + '[RAND, B-TID, KeyLifetime]' + #return (RAND, B_TID, Lifetime) + return LV_parser( EF_GBABP['Data'] ) + else: + return EF_GBABP + return None + + def update_GBA_BP(self, RAND, B_TID, key_lifetime): + ''' + update_GBA_BP([RAND], [B_TID], [key_lifetime]) + -> void (or EF_GBABP file dict if RAND not found) + + reads EF_GBABP file at address [0x6F, 0xD6], + checks if RAND provided is referenced, + and updates the file structure with provided B-TID and KeyLifetime + returns nothing (or eventually the whole file dict + if the RAND is not found) + ''' + GBA_BP = self.get_GBA_BP() + for i in GBA_BP: + if i == RAND: + print '[+] RAND found in GBA_BP' + # update transparent file with B_TID and key lifetime + self.coms.push( self.UPDATE_BINARY( P2=len(RAND)+1, + Data=[len(B_TID)] + B_TID + \ + [len(key_lifetime)] + key_lifetime )) + if self.dbg > 1: + print '[DBG] %s' % self.coms() + if self.coms()[2] == 0x90 and self.dbg: + print '[+] Successful GBA_BP update with B-TID ' \ + 'and key lifetime' + if self.dbg > 2: + print '[DBG] new value of EF_GBA_BP:\n%s' \ + % self.get_GBA_BP() + else: + if self.dbg: + print '[+] RAND not found in GBA_BP' + return GBA_BP + + def get_GBA_NL(self): + ''' + get_GBA_NL() -> [[NAF_ID, B-TID], ...] , TLV parsing style + + reads EF_GBANL file at address [0x6F, 0xDA], containing NAF_ID and B-TID + returns list of list of bytes vector on success + (or eventually the whole file dict if the format is strange) + or None on error + ''' + EF_GBANL = self.select( [0x6F, 0xDA] ) + if self.coms()[2] == (0x90, 0x00): + if len(EF_GBANL['Data'][0]) > 2: + # This is Tag-Length-Value parsing, + # with 0x80 for NAF_ID and 0x81 for B-TID + values = [] + + for rec in EF_GBANL['Data']: + NAF_ID, B_TID = [], [] + while len(rec) > 0: + tlv = first_TLV_parser( rec ) + if tlv[1] > 0xFF: + rec = rec[ tlv[1]+4 : ] + else: + rec = rec[ tlv[1]+2 : ] + if tlv[0] == 0x80: + NAF_ID = tlv[2] + elif tlv[0] == 0x81: + B_TID = tlv[2] + values.append( [NAF_ID, B_TID] ) + + print '[+] Successful GBA_NL selection: ' \ + 'Get list of [NAF_ID, B-TID]' + #return (NAF_ID, B_TID) + return values + else: + return EF_GBANL + return None + + def authenticate(self, RAND=[], AUTN=[], ctx='3G'): + ''' + self.authenticate(RAND, AUTN, ctx='3G') -> [key1, key2...], + LV parsing style + + runs the INTERNAL AUTHENTICATE command in the USIM + with the right context: + ctx = '2G', '3G', 'GBA' ('MBMS' or other not supported at this time) + RAND and AUTN are list of bytes; for '2G' context, AUTN is not used + returns a list containing the keys (list of bytes) computed in the USIM, + on success: + [RES, CK, IK (, Kc)] or [AUTS] for '3G' + [RES] or [AUTS] for 'GBA' + [RES, Kc] for '2G' + or None on error + ''' + # prepare input data for authentication + if ctx in ('3G', 'VGCS', 'GBA', 'MBMS') and len(RAND) != 16 \ + and len(AUTN) != 16: + if self.dbg: + print '[WNG] authenticate: bad parameters' + return None + + inp = [] + if ctx == '3G': + P2 = 0x81 + elif ctx == 'VGCS': + P2 = 0x82 + print '[+] Not implemented. Exit.' + return None + elif ctx == 'MBMS': + print '[+] Not implemented. Exit.' + return None + elif ctx == 'GBA': + P2 = 0x84 + inp = [0xDD] + inp.extend( [len(RAND)] + RAND + [len(AUTN)] + AUTN ) + if ctx not in ['3G', 'VGCS', 'MBMS', 'GBA']: + # and also, if ctx == '2G'... the safe way + # to avoid desynchronizing our USIM counter + P2 = 0x80 + if len(RAND) != 16: + if self.dbg: + print '[WNG] bad parameters' + return None + # override input value for 2G authent + inp = [len(RAND)] + RAND + + self.coms.push( self.INTERNAL_AUTHENTICATE(P2=P2, Data=inp) ) + if self.coms()[2][0] in (0x9F, 0x61): + self.coms.push( self.GET_RESPONSE(Le=self.coms()[2][1]) ) + if self.coms()[2] == (0x90, 0x00): + val = self.coms()[3] + if P2 == 0x80: + if self.dbg: + print '[+] Successful 2G authentication. Get [RES, Kc]' + values = LV_parser(val) + # returned values are (RES, Kc) + return values + # not adapted to 2G context with Kc, RES: to be confirmed... + if val[0] == 0xDB: + if P2 == 0x81 and self.dbg: + print '[+] Successful 3G authentication. ' \ + 'Get [RES, CK, IK(, Kc)]' + elif P2 == 0x84 and self.dbg: + print '[+] Successful GBA authentication. Get [RES]' + values = LV_parser(val[1:]) + # returned values can be (RES, CK, IK) or (RES, CK, IK, Kc) + return values + elif val[0] == 0xDC: + if self.dbg: + print '[+] Synchronization failure. Get [AUTS]' + values = LV_parser(val[1:]) + return values + #else: + if self.dbg: + print '[+] authentication error: %s' % self.coms() + return None + + def GBA_derivation(self, NAF_ID=[], IMPI=[]): + ''' + self.GBA_derivation(NAF_ID, IMPI) -> [Ks_ext_naf] + + runs the INTERNAL AUTHENTICATE command in the USIM + with the GBA derivation context: + NAF_ID is a list of bytes (use stringToByte()) + "NAF domain name"||"security protocol id", + eg: "application.org"||"0x010001000a" (> TLS with RSA and SHA) + IMPI is a list of bytes + "IMSI@ims.mncXXX.mccYYY.3gppnetwork.org" if no IMS IMPI + is specifically defined in the USIM + returns a list with GBA ext key (list of bytes) computed in the USIM: + [Ks_ext_naf] + Ks_int_naf remains available in the USIM + for further GBA_U key derivation + or None on error + + see TS 33.220 for GBA specific formats + ''' + # need to run 1st an authenicate command with 'GBA' context, + # so to have the required keys in the USIM + P2 = 0x84 + inp = [0xDE] + [len(NAF_ID)] + NAF_ID + [len(IMPI)] + IMPI + + self.coms.push( self.INTERNAL_AUTHENTICATE(P2=P2, Data=inp) ) + if self.coms()[2][0] in (0x9F, 0x61): + self.coms.push( self.GET_RESPONSE(Le=self.coms()[2][1]) ) + if self.coms()[2] == (0x90, 0x00): + val = self.coms()[3] + if val[0] == 0xDB: # not adapted to 2G context with Kc, RES + if self.dbg: + print '[+] Successful GBA derivation. Get [Ks_EXT_NAF]' + values = LV_parser(val[1:]) + return values + if self.dbg: + print '[DBG] authentication failure: %s' % self.coms() + return None + + def bf_FS_from_init( self, filename='bf_USIM', file_dict=USIM_app_FS, + init_method='select_by_aid', init_args=[1] ): + ''' + bruteforces the USIM filesystem at the application initialization level: + thanks to UICC.select_by_aid(1) + could be used another way... + stores the result in the file passed in argument + (file will be overwritten) + + TODO: does not manage file recursivity (when entering DF) + only scan 1st level files + ''' + fd = open(filename, 'w') + # loop on all possible addresses + for i in range(0x00, 0xff): + for j in range(0x00, 0xff): + # here "ret" is useless, + # but calling the method places the smartcard + # at the right address (by default: 1st AID) + ret = getattr(self, init_method)(*init_args) + fil = self.select( [i, j] ) + # if file characteristics is readable + if fil is not None: + if self.dbg: + print '[+] USIM file found at address %s %s' % (i, j) + # warning when working with DF recursivity + if (i, j) in file_dict.keys(): + fil['name'] = file_dict[(i, j)][0] + k = fil.keys() + k.sort() + fd.write('\n') + for key in k: + fd.write('%s: %s\n' % (key, fil[key])) + # if file exists but special conditions are returned + if self.coms()[2] not in ((0x6A, 0x82), (0x90, 0x00)): + if self.dbg: + print '[+] special condition %s when selecting' \ + ' / reading USIM file at address %s %s' \ + % ( self.coms()[2], i, j ) + fd.write('\n') + fd.write('file exists at address: %s %s\n' \ + % (hex(i)[2:], hex(j)[2:])) + fd.write('%s\n' % self.coms()[1]) + + if self.dbg and (i % 0x10 == 0 and j % 0x40 == 0): + print '[-] going over address %s %s' % (i, j) + + fd.write('\n') + fd.close() + + def bf_FS_from_MF(self, filename): + ''' + not implemented + ''' + #bf from MF, and recursively under each DF + #bf from each AID and recursively under each DF in AID + pass + + |