def synchronize(self, IMSI, RAND=16*'\0', AMF='\0\0', AUTS=14*'\0'): ''' synchronize the local counter SQN with AUTS provided by the USIM in response to a given 3G authentication challenge (RAND, AMF) ''' # lookup AuC_db for authentication key and SQN from IMSI if IMSI not in self.db.keys(): self._log('[ERR] IMSI is not present in AuC.db') return -1 K, SQN = self.db[IMSI][0], self.db[IMSI][1] # 33.102, section 6.3.3, for resynch, AMF is always null (0x0000) AMF = '\0\0' # compute Milenage functions and unmask SQN Mil = Milenage( self.OP ) AK = Mil.f5star( K, RAND ) SQN_MS = xor_string( AUTS[0:6], AK ) #self._log('USIM synchronization, unmasked SQN_MS: %s' % hexlify(SQN_MS)) MAC_S = Mil.f1star( K, RAND, SQN_MS, AMF ) #self._log('USIM synchronization, computed MAC_S: %s' % hexlify(MAC_S)) # authenticate the USIM if MAC_S != AUTS[6:14]: self._log('USIM authentication failure during synchronization ' \ 'for IMSI %s' % IMSI) return -1 # re-synchronize local SQN value sqn = unpack('!I', SQN_MS[2:])[0] + 1 self.db[IMSI][1] = sqn self._log('SQN resynchronized with value %i for IMSI %s' \ % (sqn, IMSI)) return 0
def synchronize(self, IMSI, RAND=16 * '\0', AMF='\0\0', AUTS=14 * '\0'): ''' synchronize the local counter SQN with AUTS provided by the USIM in response to a given 3G authentication challenge (RAND, AMF) ''' # lookup AuC_db for authentication key and SQN from IMSI if IMSI not in self.db.keys(): self._log('[ERR] IMSI is not present in AuC.db') return -1 K, SQN = self.db[IMSI][0], self.db[IMSI][1] # 33.102, section 6.3.3, for resynch, AMF is always null (0x0000) AMF = '\0\0' # compute Milenage functions and unmask SQN Mil = Milenage(self.OP) AK = Mil.f5star(K, RAND) SQN_MS = xor_string(AUTS[0:6], AK) #self._log('USIM synchronization, unmasked SQN_MS: %s' % hexlify(SQN_MS)) MAC_S = Mil.f1star(K, RAND, SQN_MS, AMF) #self._log('USIM synchronization, computed MAC_S: %s' % hexlify(MAC_S)) # authenticate the USIM if MAC_S != AUTS[6:14]: self._log('USIM authentication failure during synchronization ' \ 'for IMSI %s' % IMSI) return -1 # re-synchronize local SQN value sqn = unpack('!I', SQN_MS[2:])[0] + 1 self.db[IMSI][1] = sqn self._log('SQN resynchronized with value %i for IMSI %s' \ % (sqn, IMSI)) return 0
class personalize(object): ''' Class to program sysmo-USIM card takes a 3 digit serial number as argument to personalize the USIM card. Makes use of the fixed parameters in this file header: ICCID_pre, IMSI_pre, Ki_pre, OP, SMSP, HPLMN, PLMNsel, SST, SPN ''' # # current auth counter for the USIM to personalize: # if you do not know it, just comment the following attribute # it seems sysmoUSIM are shipped with a counter value around 31 SQN = 1 def __init__(self, serial_number='000'): # prepare data to write into the card if not len(serial_number) == 3 or not serial_number.isdigit(): print('must provided a 3 digits distinct serial number') raise() self.ICCID = ICCID_pre + serial_number self.ICCID += str(compute_luhn(self.ICCID)) self.IMSI = IMSI_pre + serial_number self.K = K_pre + serial_number self.Milenage = Milenage(OP) self.OPc = make_OPc(self.K, OP) # verify parameters if map(len, [self.K, self.OPc]) != [16, 16]: print('[-] bad length for K or OPc') raise() # write on the card if self.program_card() != 0: return if self.test_identification() != 0: return self.auth_test = 0 if self.test_authentication() != 0: return # finally add some files for infra (SMSP, HPLMN) u = UICC() program_files(u) u.disconnect() # and print results print('[+] sysmoUSIM card personalization done and tested successfully:') print('%s;%s;0x%s;0x%s' % (self.ICCID, self.IMSI, \ hexlify(self.K), hexlify(self.OPc))) def program_card(self): return program_vec(K = stringToByte(self.K), \ OPc = stringToByte(self.OPc), \ ICCID = encode_ICCID(self.ICCID), \ IMSI = encode_IMSI(self.IMSI)) def test_identification(self): u = UICC() self.ICCID = u.get_ICCID() u.disconnect() u = USIM() self.IMSI = u.get_imsi() print('[+] USIM identification:\nICCID: %s\nIMSI: %s' \ % (self.ICCID, self.IMSI)) u.disconnect() if not self.ICCID or not self.IMSI: print('[-] identification error') return 1 return 0 def test_authentication(self): if self.auth_test >= 2: return 1 u = USIM() # prepare auth challenge self.RAND = urand(16) # challenge is 128 bits if not hasattr(self, 'SQN'): self.SQN = 0 # default SQN is 0, coded on 48 bits AMF = 2*'\0' # management field, unneeded, left blank # compute Milenage functions XRES, CK, IK, AK = self.Milenage.f2345( self.K, self.RAND ) MAC_A = self.Milenage.f1(self.K, self.RAND, sqn_to_str(self.SQN), AMF) AUTN = xor_string(sqn_to_str(self.SQN), AK) + AMF + MAC_A # run auth data on the USIM ret = u.authenticate(stringToByte(self.RAND), stringToByte(AUTN), '3G') # check results (and pray) if ret == None: print('[-] authenticate() failed; something wrong happened, '\ 'maybe during card programmation ?') elif len(ret) == 1: print('[-] sync failure during authenticate(); unmasking counter') auts = byteToString(ret[0]) ak = self.Milenage.f5star(self.K, self.RAND) self.SQN = str_to_sqn(xor_string(auts, ak)[:6]) print('[+] auth counter value in USIM: %i' % self.SQN) self.SQN += 1 print('[+] retrying authenticate() with SQN: %i' % self.SQN) u.disconnect() self.test_authentication() elif len(ret) in (3, 4): # RES, CK, IK(, Kc) if ret[0:3] == map(stringToByte, [XRES, CK, IK]): print('[+] 3G auth successful with SQN: %i\n' \ 'increment it from now' % self.SQN) print('[+] USIM secrets:\nOPc: %s\nK: %s' \ % (hexlify(self.OPc), hexlify(self.K))) else: print('[-] 3G auth accepted on the USIM, ' \ 'but not matching auth vector generated: strange!') print('card returned:\n%s' % ret) u.disconnect() return 0
class AuC: """3GPP Authentication Centre use the AuC.db file with (IMSI, K, SQN[, OP]) records to produce 2G, 3G or 4G auth vectors, and resynchronize SQN """ # verbosity level: list of log types to be displayed DEBUG = ('ERR', 'WNG', 'INF', 'DBG') AUC_DB_PATH = os.path.dirname(os.path.abspath(__file__)) + os.sep #AUC_DB_PATH = 'C:\Python27\Lib\sitepackages\pycrate_corenet\' # when rewriting the AuC.db, do a back-up of the last version of the file DO_BACKUP = True # MNO OP diversification parameter # The AuC supports also a per-subscriber OP, to be set optionally in the AuC.db database OP = b'ffffffffffffffff' # SQN incrementation when a resynch is required by a USIM card SQN_SYNCH_STEP = 2 # PLMN restriction for returning 4G vectors # provide a list of allowed PLMN, or None for disabling the filter #PLMN_FILTER = ['20869'] PLMN_FILTER = None def __init__(self): """start the AuC open AuC.db file parse it into self.db (dict), containing IMSI: (K, SQN [, OP]) IMSI: string of digits K : 16 bytes buffer ALG2: integer (0, 1, 2 or 3, identifies the 2G auth algorithm) SQN : unsigned integer OP : subscriber specific OP, distinct from self.OP, optional field """ self.db = {} try: # get 3G authentication database AuC.db db_fd = open('%sAuC.db' % self.AUC_DB_PATH, 'r') # parse it into a dict object with IMSI as key for line in db_fd.readlines(): if line[0] != '#' and line.count(';') >= 3: fields = line[:-1].split(';') IMSI = str(fields[0]) K = unhexlify(fields[1].encode('ascii')) ALG2 = int(fields[2]) SQN = int(fields[3]) if len(fields) > 4 and len(fields) == 32: OP = unhexlify(fields[4].encode('ascii')) else: OP = None self.db[IMSI] = [K, ALG2, SQN, OP] self._log('INF', 'AuC.db file opened: %i record(s) found' % len(self.db)) # close the file db_fd.close() except Exception as err: self._log('ERR', 'unable to read AuC.db, path: %s' % self.AUC_DB_PATH) raise (err) self._save_required = False # # initiatlize the Milenage algo with the AuC-defined OP self.Milenage = Milenage(self.OP) # self._log('DBG', 'AuC started') def _log(self, logtype='DBG', msg=''): if logtype in self.DEBUG: log('[%s] [AuC] %s' % (logtype, msg)) def save(self): """ optionally save old AuC.db with timestamp suffix (if self.DO_BACKUP is set) write the current content of self.db dict into AuC.db, with updated SQN values """ if not self._save_required: return T = timemod.strftime('20%y%m%d_%H%M', timemod.gmtime()) # get header from original file AuC.db header = [] file_db = open('%sAuC.db' % self.AUC_DB_PATH) for line in file_db: if line[0] == '#': header.append(line) else: break header = ''.join(header) + '\n' file_db.close() if self.DO_BACKUP: # save the last current version of AuC.db os.rename('%sAuC.db' % self.AUC_DB_PATH, '%sAuC.%s.db' % (self.AUC_DB_PATH, T)) self._log('DBG', 'old AuC.db saved with timestamp') # save the current self.db into a new AuC.db file file_db = open('%s/AuC.db' % self.AUC_DB_PATH, 'w') file_db.write(header) indexes = list(self.db.keys()) indexes.sort() for IMSI in indexes: K, ALG2, SQN, OP = self.db[IMSI] if OP is not None: # OP additional parameter file_db.write('%s;%s;%i;%i;%s;\n'\ % (IMSI, hexlify(K).decode('ascii'), ALG2, SQN, hexlify(OP).decode('ascii'))) else: file_db.write('%s;%s;%i;%i;\n'\ % (IMSI, hexlify(K).decode('ascii'), ALG2, SQN)) file_db.close() self._log('INF', 'current db saved to AuC.db file') stop = save def make_2g_vector(self, IMSI, RAND=None): """ return a 2G authentication vector "triplet": RAND [16 bytes], RES [4 bytes], Kc [8 bytes] or None if the IMSI is not defined in the db or ALG2 is invalid RAND can be passed as argument """ # lookup db for authentication Key and algorithm id for IMSI try: K, ALG2, SQN, OP = self.db[IMSI] except KeyError: self._log('WNG', '[make_2g_vector] IMSI %s not present in AuC.db' % IMSI) return None # if not RAND: RAND = genrand(16) # if ALG2 == 0: if OP is not None: XRES, CK, IK, AK = self.Milenage.f2345(RAND, K, OP) else: XRES, CK, IK, AK = self.Milenage.f2345(RAND, K) RES, Ck = conv_C2(XRES), conv_C3(Ck, Ik) else: if ALG2 == 1: RES, Ck = comp128v1(K, RAND) elif ALG2 == 2: RES, Ck = comp128v2(K, RAND) elif ALG2 == 3: RES, Ck = comp128v3(K, RAND) else: return None # # return auth vector self._log('DBG', '[make_2g_vector] IMSI %s: RAND %s, RES %s, Kc %s'\ % (IMSI, hexlify(RAND).decode('ascii'), hexlify(RES).decode('ascii'), hexlify(Kc).decode('ascii'))) return RAND, RES, Kc def make_3g_vector(self, IMSI, AMF=b'\0\0', RAND=None): ''' return a 3G authentication vector "quintuplet": RAND [16 bytes], XRES [8 bytes], AUTN [16 bytes], CK [16 bytes], IK [16 bytes] or None if the IMSI is not defined in the db or does not support Milenage RAND can be passed as argument ''' # lookup db for authentication Key and counter for IMSI try: K_ALG2_SQN_OP = self.db[IMSI] except: self._log('WNG', '[make_3g_vector] IMSI %s not present in AuC.db' % IMSI) return None # K, ALG2, SQN, OP = K_ALG2_SQN_OP # if SQN == -1: # Milenage not supported self._log( 'WNG', '[make_3g_vector] IMSI %s does not support Milenage' % IMSI) return None # # increment SQN counter in the db K_ALG2_SQN_OP[2] += 1 self._save_required = True # pack SQN from integer to a 48-bit buffer SQNb = b'\0\0' + pack('>I', SQN) # generate challenge if necessary if RAND is None: RAND = genrand(16) # compute Milenage functions if OP is not None: XRES, CK, IK, AK = self.Milenage.f2345(K, RAND, OP) MAC_A = self.Milenage.f1(K, RAND, SQNb, AMF, OP) AUTN = xor_buf(SQNb, AK) + AMF + MAC_A else: XRES, CK, IK, AK = self.Milenage.f2345(K, RAND) MAC_A = self.Milenage.f1(K, RAND, SQNb, AMF) AUTN = xor_buf(SQNb, AK) + AMF + MAC_A # return auth vector self._log('DBG', '[make_3g_vector] IMSI %s, SQN %i: RAND %s, XRES %s, AUTN %s, CK %s, IK %s'\ % (IMSI, SQN, hexlify(RAND).decode('ascii'), hexlify(XRES).decode('ascii'), hexlify(AUTN).decode('ascii'), hexlify(CK).decode('ascii'), hexlify(IK).decode('ascii'))) return RAND, XRES, AUTN, CK, IK def make_4g_vector(self, IMSI, SN_ID, AMF=b'\x80\x00', RAND=None): """ return a 4G authentication vector "quadruplet": RAND [16 bytes], XRES [8 bytes], AUTN [16 bytes], KASME [32 bytes] or None if the IMSI is not defined in the db or does not support Milenage or SN_ID is invalid or not allowed SN_ID is the serving network identity, bcd-encoded buffer RAND can be passed as argument """ if not isinstance(SN_ID, bytes_types) or len(SN_ID) != 3: self._log( 'WNG', '[make_4g_vector] SN_ID invalid, %s' % hexlify(SN_ID).decode('ascii')) return None elif self.PLMN_FILTER is not None and SN_ID not in self.PLMN_FILTER: self._log( 'WNG', '[make_4g_vector] SN_ID not allowed, %s' % hexlify(SN_ID).decode('ascii')) return None # # lookup db for authentication Key and counter for IMSI try: K_ALG2_SQN_OP = self.db[IMSI] except: self._log('WNG', '[make_4g_vector] IMSI %s not present in AuC.db' % IMSI) return None # K, ALG2, SQN, OP = K_ALG2_SQN_OP # if SQN == -1: # Milenage not supported self._log( 'WNG', '[make_4g_vector] IMSI %s does not support Milenage' % IMSI) return None # # increment SQN counter in the db K_ALG2_SQN_OP[2] += 1 self._save_required = True # pack SQN from integer to a 48-bit buffer SQNb = b'\0\0' + pack('>I', SQN) # # generate challenge if RAND is None: RAND = genrand(16) # compute Milenage functions if OP is not None: XRES, CK, IK, AK = self.Milenage.f2345(K, RAND, OP) MAC_A = self.Milenage.f1(K, RAND, SQNb, AMF, OP) SQN_X_AK = xor_buf(SQNb, AK) AUTN = SQN_X_AK + AMF + MAC_A else: XRES, CK, IK, AK = self.Milenage.f2345(K, RAND) MAC_A = self.Milenage.f1(K, RAND, SQNb, AMF) SQN_X_AK = xor_buf(SQNb, AK) AUTN = SQN_X_AK + AMF + MAC_A # convert to LTE master key KASME = conv_A2(CK, IK, SN_ID, SQN_X_AK) # return auth vector self._log('DBG', '[make_4g_vector] IMSI %s, SQN %i, SN_ID %s: RAND %s, XRES %s, AUTN %s, KASME %s'\ % (IMSI, SQN, hexlify(SN_ID).decode('ascii'), hexlify(RAND).decode('ascii'), hexlify(XRES).decode('ascii'), hexlify(AUTN).decode('ascii'), hexlify(KASME).decode('ascii'))) return RAND, XRES, AUTN, KASME def synch_sqn(self, IMSI, RAND, AUTS): """ synchronize the local counter SQN with AUTS provided by the USIM in response to a given 3G or 4G authentication challenge (RAND, AMF) return 0 on successful synch, 1 on unsuccessful synch due to invalid AUTS or None if the IMSI is not defined in the db """ # lookup db for authentication Key and counter for IMSI try: K_ALG2_SQN_OP = self.db[IMSI] except: self._log('WNG', '[synch_sqn] IMSI %s not present in AuC.db' % IMSI) return None # K, ALG2, SQN, OP = K_ALG2_SQN_OP # if K_ALG2_SQN_OP[2] == -1: # Milenage not supported self._log( 'WNG', '[make_3g_vector] IMSI %s does not support Milenage' % IMSI) return None # # 33.102, section 6.3.3, for resynch, AMF is always null (0x0000) AMF = b'\0\0' # # compute Milenage functions and unmask SQN if OP is not None: AK = self.Milenage.f5star(K, RAND, OP) SQN_MS = xor_buf(AUTS[0:6], AK) MAC_S = self.Milenage.f1star(K, RAND, SQN_MS, AMF, OP) SQN_MSi = unpack('>Q', b'\0\0' + SQN_MS)[0] else: AK = self.Milenage.f5star(K, RAND) SQN_MS = xor_buf(AUTS[0:6], AK) MAC_S = self.Milenage.f1star(K, RAND, SQN_MS, AMF) SQN_MSi = unpack('>Q', b'\0\0' + SQN_MS)[0] self._log('DBG', '[synch_sqn] USIM resynchronization, SQN_MS %i, MAC_S %s'\ % (SQN_MSi, hexlify(MAC_S).decode('ascii'))) # authenticate the USIM if MAC_S != AUTS[6:14]: self._log( 'WNG', '[synch_sqn] IMSI %s, USIM authentication failure' % IMSI) return 1 # resynchronize local SQN value K_ALG2_SQN_OP[2] = SQN_MSi + self.SQN_SYNCH_STEP self._save_required = True self._log( 'DBG', '[synch_sqn] IMSI %s, SQN resynchronized to %i' % (IMSI, K_ALG2_SQN_OP[2])) return 0
class personalize(object): ''' Class to program sysmo-USIM card takes a 3 digit serial number as argument to personalize the USIM card. Makes use of the fixed parameters in this file header: ICCID_pre, IMSI_pre, Ki_pre, OP, SMSP, HPLMN, PLMNsel, SST, SPN ''' # # current auth counter for the USIM to personalize: # if you do not know it, just comment the following attribute # it seems sysmoUSIM are shipped with a counter value around 31 SQN = 1 def __init__(self, serial_number='000'): # prepare data to write into the card if not len(serial_number) == 3 or not serial_number.isdigit(): print('must provided a 3 digits distinct serial number') raise () self.ICCID = ICCID_pre + serial_number self.ICCID += str(compute_luhn(self.ICCID)) self.IMSI = IMSI_pre + serial_number self.K = Ki_pre + serial_number self.Milenage = Milenage(OP) self.OPc = make_OPc(self.K, OP) # verify parameters if map(len, [self.K, self.OPc]) != [16, 16]: print('[-] bad length for K or OPc') raise () # write on the card if self.program_card() != 0: return if self.test_identification() != 0: return self.auth_test = 0 if self.test_authentication() != 0: return # finally add some files for infra (SMSP, HPLMN) u = UICC() program_files(u) u.disconnect() # and print results print( '[+] sysmoUSIM card personalization done and tested successfully:') print('%s;%s;0x%s;0x%s' % (self.ICCID, self.IMSI, \ hexlify(self.K), hexlify(self.OPc))) def program_card(self): return program_vec(K = stringToByte(self.K), \ OPc = stringToByte(self.OPc), \ ICCID = encode_ICCID(self.ICCID), \ IMSI = encode_IMSI(self.IMSI)) def test_identification(self): u = UICC() self.ICCID = u.get_ICCID() u.disconnect() u = USIM() self.IMSI = u.get_imsi() print('[+] USIM identification:\nICCID: %s\nIMSI: %s' \ % (self.ICCID, self.IMSI)) u.disconnect() if not self.ICCID or not self.IMSI: print('[-] identification error') return 1 return 0 def test_authentication(self): if self.auth_test >= 2: return 1 u = USIM() # prepare auth challenge self.RAND = urand(16) # challenge is 128 bits if not hasattr(self, 'SQN'): self.SQN = 0 # default SQN is 0, coded on 48 bits AMF = 2 * '\0' # management field, unneeded, left blank # compute Milenage functions XRES, CK, IK, AK = self.Milenage.f2345(self.K, self.RAND) MAC_A = self.Milenage.f1(self.K, self.RAND, sqn_to_str(self.SQN), AMF) AUTN = xor_string(sqn_to_str(self.SQN), AK) + AMF + MAC_A # run auth data on the USIM ret = u.authenticate(stringToByte(self.RAND), stringToByte(AUTN), '3G') # check results (and pray) if ret == None: print('[-] authenticate() failed; something wrong happened, '\ 'maybe during card programmation ?') elif len(ret) == 1: print('[-] sync failure during authenticate(); unmasking counter') auts = byteToString(ret[0]) ak = self.Milenage.f5star(self.K, self.RAND) self.SQN = str_to_sqn(xor_string(auts, ak)[:6]) print('[+] auth counter value in USIM: %i' % self.SQN) self.SQN += 1 print('[+] retrying authenticate() with SQN: %i' % self.SQN) u.disconnect() self.test_authentication() elif len(ret) in (3, 4): # RES, CK, IK(, Kc) if ret[0:3] == map(stringToByte, [XRES, CK, IK]): print('[+] 3G auth successful with SQN: %i\n' \ 'increment it from now' % self.SQN) print('[+] USIM secrets:\nOPc: %s\nK: %s' \ % (hexlify(self.OPc), hexlify(self.K))) else: print('[-] 3G auth accepted on the USIM, ' \ 'but not matching auth vector generated: strange!') print('card returned:\n%s' % ret) u.disconnect() return 0