Compare commits
5 Commits
fairwaves/
...
zecke/tmp2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d56eb30eea | ||
|
|
533a0dce3a | ||
|
|
29474b0d5b | ||
|
|
800c9eb097 | ||
|
|
73b686f7ee |
38
README
Normal file
38
README
Normal file
@@ -0,0 +1,38 @@
|
||||
This utility allows to :
|
||||
|
||||
* Program customizable SIMs. Two modes are possible:
|
||||
|
||||
- one where you specify every parameter manually :
|
||||
|
||||
./pySim-prog.py -n 26C3 -c 49 -x 262 -y 42 -i <IMSI> -s <ICCID>
|
||||
|
||||
|
||||
- one where they are generated from some minimal set :
|
||||
|
||||
./pySim-prog.py -n 26C3 -c 49 -x 262 -y 42 -z <random_string_of_choice> -j <card_num>
|
||||
|
||||
With <random_string_of_choice> and <card_num>, the soft will generate
|
||||
'predictable' IMSI and ICCID, so make sure you choose them so as not to
|
||||
conflict with anyone. (for eg. your name as <random_string_of_choice> and
|
||||
0 1 2 ... for <card num>).
|
||||
|
||||
You also need to enter some parameters to select the device :
|
||||
-t TYPE : type of card (supersim, magicsim, fakemagicsim or try 'auto')
|
||||
-d DEV : Serial port device (default /dev/ttyUSB0)
|
||||
-b BAUD : Baudrate (default 9600)
|
||||
|
||||
* Interact with SIMs from a python interactive shell (ipython for eg :)
|
||||
|
||||
from pySim.transport.serial import SerialSimLink
|
||||
from pySim.commands import SimCardCommands
|
||||
|
||||
sl = SerialSimLink(device='/dev/ttyUSB0', baudrate=9600)
|
||||
sc = SimCardCommands(sl)
|
||||
|
||||
sl.wait_for_card()
|
||||
|
||||
# Print IMSI
|
||||
print sc.read_binary(['3f00', '7f20', '6f07'])
|
||||
|
||||
# Run A3/A8
|
||||
print sc.run_gsm('00112233445566778899aabbccddeeff')
|
||||
91
README.md
91
README.md
@@ -1,91 +0,0 @@
|
||||
pySim-prog - Utility for programmable SIM/USIM-Cards
|
||||
====================================================
|
||||
|
||||
This repository contains a Python-language program that can be used
|
||||
to program (write) certain fields/parameters on so-called programmable
|
||||
SIM/USIM cards.
|
||||
|
||||
Such SIM/USIM cards are special cards, which - unlike those issued by
|
||||
regular commercial operators - come with the kind of keys that allow you
|
||||
to write the files/fields that normally only an operator can program.
|
||||
|
||||
This is useful particularly if you are running your own cellular
|
||||
network, and want to issue your own SIM/USIM cards for that network.
|
||||
|
||||
|
||||
Homepage
|
||||
--------
|
||||
|
||||
The official homepage of the project is
|
||||
<http://osmocom.org/projects/pysim/wiki>
|
||||
|
||||
GIT Repository
|
||||
--------------
|
||||
|
||||
You can clone from the official libosmocore.git repository using
|
||||
|
||||
git clone git://git.osmocom.org/pysim.git
|
||||
|
||||
There is a cgit interface at <http://git.osmocom.org/pysim/>
|
||||
|
||||
Mailing List
|
||||
------------
|
||||
|
||||
There is no separate mailing list for this project. However,
|
||||
discussions related to pysim-prog are happening on the
|
||||
openbsc@lists.osmocom.org mailing list, please see
|
||||
<https://lists.osmocom.org/mailman/listinfo/openbsc> for subscription
|
||||
options and the list archive.
|
||||
|
||||
Please observe the [Osmocom Mailing List
|
||||
Rules](https://osmocom.org/projects/cellular-infrastructure/wiki/Mailing_List_Rules)
|
||||
when posting.
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
Our coding standards are described at
|
||||
<https://osmocom.org/projects/cellular-infrastructure/wiki/Coding_standards>
|
||||
|
||||
We are currently accepting patches by e-mail to the above-mentioned
|
||||
mailing list.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
* Program customizable SIMs. Two modes are possible:
|
||||
|
||||
- one where you specify every parameter manually :
|
||||
|
||||
./pySim-prog.py -n 26C3 -c 49 -x 262 -y 42 -i <IMSI> -s <ICCID>
|
||||
|
||||
|
||||
- one where they are generated from some minimal set :
|
||||
|
||||
./pySim-prog.py -n 26C3 -c 49 -x 262 -y 42 -z <random_string_of_choice> -j <card_num>
|
||||
|
||||
With <random_string_of_choice> and <card_num>, the soft will generate
|
||||
'predictable' IMSI and ICCID, so make sure you choose them so as not to
|
||||
conflict with anyone. (for eg. your name as <random_string_of_choice> and
|
||||
0 1 2 ... for <card num>).
|
||||
|
||||
You also need to enter some parameters to select the device :
|
||||
-t TYPE : type of card (supersim, magicsim, fakemagicsim or try 'auto')
|
||||
-d DEV : Serial port device (default /dev/ttyUSB0)
|
||||
-b BAUD : Baudrate (default 9600)
|
||||
|
||||
* Interact with SIMs from a python interactive shell (ipython for eg :)
|
||||
|
||||
from pySim.transport.serial import SerialSimLink
|
||||
from pySim.commands import SimCardCommands
|
||||
|
||||
sl = SerialSimLink(device='/dev/ttyUSB0', baudrate=9600)
|
||||
sc = SimCardCommands(sl)
|
||||
|
||||
sl.wait_for_card()
|
||||
|
||||
# Print IMSI
|
||||
print sc.read_binary(['3f00', '7f20', '6f07'])
|
||||
|
||||
# Run A3/A8
|
||||
print sc.run_gsm('00112233445566778899aabbccddeeff')
|
||||
@@ -1,123 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# Utility to randomize Ki and other values in a Fairwaves SIM card DB file
|
||||
#
|
||||
# Copyright (C) 2017-2018 Alexander Chemeris <alexander.chemeris@gmail.com>
|
||||
#
|
||||
# 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 optparse import OptionParser
|
||||
import os
|
||||
import sys
|
||||
import csv
|
||||
import random
|
||||
|
||||
from pySim.utils import derive_milenage_opc
|
||||
|
||||
#from pySim.utils import h2b
|
||||
def h2b(s):
|
||||
return ''.join([chr((int(x,16)<<4)+int(y,16)) for x,y in zip(s[0::2], s[1::2])])
|
||||
|
||||
def load_sim_db(filename):
|
||||
sim_db = {}
|
||||
with open(filename, 'r') as f:
|
||||
reader = csv.reader(f, delimiter=' ')
|
||||
# Skip the header
|
||||
reader.next()
|
||||
for l in reader:
|
||||
sim_db[l[0]] = l
|
||||
return sim_db
|
||||
|
||||
def write_sim_db(filename, sim_db):
|
||||
with open(filename, 'a') as f:
|
||||
cw = csv.writer(f, delimiter=' ')
|
||||
for iccid in sorted(sim_db.iterkeys()):
|
||||
cw.writerow([x for x in sim_db[iccid]])
|
||||
|
||||
def process_sim(sim_keys, opts):
|
||||
# Update IMSI
|
||||
imsi = sim_keys[1]
|
||||
imsi = "%03d%02d%s" % (opts.mcc, opts.mnc, imsi[5:])
|
||||
sim_keys[1] = imsi
|
||||
|
||||
# Update Ki
|
||||
ki = ''.join(['%02x' % random.randrange(0,256) for i in range(16)]).upper()
|
||||
sim_keys[8] = ki
|
||||
|
||||
# Update OPC
|
||||
op_opc = derive_milenage_opc(ki, opts.op).upper()
|
||||
sim_keys[9] = '01' + op_opc
|
||||
|
||||
return sim_keys
|
||||
|
||||
|
||||
def process_db(sim_db, opts):
|
||||
sim_db_new = {}
|
||||
for iccid, sim_keys in sim_db.items():
|
||||
sim_db_new[iccid] = process_sim(sim_keys, opts)
|
||||
return sim_db_new
|
||||
|
||||
|
||||
def parse_options():
|
||||
|
||||
parser = OptionParser(usage="usage: %prog [options]",
|
||||
description="Utility to randomize Ki and other values in a Fairwaves SIM card DB file.")
|
||||
|
||||
parser.add_option("-s", "--sim-db", dest="sim_db_filename", type='string', metavar="FILE",
|
||||
help="filename of a SIM DB to load keys from (space separated)",
|
||||
default="sim_db.dat",
|
||||
)
|
||||
parser.add_option("-o", "--out-db", dest="out_db_filename", type='string', metavar="FILE",
|
||||
help="filename of a SIM DB to write keys to (space separated)",
|
||||
default=None,
|
||||
)
|
||||
parser.add_option("-x", "--mcc", dest="mcc", type="int",
|
||||
help="Mobile Country Code [default: %default]",
|
||||
default=001,
|
||||
)
|
||||
parser.add_option("-y", "--mnc", dest="mnc", type="int",
|
||||
help="Mobile Network Code [default: %default]",
|
||||
default=01,
|
||||
)
|
||||
parser.add_option("--op", dest="op",
|
||||
help="Set OP to derive OPC from OP and KI [default: %default]",
|
||||
default='00000000000000000000000000000000',
|
||||
)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if args:
|
||||
parser.error("Extraneous arguments")
|
||||
|
||||
return options
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# Parse options
|
||||
opts = parse_options()
|
||||
|
||||
if opts.out_db_filename is None:
|
||||
print("Please specify output DB filename")
|
||||
sys.exit(1)
|
||||
|
||||
print("Loading SIM DB ...")
|
||||
sim_db = load_sim_db(opts.sim_db_filename)
|
||||
|
||||
sim_db = process_db(sim_db, opts)
|
||||
|
||||
print("Writing SIM DB ...")
|
||||
write_sim_db(opts.out_db_filename, sim_db)
|
||||
@@ -1,154 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# Utility to write data from a Fairwaves SIM card DB to Osmocom HLR DB
|
||||
#
|
||||
# Copyright (C) 2017-2018 Alexander Chemeris <alexander.chemeris@gmail.com>
|
||||
#
|
||||
# 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 optparse import OptionParser
|
||||
import os
|
||||
import sys
|
||||
import csv
|
||||
|
||||
#from pySim.utils import h2b
|
||||
def h2b(s):
|
||||
return ''.join([chr((int(x,16)<<4)+int(y,16)) for x,y in zip(s[0::2], s[1::2])])
|
||||
|
||||
def load_sim_db(filename):
|
||||
sim_db = {}
|
||||
with open(filename, 'r') as f:
|
||||
reader = csv.reader(f, delimiter=' ')
|
||||
# Skip the header
|
||||
# reader.next()
|
||||
for l in reader:
|
||||
sim_db[l[0]] = l
|
||||
return sim_db
|
||||
|
||||
def _dbi_binary_quote(s):
|
||||
# Count usage of each char
|
||||
cnt = {}
|
||||
for c in s:
|
||||
cnt[c] = cnt.get(c, 0) + 1
|
||||
|
||||
# Find best offset
|
||||
e = 0
|
||||
m = len(s)
|
||||
for i in range(1, 256):
|
||||
if i == 39:
|
||||
continue
|
||||
sum_ = cnt.get(i, 0) + cnt.get((i+1)&0xff, 0) + cnt.get((i+39)&0xff, 0)
|
||||
if sum_ < m:
|
||||
m = sum_
|
||||
e = i
|
||||
if m == 0: # No overhead ? use this !
|
||||
break;
|
||||
|
||||
# Generate output
|
||||
out = []
|
||||
out.append( chr(e) ) # Offset
|
||||
for c in s:
|
||||
x = (256 + ord(c) - e) % 256
|
||||
if x in (0, 1, 39):
|
||||
out.append('\x01')
|
||||
out.append(chr(x+1))
|
||||
else:
|
||||
out.append(chr(x))
|
||||
|
||||
return ''.join(out)
|
||||
|
||||
def write_key_hlr(opts, sim_data):
|
||||
# SQLite3 OpenBSC HLR
|
||||
import sqlite3
|
||||
conn = sqlite3.connect(opts.hlr_db_filename)
|
||||
|
||||
imsi = sim_data[1]
|
||||
ki = sim_data[8]
|
||||
|
||||
c = conn.execute('SELECT id FROM Subscriber WHERE imsi = ?', (imsi,))
|
||||
sub_id = c.fetchone()
|
||||
if sub_id is None:
|
||||
print("IMSI %s is not found in the HLR" % (imsi,))
|
||||
return None
|
||||
sub_id = sub_id[0]
|
||||
print("IMSI %s has ID %d, writing Ki %s" % (imsi, sub_id, ki))
|
||||
|
||||
# c = conn.execute(
|
||||
# 'INSERT INTO Subscriber ' +
|
||||
# '(imsi, name, extension, authorized, created, updated) ' +
|
||||
# 'VALUES ' +
|
||||
# '(?,?,?,1,datetime(\'now\'),datetime(\'now\'));',
|
||||
# [
|
||||
# params['imsi'],
|
||||
# params['name'],
|
||||
# '9' + params['iccid'][-5:-1]
|
||||
# ],
|
||||
# )
|
||||
# sub_id = c.lastrowid
|
||||
# c.close()
|
||||
|
||||
c = conn.execute(
|
||||
'INSERT OR REPLACE INTO AuthKeys ' +
|
||||
'(subscriber_id, algorithm_id, a3a8_ki)' +
|
||||
'VALUES ' +
|
||||
'(?,?,?)',
|
||||
[ sub_id, 2, sqlite3.Binary(_dbi_binary_quote(h2b(ki))) ],
|
||||
)
|
||||
|
||||
c = conn.execute(
|
||||
'DELETE FROM AuthLastTuples WHERE subscriber_id = ?',
|
||||
[ sub_id ],
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return True
|
||||
|
||||
|
||||
def parse_options():
|
||||
|
||||
parser = OptionParser(usage="usage: %prog [options]",
|
||||
description="Utility to write data from a Fairwaves SIM card DB to Osmocom HLR DB.")
|
||||
|
||||
parser.add_option("-s", "--sim-db", dest="sim_db_filename", type='string', metavar="FILE",
|
||||
help="filename of a SIM DB to load keys from (space searated)",
|
||||
default="sim_db.dat",
|
||||
)
|
||||
parser.add_option("-d", "--hlr", dest="hlr_db_filename", type='string', metavar="FILE",
|
||||
help="filename of a HLR SQLite3 DB to write the keys to",
|
||||
default="hlr.sqlite3",
|
||||
)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if args:
|
||||
parser.error("Extraneous arguments")
|
||||
|
||||
return options
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# Parse options
|
||||
opts = parse_options()
|
||||
|
||||
print("Loading SIM DB ...")
|
||||
sim_db = load_sim_db(opts.sim_db_filename)
|
||||
|
||||
for iccid, sim in sim_db.items():
|
||||
write_key_hlr(opts, sim)
|
||||
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# Utility to remove duplicates from a Fairwaves SIM card DB file
|
||||
#
|
||||
# Copyright (C) 2017-2018 Alexander Chemeris <alexander.chemeris@gmail.com>
|
||||
#
|
||||
# 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 optparse import OptionParser
|
||||
import os
|
||||
import sys
|
||||
import csv
|
||||
|
||||
#from pySim.utils import h2b
|
||||
def h2b(s):
|
||||
return ''.join([chr((int(x,16)<<4)+int(y,16)) for x,y in zip(s[0::2], s[1::2])])
|
||||
|
||||
def load_sim_db(filename):
|
||||
sim_db = {}
|
||||
with open(filename, 'r') as f:
|
||||
reader = csv.reader(f, delimiter=' ')
|
||||
# Skip the header
|
||||
# reader.next()
|
||||
for l in reader:
|
||||
sim_db[l[0]] = l
|
||||
return sim_db
|
||||
|
||||
def write_sim_db(filename, sim_db):
|
||||
with open(filename, 'a') as f:
|
||||
cw = csv.writer(f, delimiter=' ')
|
||||
for iccid in sorted(sim_db.iterkeys()):
|
||||
cw.writerow([x for x in sim_db[iccid]])
|
||||
|
||||
|
||||
def parse_options():
|
||||
|
||||
parser = OptionParser(usage="usage: %prog [options]",
|
||||
description="Utility to remove duplicates from a Fairwaves SIM card DB file")
|
||||
|
||||
parser.add_option("-s", "--sim-db", dest="sim_db_filename", type='string', metavar="FILE",
|
||||
help="filename of a SIM DB to load keys from (space separated)",
|
||||
default="sim_db.dat",
|
||||
)
|
||||
parser.add_option("-o", "--out-db", dest="out_db_filename", type='string', metavar="FILE",
|
||||
help="filename of a SIM DB to write keys to (space separated)",
|
||||
default=None,
|
||||
)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if args:
|
||||
parser.error("Extraneous arguments")
|
||||
|
||||
return options
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# Parse options
|
||||
opts = parse_options()
|
||||
|
||||
if opts.out_db_filename is None:
|
||||
print("Please specify output DB filename")
|
||||
sys.exit(1)
|
||||
|
||||
print("Loading SIM DB ...")
|
||||
sim_db = load_sim_db(opts.sim_db_filename)
|
||||
print("Writing SIM DB ...")
|
||||
write_sim_db(opts.out_db_filename, sim_db)
|
||||
@@ -1,289 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# Utility to update SPN field of a SIM card
|
||||
#
|
||||
# Copyright (C) 2017-2018 Alexander Chemeris <alexander.chemeris@gmail.com>
|
||||
#
|
||||
# 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 optparse import OptionParser
|
||||
import os
|
||||
import sys
|
||||
import csv
|
||||
import random
|
||||
import subprocess
|
||||
|
||||
from pySim.commands import SimCardCommands
|
||||
from pySim.utils import h2b, swap_nibbles, rpad, dec_imsi, dec_iccid, derive_milenage_opc
|
||||
from pySim.cards import card_autodetect
|
||||
|
||||
|
||||
def load_sim_db(filename):
|
||||
sim_db = {}
|
||||
with open(filename, 'r') as f:
|
||||
reader = csv.reader(f, delimiter=' ')
|
||||
# Skip the header
|
||||
reader.next()
|
||||
for l in reader:
|
||||
sim_db[l[0]] = l
|
||||
return sim_db
|
||||
|
||||
def write_params_csv(filename, sim_keys):
|
||||
with open(filename, 'a') as f:
|
||||
cw = csv.writer(f, delimiter=' ')
|
||||
cw.writerow([x for x in sim_keys])
|
||||
|
||||
|
||||
def program_sim_card(card, sim_db, opts):
|
||||
# Program the card
|
||||
print("Reading SIM card ...")
|
||||
|
||||
# EF.ICCID
|
||||
(iccid, sw) = card.read_iccid()
|
||||
if sw != '9000':
|
||||
print("ICCID: Can't read, response code = %s" % (sw,))
|
||||
sys.exit(1)
|
||||
print("ICCID: %s" % (iccid))
|
||||
|
||||
# Find SIM card keys in the DB
|
||||
sim_keys = sim_db.get(iccid+'F')
|
||||
if sim_keys == None:
|
||||
print("Can't find SIM card in the SIM DB.")
|
||||
sys.exit(1)
|
||||
|
||||
# EF.IMSI
|
||||
(imsi, sw) = card.read_imsi()
|
||||
if sw != '9000':
|
||||
print("IMSI: Can't read, response code = %s" % (sw,))
|
||||
sys.exit(1)
|
||||
print("IMSI: %s" % (imsi))
|
||||
|
||||
# EF.SPN
|
||||
((name, hplmn_disp, oplmn_disp), sw) = card.read_spn()
|
||||
if sw == '9000':
|
||||
print("Service Provider Name: %s" % name)
|
||||
print(" display for HPLMN %s" % hplmn_disp)
|
||||
print(" display for other PLMN %s" % oplmn_disp)
|
||||
else:
|
||||
print("Old SPN: Can't read, response code = %s" % (sw,))
|
||||
|
||||
print("Entring ADM code...")
|
||||
|
||||
# Enter ADM code to get access to proprietary files
|
||||
sw = card.verify_adm(h2b(sim_keys[6]))
|
||||
if sw != '9000':
|
||||
print("Fail to verify ADM code with result = %s" % (sw,))
|
||||
sys.exit(1)
|
||||
|
||||
# Read EF.Ki
|
||||
(ki, sw) = card.read_ki()
|
||||
if sw == '9000':
|
||||
ki = ki.upper()
|
||||
print("Ki: %s" % ki)
|
||||
else:
|
||||
print("Ki: Can't read, response code = %s" % (sw,))
|
||||
|
||||
# Read EF.OP/OPC
|
||||
((op_opc_type, op_opc), sw) = card.read_op_opc()
|
||||
if sw == '9000':
|
||||
op_opc = op_opc.upper()
|
||||
print("%s: %s" % (op_opc_type, op_opc))
|
||||
else:
|
||||
print("Ki: Can't read, response code = %s" % (sw,))
|
||||
|
||||
# Read EF.A3A8
|
||||
(a3a8, sw) = card.read_a3a8()
|
||||
if sw == '9000':
|
||||
print("A3/A8: %s" % (a3a8,))
|
||||
else:
|
||||
print("A3/A8: Can't read, response code = %s" % (sw,))
|
||||
|
||||
print("Programming...")
|
||||
|
||||
# Update SPN
|
||||
sw = card.update_spn(opts.name, False, False)
|
||||
if sw != '9000':
|
||||
print("SPN: Fail to update with result = %s" % (sw,))
|
||||
sys.exit(1)
|
||||
|
||||
# Update Ki
|
||||
ki = sim_keys[8]
|
||||
# ki = ''.join(['%02x' % random.randrange(0,256) for i in range(16)]).upper()
|
||||
# sim_keys[8] = ki
|
||||
sw = card.update_ki(sim_keys[8])
|
||||
if sw != '9000':
|
||||
print("Ki: Fail to update with result = %s" % (sw,))
|
||||
sys.exit(1)
|
||||
|
||||
# Update OPC
|
||||
op_opc = sim_keys[9][2:]
|
||||
# op_opc = derive_milenage_opc(ki, opts.op).upper()
|
||||
# sim_keys[9] = '01' + op_opc
|
||||
sw = card.update_opc(sim_keys[9][2:])
|
||||
if sw != '9000':
|
||||
print("OPC: Fail to update with result = %s" % (sw,))
|
||||
sys.exit(1)
|
||||
|
||||
# Update Home PLMN
|
||||
sw = card.update_hplmn_act(opts.mcc, opts.mnc)
|
||||
if sw != '9000':
|
||||
print("MCC/MNC: Fail to update with result = %s" % (sw,))
|
||||
sys.exit(1)
|
||||
|
||||
# Update IMSI
|
||||
imsi = sim_keys[1]
|
||||
# imsi = "%03d%02d%s" % (opts.mcc, opts.mnc, imsi[5:])
|
||||
# sim_keys[1] = imsi
|
||||
sw = card.update_imsi(imsi)
|
||||
if sw != '9000':
|
||||
print("IMSI: Fail to update with result = %s" % (sw,))
|
||||
sys.exit(1)
|
||||
|
||||
# Verify EF.IMSI
|
||||
(imsi_new, sw) = card.read_imsi()
|
||||
if sw != '9000':
|
||||
print("IMSI: Can't read, response code = %s" % (sw,))
|
||||
sys.exit(1)
|
||||
print("IMSI: %s" % (imsi_new))
|
||||
|
||||
# Verify EF.SPN
|
||||
((name, hplmn_disp, oplmn_disp), sw) = card.read_spn()
|
||||
if sw == '9000':
|
||||
print("Service Provider Name: %s" % name)
|
||||
print(" display for HPLMN %s" % hplmn_disp)
|
||||
print(" display for other PLMN %s" % oplmn_disp)
|
||||
else:
|
||||
print("New SPN: Can't read, response code = %s" % (sw,))
|
||||
|
||||
# Verify EF.Ki
|
||||
(ki_new, sw) = card.read_ki()
|
||||
if sw == '9000':
|
||||
ki_new = ki_new.upper()
|
||||
print("Ki: %s (%s)" % (ki_new, "match" if (ki==ki_new) else ("DON'T match %s" % ki)))
|
||||
else:
|
||||
print("New Ki: Can't read, response code = %s" % (sw,))
|
||||
|
||||
# Verify EF.OP/OPC
|
||||
((op_opc_type_new, op_opc_new), sw) = card.read_op_opc()
|
||||
if sw == '9000':
|
||||
op_opc_new = op_opc_new.upper()
|
||||
print("%s: %s (%s)" % (op_opc_type_new, op_opc_new, "match" if (op_opc==op_opc_new) else ("DON'T match %s" % op_opc)))
|
||||
else:
|
||||
print("Ki: Can't read, response code = %s" % (sw,))
|
||||
|
||||
# Done with this card
|
||||
print "Done !\n"
|
||||
|
||||
return sim_keys
|
||||
|
||||
|
||||
def parse_options():
|
||||
|
||||
parser = OptionParser(usage="usage: %prog [options]",
|
||||
description="An example utility to program Fairwaves SIM cards."
|
||||
" Modify it to your own specific needs.")
|
||||
|
||||
parser.add_option("-d", "--device", dest="device", metavar="DEV",
|
||||
help="Serial Device for SIM access [default: %default]",
|
||||
default="/dev/ttyUSB0",
|
||||
)
|
||||
parser.add_option("-b", "--baud", dest="baudrate", type="int", metavar="BAUD",
|
||||
help="Baudrate used for SIM access [default: %default]",
|
||||
default=9600,
|
||||
)
|
||||
parser.add_option("-p", "--pcsc-device", dest="pcsc_dev", type='int', metavar="PCSC",
|
||||
help="Which PC/SC reader number for SIM access",
|
||||
default=None,
|
||||
)
|
||||
parser.add_option("-s", "--sim-db", dest="sim_db_filename", type='string', metavar="FILE",
|
||||
help="filename of a SIM DB to load keys from (space searated)",
|
||||
default="sim_db.dat",
|
||||
)
|
||||
parser.add_option("-o", "--out-db", dest="out_db_filename", type='string', metavar="FILE",
|
||||
help="filename of a SIM DB to write keys to (space searated)",
|
||||
default="out.csv",
|
||||
)
|
||||
parser.add_option("--batch", dest="batch",
|
||||
help="Process SIM cards in batch mode - don't exit after programming and wait for the next SIM card to be inserted.",
|
||||
default=False, action="store_true",
|
||||
)
|
||||
parser.add_option("--sound", dest="sound_file", type='string', metavar="SOUND_FILE",
|
||||
help="Only in the batch mode. Play the given sound file on successful SIM programming",
|
||||
)
|
||||
parser.add_option("-n", "--name", dest="name",
|
||||
help="Operator name [default: %default]",
|
||||
default="Fairwaves",
|
||||
)
|
||||
parser.add_option("-x", "--mcc", dest="mcc", type="int",
|
||||
help="Mobile Country Code [default: %default]",
|
||||
default=001,
|
||||
)
|
||||
parser.add_option("-y", "--mnc", dest="mnc", type="int",
|
||||
help="Mobile Network Code [default: %default]",
|
||||
default=01,
|
||||
)
|
||||
parser.add_option("--op", dest="op",
|
||||
help="Set OP to derive OPC from OP and KI [default: %default]",
|
||||
default='00000000000000000000000000000000',
|
||||
)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if args:
|
||||
parser.error("Extraneous arguments")
|
||||
|
||||
return options
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# Parse options
|
||||
opts = parse_options()
|
||||
|
||||
# Connect to the card
|
||||
if opts.pcsc_dev is None:
|
||||
from pySim.transport.serial import SerialSimLink
|
||||
sl = SerialSimLink(device=opts.device, baudrate=opts.baudrate)
|
||||
else:
|
||||
from pySim.transport.pcsc import PcscSimLink
|
||||
sl = PcscSimLink(opts.pcsc_dev)
|
||||
|
||||
# Create command layer
|
||||
scc = SimCardCommands(transport=sl)
|
||||
|
||||
print("Loading SIM DB ...")
|
||||
sim_db = load_sim_db(opts.sim_db_filename)
|
||||
|
||||
if opts.batch:
|
||||
print("Batch mode enabled! Press Ctrl-C to exit")
|
||||
|
||||
# Loop once in non-batch mode and loop forever in batch mode
|
||||
first_run = True
|
||||
while first_run or opts.batch:
|
||||
print("Insert a SIM card to program...")
|
||||
sl.wait_for_card(newcardonly=not first_run)
|
||||
first_run = False
|
||||
|
||||
card = card_autodetect(scc)
|
||||
if card is None:
|
||||
print("Card autodetect failed")
|
||||
continue
|
||||
print "Autodetected card type %s" % card.name
|
||||
|
||||
sim_keys = program_sim_card(card, sim_db, opts)
|
||||
write_params_csv(opts.out_db_filename, sim_keys)
|
||||
if opts.sound_file is not None and opts.sound_file != "":
|
||||
subprocess.call(["paplay", opts.sound_file])
|
||||
168
pySim-prog.py
168
pySim-prog.py
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# Utility to deal with sim cards and program the 'magic' ones easily
|
||||
@@ -39,7 +39,7 @@ except ImportError:
|
||||
|
||||
from pySim.commands import SimCardCommands
|
||||
from pySim.cards import _cards_classes
|
||||
from pySim.utils import h2b, swap_nibbles, rpad, derive_milenage_opc, calculate_luhn
|
||||
from pySim.utils import h2b, swap_nibbles, rpad
|
||||
|
||||
|
||||
def parse_options():
|
||||
@@ -70,12 +70,6 @@ def parse_options():
|
||||
default=False,
|
||||
)
|
||||
|
||||
parser.add_option("-S", "--source", dest="source",
|
||||
help="Data Source[default: %default]",
|
||||
default="cmdline",
|
||||
)
|
||||
|
||||
# if mode is "cmdline"
|
||||
parser.add_option("-n", "--name", dest="name",
|
||||
help="Operator name [default: %default]",
|
||||
default="Magic",
|
||||
@@ -116,10 +110,9 @@ def parse_options():
|
||||
)
|
||||
parser.add_option("--acc", dest="acc",
|
||||
help="Set ACC bits (Access Control Code). not all card types are supported",
|
||||
)
|
||||
parser.add_option("--read-imsi", dest="read_imsi", action="store_true",
|
||||
help="Read the IMSI from the CARD", default=False
|
||||
)
|
||||
|
||||
|
||||
parser.add_option("-z", "--secret", dest="secret", metavar="STR",
|
||||
help="Secret used for ICCID/IMSI autogen",
|
||||
)
|
||||
@@ -134,20 +127,12 @@ def parse_options():
|
||||
help="Optional batch state file",
|
||||
)
|
||||
|
||||
# if mode is "csv"
|
||||
parser.add_option("--read-csv", dest="read_csv", metavar="FILE",
|
||||
help="Read parameters from CSV file rather than command line")
|
||||
|
||||
|
||||
parser.add_option("--write-csv", dest="write_csv", metavar="FILE",
|
||||
help="Append generated parameters in CSV file",
|
||||
)
|
||||
parser.add_option("--write-hlr", dest="write_hlr", metavar="FILE",
|
||||
help="Append generated parameters to OpenBSC HLR sqlite3",
|
||||
)
|
||||
parser.add_option("--dry-run", dest="dry_run",
|
||||
help="Perform a 'dry run', don't actually program the card",
|
||||
default=False, action="store_true")
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
@@ -156,20 +141,6 @@ def parse_options():
|
||||
print kls.name
|
||||
sys.exit(0)
|
||||
|
||||
if options.source == 'csv':
|
||||
if (options.imsi is None) and (options.batch_mode is False) and (options.read_imsi is False):
|
||||
parser.error("CSV mode needs either an IMSI, --read-imsi or batch mode")
|
||||
if options.read_csv is None:
|
||||
parser.error("CSV mode requires a CSV input file")
|
||||
elif options.source == 'cmdline':
|
||||
if ((options.imsi is None) or (options.iccid is None)) and (options.num is None):
|
||||
parser.error("If either IMSI or ICCID isn't specified, num is required")
|
||||
else:
|
||||
parser.error("Only `cmdline' and `csv' sources supported")
|
||||
|
||||
if (options.read_csv is not None) and (options.source != 'csv'):
|
||||
parser.error("You cannot specify a CSV input file in source != csv")
|
||||
|
||||
if (options.batch_mode) and (options.num is None):
|
||||
options.num = 0
|
||||
|
||||
@@ -177,6 +148,9 @@ def parse_options():
|
||||
if (options.imsi is not None) or (options.iccid is not None):
|
||||
parser.error("Can't give ICCID/IMSI for batch mode, need to use automatic parameters ! see --num and --secret for more informations")
|
||||
|
||||
if ((options.imsi is None) or (options.iccid is None)) and (options.num is None):
|
||||
parser.error("If either IMSI or ICCID isn't specified, num is required")
|
||||
|
||||
if args:
|
||||
parser.error("Extraneous arguments")
|
||||
|
||||
@@ -234,6 +208,24 @@ def _dbi_binary_quote(s):
|
||||
|
||||
return ''.join(out)
|
||||
|
||||
def calculate_luhn(cc):
|
||||
num = map(int, str(cc))
|
||||
check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10
|
||||
return 0 if check_digit == 10 else check_digit
|
||||
|
||||
def derive_milenage_opc(ki_hex, op_hex):
|
||||
"""
|
||||
Run the milenage algorithm.
|
||||
"""
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.strxor import strxor
|
||||
from pySim.utils import b2h
|
||||
|
||||
# We pass in hex string and now need to work on bytes
|
||||
aes = AES.new(h2b(ki_hex))
|
||||
opc_bytes = aes.encrypt(h2b(op_hex))
|
||||
return b2h(strxor(opc_bytes, h2b(op_hex)))
|
||||
|
||||
def gen_parameters(opts):
|
||||
"""Generates Name, ICCID, MCC, MNC, IMSI, SMSP, Ki, PIN-ADM from the
|
||||
options given by the user"""
|
||||
@@ -349,7 +341,7 @@ def gen_parameters(opts):
|
||||
else:
|
||||
ki = ''.join(['%02x' % random.randrange(0,256) for i in range(16)])
|
||||
|
||||
# OPC (random)
|
||||
# Ki (random)
|
||||
if opts.opc is not None:
|
||||
opc = opts.opc
|
||||
if not re.match('^[0-9a-fA-F]{32}$', opc):
|
||||
@@ -361,9 +353,10 @@ def gen_parameters(opts):
|
||||
opc = ''.join(['%02x' % random.randrange(0,256) for i in range(16)])
|
||||
|
||||
if opts.pin_adm is not None:
|
||||
pin_adm = opts.pin_adm
|
||||
if not re.match('^([0-9a-fA-F][0-9a-fA-F])+$', pin_adm):
|
||||
raise ValueError('ADM pin needs to be in hex format (even number of hex digits)')
|
||||
if len(opts.pin_adm) > 8:
|
||||
raise ValueError("PIN-ADM needs to be <=8 digits")
|
||||
pin_adm = ''.join(['%02x'%(ord(x)) for x in opts.pin_adm])
|
||||
pin_adm = rpad(pin_adm, 16)
|
||||
else:
|
||||
pin_adm = None
|
||||
|
||||
@@ -397,8 +390,8 @@ def print_parameters(params):
|
||||
""" % params
|
||||
|
||||
|
||||
def write_params_csv(opts, params):
|
||||
# csv
|
||||
def write_parameters(opts, params):
|
||||
# CSV
|
||||
if opts.write_csv:
|
||||
import csv
|
||||
row = ['name', 'iccid', 'mcc', 'mnc', 'imsi', 'smsp', 'ki', 'opc']
|
||||
@@ -407,34 +400,6 @@ def write_params_csv(opts, params):
|
||||
cw.writerow([params[x] for x in row])
|
||||
f.close()
|
||||
|
||||
def _read_params_csv(opts, imsi):
|
||||
import csv
|
||||
row = ['name', 'iccid', 'mcc', 'mnc', 'imsi', 'smsp', 'ki', 'opc']
|
||||
f = open(opts.read_csv, 'r')
|
||||
cr = csv.DictReader(f, row)
|
||||
i = 0
|
||||
for row in cr:
|
||||
if opts.num is not None and opts.read_imsi is False:
|
||||
if opts.num == i:
|
||||
f.close()
|
||||
return row;
|
||||
i += 1
|
||||
if row['imsi'] == imsi:
|
||||
f.close()
|
||||
return row;
|
||||
|
||||
f.close()
|
||||
return None
|
||||
|
||||
def read_params_csv(opts, imsi):
|
||||
row = _read_params_csv(opts, imsi)
|
||||
if row is not None:
|
||||
row['mcc'] = int(row['mcc'])
|
||||
row['mnc'] = int(row['mnc'])
|
||||
return row
|
||||
|
||||
|
||||
def write_params_hlr(opts, params):
|
||||
# SQLite3 OpenBSC HLR
|
||||
if opts.write_hlr:
|
||||
import sqlite3
|
||||
@@ -448,7 +413,7 @@ def write_params_hlr(opts, params):
|
||||
[
|
||||
params['imsi'],
|
||||
params['name'],
|
||||
'9' + params['iccid'][-5:-1]
|
||||
'9' + params['iccid'][-5:]
|
||||
],
|
||||
)
|
||||
sub_id = c.lastrowid
|
||||
@@ -465,10 +430,6 @@ def write_params_hlr(opts, params):
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def write_parameters(opts, params):
|
||||
write_params_csv(opts, params)
|
||||
write_params_hlr(opts, params)
|
||||
|
||||
|
||||
BATCH_STATE = [ 'name', 'country', 'mcc', 'mnc', 'smsp', 'secret', 'num' ]
|
||||
BATCH_INCOMPATIBLE = ['iccid', 'imsi', 'ki']
|
||||
@@ -566,57 +527,35 @@ if __name__ == '__main__':
|
||||
card = None
|
||||
|
||||
while not done:
|
||||
|
||||
if opts.dry_run is False:
|
||||
# Connect transport
|
||||
print "Insert card now (or CTRL-C to cancel)"
|
||||
sl.wait_for_card(newcardonly=not first)
|
||||
# Connect transport
|
||||
print "Insert card now (or CTRL-C to cancel)"
|
||||
sl.wait_for_card(newcardonly=not first)
|
||||
|
||||
# Not the first anymore !
|
||||
first = False
|
||||
|
||||
if opts.dry_run is False:
|
||||
# Get card
|
||||
card = card_detect(opts, scc)
|
||||
if card is None:
|
||||
if opts.batch_mode:
|
||||
first = False
|
||||
continue
|
||||
else:
|
||||
sys.exit(-1)
|
||||
# Get card
|
||||
card = card_detect(opts, scc)
|
||||
if card is None:
|
||||
if opts.batch_mode:
|
||||
first = False
|
||||
continue
|
||||
else:
|
||||
sys.exit(-1)
|
||||
|
||||
# Erase if requested
|
||||
if opts.erase:
|
||||
print "Formatting ..."
|
||||
card.erase()
|
||||
card.reset()
|
||||
# Erase if requested
|
||||
if opts.erase:
|
||||
print "Formatting ..."
|
||||
card.erase()
|
||||
card.reset()
|
||||
|
||||
# Generate parameters
|
||||
if opts.source == 'cmdline':
|
||||
cp = gen_parameters(opts)
|
||||
elif opts.source == 'csv':
|
||||
if opts.read_imsi:
|
||||
if opts.dry_run:
|
||||
# Connect transport
|
||||
print "Insert card now (or CTRL-C to cancel)"
|
||||
sl.wait_for_card(newcardonly=not first)
|
||||
(res,_) = scc.read_binary(EF['IMSI'])
|
||||
imsi = swap_nibbles(res)[3:]
|
||||
else:
|
||||
imsi = opts.imsi
|
||||
cp = read_params_csv(opts, imsi)
|
||||
if cp is None:
|
||||
print "Error reading parameters\n"
|
||||
sys.exit(2)
|
||||
cp = gen_parameters(opts)
|
||||
print_parameters(cp)
|
||||
|
||||
if opts.dry_run is False:
|
||||
# Program the card
|
||||
print "Programming ..."
|
||||
if opts.dry_run is not True:
|
||||
card.program(cp)
|
||||
else:
|
||||
print "Dry Run: NOT PROGRAMMING!"
|
||||
# Program the card
|
||||
print "Programming ..."
|
||||
card.program(cp)
|
||||
|
||||
# Write parameters permanently
|
||||
write_parameters(opts, cp)
|
||||
@@ -631,3 +570,4 @@ if __name__ == '__main__':
|
||||
|
||||
if not opts.batch_mode:
|
||||
done = True
|
||||
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
#!/usr/bin/env python2
|
||||
|
||||
#
|
||||
# Utility to display all files from a SIM card
|
||||
#
|
||||
#
|
||||
# Copyright (C) 2009 Sylvain Munaut <tnt@246tNt.com>
|
||||
# Copyright (C) 2010 Harald Welte <laforge@gnumonks.org>
|
||||
# Copyright (C) 2013 Alexander Chemeris <alexander.chemeris@gmail.com>
|
||||
#
|
||||
# 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 hashlib
|
||||
from optparse import OptionParser
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import sys
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
# Python < 2.5
|
||||
import simplejson as json
|
||||
|
||||
from pySim.commands import SimCardCommands
|
||||
from pySim.utils import h2b, swap_nibbles, rpad, dec_imsi, dec_iccid, dec_select_ef_response
|
||||
from pySim.ts_51_011 import EF, DF
|
||||
|
||||
def parse_options():
|
||||
|
||||
parser = OptionParser(usage="usage: %prog [options]")
|
||||
|
||||
parser.add_option("-d", "--device", dest="device", metavar="DEV",
|
||||
help="Serial Device for SIM access [default: %default]",
|
||||
default="/dev/ttyUSB0",
|
||||
)
|
||||
parser.add_option("-b", "--baud", dest="baudrate", type="int", metavar="BAUD",
|
||||
help="Baudrate used for SIM access [default: %default]",
|
||||
default=9600,
|
||||
)
|
||||
parser.add_option("-p", "--pcsc-device", dest="pcsc_dev", type='int', metavar="PCSC",
|
||||
help="Which PC/SC reader number for SIM access",
|
||||
default=None,
|
||||
)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if args:
|
||||
parser.error("Extraneous arguments")
|
||||
|
||||
return options
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# Parse options
|
||||
opts = parse_options()
|
||||
|
||||
# Connect to the card
|
||||
if opts.pcsc_dev is None:
|
||||
from pySim.transport.serial import SerialSimLink
|
||||
sl = SerialSimLink(device=opts.device, baudrate=opts.baudrate)
|
||||
else:
|
||||
from pySim.transport.pcsc import PcscSimLink
|
||||
sl = PcscSimLink(opts.pcsc_dev)
|
||||
|
||||
# Create command layer
|
||||
scc = SimCardCommands(transport=sl)
|
||||
|
||||
# Wait for SIM card
|
||||
sl.wait_for_card()
|
||||
|
||||
# Program the card
|
||||
print("Reading ...")
|
||||
|
||||
# Read all
|
||||
for (name, path) in EF.items():
|
||||
try:
|
||||
resp = scc.select_file(path)
|
||||
(length, file_id, file_type, increase_cmd, access_cond,
|
||||
file_status, data_len, ef_struct, record_len) = dec_select_ef_response(resp[-1])
|
||||
# print name, resp
|
||||
print name, (length, file_id, file_type, increase_cmd, access_cond, file_status, data_len, ef_struct, record_len)
|
||||
|
||||
if not access_cond[0] == '0' and not access_cond[0] == '1':
|
||||
print("%s: Requires %s access to read." % (name, access_cond[0],))
|
||||
continue
|
||||
|
||||
if ef_struct == '00':
|
||||
# transparent
|
||||
(res, sw) = scc.read_binary_selected(length)
|
||||
if sw == '9000':
|
||||
print("%s: %s" % (name, res,))
|
||||
else:
|
||||
print("%s: Can't read, response code = %s" % (name, sw,))
|
||||
elif (ef_struct == '01' or ef_struct == '03') and record_len>0:
|
||||
for i in range(1,length/record_len+1):
|
||||
# linear fixed
|
||||
(res, sw) = scc.read_record_selected(record_len, i)
|
||||
if sw == '9000':
|
||||
print("%s[%d]: %s" % (name, i, res,))
|
||||
else:
|
||||
print("%s[%d]: Can't read, response code = %s" % (name, i, sw,))
|
||||
elif ef_struct == '03':
|
||||
# cyclic
|
||||
raise RuntimeError("Don't know how to read a cyclic EF")
|
||||
else:
|
||||
raise RuntimeError("Unknown EF type")
|
||||
except RuntimeError as e:
|
||||
print("%s: Can't read (%s)" % (name,e.message,))
|
||||
|
||||
# Done for this card and maybe for everything ?
|
||||
print "Done !\n"
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# Utility to display some informations about a SIM card
|
||||
@@ -37,7 +37,7 @@ except ImportError:
|
||||
|
||||
from pySim.commands import SimCardCommands
|
||||
from pySim.utils import h2b, swap_nibbles, rpad, dec_imsi, dec_iccid
|
||||
from pySim.ts_51_011 import EF, DF
|
||||
|
||||
|
||||
def parse_options():
|
||||
|
||||
@@ -107,20 +107,13 @@ if __name__ == '__main__':
|
||||
else:
|
||||
print("SMSP: Can't read, response code = %s" % (sw,))
|
||||
|
||||
# EF.SPN
|
||||
(res, sw) = scc.read_binary(EF['SPN'])
|
||||
if sw == '9000':
|
||||
print("SPN: %s" % (res,))
|
||||
else:
|
||||
print("SPN: Can't read, response code = %s" % (sw,))
|
||||
|
||||
# EF.HPLMN
|
||||
(res, sw) = scc.read_binary(EF['PLMNsel'])
|
||||
if sw == '9000':
|
||||
print("HPLMN: %s" % (res))
|
||||
# (res, sw) = scc.read_binary(['3f00', '7f20', '6f30'])
|
||||
# if sw == '9000':
|
||||
# print("HPLMN: %s" % (res))
|
||||
# print("HPLMN: %s" % (dec_hplmn(res),))
|
||||
else:
|
||||
print("HPLMN: Can't read, response code = %s" % (sw,))
|
||||
# else:
|
||||
# print("HPLMN: Can't read, response code = %s" % (sw,))
|
||||
# FIXME
|
||||
|
||||
# EF.ACC
|
||||
@@ -132,13 +125,13 @@ if __name__ == '__main__':
|
||||
|
||||
# EF.MSISDN
|
||||
try:
|
||||
# print(scc.record_size(EF['MSISDN']))
|
||||
(res, sw) = scc.read_record(EF['MSISDN'], 1)
|
||||
# print(scc.record_size(['3f00', '7f10', '6f40']))
|
||||
(res, sw) = scc.read_record(['3f00', '7f10', '6f40'], 1)
|
||||
if sw == '9000':
|
||||
if res[1] != 'f':
|
||||
print("MSISDN: %s" % (res,))
|
||||
else:
|
||||
print("MSISDN: %s (Not available)" % (res,))
|
||||
print("MSISDN: Not available")
|
||||
else:
|
||||
print("MSISDN: Can't read, response code = %s" % (sw,))
|
||||
except:
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
#!/usr/bin/env python2
|
||||
|
||||
#
|
||||
# Utility to run an A3/A8 algorithm on a SIM card
|
||||
#
|
||||
# Copyright (C) 2018 Alexander Chemeris <alexander.chemeris@gmail.com>
|
||||
#
|
||||
# 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 optparse import OptionParser
|
||||
from pySim.commands import SimCardCommands
|
||||
|
||||
def parse_options():
|
||||
|
||||
parser = OptionParser(usage="usage: %prog [options]",
|
||||
description="Utility to run an A3/A8 algorithm on a SIM card. "
|
||||
"Prints generated SRES and Kc for a given RAND number "
|
||||
"and exits.")
|
||||
|
||||
parser.add_option("-d", "--device", dest="device", metavar="DEV",
|
||||
help="Serial Device for SIM access [default: %default]",
|
||||
default="/dev/ttyUSB0",
|
||||
)
|
||||
parser.add_option("-b", "--baud", dest="baudrate", type="int", metavar="BAUD",
|
||||
help="Baudrate used for SIM access [default: %default]",
|
||||
default=9600,
|
||||
)
|
||||
parser.add_option("-p", "--pcsc-device", dest="pcsc_dev", type='int', metavar="PCSC",
|
||||
help="Which PC/SC reader number for SIM access",
|
||||
default=None,
|
||||
)
|
||||
parser.add_option("-r", "--rand", dest="rand", metavar="RAND",
|
||||
help="16 bytes of RAND value",
|
||||
default=None,
|
||||
)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if args:
|
||||
parser.error("Extraneous arguments")
|
||||
|
||||
return options
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# Parse options
|
||||
opts = parse_options()
|
||||
|
||||
if opts.rand is None:
|
||||
print("Please specify RAND value")
|
||||
sys.exit(1)
|
||||
if len(opts.rand) != 32:
|
||||
print("RAND must be 16 bytes long")
|
||||
sys.exit(1)
|
||||
|
||||
# Connect to the card
|
||||
if opts.pcsc_dev is None:
|
||||
from pySim.transport.serial import SerialSimLink
|
||||
sl = SerialSimLink(device=opts.device, baudrate=opts.baudrate)
|
||||
else:
|
||||
from pySim.transport.pcsc import PcscSimLink
|
||||
sl = PcscSimLink(opts.pcsc_dev)
|
||||
|
||||
# Create command layer
|
||||
scc = SimCardCommands(transport=sl)
|
||||
|
||||
# Wait for SIM card
|
||||
sl.wait_for_card()
|
||||
|
||||
# Program the card
|
||||
print("Running GSM algorithm with RAND %s" % (opts.rand,))
|
||||
|
||||
# Run GSM A3/A8
|
||||
(res, sw) = scc.run_gsm(opts.rand)
|
||||
if sw == '9000':
|
||||
sres, kc = res
|
||||
print("SRES = %s" % (sres,))
|
||||
print("Kc = %s" % (kc,))
|
||||
else:
|
||||
print("Error %s, result data '%s'" % (sw, res))
|
||||
|
||||
# Done for this card and maybe for everything ?
|
||||
print "Done !\n"
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
|
||||
281
pySim/cards.py
281
pySim/cards.py
@@ -7,7 +7,6 @@
|
||||
#
|
||||
# Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com>
|
||||
# Copyright (C) 2011 Harald Welte <laforge@gnumonks.org>
|
||||
# Copyright (C) 2017 Alexander.Chemeris <Alexander.Chemeris@gmail.com>
|
||||
#
|
||||
# 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
|
||||
@@ -23,82 +22,17 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from pySim.ts_51_011 import EF, DF
|
||||
from pySim.utils import *
|
||||
from smartcard.util import toBytes
|
||||
from pySim.utils import b2h, h2b, swap_nibbles, rpad, lpad, enc_imsi, enc_iccid, enc_plmn
|
||||
|
||||
|
||||
class Card(object):
|
||||
|
||||
def __init__(self, scc):
|
||||
self._scc = scc
|
||||
self._adm_chv_num = 4
|
||||
|
||||
def reset(self):
|
||||
self._scc.reset_card()
|
||||
|
||||
def verify_adm(self, key):
|
||||
'''
|
||||
Authenticate with ADM key
|
||||
'''
|
||||
(res, sw) = self._scc.verify_chv(self._adm_chv_num, key)
|
||||
return sw
|
||||
|
||||
def read_iccid(self):
|
||||
(res, sw) = self._scc.read_binary(EF['ICCID'])
|
||||
if sw == '9000':
|
||||
return (dec_iccid(res), sw)
|
||||
else:
|
||||
return (None, sw)
|
||||
|
||||
def read_imsi(self):
|
||||
(res, sw) = self._scc.read_binary(EF['IMSI'])
|
||||
if sw == '9000':
|
||||
return (dec_imsi(res), sw)
|
||||
else:
|
||||
return (None, sw)
|
||||
|
||||
def update_imsi(self, imsi):
|
||||
data, sw = self._scc.update_binary(EF['IMSI'], enc_imsi(imsi))
|
||||
return sw
|
||||
|
||||
def update_acc(self, acc):
|
||||
data, sw = self._scc.update_binary(EF['ACC'], lpad(acc, 4))
|
||||
return sw
|
||||
|
||||
def update_hplmn_act(self, mcc, mnc, access_tech='FFFF'):
|
||||
"""
|
||||
Update Home PLMN with access technology bit-field
|
||||
|
||||
See Section "10.3.37 EFHPLMNwAcT (HPLMN Selector with Access Technology)"
|
||||
in ETSI TS 151 011 for the details of the access_tech field coding.
|
||||
Some common values:
|
||||
access_tech = '0080' # Only GSM is selected
|
||||
access_tech = 'FFFF' # All technologues selected, even Reserved for Future Use ones
|
||||
"""
|
||||
# get size and write EF.HPLMNwAcT
|
||||
r = self._scc.select_file(EF['HPLMNwAcT'])
|
||||
size = int(r[-1][4:8], 16)
|
||||
hplmn = enc_plmn(mcc, mnc)
|
||||
content = hplmn + access_tech
|
||||
data, sw = self._scc.update_binary(EF['HPLMNwAcT'], content + 'ffffff0000' * (size/5-1))
|
||||
return sw
|
||||
|
||||
def update_smsp(self, smsp):
|
||||
data, sw = self._scc.update_record(EF['SMSP'], 1, rpad(smsp, 84))
|
||||
return sw
|
||||
|
||||
def read_spn(self):
|
||||
(spn, sw) = self._scc.read_binary(EF['SPN'])
|
||||
if sw == '9000':
|
||||
return (dec_spn(spn), sw)
|
||||
else:
|
||||
return (None, sw)
|
||||
|
||||
def update_spn(self, name, hplmn_disp=False, oplmn_disp=False):
|
||||
content = enc_spn(name, hplmn_disp, oplmn_disp)
|
||||
data, sw = self._scc.update_binary(EF['SPN'], rpad(content, 32))
|
||||
return sw
|
||||
|
||||
|
||||
class _MagicSimBase(Card):
|
||||
"""
|
||||
@@ -419,12 +353,7 @@ class SysmoSIMgr2(Card):
|
||||
|
||||
@classmethod
|
||||
def autodetect(kls, scc):
|
||||
try:
|
||||
# Look for ATR
|
||||
if scc.get_atr() == toBytes("3B 7D 94 00 00 55 55 53 0A 74 86 93 0B 24 7C 4D 54 68"):
|
||||
return kls(scc)
|
||||
except:
|
||||
return None
|
||||
# TODO: look for ATR 3B 7D 94 00 00 55 55 53 0A 74 86 93 0B 24 7C 4D 54 68
|
||||
return None
|
||||
|
||||
def program(self, p):
|
||||
@@ -496,34 +425,28 @@ class SysmoUSIMSJS1(Card):
|
||||
def __init__(self, ssc):
|
||||
super(SysmoUSIMSJS1, self).__init__(ssc)
|
||||
self._scc.cla_byte = "00"
|
||||
self._scc.sel_ctrl = "000C"
|
||||
|
||||
@classmethod
|
||||
def autodetect(kls, scc):
|
||||
try:
|
||||
# Look for ATR
|
||||
if scc.get_atr() == toBytes("3B 9F 96 80 1F C7 80 31 A0 73 BE 21 13 67 43 20 07 18 00 00 01 A5"):
|
||||
return kls(scc)
|
||||
except:
|
||||
return None
|
||||
# TODO: look for ATR 3B 9F 96 80 1F C7 80 31 A0 73 BE 21 13 67 43 20 07 18 00 00 01 A5
|
||||
return None
|
||||
|
||||
def program(self, p):
|
||||
|
||||
# authenticate as ADM using default key (written on the card..)
|
||||
if not p['pin_adm']:
|
||||
raise ValueError("Please provide a PIN-ADM as there is no default one")
|
||||
self._scc.verify_chv(0x0A, h2b(p['pin_adm']))
|
||||
|
||||
# select MF
|
||||
r = self._scc.select_file(['3f00'])
|
||||
|
||||
# write EF.ICCID
|
||||
data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid']))
|
||||
|
||||
# select DF_GSM
|
||||
r = self._scc.select_file(['7f20'])
|
||||
|
||||
# authenticate as ADM using default key (written on the card..)
|
||||
if not p['pin_adm']:
|
||||
raise ValueError("Please provide a PIN-ADM as there is no default one")
|
||||
|
||||
self._scc.verify_chv(0x0A, h2b(p['pin_adm']))
|
||||
|
||||
|
||||
# set Ki in proprietary file
|
||||
data, sw = self._scc.update_binary('00FF', p['ki'])
|
||||
|
||||
@@ -531,189 +454,21 @@ class SysmoUSIMSJS1(Card):
|
||||
content = "01" + p['opc']
|
||||
data, sw = self._scc.update_binary('00F7', content)
|
||||
|
||||
|
||||
# write EF.IMSI
|
||||
data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi']))
|
||||
|
||||
# EF.SMSP
|
||||
r = self._scc.select_file(['3f00', '7f10'])
|
||||
data, sw = self._scc.update_record('6f42', 1, lpad(p['smsp'], 104), force_len=True)
|
||||
# write EF.AUTH
|
||||
content = "0101"
|
||||
r = self._scc.select_file(['7FCC', '6f00'])
|
||||
data, sw = self._scc.update_binary('6f00', content)
|
||||
|
||||
|
||||
|
||||
def erase(self):
|
||||
return
|
||||
|
||||
|
||||
class FairwavesSIM(Card):
|
||||
"""
|
||||
FairwavesSIM
|
||||
|
||||
The SIM card is operating according to the standard.
|
||||
For Ki/OP/OPC programming the following files are additionally open for writing:
|
||||
3F00/7F20/FF01 – OP/OPC:
|
||||
byte 1 = 0x01, bytes 2-17: OPC;
|
||||
byte 1 = 0x00, bytes 2-17: OP;
|
||||
3F00/7F20/FF02: Ki
|
||||
3F00/7F20/FF03: 2G/3G auth algorithm
|
||||
byte 1 = GSM SIM A3/A8 algorithm selection
|
||||
byte 2 = USIM A3/A8 algorithm selection
|
||||
Algorithms:
|
||||
0x01 = Milenage
|
||||
0x03 = COMP128v1
|
||||
0x06 = COMP128v2
|
||||
0x07 = COMP128v3
|
||||
"""
|
||||
|
||||
name = 'Fairwaves SIM'
|
||||
# Propriatary files
|
||||
_EF_num = {
|
||||
'Ki': 'FF02',
|
||||
'OP/OPC': 'FF01',
|
||||
'A3A8': 'FF03',
|
||||
}
|
||||
_EF = {
|
||||
'Ki': DF['GSM']+[_EF_num['Ki']],
|
||||
'OP/OPC': DF['GSM']+[_EF_num['OP/OPC']],
|
||||
'A3A8': DF['GSM']+[_EF_num['A3A8']],
|
||||
}
|
||||
|
||||
def __init__(self, ssc):
|
||||
super(FairwavesSIM, self).__init__(ssc)
|
||||
self._adm_chv_num = 0x11
|
||||
self._adm2_chv_num = 0x12
|
||||
|
||||
|
||||
@classmethod
|
||||
def autodetect(kls, scc):
|
||||
try:
|
||||
# Look for ATR
|
||||
if scc.get_atr() == toBytes("3B 9F 96 80 1F C7 80 31 A0 73 BE 21 13 67 44 22 06 10 00 00 01 A9"):
|
||||
return kls(scc)
|
||||
except:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def verify_adm2(self, key):
|
||||
'''
|
||||
Authenticate with ADM2 key.
|
||||
|
||||
Fairwaves SIM cards support hierarchical key structure and ADM2 key
|
||||
is a key which has access to proprietary files (Ki and OP/OPC).
|
||||
That said, ADM key inherits permissions of ADM2 key and thus we rarely
|
||||
need ADM2 key per se.
|
||||
'''
|
||||
(res, sw) = self._scc.verify_chv(self._adm2_chv_num, key)
|
||||
return sw
|
||||
|
||||
|
||||
def read_ki(self):
|
||||
"""
|
||||
Read Ki in proprietary file.
|
||||
|
||||
Requires ADM1 access level
|
||||
"""
|
||||
return self._scc.read_binary(self._EF['Ki'])
|
||||
|
||||
|
||||
def update_ki(self, ki):
|
||||
"""
|
||||
Set Ki in proprietary file.
|
||||
|
||||
Requires ADM1 access level
|
||||
"""
|
||||
data, sw = self._scc.update_binary(self._EF['Ki'], ki)
|
||||
return sw
|
||||
|
||||
|
||||
def read_op_opc(self):
|
||||
"""
|
||||
Read Ki in proprietary file.
|
||||
|
||||
Requires ADM1 access level
|
||||
"""
|
||||
(ef, sw) = self._scc.read_binary(self._EF['OP/OPC'])
|
||||
type = 'OP' if ef[0:2] == '00' else 'OPC'
|
||||
return ((type, ef[2:]), sw)
|
||||
|
||||
|
||||
def update_op(self, op):
|
||||
"""
|
||||
Set OP in proprietary file.
|
||||
|
||||
Requires ADM1 access level
|
||||
"""
|
||||
content = '00' + op
|
||||
data, sw = self._scc.update_binary(self._EF['OP/OPC'], content)
|
||||
return sw
|
||||
|
||||
|
||||
def update_opc(self, opc):
|
||||
"""
|
||||
Set OPC in proprietary file.
|
||||
|
||||
Requires ADM1 access level
|
||||
"""
|
||||
content = '01' + opc
|
||||
data, sw = self._scc.update_binary(self._EF['OP/OPC'], content)
|
||||
return sw
|
||||
|
||||
def read_a3a8(self):
|
||||
(ef, sw) = self._scc.read_binary(self._EF['A3A8'])
|
||||
return (ef, sw)
|
||||
|
||||
def update_a3a8(self, content):
|
||||
(ef, sw) = self._scc.update_binary(self._EF['A3A8'], content)
|
||||
return (ef, sw)
|
||||
|
||||
|
||||
def program(self, p):
|
||||
# authenticate as ADM1
|
||||
if not p['pin_adm']:
|
||||
raise ValueError("Please provide a PIN-ADM as there is no default one")
|
||||
sw = self.verify_adm(h2b(p['pin_adm']))
|
||||
if sw != '9000':
|
||||
raise RuntimeError('Failed to authenticate with ADM key %s'%(p['pin_adm'],))
|
||||
|
||||
# TODO: Set operator name
|
||||
if p.get('smsp') is not None:
|
||||
sw = self.update_smsp(p['smsp'])
|
||||
if sw != '9000':
|
||||
print("Programming SMSP failed with code %s"%sw)
|
||||
# This SIM doesn't support changing ICCID
|
||||
if p.get('mcc') is not None and p.get('mnc') is not None:
|
||||
sw = self.update_hplmn_act(p['mcc'], p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming MCC/MNC failed with code %s"%sw)
|
||||
if p.get('imsi') is not None:
|
||||
sw = self.update_imsi(p['imsi'])
|
||||
if sw != '9000':
|
||||
print("Programming IMSI failed with code %s"%sw)
|
||||
if p.get('ki') is not None:
|
||||
sw = self.update_ki(p['ki'])
|
||||
if sw != '9000':
|
||||
print("Programming Ki failed with code %s"%sw)
|
||||
if p.get('opc') is not None:
|
||||
sw = self.update_opc(p['opc'])
|
||||
if sw != '9000':
|
||||
print("Programming OPC failed with code %s"%sw)
|
||||
if p.get('acc') is not None:
|
||||
sw = self.update_acc(p['acc'])
|
||||
if sw != '9000':
|
||||
print("Programming ACC failed with code %s"%sw)
|
||||
|
||||
def erase(self):
|
||||
return
|
||||
|
||||
|
||||
# In order for autodetection ...
|
||||
_cards_classes = [ FakeMagicSim, SuperSim, MagicSim, GrcardSim,
|
||||
SysmoSIMgr1, SysmoSIMgr2, SysmoUSIMgr1, SysmoUSIMSJS1,
|
||||
FairwavesSIM ]
|
||||
|
||||
def card_autodetect(scc):
|
||||
for kls in _cards_classes:
|
||||
card = kls.autodetect(scc)
|
||||
if card is not None:
|
||||
card.reset()
|
||||
return card
|
||||
return None
|
||||
SysmoSIMgr1, SysmoSIMgr2, SysmoUSIMgr1, SysmoUSIMSJS1 ]
|
||||
|
||||
@@ -29,10 +29,6 @@ class SimCardCommands(object):
|
||||
def __init__(self, transport):
|
||||
self._tp = transport;
|
||||
self._cla_byte = "a0"
|
||||
self.sel_ctrl = "0000"
|
||||
|
||||
def get_atr(self):
|
||||
return self._tp.get_atr()
|
||||
|
||||
@property
|
||||
def cla_byte(self):
|
||||
@@ -41,31 +37,22 @@ class SimCardCommands(object):
|
||||
def cla_byte(self, value):
|
||||
self._cla_byte = value
|
||||
|
||||
@property
|
||||
def sel_ctrl(self):
|
||||
return self._sel_ctrl
|
||||
@sel_ctrl.setter
|
||||
def sel_ctrl(self, value):
|
||||
self._sel_ctrl = value
|
||||
|
||||
def select_file(self, dir_list):
|
||||
rv = []
|
||||
for i in dir_list:
|
||||
data, sw = self._tp.send_apdu_checksw(self.cla_byte + "a4" + self.sel_ctrl + "02" + i)
|
||||
data, sw = self._tp.send_apdu_checksw(self.cla_byte + "a4000C02" + i)
|
||||
rv.append(data)
|
||||
return rv
|
||||
|
||||
def read_binary_selected(self, length, offset=0):
|
||||
pdu = self.cla_byte + 'b0%04x%02x' % (offset, (min(256, length) & 0xff))
|
||||
return self._tp.send_apdu(pdu)
|
||||
|
||||
def read_binary(self, ef, length=None, offset=0):
|
||||
if not hasattr(type(ef), '__iter__'):
|
||||
ef = [ef]
|
||||
r = self.select_file(ef)
|
||||
if length is None:
|
||||
length = int(r[-1][4:8], 16) - offset
|
||||
return self.read_binary_selected(length, offset)
|
||||
pdu = self.cla_byte + 'b0%04x%02x' % (offset, (min(256, length) & 0xff))
|
||||
return self._tp.send_apdu(pdu)
|
||||
|
||||
def update_binary(self, ef, data, offset=0):
|
||||
if not hasattr(type(ef), '__iter__'):
|
||||
@@ -74,16 +61,13 @@ class SimCardCommands(object):
|
||||
pdu = self.cla_byte + 'd6%04x%02x' % (offset, len(data)/2) + data
|
||||
return self._tp.send_apdu_checksw(pdu)
|
||||
|
||||
def read_record_selected(self, rec_length, rec_no):
|
||||
pdu = self.cla_byte + 'b2%02x04%02x' % (rec_no, rec_length)
|
||||
return self._tp.send_apdu(pdu)
|
||||
|
||||
def read_record(self, ef, rec_no):
|
||||
if not hasattr(type(ef), '__iter__'):
|
||||
ef = [ef]
|
||||
r = self.select_file(ef)
|
||||
rec_length = int(r[-1][28:30], 16)
|
||||
return self.read_record_selected(rec_length, rec_no)
|
||||
pdu = self.cla_byte + 'b2%02x04%02x' % (rec_no, rec_length)
|
||||
return self._tp.send_apdu(pdu)
|
||||
|
||||
def update_record(self, ef, rec_no, data, force_len=False):
|
||||
if not hasattr(type(ef), '__iter__'):
|
||||
@@ -106,29 +90,15 @@ class SimCardCommands(object):
|
||||
r = self.select_file(ef)
|
||||
return int(r[-1][4:8], 16) // int(r[-1][28:30], 16)
|
||||
|
||||
def run_gsm_raw(self, rand):
|
||||
'''
|
||||
A3/A8 algorithm in the SIM card using the given RAND.
|
||||
This function returns a raw result tuple.
|
||||
'''
|
||||
def run_gsm(self, rand):
|
||||
if len(rand) != 32:
|
||||
raise ValueError('Invalid rand')
|
||||
self.select_file(['3f00', '7f20'])
|
||||
return self._tp.send_apdu(self.cla_byte + '88000010' + rand)
|
||||
|
||||
def run_gsm(self, rand):
|
||||
'''
|
||||
A3/A8 algorithm in the SIM card using the given RAND.
|
||||
This function returns a parsed ((SRES, Kc), sw) tuple.
|
||||
'''
|
||||
(res, sw) = self.run_gsm_raw(rand)
|
||||
if sw != '9000':
|
||||
return (res, sw)
|
||||
return ((res[0:8], res[8:]), sw)
|
||||
|
||||
def reset_card(self):
|
||||
return self._tp.reset_card()
|
||||
|
||||
def verify_chv(self, chv_no, code):
|
||||
fc = rpad(b2h(code), 16)
|
||||
return self._tp.send_apdu_checksw(self.cla_byte + '2000' + ('%02X' % chv_no) + '08' + fc)
|
||||
return self._tp.send_apdu_checksw(self.cla_byte + '2000' + ('%02X' % chv_no) + '08' + fc)
|
||||
@@ -56,9 +56,6 @@ class PcscSimLink(LinkBase):
|
||||
except NoCardException:
|
||||
raise NoCardError()
|
||||
|
||||
def get_atr(self):
|
||||
return self._con.getATR()
|
||||
|
||||
def disconnect(self):
|
||||
self._con.disconnect()
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ class SerialSimLink(LinkBase):
|
||||
)
|
||||
self._rst_pin = rst
|
||||
self._debug = debug
|
||||
self._atr = None
|
||||
|
||||
def __del__(self):
|
||||
self._sl.close()
|
||||
@@ -92,9 +91,6 @@ class SerialSimLink(LinkBase):
|
||||
def connect(self):
|
||||
self.reset_card()
|
||||
|
||||
def get_atr(self):
|
||||
return self._atr
|
||||
|
||||
def disconnect(self):
|
||||
pass # Nothing to do really ...
|
||||
|
||||
@@ -106,7 +102,6 @@ class SerialSimLink(LinkBase):
|
||||
raise ProtocolError()
|
||||
|
||||
def _reset_card(self):
|
||||
self._atr = None
|
||||
rst_meth_map = {
|
||||
'rts': self._sl.setRTS,
|
||||
'dtr': self._sl.setDTR,
|
||||
@@ -138,24 +133,18 @@ class SerialSimLink(LinkBase):
|
||||
return -1
|
||||
t0 = ord(b)
|
||||
self._dbg_print("T0: 0x%x" % t0)
|
||||
self._atr = [0x3b, ord(b)]
|
||||
|
||||
for i in range(4):
|
||||
if t0 & (0x10 << i):
|
||||
b = self._rx_byte()
|
||||
self._atr.apend(ord(b))
|
||||
self._dbg_print("T%si = %x" % (chr(ord('A')+i), ord(b)))
|
||||
self._dbg_print("T%si = %x" % (chr(ord('A')+i), ord(self._rx_byte())))
|
||||
|
||||
for i in range(0, t0 & 0xf):
|
||||
b = self._rx_byte()
|
||||
self._atr.apend(ord(b))
|
||||
self._dbg_print("Historical = %x" % ord(b))
|
||||
self._dbg_print("Historical = %x" % ord(self._rx_byte()))
|
||||
|
||||
while True:
|
||||
x = self._rx_byte()
|
||||
if not x:
|
||||
break
|
||||
self._atr.apend(ord(x))
|
||||
self._dbg_print("Extra: %x" % ord(x))
|
||||
|
||||
return 1
|
||||
|
||||
@@ -1,251 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Various constants from ETSI TS 151.011
|
||||
"""
|
||||
|
||||
#
|
||||
# Copyright (C) 2017 Alexander.Chemeris <Alexander.Chemeris@gmail.com>
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
MF_num = '3F00'
|
||||
|
||||
DF_num = {
|
||||
'TELECOM': '7F10',
|
||||
|
||||
'GSM': '7F20',
|
||||
'IS-41': '7F22',
|
||||
'FP-CTS': '7F23',
|
||||
|
||||
'GRAPHICS': '5F50',
|
||||
|
||||
'IRIDIUM': '5F30',
|
||||
'GLOBST': '5F31',
|
||||
'ICO': '5F32',
|
||||
'ACeS': '5F33',
|
||||
|
||||
'EIA/TIA-553': '5F40',
|
||||
'CTS': '5F60',
|
||||
'SOLSA': '5F70',
|
||||
|
||||
'MExE': '5F3C',
|
||||
}
|
||||
|
||||
EF_num = {
|
||||
# MF
|
||||
'ICCID': '2FE2',
|
||||
'ELP': '2F05',
|
||||
|
||||
# DF_TELECOM
|
||||
'ADN': '6F3A',
|
||||
'FDN': '6F3B',
|
||||
'SMS': '6F3C',
|
||||
'CCP': '6F3D',
|
||||
'MSISDN': '6F40',
|
||||
'SMSP': '6F42',
|
||||
'SMSS': '6F43',
|
||||
'LND': '6F44',
|
||||
'SMSR': '6F47',
|
||||
'SDN': '6F49',
|
||||
'EXT1': '6F4A',
|
||||
'EXT2': '6F4B',
|
||||
'EXT3': '6F4C',
|
||||
'BDN': '6F4D',
|
||||
'EXT4': '6F4E',
|
||||
'CMI': '6F58',
|
||||
'ECCP': '6F4F',
|
||||
|
||||
# DF_GRAPHICS
|
||||
'IMG': '4F20',
|
||||
|
||||
# DF_SoLSA
|
||||
'SAI': '4F30',
|
||||
'SLL': '4F31',
|
||||
|
||||
# DF_MExE
|
||||
'MExE-ST': '4F40',
|
||||
'ORPK': '4F41',
|
||||
'ARPK': '4F42',
|
||||
'TPRPK': '4F43',
|
||||
|
||||
# DF_GSM
|
||||
'LP': '6F05',
|
||||
'IMSI': '6F07',
|
||||
'Kc': '6F20',
|
||||
'DCK': '6F2C',
|
||||
'PLMNsel': '6F30',
|
||||
'HPPLMN': '6F31',
|
||||
'CNL': '6F32',
|
||||
'ACMmax': '6F37',
|
||||
'SST': '6F38',
|
||||
'ACM': '6F39',
|
||||
'GID1': '6F3E',
|
||||
'GID2': '6F3F',
|
||||
'PUCT': '6F41',
|
||||
'CBMI': '6F45',
|
||||
'SPN': '6F46',
|
||||
'CBMID': '6F48',
|
||||
'BCCH': '6F74',
|
||||
'ACC': '6F78',
|
||||
'FPLMN': '6F7B',
|
||||
'LOCI': '6F7E',
|
||||
'AD': '6FAD',
|
||||
'PHASE': '6FAE',
|
||||
'VGCS': '6FB1',
|
||||
'VGCSS': '6FB2',
|
||||
'VBS': '6FB3',
|
||||
'VBSS': '6FB4',
|
||||
'eMLPP': '6FB5',
|
||||
'AAeM': '6FB6',
|
||||
'ECC': '6FB7',
|
||||
'CBMIR': '6F50',
|
||||
'NIA': '6F51',
|
||||
'KcGPRS': '6F52',
|
||||
'LOCIGPRS': '6F53',
|
||||
'SUME': '6F54',
|
||||
'PLMNwAcT': '6F60',
|
||||
'OPLMNwAcT': '6F61',
|
||||
# Figure 8 names it HPLMNAcT, but in the text it's names it HPLMNwAcT
|
||||
'HPLMNAcT': '6F62',
|
||||
'HPLMNwAcT': '6F62',
|
||||
'CPBCCH': '6F63',
|
||||
'INVSCAN': '6F64',
|
||||
'PNN': '6FC5',
|
||||
'OPL': '6FC6',
|
||||
'MBDN': '6FC7',
|
||||
'EXT6': '6FC8',
|
||||
'MBI': '6FC9',
|
||||
'MWIS': '6FCA',
|
||||
'CFIS': '6FCB',
|
||||
'EXT7': '6FCC',
|
||||
'SPDI': '6FCD',
|
||||
'MMSN': '6FCE',
|
||||
'EXT8': '6FCF',
|
||||
'MMSICP': '6FD0',
|
||||
'MMSUP': '6FD1',
|
||||
'MMSUCP': '6FD2',
|
||||
}
|
||||
|
||||
DF = {
|
||||
'TELECOM': [MF_num, DF_num['TELECOM']],
|
||||
|
||||
'GSM': [MF_num, DF_num['GSM']],
|
||||
'IS-41': [MF_num, DF_num['IS-41']],
|
||||
'FP-CTS': [MF_num, DF_num['FP-CTS']],
|
||||
|
||||
'GRAPHICS': [MF_num, DF_num['GRAPHICS']],
|
||||
|
||||
'IRIDIUM': [MF_num, DF_num['IRIDIUM']],
|
||||
'GLOBST': [MF_num, DF_num['GLOBST']],
|
||||
'ICO': [MF_num, DF_num['ICO']],
|
||||
'ACeS': [MF_num, DF_num['ACeS']],
|
||||
|
||||
'EIA/TIA-553': [MF_num, DF_num['EIA/TIA-553']],
|
||||
'CTS': [MF_num, DF_num['CTS']],
|
||||
'SoLSA': [MF_num, DF_num['SOLSA']],
|
||||
|
||||
'MExE': [MF_num, DF_num['MExE']],
|
||||
}
|
||||
|
||||
|
||||
EF = {
|
||||
'ICCID': [MF_num, EF_num['ICCID']],
|
||||
'ELP': [MF_num, EF_num['ELP']],
|
||||
|
||||
'ADN': DF['TELECOM']+[EF_num['ADN']],
|
||||
'FDN': DF['TELECOM']+[EF_num['FDN']],
|
||||
'SMS': DF['TELECOM']+[EF_num['SMS']],
|
||||
'CCP': DF['TELECOM']+[EF_num['CCP']],
|
||||
'MSISDN': DF['TELECOM']+[EF_num['MSISDN']],
|
||||
'SMSP': DF['TELECOM']+[EF_num['SMSP']],
|
||||
'SMSS': DF['TELECOM']+[EF_num['SMSS']],
|
||||
'LND': DF['TELECOM']+[EF_num['LND']],
|
||||
'SMSR': DF['TELECOM']+[EF_num['SMSR']],
|
||||
'SDN': DF['TELECOM']+[EF_num['SDN']],
|
||||
'EXT1': DF['TELECOM']+[EF_num['EXT1']],
|
||||
'EXT2': DF['TELECOM']+[EF_num['EXT2']],
|
||||
'EXT3': DF['TELECOM']+[EF_num['EXT3']],
|
||||
'BDN': DF['TELECOM']+[EF_num['BDN']],
|
||||
'EXT4': DF['TELECOM']+[EF_num['EXT4']],
|
||||
'CMI': DF['TELECOM']+[EF_num['CMI']],
|
||||
'ECCP': DF['TELECOM']+[EF_num['ECCP']],
|
||||
|
||||
'IMG': DF['GRAPHICS']+[EF_num['IMG']],
|
||||
|
||||
'SAI': DF['SoLSA']+[EF_num['SAI']],
|
||||
'SLL': DF['SoLSA']+[EF_num['SLL']],
|
||||
|
||||
'MExE-ST': DF['MExE']+[EF_num['MExE-ST']],
|
||||
'ORPK': DF['MExE']+[EF_num['ORPK']],
|
||||
'ARPK': DF['MExE']+[EF_num['ARPK']],
|
||||
'TPRPK': DF['MExE']+[EF_num['TPRPK']],
|
||||
|
||||
'LP': DF['GSM']+[EF_num['LP']],
|
||||
'IMSI': DF['GSM']+[EF_num['IMSI']],
|
||||
'Kc': DF['GSM']+[EF_num['Kc']],
|
||||
'DCK': DF['GSM']+[EF_num['DCK']],
|
||||
'PLMNsel': DF['GSM']+[EF_num['PLMNsel']],
|
||||
'HPPLMN': DF['GSM']+[EF_num['HPPLMN']],
|
||||
'CNL': DF['GSM']+[EF_num['CNL']],
|
||||
'ACMmax': DF['GSM']+[EF_num['ACMmax']],
|
||||
'SST': DF['GSM']+[EF_num['SST']],
|
||||
'ACM': DF['GSM']+[EF_num['ACM']],
|
||||
'GID1': DF['GSM']+[EF_num['GID1']],
|
||||
'GID2': DF['GSM']+[EF_num['GID2']],
|
||||
'PUCT': DF['GSM']+[EF_num['PUCT']],
|
||||
'CBMI': DF['GSM']+[EF_num['CBMI']],
|
||||
'SPN': DF['GSM']+[EF_num['SPN']],
|
||||
'CBMID': DF['GSM']+[EF_num['CBMID']],
|
||||
'BCCH': DF['GSM']+[EF_num['BCCH']],
|
||||
'ACC': DF['GSM']+[EF_num['ACC']],
|
||||
'FPLMN': DF['GSM']+[EF_num['FPLMN']],
|
||||
'LOCI': DF['GSM']+[EF_num['LOCI']],
|
||||
'AD': DF['GSM']+[EF_num['AD']],
|
||||
'PHASE': DF['GSM']+[EF_num['PHASE']],
|
||||
'VGCS': DF['GSM']+[EF_num['VGCS']],
|
||||
'VGCSS': DF['GSM']+[EF_num['VGCSS']],
|
||||
'VBS': DF['GSM']+[EF_num['VBS']],
|
||||
'VBSS': DF['GSM']+[EF_num['VBSS']],
|
||||
'eMLPP': DF['GSM']+[EF_num['eMLPP']],
|
||||
'AAeM': DF['GSM']+[EF_num['AAeM']],
|
||||
'ECC': DF['GSM']+[EF_num['ECC']],
|
||||
'CBMIR': DF['GSM']+[EF_num['CBMIR']],
|
||||
'NIA': DF['GSM']+[EF_num['NIA']],
|
||||
'KcGPRS': DF['GSM']+[EF_num['KcGPRS']],
|
||||
'LOCIGPRS': DF['GSM']+[EF_num['LOCIGPRS']],
|
||||
'SUME': DF['GSM']+[EF_num['SUME']],
|
||||
'PLMNwAcT': DF['GSM']+[EF_num['PLMNwAcT']],
|
||||
'OPLMNwAcT': DF['GSM']+[EF_num['OPLMNwAcT']],
|
||||
# Figure 8 names it HPLMNAcT, but in the text it's named HPLMNwAcT
|
||||
'HPLMNAcT': DF['GSM']+[EF_num['HPLMNAcT']],
|
||||
'HPLMNwAcT': DF['GSM']+[EF_num['HPLMNAcT']],
|
||||
'CPBCCH': DF['GSM']+[EF_num['CPBCCH']],
|
||||
'INVSCAN': DF['GSM']+[EF_num['INVSCAN']],
|
||||
'PNN': DF['GSM']+[EF_num['PNN']],
|
||||
'OPL': DF['GSM']+[EF_num['OPL']],
|
||||
'MBDN': DF['GSM']+[EF_num['MBDN']],
|
||||
'EXT6': DF['GSM']+[EF_num['EXT6']],
|
||||
'MBI': DF['GSM']+[EF_num['MBI']],
|
||||
'MWIS': DF['GSM']+[EF_num['MWIS']],
|
||||
'CFIS': DF['GSM']+[EF_num['CFIS']],
|
||||
'EXT7': DF['GSM']+[EF_num['EXT7']],
|
||||
'SPDI': DF['GSM']+[EF_num['SPDI']],
|
||||
'MMSN': DF['GSM']+[EF_num['MMSN']],
|
||||
'EXT8': DF['GSM']+[EF_num['EXT8']],
|
||||
'MMSICP': DF['GSM']+[EF_num['MMSICP']],
|
||||
'MMSUP': DF['GSM']+[EF_num['MMSUP']],
|
||||
'MMSUCP': DF['GSM']+[EF_num['MMSUCP']],
|
||||
}
|
||||
@@ -34,12 +34,6 @@ def h2i(s):
|
||||
def i2h(s):
|
||||
return ''.join(['%02x'%(x) for x in s])
|
||||
|
||||
def h2s(s):
|
||||
return ''.join([chr((int(x,16)<<4)+int(y,16)) for x,y in zip(s[0::2], s[1::2]) if not (x == 'f' and y == 'f') ])
|
||||
|
||||
def s2h(s):
|
||||
return b2h(s)
|
||||
|
||||
def swap_nibbles(s):
|
||||
return ''.join([x+y for x,y in zip(s[1::2], s[0::2])])
|
||||
|
||||
@@ -60,7 +54,7 @@ def dec_imsi(ef):
|
||||
"""Converts an EF value to the imsi string representation"""
|
||||
if len(ef) < 4:
|
||||
return None
|
||||
l = int(ef[0:2], 16) * 2 # Length of the IMSI string
|
||||
l = int(ef[0:2]) * 2 # Length of the IMSI string
|
||||
swapped = swap_nibbles(ef[2:])
|
||||
oe = (int(swapped[0])>>3) & 1 # Odd (1) / Even (0)
|
||||
if oe:
|
||||
@@ -77,58 +71,5 @@ def enc_iccid(iccid):
|
||||
return swap_nibbles(rpad(iccid, 20))
|
||||
|
||||
def enc_plmn(mcc, mnc):
|
||||
"""Converts integer MCC/MNC into 3 bytes for EF"""
|
||||
return swap_nibbles(lpad('%03d' % mcc, 3) + lpad('%02d' % mnc, 3))
|
||||
|
||||
def dec_spn(ef):
|
||||
byte1 = int(ef[0:2])
|
||||
hplmn_disp = (byte1&0x01 == 0x01)
|
||||
oplmn_disp = (byte1&0x02 == 0x02)
|
||||
name = h2s(ef[2:])
|
||||
return (name, hplmn_disp, oplmn_disp)
|
||||
|
||||
def enc_spn(name, hplmn_disp=False, oplmn_disp=False):
|
||||
byte1 = 0x00
|
||||
if hplmn_disp: byte1 = byte1|0x01
|
||||
if oplmn_disp: byte1 = byte1|0x02
|
||||
return i2h([byte1])+s2h(name)
|
||||
|
||||
def derive_milenage_opc(ki_hex, op_hex):
|
||||
"""
|
||||
Run the milenage algorithm to calculate OPC from Ki and OP
|
||||
"""
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.strxor import strxor
|
||||
from pySim.utils import b2h
|
||||
|
||||
# We pass in hex string and now need to work on bytes
|
||||
aes = AES.new(h2b(ki_hex))
|
||||
opc_bytes = aes.encrypt(h2b(op_hex))
|
||||
return b2h(strxor(opc_bytes, h2b(op_hex)))
|
||||
|
||||
def calculate_luhn(cc):
|
||||
"""
|
||||
Calculate Luhn checksum used in e.g. ICCID and IMEI
|
||||
"""
|
||||
num = map(int, str(cc))
|
||||
check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10
|
||||
return 0 if check_digit == 10 else check_digit
|
||||
|
||||
def dec_select_ef_response(response):
|
||||
'''
|
||||
As defined in the TS 151.011 9.2.1 SELECT
|
||||
'''
|
||||
|
||||
length = int(response[4:8], 16)
|
||||
file_id = response[8:12]
|
||||
file_type = response[12:14]
|
||||
increase_cmd = response[14:16]
|
||||
access_cond = response[16:22]
|
||||
file_status = response[22:24]
|
||||
data_len = int(response[24:26], 16)
|
||||
ef_struct = response[26:28]
|
||||
if len(response) >= 30:
|
||||
record_len = int(response[28:30], 16)
|
||||
else:
|
||||
record_len = 0
|
||||
return (length, file_id, file_type, increase_cmd, access_cond, file_status, data_len, ef_struct, record_len)
|
||||
"""Converts integer MCC/MNC into 6 bytes for EF"""
|
||||
return swap_nibbles(lpad('%d' % mcc, 3) + lpad('%d' % mnc, 3))
|
||||
|
||||
Reference in New Issue
Block a user