sja2: Add support for new simcard model

Adding support for the new SJA2 simcard type requires comprensive
refactoring of sysmo-usim-tool. We resolve the commonalities of
both simcard models in a class based approach so that code duplication
is reduced to a minium.

- Refactor sysmo-usim-tool into a class based model
- Add support for SJA2 simcard models
- Add tests for SJA2 simcard models

Change-Id: I0feab71bdaab079b35737bb4e0e2a417c4a1f96b
Related: SYS#4466
for/master/ret
Philipp Maier 3 years ago
parent 08623baa4a
commit 8dd52e04ac

@ -904,8 +904,8 @@ class ISO7816(object):
Data = Data[L+2:]
return fil
@staticmethod
def parse_compact_security_attribute(Data, fil):
def parse_compact_security_attribute(self, Data, fil):
'''
parses a list of bytes provided in Data
interprets the content as the compact form for security parameters

@ -175,7 +175,23 @@ class USIM(UICC):
self.USIM_AID = aid
if self.dbg:
log(3, '(USIM.__init__) USIM AID selection succeeded\n')
def SELECT_ADF_ISIM(self):
# USIM selection from AID
if self.dbg:
log(3, '(ISIM.__init__) 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, 0x04) :
usim = self.select(addr=aid, type='aid')
if usim is None and self.dbg:
log(2, '(ISIM.__init__) ISIM AID selection failed')
if usim is not None:
self.USIM_AID = aid
if self.dbg:
log(3, '(ISIM.__init__) ISIM AID selection succeeded\n')
@staticmethod
def sw_status(sw1, sw2):
status = SIM.sw_status(sw1, sw2)

@ -216,7 +216,10 @@ def decode_BCD(data=[]):
if (B&0x0F) < 10: string += str(B&0x0F)
# 2nd digit (4 MSB), can be padding (e.g. 0xF)
if (B>>4) < 10: string += str(B>>4)
return string
if len(string) <= 0:
return None
else:
return string
def compute_luhn(digit_str=''):
'''

