shadysim: Implement OTA security for sysmoUSIM SJS1
This commit is contained in:
parent
d08dd0637c
commit
68cdcdb88b
|
@ -22,9 +22,10 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
# December 2014, Dieter Spaar: add OTA security for sysmoSIM SJS1
|
||||||
|
|
||||||
from pySim.commands import SimCardCommands
|
from pySim.commands import SimCardCommands
|
||||||
from pySim.utils import swap_nibbles, rpad, b2h
|
from pySim.utils import swap_nibbles, rpad, b2h, i2h
|
||||||
try:
|
try:
|
||||||
import argparse
|
import argparse
|
||||||
except Exception, err:
|
except Exception, err:
|
||||||
|
@ -32,6 +33,11 @@ except Exception, err:
|
||||||
import zipfile
|
import zipfile
|
||||||
import time
|
import time
|
||||||
import struct
|
import struct
|
||||||
|
import binascii
|
||||||
|
|
||||||
|
# Python Cryptography Toolkit (pycrypto)
|
||||||
|
|
||||||
|
from Crypto.Cipher import DES3
|
||||||
|
|
||||||
#------
|
#------
|
||||||
|
|
||||||
|
@ -51,20 +57,145 @@ class AppLoaderCommands(object):
|
||||||
self._tp = transport
|
self._tp = transport
|
||||||
self._apduCounter = 0;
|
self._apduCounter = 0;
|
||||||
|
|
||||||
|
def test_rfm(self):
|
||||||
|
|
||||||
|
# use only one of the following
|
||||||
|
|
||||||
|
if (1):
|
||||||
|
# SIM: select MF/GSM/EF_IMSI and read content (requires keyset two)
|
||||||
|
if not args.smpp:
|
||||||
|
print self.send_wrapped_apdu_rfm_sim('A0A40000023F00' + 'A0A40000027F20' + 'A0A40000026F07' + 'A0B0000009');
|
||||||
|
else:
|
||||||
|
self.send_wrapped_apdu_rfm_sim('A0A40000023F00' + 'A0A40000027F20' + 'A0A40000026F07' + 'A0B0000009');
|
||||||
|
else:
|
||||||
|
# USIM: select MF/GSM/EF_IMSI and read content (requires keyset three)
|
||||||
|
if not args.smpp:
|
||||||
|
print self.send_wrapped_apdu_rfm_usim('00A40004023F00' + '00A40004027F20' + '00A40004026F07' + '00B0000009');
|
||||||
|
else:
|
||||||
|
self.send_wrapped_apdu_rfm_usim('00A40004023F00' + '00A40004027F20' + '00A40004026F07' + '00B0000009');
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
def send_terminal_profile(self):
|
def send_terminal_profile(self):
|
||||||
return self._tp.send_apdu_checksw('A010000011FFFF000000000000000000000000000000')
|
rv = self._tp.send_apdu('A010000011FFFF000000000000000000000000000000')
|
||||||
|
if "91" == rv[1][0:2]:
|
||||||
|
# In case of "91xx" -> Fetch data, execute cmd and reply
|
||||||
|
self._tp.send_apdu('A0120000' + rv[1][2:4]) # FETCH
|
||||||
|
# otherwise "9300" (SIM Busy)
|
||||||
|
return self._tp.send_apdu('A01400000C810301030002028281030100') # TERMINAL RESPONSE
|
||||||
|
return rv;
|
||||||
|
|
||||||
# Wrap an APDU inside an SMS-PP APDU
|
# Wrap an APDU inside an SMS-PP APDU
|
||||||
def send_wrapped_apdu(self, data):
|
def send_wrapped_apdu_internal(self, data, tar, msl, kic_index, kid_index):
|
||||||
# Command packet header
|
#
|
||||||
# SPI: PoR required
|
# See ETSI TS 102 225 and ETSI TS 102 226 for more details
|
||||||
# TAR: Remote App Management (000000)
|
# about OTA security.
|
||||||
envelopeData = '0D0001000000000000000000' + ('%02x' % (self._apduCounter & 0xff)) + '00' + data;
|
#
|
||||||
self._apduCounter = self._apduCounter + 1
|
# So far only no signature check, RC or CC are supported.
|
||||||
|
# The only supported ciphering mode is "Triple DES in outer-CBC
|
||||||
|
# mode using two different keys" which is also used for CC.
|
||||||
|
|
||||||
# Command
|
# SPI first octet: set to MSL
|
||||||
|
spi_1 = msl;
|
||||||
|
|
||||||
|
# length of signature
|
||||||
|
|
||||||
|
if ((spi_1 & 0x03) == 0): # no integrity check
|
||||||
|
len_sig = 0;
|
||||||
|
elif ((spi_1 & 0x03) == 1): # RC
|
||||||
|
len_sig = 4;
|
||||||
|
elif ((spi_1 & 0x03) == 2): # CC
|
||||||
|
len_sig = 8;
|
||||||
|
else:
|
||||||
|
print "Invalid spi_1"
|
||||||
|
exit(0);
|
||||||
|
|
||||||
|
pad_cnt = 0;
|
||||||
|
# Padding if Ciphering is used
|
||||||
|
if ((spi_1 & 0x04) != 0): # check ciphering bit
|
||||||
|
len_cipher = 6 + len_sig + (len(data) / 2)
|
||||||
|
pad_cnt = 8 - (len_cipher % 8) # 8 Byte blocksize for DES-CBC (TODO: different padding)
|
||||||
|
# TODO: there is probably a better way to add "pad_cnt" padding bytes
|
||||||
|
for i in range(0, pad_cnt):
|
||||||
|
data = data + '00';
|
||||||
|
|
||||||
|
# CHL + SPI first octet
|
||||||
|
part_head = ('%02x' % (0x0D + len_sig)) + ('%02x' % (spi_1))
|
||||||
|
|
||||||
|
Kic = '00';
|
||||||
|
KID = '00';
|
||||||
|
if ((spi_1 & 0x04) != 0): # check ciphering bit
|
||||||
|
Kic = ('%02x' % (0x05 + (kic_index << 4))) # 05: Triple DES in outer-CBC mode using two different keys
|
||||||
|
if ((spi_1 & 0x03) == 2): # CC
|
||||||
|
KID = ('%02x' % (0x05 + (kid_index << 4))) # 05: Triple DES in outer-CBC mode using two different keys
|
||||||
|
|
||||||
|
# SPI second octet (01: POR required) + Kic + KID + TAR
|
||||||
|
# TODO: depending on the returned data use ciphering (10) and/or a signature (08)
|
||||||
|
part_head = part_head + '01' + Kic + KID + tar;
|
||||||
|
|
||||||
|
# CNTR + PCNTR (CNTR not used)
|
||||||
|
part_cnt = '0000000000' + ('%02x' % (pad_cnt))
|
||||||
|
|
||||||
|
envelopeData = part_head + part_cnt + data;
|
||||||
|
|
||||||
|
# two bytes CPL, CPL is part of RC/CC/DS
|
||||||
|
envelopeData = ('%04x' % (len(envelopeData) / 2 + len_sig)) + envelopeData
|
||||||
|
|
||||||
|
if (len_sig == 8):
|
||||||
|
# Padding
|
||||||
|
temp_data = envelopeData
|
||||||
|
len_cipher = (len(temp_data) / 2)
|
||||||
|
pad_cnt = 8 - (len_cipher % 8) # 8 Byte blocksize for DES-CBC (TODO: add different padding)
|
||||||
|
# TODO: there is probably a better way to add "pad_cnt" padding bytes
|
||||||
|
for i in range(0, pad_cnt):
|
||||||
|
temp_data = temp_data + '00';
|
||||||
|
|
||||||
|
key = binascii.a2b_hex(args.kid);
|
||||||
|
iv = binascii.a2b_hex('0000000000000000');
|
||||||
|
cipher = DES3.new(key, DES3.MODE_CBC, iv);
|
||||||
|
ciph = cipher.encrypt(binascii.a2b_hex(temp_data));
|
||||||
|
envelopeData = part_cnt + binascii.b2a_hex(ciph[len(ciph) - 8:]) + data;
|
||||||
|
elif (len_sig == 4):
|
||||||
|
crc32 = binascii.crc32(binascii.a2b_hex(envelopeData))
|
||||||
|
envelopeData = part_cnt + ('%08x' % (crc32 & 0xFFFFFFFF)) + data;
|
||||||
|
elif (len_sig == 0):
|
||||||
|
envelopeData = part_cnt + data;
|
||||||
|
else:
|
||||||
|
print "Invalid len_sig"
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
# Ciphering (CNTR + PCNTR + RC/CC/DS + data)
|
||||||
|
|
||||||
|
if ((spi_1 & 0x04) != 0): # check ciphering bit
|
||||||
|
key = binascii.a2b_hex(args.kic);
|
||||||
|
iv = binascii.a2b_hex('0000000000000000');
|
||||||
|
cipher = DES3.new(key, DES3.MODE_CBC, iv);
|
||||||
|
ciph = cipher.encrypt(binascii.a2b_hex(envelopeData));
|
||||||
|
envelopeData = part_head + binascii.b2a_hex(ciph)
|
||||||
|
else:
|
||||||
|
envelopeData = part_head + envelopeData;
|
||||||
|
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
|
||||||
|
# Command (add UDHI: USIM Toolkit Security Header)
|
||||||
|
# TS 23.048
|
||||||
|
#
|
||||||
|
# 02: UDHDL
|
||||||
|
# 70: IEIA (CPI=70)
|
||||||
|
# 00: IEIDLa
|
||||||
|
#
|
||||||
|
# two bytes CPL
|
||||||
|
# no CHI
|
||||||
|
#
|
||||||
envelopeData = '027000' + ('%04x' % (len(envelopeData) / 2)) + envelopeData;
|
envelopeData = '027000' + ('%04x' % (len(envelopeData) / 2)) + envelopeData;
|
||||||
|
|
||||||
|
# For sending via SMPP, those are the data which can be put into
|
||||||
|
# the "hex" field of the "sendwp" XML file (see examples in libsmpp34).
|
||||||
|
|
||||||
|
if args.smpp:
|
||||||
|
print "SMPP: " + envelopeData;
|
||||||
|
return ('00', '9000');
|
||||||
|
|
||||||
# SMS-TDPU header: MS-Delivery, no more messages, TP-UD header, no reply path,
|
# SMS-TDPU header: MS-Delivery, no more messages, TP-UD header, no reply path,
|
||||||
# TP-OA = TON/NPI 55667788, TP-PID = SIM Download, BS timestamp
|
# TP-OA = TON/NPI 55667788, TP-PID = SIM Download, BS timestamp
|
||||||
envelopeData = '400881556677887ff600112912000004' + ('%02x' % (len(envelopeData) / 2)) + envelopeData;
|
envelopeData = '400881556677887ff600112912000004' + ('%02x' % (len(envelopeData) / 2)) + envelopeData;
|
||||||
|
@ -75,14 +206,32 @@ class AppLoaderCommands(object):
|
||||||
|
|
||||||
# d1 = SMS-PP Download, d2 = Cell Broadcast Download
|
# d1 = SMS-PP Download, d2 = Cell Broadcast Download
|
||||||
envelopeData = 'd1' + hex_ber_length(envelopeData) + envelopeData;
|
envelopeData = 'd1' + hex_ber_length(envelopeData) + envelopeData;
|
||||||
response = self._tp.send_apdu_checksw('a0c20000' + ('%02x' % (len(envelopeData) / 2)) + envelopeData)[0]
|
(response, sw) = self._tp.send_apdu('a0c20000' + ('%02x' % (len(envelopeData) / 2)) + envelopeData)
|
||||||
|
if "9e" == sw[0:2]: # more bytes available, get response
|
||||||
|
response = self._tp.send_apdu_checksw('A0C00000' + sw[2:4])[0] # GET RESPONSE
|
||||||
|
|
||||||
# Unwrap response
|
# Unwrap response
|
||||||
response = response[(int(response[10:12],16)*2)+12:]
|
response = response[(int(response[10:12],16)*2)+12:]
|
||||||
return (response[6:], response[2:6])
|
return (response[6:], response[2:6])
|
||||||
|
|
||||||
|
def send_wrapped_apdu_ram(self, data):
|
||||||
|
if (len(args.kic) == 0) and (len(args.kid) == 0):
|
||||||
|
# TAR RAM: 000000, no security (JLM SIM)
|
||||||
|
return self.send_wrapped_apdu_internal(data, '000000', 0, 0, 0)
|
||||||
|
else:
|
||||||
|
# TAR RAM: 000000, sysmoSIM SJS1: MSL = 6, first keyset
|
||||||
|
return self.send_wrapped_apdu_internal(data, '000000', 6, 1, 1)
|
||||||
|
|
||||||
|
def send_wrapped_apdu_rfm_sim(self, data):
|
||||||
|
# TAR RFM SIM: B00010, sysmoSIM SJS1: MSL = 6, second keyset
|
||||||
|
return self.send_wrapped_apdu_internal(data, 'B00010', 6, 2, 2)
|
||||||
|
|
||||||
|
def send_wrapped_apdu_rfm_usim(self, data):
|
||||||
|
# TAR RFM USIM: B00011, sysmoSIM SJS1: MSL = 6, third keyset
|
||||||
|
return self.send_wrapped_apdu_internal(data, 'B00011', 6, 3, 3)
|
||||||
|
|
||||||
def send_wrapped_apdu_checksw(self, data, sw="9000"):
|
def send_wrapped_apdu_checksw(self, data, sw="9000"):
|
||||||
response = self.send_wrapped_apdu(data)
|
response = self.send_wrapped_apdu_ram(data)
|
||||||
if response[1] != sw:
|
if response[1] != sw:
|
||||||
raise RuntimeError("SW match failed! Expected %s and got %s." % (sw.lower(), response[1]))
|
raise RuntimeError("SW match failed! Expected %s and got %s." % (sw.lower(), response[1]))
|
||||||
return response
|
return response
|
||||||
|
@ -246,6 +395,9 @@ parser.add_argument('-t', '--list-applets', action='store_true')
|
||||||
parser.add_argument('--tar')
|
parser.add_argument('--tar')
|
||||||
parser.add_argument('--dump-phonebook', action='store_true')
|
parser.add_argument('--dump-phonebook', action='store_true')
|
||||||
parser.add_argument('--set-phonebook-entry', nargs=4)
|
parser.add_argument('--set-phonebook-entry', nargs=4)
|
||||||
|
parser.add_argument('--kic', default='')
|
||||||
|
parser.add_argument('--kid', default='')
|
||||||
|
parser.add_argument('--smpp', action='store_true')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
@ -261,12 +413,18 @@ else:
|
||||||
sc = SimCardCommands(sl)
|
sc = SimCardCommands(sl)
|
||||||
ac = AppLoaderCommands(sl)
|
ac = AppLoaderCommands(sl)
|
||||||
|
|
||||||
sl.wait_for_card(newcardonly=args.new_card_required)
|
if not args.smpp:
|
||||||
time.sleep(args.sleep_after_insertion)
|
sl.wait_for_card(newcardonly=args.new_card_required)
|
||||||
|
time.sleep(args.sleep_after_insertion)
|
||||||
|
|
||||||
# Get the ICCID
|
if not args.smpp:
|
||||||
print "ICCID: " + swap_nibbles(sc.read_binary(['3f00', '2fe2'])[0])
|
# Get the ICCID
|
||||||
ac.send_terminal_profile()
|
print "ICCID: " + swap_nibbles(sc.read_binary(['3f00', '2fe2'])[0])
|
||||||
|
ac.send_terminal_profile()
|
||||||
|
|
||||||
|
# for RFM testing
|
||||||
|
#ac.test_rfm()
|
||||||
|
#exit(0)
|
||||||
|
|
||||||
if args.pin:
|
if args.pin:
|
||||||
sc.verify_chv(1, args.pin)
|
sc.verify_chv(1, args.pin)
|
||||||
|
@ -308,9 +466,9 @@ if args.set_phonebook_entry:
|
||||||
sc.update_record(['3f00','7f10','6f3a'], record_num, record)
|
sc.update_record(['3f00','7f10','6f3a'], record_num, record)
|
||||||
|
|
||||||
if args.list_applets:
|
if args.list_applets:
|
||||||
(data, status) = ac.send_wrapped_apdu('80f21000024f0000c0000000')
|
(data, status) = ac.send_wrapped_apdu_ram('80f21000024f0000c0000000')
|
||||||
while status == '6310':
|
while status == '6310':
|
||||||
(partData, status) = ac.send_wrapped_apdu('80f21001024f0000c0000000')
|
(partData, status) = ac.send_wrapped_apdu_ram('80f21001024f0000c0000000')
|
||||||
data = data + partData
|
data = data + partData
|
||||||
|
|
||||||
while len(data) > 0:
|
while len(data) > 0:
|
||||||
|
|
Loading…
Reference in New Issue