@ -0,0 +1,209 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
User interface parts that are common for all tools
(C) 2017 by Sysmocom s.f.m.c. GmbH
All Rights Reserved
Author: Philipp Maier
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, see <http://www.gnu.org/licenses/>.
"""
from utils import *
import sys, getopt
COMMON_GETOPTS = "hfa:J:nN:lL:kK:tT:oO:C:sSi"
COMMON_GETOPTS_LONG = ["help", "force", "adm1=", "set-imsi", "mnclen",
"set-mnclen", "milenage", "set-milenage", "ki",
"set-ki=", "auth", "set-auth=", "opc", "set-op=",
"set-opc=", "seq-parameters", "reset-seq-parameters"
"iccid"]
# Parse common commandline options and keep them as flags
class Common():
sim = None
show_helptext = None
force = False
adm1 = None
write_imsi = None
write_mnclen = None
show_mnclen = None
show_milenage = False
write_milenage = None
show_ki = None
write_ki = None
show_auth = False
write_auth = None
show_opc = False
write_op = None
write_opc = None
show_seq_par = False
reset_seq_par = False
show_iccid = False
def __init__(self, argv, getopts, getopts_long):
self._banner()
# Analyze commandline options
try:
opts, args = getopt.getopt(argv, COMMON_GETOPTS + getopts,
COMMON_GETOPTS_LONG + getopts_long)
except getopt.GetoptError:
print " * Error: Invalid commandline options"
sys.exit(2)
# Set flags for common options
for opt, arg in opts:
if opt in ("-h", "--help"):
self.__common_helptext()
sys.exit(0)
elif opt in ("-f", "--force"):
self.force = True
elif opt in ("-a", "--adm1"):
self.adm1 = ascii_to_list(arg)
elif opt in ("-J", "--set-imsi"):
self.write_imsi = asciihex_to_list(pad_asciihex(arg, True, '9'))
elif opt in ("-n", "--mnclen"):
self.show_mnclen = True
elif opt in ("-N", "--set-mnclen"):
self.write_mnclen = asciihex_to_list(arg)
elif opt in ("-l", "--milenage"):
self.show_milenage = True
elif opt in ("-L", "--set-milenage"):
self.write_milenage = asciihex_to_list(arg)
elif opt in ("-k", "--ki"):
self.show_ki = True
elif opt in ("-K", "--set-ki"):
self.write_ki = asciihex_to_list(arg)
elif opt in ("-t", "--auth"):
self.show_auth = True
elif opt in ("-T", "--set-auth"):
self.write_auth = arg.split(':',1)
elif opt in ("-o", "--opc"):
self.show_opc = True
elif opt in ("-O", "--set-op"):
self.write_op = asciihex_to_list(arg)
elif opt in ("-C", "--set-opc"):
self.write_opc = asciihex_to_list(arg)
elif opt in ("-s", "--sqe-parameters"):
self.show_seq_par = True
elif opt in ("-S", "--reset-sqe-parameters"):
self.reset_seq_par = True
elif opt in ("-i", "--iccid"):
self.show_iccid = True
# Check for ADM1 key
if not self.adm1:
print " * Error: adm1 parameter missing -- exiting..."
print ""
sys.exit(1)
# Set flags for specific options
self._options(opts)
# Initialize
self._init()
# Execute tasks
self.__common_execute()
# Print the part of the helptext that is common for all tools
def __common_helptext(self):
print(" * Commandline options:")
print(" -h, --help ..................... Show this screen")
print(" -f, --force .................... Enforce authentication after failure")
print(" -a, --adm1 CHV ................. Administrator PIN (e.g 55538407)")
print(" -J, --set-imsi ................. Set IMSI value")
print(" -n, --mnclen ................... Show MNC length value")
print(" -N, --set-mnclen ............... Set MNC length value")
print(" -l, --milenage ................. Show milenage parameters")
print(" -L, --set-milenage HEXSTRING ... Set milenage parameters")
print(" -k, --ki ....................... Show KI value")
print(" -K, --set-ki ................... Set KI value")
print(" -t, --auth ..................... Show Authentication algorithms")
print(" -T, --set-auth 2G:3G ........... Set 2G/3G Auth algo (e.g. COMP128v1:COMP128v1)")
print(" -o, --opc ...................... Show OP/c configuration")
print(" -O, --set-op HEXSTRING ......... Set OP value")
print(" -C, --set-opc HEXSTRING ........ Set OPc value")
print(" -s --seq-parameters ........... Show MILENAGE SEQ/SQN parameters")
print(" -S --reset-seq-parameters ..... Reset MILENAGE SEQ/SQN parameters to default")
print(" -i --iccid .................... Show ICCID")
self._helptext()
# Execute common tasks
def __common_execute(self):
# Autnetnication is a primary task that must always run before
# any other task is carried out
if self.sim.admin_auth(self.adm1, self.force) == False:
exit(1)
# First run the card specific tasks
self._execute()
# And then the common tasks
if self.write_imsi:
self.sim.write_imsi(self.write_imsi)
if self.show_mnclen:
self.sim.show_mnclen()
if self.write_mnclen:
self.sim.write_mnclen(self.write_mnclen)
if self.write_milenage:
self.sim.write_milenage_params(self.write_milenage)
if self.show_milenage:
self.sim.show_milenage_params()
if self.write_ki:
self.sim.write_ki_params(self.write_ki)
if self.show_ki:
self.sim.show_ki_params()
if self.show_auth:
self.sim.show_auth_params()
if self.write_auth:
self.sim.write_auth_params(self.write_auth[0], self.write_auth[1])
if self.show_opc:
self.sim.show_opc_params()
if self.write_op:
self.sim.write_opc_params(0, self.write_op)
if self.write_opc:
self.sim.write_opc_params(1, self.write_opc)
if self.show_seq_par:
self.sim.show_milenage_sqn_params()
if self.reset_seq_par:
self.sim.reset_milenage_sqn_params()
if self.show_iccid:
self.sim.show_iccid()
print "Done!"

@ -0,0 +1,75 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from utils import *
from sysmo_isim_sja2 import *
if __name__ == "__main__":
print("HELLO EF_SIM_AUTH_KEY!")
myfile = SYSMO_ISIMSJA2_FILE_EF_SIM_AUTH_KEY()
print myfile
myfile.use_opc = True
myfile.sres_dev_func = 2
myfile.opc = [0x23] * 16
myfile.key = [0x42] * 16
print myfile
myfile2 = SYSMO_ISIMSJA2_FILE_EF_SIM_AUTH_KEY(myfile.encode())
print myfile2
print("=========================================================")
myfile = SYSMO_ISIMSJA2_FILE_EF_USIM_AUTH_KEY_2G()
print myfile
myfile.algo = SYSMO_ISIMSJA2_ALGO_MILENAGE;
myfile.sres_dev_func = 2
myfile.opc = [0x23] * 16
myfile.key = [0x42] * 16
print myfile
myfile2 = SYSMO_ISIMSJA2_FILE_EF_USIM_AUTH_KEY_2G(myfile.encode())
print myfile2
print("==========================================================")
myfile = SYSMO_ISIMSJA2_FILE_EF_MILENAGE_CFG()
print myfile
myfile.R1 = 0xAA
myfile.R2 = 0xBB
myfile.R3 = 0xCC
myfile.R4 = 0xDD
myfile.R5 = 0xEE
myfile.C1 = [0xDE] * 16
myfile.C2 = [0xAD] * 16
myfile.C3 = [0xBE] * 16
myfile.C4 = [0xEF] * 16
myfile.C5 = [0x12] * 16
print myfile
myfile2 = SYSMO_ISIMSJA2_FILE_EF_MILENAGE_CFG(myfile.encode())
print myfile2
print("==========================================================")
myfile = SYSMO_ISIMSJA2_FILE_EF_USIM_SQN()
print myfile
print myfile.encode()
myfile2 = SYSMO_ISIMSJA2_FILE_EF_USIM_SQN(myfile.encode())
print myfile2
print myfile2.encode()

@ -26,6 +26,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
from card.USIM import USIM
from card.SIM import SIM
from card.utils import *
from utils import *
# Files
GSM_SIM_MF = [0x3F, 0x00]
@ -58,7 +59,6 @@ GSM_SIM_INS_UPDATE_RECORD_ABS = 0x04
class Card_res_apdu():
apdu = None
sw = None
swok = True
# convert Benoit Michau style result to sysmocom style result
def from_mich(self, mich):
@ -66,14 +66,19 @@ class Card_res_apdu():
self.sw = [ mich[2][0], mich[2][1] ]
def __str__(self):
dump = "APDU: " + hexdump(self.apdu)
dump += " SW: " + hexdump(self.sw)
dump = ""
if len(self.apdu) > 0:
dump = "APDU: " + hexdump(self.apdu)
else:
dump = "APDU: (no data)"
dump += ", SW: " + hexdump(self.sw)
return dump
# A class to abstract a simcard.
class Simcard():
card = None
filelen = 0 #length of the currently selected file
# Constructor: Create a new simcard object
def __init__(self, cardtype = GSM_USIM, atr = None):
@ -88,25 +93,81 @@ class Simcard():
def __get_cla(self, usim):
return self.card.CLA
# Select a file
def select(self, fid, dry = False, strict = True):
# Get file size from FCP
def __get_len_from_tlv(self, fcp):
# Note: This has been taken from http://git.osmocom.org/pysim/tree/pySim/commands.py,
# but pySim uses ascii-hex strings for its internal data representation. We use
# regular lists with integers, so we must convert to an ascii-hex string first:
fcp = ''.join('{:02x}'.format(x) for x in fcp)
# see also: ETSI TS 102 221, chapter 11.1.1.3.1 Response for MF,
# DF or ADF
from pytlv.TLV import TLV
tlvparser = TLV(['82', '83', '84', 'a5', '8a', '8b', '8c', '80', 'ab', 'c6', '81', '88'])
# pytlv is case sensitive!
fcp = fcp.lower()
if fcp[0:2] != '62':
raise ValueError('Tag of the FCP template does not match, expected 62 but got %s'%fcp[0:2])
# Unfortunately the spec is not very clear if the FCP length is
# coded as one or two byte vale, so we have to try it out by
# checking if the length of the remaining TLV string matches
# what we get in the length field.
# See also ETSI TS 102 221, chapter 11.1.1.3.0 Base coding.
exp_tlv_len = int(fcp[2:4], 16)
if len(fcp[4:])/2 == exp_tlv_len:
skip = 4
else:
exp_tlv_len = int(fcp[2:6], 16)
if len(fcp[4:])/2 == exp_tlv_len:
skip = 6
# Skip FCP tag and length
tlv = fcp[skip:]
tlv_parsed = tlvparser.parse(tlv)
if '80' in tlv_parsed:
return int(tlv_parsed['80'], 16)
else:
return 0
# Get the file length from a response (select)
def __len(self, res, p2):
if p2 == 0x04:
return self.__get_len_from_tlv(res)
else:
return int(res[-1][4:8], 16)
# Select a file and retrieve its length
def select(self, fid):
self.filelen = 0
p2 = 0x04
res = Card_res_apdu()
res.from_mich(self.card.SELECT_FILE(P2 = 0x0C, Data = fid))
res.from_mich(self.card.SELECT_FILE(P2 = p2, Data = fid))
# Stop here, on failure
if res.sw[0] != 0x61:
return res
res.from_mich(self.card.GET_RESPONSE(res.sw[1]))
self.filelen = self.__len(res.apdu, p2)
return res
# Perform card holder verification
def verify_chv(self, chv, chv_no, dry = False, strict = True):
def verify_chv(self, chv, chv_no):
res = Card_res_apdu()
res.from_mich(self.card.VERIFY(P2 = chv_no, Data = chv))
return res
# Read CHV retry counter
def chv_retrys(self, chv_no, dry = False, strict = True):
def chv_retrys(self, chv_no):
res = self.card.VERIFY(P2 = chv_no)
return res[2][1] & 0x0F
# Perform file operation (Write)
def update_binary(self, data, offset = 0, dry = False, strict = True):
def update_binary(self, data, offset = 0):
offs_high = (offset >> 8) & 0xFF
offs_low = offset & 0xFF
res = Card_res_apdu()
@ -114,7 +175,7 @@ class Simcard():
return res
# Perform file operation (Read, byte oriented)
def read_binary(self, length, offset = 0, dry = False, strict = True):
def read_binary(self, length, offset = 0):
offs_high = (offset >> 8) & 0xFF
offs_low = offset & 0xFF
res = Card_res_apdu()
@ -122,14 +183,14 @@ class Simcard():
return res
# Perform file operation (Read, record oriented)
def read_record(self, length, rec_no = 0, dry = False, strict = True):
def read_record(self, length, rec_no = 0):
res = Card_res_apdu()
res.from_mich(self.card.READ_RECORD(rec_no, GSM_SIM_INS_READ_RECORD_ABS, length))
return res
# Perform file operation (Read, record oriented)
def update_record(self, data, rec_no = 0, dry = False, strict = True):
def update_record(self, data, rec_no = 0):
res = Card_res_apdu()
res.from_mich(self.card.UPDATE_RECORD(rec_no, GSM_SIM_INS_UPDATE_RECORD_ABS, data))
return res

@ -0,0 +1,78 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Commandline interface for sysmoUSIM-SJS1
(C) 2017 by Sysmocom s.f.m.c. GmbH
All Rights Reserved
Author: Philipp Maier
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, see <http://www.gnu.org/licenses/>.
"""
import sys, getopt
from utils import *
from simcard import *
from sysmo_isim_sja2 import *
from common import *
class Application(Common):
getopt_dump = False
# Automatically executed by superclass
def _banner(self):
print("sysmoISIM-SJA2 parameterization tool")
print("Copyright (c)2019 Sysmocom s.f.m.c. GmbH")
print("")
# Automatically executed by superclass
def _options(self, opts):
for opt, arg in opts:
if opt in ("-d", "--dump"):
self.getopt_dump = True
# Automatically executed by superclass when -h or --help is supplied as option
def _helptext(self):
print(" -d, --dump ..................... Dump propritary file contents")
print("")
print(" For Option -T, the following algorithms are valid:")
print('\n'.join([' %d %s' % entry for entry in sysmo_isimsja2_algorithms]))
print("")
# Automatically executed by superclass before _execute() is called
def _init(self):
self.sim = Sysmo_isim_sja2()
# Automatically executed by superclass
def _execute(self):
if self.getopt_dump:
self.sim.dump()
def main(argv):
Application(argv, "d", ["dump"])
if __name__ == "__main__":
main(sys.argv[1:])

@ -26,196 +26,72 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys, getopt
from utils import *
from simcard import *
from sysmo_usimsjs1 import *
def banner():
print "sysmoUSIM-SJS1 parameterization tool"
print "Copyright (c)2017 Sysmocom s.f.m.c. GmbH"
print ""
def helptext():
print " * Commandline options:"
print " -a, --adm1 CHV ................. Administrator PIN (e.g 55538407)"
print " -u, --usim ..................... Enable USIM mode"
print " -c, --classic .................. Disable USIM mode (make classic-sim)"
print " -m, --mode ..................... Display mode (classic-sim or USIM?)"
print " -t, --auth ..................... Show Authentication algorithms"
print " -T, --set-auth list ............ List available algorithms"
print " -T, --set-auth 2G:3G ........... Set 2G/3G Auth algo (e.g. COMP128v1:COMP128v1)"
print " -l, --milenage ................. Show milenage parameters"
print " -L, --set-milenage HEXSTRING ... Set milenage parameters"
print " -o, --opc ...................... Show OP/c configuration"
print " -O, --set-op HEXSTRING ......... Set OP value"
print " -C, --set-opc HEXSTRING ........ Set OPc value"
print " -k, --ki ....................... Show KI value"
print " -K, --set-ki ................... Set KI value"
print " -s --seq-parameters ........... Show MILENAGE SEQ/SQN parameters"
print " -S --reset-seq-parameters...... Reset MILENAGE SEQ/SQN parameters to default"
print " -I, --set-iccid ................ Set ICCID value"
print " -J, --set-imsi ................. Set IMSI value"
print " -n, --mnclen ................... Show MNC length value"
print " -N, --set-mnclen ............... Set MNC length value"
print ""
from sysmo_usim_sjs1 import *
from common import *
class Application(Common):
write_iccid = None
write_sim_mode = None # True = USIM, False = classic SIM
show_sim_mode = False
write_iccid = None
write_imsi = None
# Automatically executed by superclass
def _banner(self):
print("sysmoUSIM-SJS1 parameterization tool")
print("Copyright (c)2017-2019 Sysmocom s.f.m.c. GmbH")
print("")
# Automatically executed by superclass
def _options(self, opts):
for opt, arg in opts:
if opt in ("-I", "--set-iccid"):
self.write_iccid = asciihex_to_list(pad_asciihex(arg))
elif opt in ("-u", "--usim"):
self.write_sim_mode = True
elif opt in ("-c", "--classic"):
self.write_sim_mode = False
elif opt in ("-m", "--mode"):
self.show_sim_mode = True
# Automatically executed by superclass when -h or --help is supplied as option
def _helptext(self):
print(" -I, --set-iccid ................ Set ICCID value")
print(" -u, --usim ..................... Enable USIM mode")
print(" -c, --classic .................. Disable USIM mode (make classic-sim)")
print(" -m, --mode ..................... Display mode (classic-sim or USIM?)")
print("")
print(" For Option -T, the following algorithms are valid:")
print('\n'.join([' %d %s' % entry for entry in sysmo_usim_algorithms]))
print("")
def main(argv):
banner()
getopt_adm1 = None
getopt_write_sim_mode = None # True = USIM, False = classic SIM
getopt_show_sim_mode = False
getopt_show_auth = False
getopt_write_auth = None
getopt_show_milenage = False
getopt_write_milenage = None
getopt_show_opc = False
getopt_write_op = None
getopt_write_opc = None
getopt_show_ki = None
getopt_write_ki = None
getopt_force = False
getopt_write_iccid = None
getopt_seq_par = False
getopt_reset_seq_par = False
getopt_write_imsi = None
getopt_show_mnclen = None
getopt_write_mnclen = None
# Analyze commandline options
try:
opts, args = getopt.getopt(argv,
"ha:ucmtT:lL:oO:C:kK:fiI:sSJ:nN:",
["help","adm1=","usim","classic",
"mode","auth","set-auth=","milenage",
"set-milenage","opc","set-op=","set-opc=",
"ki","set-ki=","force","iccid","set-iccid=",
"seq-parameters", "reset-seq-parameters",
"set-imsi", "set-mnclen", "mnclen"])
except getopt.GetoptError:
print " * Error: Invalid commandline options"
sys.exit(2)
for opt, arg in opts:
if opt in ("-h", "--help"):
helptext()
sys.exit()
elif opt in ("-a", "--adm1"):
getopt_adm1 = ascii_to_list(arg)
elif opt in ("-u", "--usim"):
getopt_write_sim_mode = True
elif opt in ("-c", "--classic"):
getopt_write_sim_mode = False
elif opt in ("-m", "--mode"):
getopt_show_sim_mode = True
elif opt in ("-t", "--auth"):
getopt_show_auth = True
elif opt in ("-T", "--set-auth"):
if arg.upper() == 'LIST':
print 'Valid -T arguments are algorithm number or string.'
print 'Available:'
print '\n'.join([' %d %s' % entry for entry in sysmo_usim_algorithms])
sys.exit()
getopt_write_auth = arg.split(':',1)
elif opt in ("-l", "--milenage"):
getopt_show_milenage = True
elif opt in ("-L", "--set-milenage"):
getopt_write_milenage = asciihex_to_list(arg)
elif opt in ("-o", "--opc"):
getopt_show_opc = True
elif opt in ("-O", "--set-op"):
getopt_write_op = asciihex_to_list(arg)
elif opt in ("-C", "--set-opc"):
getopt_write_opc = asciihex_to_list(arg)
elif opt in ("-k", "--ki"):
getopt_show_ki = True
elif opt in ("-K", "--set-ki"):
getopt_write_ki = asciihex_to_list(arg)
elif opt in ("-f", "--force"):
getopt_force = True
elif opt in ("-I", "--set-iccid"):
getopt_write_iccid = asciihex_to_list(pad_asciihex(arg))
elif opt in ("-s", "--sqe-parameters"):
getopt_seq_par = True
elif opt in ("-S", "--reset-sqe-parameters"):
getopt_reset_seq_par = True
elif opt in ("-J", "--set-imsi"):
getopt_write_imsi = asciihex_to_list(pad_asciihex(arg, True, '9'))
elif opt in ("-n", "--mnclen"):
getopt_show_mnclen = True
elif opt in ("-N", "--set-mnclen"):
getopt_write_mnclen = asciihex_to_list(arg)
if not getopt_adm1:
print " * Error: adm1 parameter missing -- exiting..."
print ""
sys.exit(1)
# Claim terminal
sim = Sysmo_usimsjs1()
# Authenticate
if sim.admin_auth(getopt_adm1, getopt_force) == False:
exit(1)
# Execute tasks
if getopt_write_sim_mode != None:
sim.write_sim_mode(getopt_write_sim_mode)
if getopt_show_sim_mode:
sim.show_sim_mode()
if getopt_write_auth:
sim.write_auth_params(getopt_write_auth[0], getopt_write_auth[1])
if getopt_show_auth:
sim.show_auth_params()
if getopt_write_milenage:
ef_mlngc = SYSMO_USIMSJS1_FILE_EF_MLNGC(getopt_write_milenage)
sim.write_milenage_params(ef_mlngc)
if getopt_show_milenage:
sim.show_milenage_params()
if getopt_seq_par:
sim.show_milenage_sqn_params()
if getopt_write_op:
sim.write_opc_params(0, getopt_write_op)
if getopt_write_opc:
sim.write_opc_params(1, getopt_write_opc)
if getopt_show_opc:
sim.show_opc_params()
if getopt_write_ki:
sim.write_ki_params(getopt_write_ki)
if getopt_show_ki:
sim.show_ki_params()
if getopt_write_iccid:
sim.write_iccid(getopt_write_iccid)
if getopt_reset_seq_par:
sim.reset_milenage_sqn_params()
if getopt_write_imsi:
sim.write_imsi(getopt_write_imsi)
if getopt_show_mnclen:
sim.show_mnclen()
if getopt_write_mnclen:
sim.write_mnclen(getopt_write_mnclen)
print "Done!"
# Automatically executed by superclass before _execute() is called
def _init(self):
self.sim = Sysmo_usim_sjs1()
# Automatically executed by superclass
def _execute(self):
if self.write_iccid:
self.sim.write_iccid(self.write_iccid)
if self.write_sim_mode != None:
self.sim.write_sim_mode(self.write_sim_mode)
if self.show_sim_mode:
self.sim.show_sim_mode()
def main(argv):
Application(argv, "ucmI:", ["usim", "classic", "mode", "set-iccid="])
if __name__ == "__main__":
main(sys.argv[1:])

@ -0,0 +1,839 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Gadgets to modify SYSMO USIM SJA2 parameters
(C) 2017 by Sysmocom s.f.m.c. GmbH
All Rights Reserved
Author: Philipp Maier
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, see <http://www.gnu.org/licenses/>.
"""
import sys
from utils import *
from sysmo_usim import *
import math
# Partial File tree:
# The following tree is incomplete, it just contains the propritary files we
# need to perform the tasks implemented below:
#
# [MF 0x3F00]
# |
# +--[DF_SYSTEM 0xA515]
# | |
# | +--[EF_SIM_AUTH_KEY 0x6F20] (regular file)
# |
# +--[ADF_USIM]
# | |
# | +--[USIM_AUTH_KEY 0xAF20] (regular file)
# | |
# | +--[EF_USIM_AUTH_KEY_2G 0xAF22] (link to DF_SYSTEM/EF_SIM_AUTH_KEY)
# |
# +--[ADF_ISIM]
# |
# +--[USIM_AUTH_KEY 0xAF20] (regular file)
# |
# +--[EF_USIM_AUTH_KEY_2G 0xAF22] (link to DF_SYSTEM/EF_SIM_AUTH_KEY)
#
# Note: EF_MILENAGE_CFG and EF_USIM_SQN not yet listed here.
# Propritary files
SYSMO_ISIMSJA2_DF_SYSTEM = [0xA5, 0x15]
SYSMO_ISIMSJA2_EF_SIM_AUTH_KEY = [0x6F, 0x20] # DF_SYSTEM
SYSMO_ISIMSJA2_EF_USIM_AUTH_KEY = [0xAF, 0x20] # ADF.USIM
SYSMO_ISIMSJA2_EF_USIM_AUTH_KEY_2G = [0xAF, 0x22] # ADF.USIM
SYSMO_ISIMSJA2_EF_USIM_AUTH_KEY_GBA = [0xAF, 0x23] # ADF.USIM
SYSMO_ISIMSJA2_EF_MILENAGE_CFG = [0xAF, 0x21] # ADF.USIM
SYSMO_ISIMSJA2_EF_USIM_SQN = [0xAF, 0x30] # ADF.USIM
SYSMO_ISIMSJA2_EF_GBA_SK = [0xAF, 0x31] # ADF.USIM
SYSMO_ISIMSJA2_EF_GBA_REC_LIST = [0xAF, 0x32] # ADF.USIM
SYSMO_ISIMSJA2_EF_GBA_INT_KEY = [0xAF, 0x32] # ADF.USIM
# Authentication algorithms
SYSMO_ISIMSJA2_ALGO_COMP12V1 = 0x01
SYSMO_ISIMSJA2_ALGO_COMP12V2 = 0x02
SYSMO_ISIMSJA2_ALGO_COMP12V3 = 0x03
SYSMO_ISIMSJA2_ALGO_MILENAGE = 0x04
SYSMO_ISIMSJA2_ALGO_SHA1AKA = 0x05
SYSMO_ISIMSJA2_ALGO_XOR = 0x0F
sysmo_isimsja2_algorithms = (
(SYSMO_ISIMSJA2_ALGO_COMP12V1, 'COMP128v1'),
(SYSMO_ISIMSJA2_ALGO_COMP12V2, 'COMP128v2'),
(SYSMO_ISIMSJA2_ALGO_COMP12V3, 'XOR-2G'),
(SYSMO_ISIMSJA2_ALGO_MILENAGE, 'MILENAGE'),
(SYSMO_ISIMSJA2_ALGO_SHA1AKA , 'SHA1-AKA'),
(SYSMO_ISIMSJA2_ALGO_XOR, 'XOR'),
)
class SYSMO_ISIMSJA2_FILE_EF_XSIM_AUTH_KEY:
"""
Superclass model that generates that handles the header byte of
SYSMO_ISIMSJA2_EF_USIM_AUTH_KEY, SYSMO_ISIMSJA2_EF_USIM_AUTH_KEY_2G
and SYSMO_ISIMSJA2_EF_USIM_AUTH_KEY_GBA.
"""
algo = SYSMO_ISIMSJA2_ALGO_COMP12V1
use_opc = False
sres_dev_func = 1
def __init__(self, content = None):
if content == None:
return
header = content[0]
self.algo = header & 0x0F
self.use_opc = bool((header >> 4) & 1)
if (header >> 5) & 1:
self.sres_dev_func = 2
else:
self.sres_dev_func = 1
def __str__(self):
dump = ""
pfx = " "
dump += pfx + "Algorithm: "
dump += id_to_str(sysmo_isimsja2_algorithms, self.algo)
dump += "\n"
if self.use_opc == True:
dump += pfx + "Milenage: use OPc\n"
else:
dump += pfx + "Milenage: use OP\n"
dump += pfx + "Milenage: use SRES deviation function " + str(self.sres_dev_func) + "\n"
return dump
def encode(self):
out = [0x00]
out[0] = self.algo & 0x0F
if self.use_opc == True:
out[0] |= 1 << 4
out[0] |= ((self.sres_dev_func-1) & 1) << 5
return out
class SYSMO_ISIMSJA2_FILE_EF_SIM_AUTH_KEY(SYSMO_ISIMSJA2_FILE_EF_XSIM_AUTH_KEY):
key = [0xAA] * 16
opc = [0xBB] * 16
def __init__(self, content = None):
if content == None:
return
SYSMO_ISIMSJA2_FILE_EF_XSIM_AUTH_KEY.__init__(self, content)
self.key = content[1:17]
self.opc = content[17:33]
def __str__(self):
dump = ""
pfx = " "
dump += SYSMO_ISIMSJA2_FILE_EF_XSIM_AUTH_KEY.__str__(self)
if self.algo == SYSMO_ISIMSJA2_ALGO_MILENAGE:
dump += pfx + "Key: " + hexdump(self.key) + "\n"
dump += pfx + "OPc: " + hexdump(self.opc)
elif self.algo == SYSMO_ISIMSJA2_ALGO_XOR:
dump += pfx + "Key: " + hexdump(self.key) + "\n"
dump += pfx + "OPc: " + hexdump(self.opc)
elif self.algo == SYSMO_ISIMSJA2_ALGO_SHA1AKA:
dump += pfx + "Root key: " + hexdump(self.key) + "\n"
dump += pfx + "OPc: " + hexdump(self.opc) + " (unused)"
else:
dump += pfx + "Key: " + hexdump(self.key) + "\n"
dump += pfx + "OPc: " + hexdump(self.opc) + " (unused)"
return dump
def encode(self):
out = SYSMO_ISIMSJA2_FILE_EF_XSIM_AUTH_KEY.encode(self)
out += self.key + self.opc
return out
class SYSMO_ISIMSJA2_FILE_EF_USIM_AUTH_KEY(SYSMO_ISIMSJA2_FILE_EF_XSIM_AUTH_KEY):
full_res = True # Return full 8-byte RES or first 4 bytes only
ext_res = False # Return 16 byte RES (ignores full_res, only valid with 3G XOR)
key = [0x00] * 16
opc = [0x00] * 16 # Only for Milenage
def __init__(self, content = None):
if content == None:
return
SYSMO_ISIMSJA2_FILE_EF_XSIM_AUTH_KEY.__init__(self, content)
header = content[0]
self.full_res = bool((header >> 6) & 1)
self.ext_res = bool((header >> 7) & 1)
self.key = content[1:17]
if len(content) > 17:
self.opc = content[17:33]
def __str__(self):
dump = ""
pfx = " "
dump += SYSMO_ISIMSJA2_FILE_EF_XSIM_AUTH_KEY.__str__(self)
if self.full_res == True and self.ext_res == False:
dump += pfx + "3G: Return full 8-byte RES\n"
elif self.full_res == False and self.ext_res == False:
dump += pfx + "3G: Return first four bytes of RES\n"
elif self.ext_res == True:
dump += pfx + "3G: Return 16-byte RES (XOR 3G only)\n"
else:
dump += pfx + "(invalid RES length setting)"
if self.algo != SYSMO_ISIMSJA2_ALGO_XOR and self.ext_res:
dump += pfx + "Warning: 16-byte RES is only valid with XOR 3G!\n"
if self.algo == SYSMO_ISIMSJA2_ALGO_MILENAGE:
dump += pfx + "Key: " + hexdump(self.key) + "\n"
dump += pfx + "OPc: " + hexdump(self.opc)
elif self.algo == SYSMO_ISIMSJA2_ALGO_XOR:
dump += pfx + "Key: " + hexdump(self.key) + "\n"
dump += pfx + "OPc: " + hexdump(self.opc)
elif self.algo == SYSMO_ISIMSJA2_ALGO_SHA1AKA:
dump += pfx + "Root key: " + hexdump(self.key) + "\n"
dump += pfx + "OPc: " + hexdump(self.opc) + " (unused)"
else:
dump += pfx + "Key: " + hexdump(self.key) + "\n"
dump += pfx + "OPc: " + hexdump(self.opc) + " (unused)"
return dump
def encode(self):
out = SYSMO_ISIMSJA2_FILE_EF_XSIM_AUTH_KEY.encode(self)
if self.full_res == True:
out[0] |= 1 << 6
if self.ext_res == True:
out[0] |= 1 << 7
out += self.key
# Note: Normally an OPc is only used with milenage, but lets
# write the value anyway, even if it is not used.
out += self.opc
return out
# EF_USIM_AUTH_KEY_2G and EF_USIM_AUTH_KEY_GBA have the same layout as
# EF_USIM_AUTH_KEY, so there is nothing to specialize other than the class name
class SYSMO_ISIMSJA2_FILE_EF_USIM_AUTH_KEY_2G(SYSMO_ISIMSJA2_FILE_EF_USIM_AUTH_KEY):
pass
class SYSMO_ISIMSJA2_FILE_EF_USIM_AUTH_KEY_GBA(SYSMO_ISIMSJA2_FILE_EF_USIM_AUTH_KEY):
pass
class SYSMO_ISIMSJA2_FILE_EF_MILENAGE_CFG:
R1 = 0x40
R2 = 0x00
R3 = 0x20
R4 = 0x40
R5 = 0x60
C1 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
C2 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]
C3 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02]
C4 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04]
C5 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08]
def __init__(self, content = None):
if content == None:
return
if len(content) != 85:
return
self.R1 = content[0]
self.R2 = content[1]
self.R3 = content[2]
self.R4 = content[3]
self.R5 = content[4]
self.C1 = content[5:5+16]
self.C2 = content[21:37]
self.C3 = content[37:53]
self.C4 = content[53:69]
self.C5 = content[69:85]
def __str__(self):
dump = " R1: " + str(hex(self.R1)) + "\n"
dump += " R2: " + str(hex(self.R2)) + "\n"
dump += " R3: " + str(hex(self.R3)) + "\n"
dump += " R4: " + str(hex(self.R4)) + "\n"
dump += " R5: " + str(hex(self.R5)) + "\n"
dump += " C1: " + hexdump(self.C1) + "\n"
dump += " C2: " + hexdump(self.C2) + "\n"
dump += " C3: " + hexdump(self.C3) + "\n"
dump += " C4: " + hexdump(self.C4) + "\n"
dump += " C5: " + hexdump(self.C5)
return dump
def encode(self):
out = [self.R1, self.R2, self.R3, self.R4, self.R5]
out += self.C1 + self.C2 + self.C3 + self.C4 + self.C5
return out
class SYSMO_ISIMSJA2_FILE_EF_USIM_SQN:
# Flag1:
ind_size_bits = 5 # speficy file length by 2^ind_len
sqn_check_enabled = True # perform SQN checks below
sqn_age_limit_enabled = False # perform age limit check: (SQNms-SQN) <= AGE_LIMIT)
sqn_max_delta_enabled = True # perform delta max check: (SWN-SQNms) <= DELTA MAX)
sqn_check_skip_first = True # accept any SQN on the first authentication
# Flag2:
conceal_autn = True # Conceal the value of AUTN
conceal_auts = True # Conceal the value of AUTS
no_amf_clear = False # Do not clear AMF when computing MAC-S
# Data:
max_delta = 2**28 << ind_size_bits
age_limit = 2**28 << ind_size_bits
freshness_data = [0x00] * (6*2**ind_size_bits) # initalize to zero
def __init__(self, content = None):
if content == None:
return
# Check if we have at least the header
if len(content) <= 2:
raise ValueError("unexpected length of %u bytes", len(content))
flag1 = content[0]
self.ind_size_bits = flag1 & 0xf
# The parameter ind_size_bits is not user configurable,
# its a fixed configuration that is specific to the
# card profile and it can be determined by looking at the
# file length (length of the freshness data). If we find
# an ind_size_bits that is intconstant to the file length,
# we automatically set the value to the correct length
ind_size_bits_calculated = int(math.log((len(content) - 14) / 6, 2))
if ind_size_bits_calculated != self.ind_size_bits:
print " Warning: SQN Parameter ind_size_bits is set to " + str(self.ind_size_bits) + ", resetting it to " + str(ind_size_bits_calculated) + "!"
self.ind_size_bits = ind_size_bits_calculated
self.reset() #ensure freshness data is correctly reset
self.sqn_check_enabled = bool((flag1 >> 4) & 1)
self.sqn_age_limit_enabled = bool((flag1 >> 5) & 1)
self.sqn_max_delta_enabled = bool((flag1 >> 6) & 1)
self.sqn_check_skip_first = bool((flag1 >> 7) & 1)
flag2 = content[1]
self.conceal_autn = bool(flag2 & 1)
self.conceal_auts = bool((flag2 >> 1) & 1)
self.no_amf_clear = bool((flag2 >> 2) & 1)
# Check if the data body is complete
if len(content) < 14+(6*2**self.ind_size_bits):
raise ValueError("unexpected length of %u bytes" % len(content))
self.max_delta = list_to_int(content[2:8])
self.age_limit = list_to_int(content[8:14])
self.freshness_data = content[15:(6*2**self.ind_size_bits)]
def __str__(self):
pfx = " "
dump = ""
dump += "%sIND (bits): %u\n" % (pfx, self.ind_size_bits)
if self.sqn_check_enabled:
dump += "%sSQN Check enabled\n" % pfx
else:
dump += "%sSQN Check disabled\n" % pfx
if self.sqn_age_limit_enabled:
dump += "%sSQN Age Limit enabled\n" % pfx
else:
dump += "%sSQN Age Limit disabled\n" % pfx
if self.sqn_max_delta_enabled:
dump += "%sSQN Max Delta enabled\n" % pfx
else:
dump += "%sSQN Max Delta disabled\n" % pfx
if self.sqn_check_skip_first:
dump += "%sSQN Skip first enabled\n" % pfx
else:
dump += "%sSQN Skip first disabled\n" % pfx
if self.conceal_autn:
dump += "%sSQN Conceal AUTN enabled\n" % pfx
else:
dump += "%sSQN Conceal AUTN disabled\n" % pfx
if self.conceal_auts:
dump += "%sSQN Conceal AUTS enabled\n" % pfx
else:
dump += "%sSQN Conceal AUTS disabled\n" % pfx
if self.no_amf_clear:
dump += "%sSQN No AMF clear enabled\n" % pfx
else:
dump += "%sSQN No AMF clear disabled\n" % pfx
dump += "%sMax Delta: %u\n" % (pfx, self.max_delta)
dump += "%sAge Limit: %u\n" % (pfx, self.age_limit)
dump += pfx + "Freshness Data:\n" + hexdump(self.freshness_data, True)
return dump
def encode(self):
out = [0x00, 0x00]
# Flag1:
out[0] = self.ind_size_bits & 0x0f
if self.sqn_check_enabled:
out[0] |= 1 << 4
if self.sqn_age_limit_enabled:
out[0] |= 1 << 5
if self.sqn_max_delta_enabled:
out[0] |= 1 << 6
if self.sqn_check_skip_first:
out[0] |= 1 << 7
# Flag2:
if self.conceal_autn:
out[1] |= 1 << 0
if self.conceal_auts:
out[1] |= 1 << 1
if self.no_amf_clear:
out[1] |= 1 << 2
# Data:
out += int_to_list(self.max_delta, 6)
out += int_to_list(self.age_limit, 6)
out += self.freshness_data
return out
def reset(self):
self.freshness_data = [0x00] * (6*2**self.ind_size_bits)
class Sysmo_isim_sja2(Sysmo_usim):
def __init__(self):
card_detected = False
# Try card model #1
try:
atr = "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 30 34 05 4B A9"
print "Trying to find card with ATR: " + atr
Sysmo_usim.__init__(self, atr)
card_detected = True
except:
print " * Card not detected!"
if card_detected == True:
return
# Try card model #2
try:
atr = "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 31 33 02 51 B2"
print "Trying to find card with ATR: " + atr
Sysmo_usim.__init__(self, atr)
card_detected = True
except:
print " * Card not detected!"
if card_detected == True:
return
# Exit when we are not able to detect the card
if card_detected != True:
sys.exit(1)
# Show current milenage parameters
def show_milenage_params(self):
print("Reading Milenage parameters...")
self._init()
print(" * Reading...")
self.sim.card.SELECT_ADF_USIM()
self.sim.select(SYSMO_ISIMSJA2_EF_MILENAGE_CFG)
res = self._read_binary(85)
ef = SYSMO_ISIMSJA2_FILE_EF_MILENAGE_CFG(res.apdu)
print(" * Current Milenage Parameters:")
print(str(ef))
print("")
# Write new milenage parameters
def write_milenage_params(self, params):
print("Programming Milenage parameters...")
if (len(params) < 85):
print("Error: Short milenage parameters!")
return
params_swapped = params[80:85] + params[0:80]
self._init()
print(" * New Milenage Parameters for (EF.MILENAGE_CFG):")
ef_milenage_cfg = SYSMO_ISIMSJA2_FILE_EF_MILENAGE_CFG(params_swapped)
print str(ef_milenage_cfg)
print(" * Programming...")
# Note: The milenage configuration file in ADF_USIM and
# ADF_ISIM are linked, however we write to both locations,
# just to be sure.
self.sim.card.SELECT_ADF_USIM()
self.sim.select(SYSMO_ISIMSJA2_EF_MILENAGE_CFG)
self.sim.update_binary(ef_milenage_cfg.encode())
self.sim.card.SELECT_ADF_ISIM()
self.sim.select(SYSMO_ISIMSJA2_EF_MILENAGE_CFG)
self.sim.update_binary(ef_milenage_cfg.encode())
print("")
# Select DF_SYSTEM/EF_SIM_AUTH_KEY
def __select_ef_sim_auth_key(self):
self.sim.select(GSM_SIM_MF)
self.sim.select(SYSMO_ISIMSJA2_DF_SYSTEM)
self.sim.select(SYSMO_ISIMSJA2_EF_SIM_AUTH_KEY)
# Authentication keys exist in various different files, which are
# similar, thie method simplifies the selection of those files
def __select_xsim_auth_key(self, isim = False, _2G = False):
self.sim.select(GSM_SIM_MF)
if isim:
self.sim.card.SELECT_ADF_ISIM()
else:
self.sim.card.SELECT_ADF_USIM()
if _2G:
self.sim.select(SYSMO_ISIMSJA2_EF_USIM_AUTH_KEY_2G)
else:
self.sim.select(SYSMO_ISIMSJA2_EF_USIM_AUTH_KEY)
# In the SJA2 model the key material and the algorithm configuration
# is distributed over multiple files, which may also have redundant
# contents. Files can also be hard linked to other files so that
# changes in one file may appear in another file as well. The dump
# method provides an overview of contents of all files at once in
# order to help debugging problems
def dump(self):
print("Reading propritary files...")
self._init()
# DF_SYSTEM/EF_SIM_AUTH_KEY:
self.__select_ef_sim_auth_key()
res = self._read_binary(self.sim.filelen)
print " * DF_SYSTEM/EF_SIM_AUTH_KEY:"
print SYSMO_ISIMSJA2_FILE_EF_SIM_AUTH_KEY(res.apdu)
# ADF_USIM/EF_USIM_AUTH_KEY_2G:
self.__select_xsim_auth_key(isim = False, _2G = True)
res = self._read_binary(self.sim.filelen)
print " * ADF_USIM/EF_USIM_AUTH_KEY_2G:"
print SYSMO_ISIMSJA2_FILE_EF_USIM_AUTH_KEY_2G(res.apdu)
# ADF_USIM/EF_ISIM_AUTH_KEY_2G:
self.__select_xsim_auth_key(isim = True, _2G = True)
res = self._read_binary(self.sim.filelen)
print " * ADF_ISIM/EF_ISIM_AUTH_KEY_2G:"
print SYSMO_ISIMSJA2_FILE_EF_USIM_AUTH_KEY_2G(res.apdu)
# ADF_USIM/EF_USIM_AUTH_KEY:
self.__select_xsim_auth_key(isim = False, _2G = False)
res = self._read_binary(self.sim.filelen)
print " * ADF_USIM/EF_USIM_AUTH_KEY:"
print SYSMO_ISIMSJA2_FILE_EF_USIM_AUTH_KEY(res.apdu)
# ADF_ISIM/EF_ISIM_AUTH_KEY:
self.__select_xsim_auth_key(isim = True, _2G = False)
res = self._read_binary(self.sim.filelen)
print " * ADF_ISIM/EF_ISIM_AUTH_KEY:"
print SYSMO_ISIMSJA2_FILE_EF_USIM_AUTH_KEY(res.apdu)
# ADF_USIM/EF_MILENAGE_CFG:
self.sim.select(GSM_SIM_MF)
self.sim.card.SELECT_ADF_USIM()
self.sim.select(SYSMO_ISIMSJA2_EF_MILENAGE_CFG)
res = self._read_binary(self.sim.filelen)
print " * ADF_USIM/EF_MILENAGE_CFG:"
print SYSMO_ISIMSJA2_FILE_EF_MILENAGE_CFG(res.apdu)
# ADF_ISIM/EF_MILENAGE_CFG:
self.sim.select(GSM_SIM_MF)
self.sim.card.SELECT_ADF_ISIM()
self.sim.select(SYSMO_ISIMSJA2_EF_MILENAGE_CFG)
res = self._read_binary(self.sim.filelen)
print " * ADF_ISIM/EF_MILENAGE_CFG:"
print SYSMO_ISIMSJA2_FILE_EF_MILENAGE_CFG(res.apdu)
# ADF_USIM/EF_USIM_SQN: