Tool to (re)configure the sysmoUSIM and sysmoISIM cards
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

457 lines
17 KiB

  1. # -*- coding: UTF-8 -*-
  2. """
  3. card: Library adapted to request (U)SIM cards and other types of telco cards.
  4. Copyright (C) 2010 Benoit Michau
  5. This program is free software; you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation; either version 2 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License along
  14. with this program; if not, write to the Free Software Foundation, Inc.,
  15. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  16. """
  17. #################################
  18. # Python library to work on
  19. # SIM card
  20. # communication based on ISO7816 card
  21. #
  22. # needs pyscard from:
  23. # http://pyscard.sourceforge.net/
  24. #################################
  25. from card.ICC import ISO7816
  26. from card.FS import SIM_FS, MF_FS
  27. from card.utils import *
  28. SIM_service_table = {
  29. 1 : "CHV1 disable function",
  30. 2 : "Abbreviated Dialling Numbers (ADN)",
  31. 3 : "Fixed Dialling Numbers (FDN)",
  32. 4 : "Short Message Storage (SMS)",
  33. 5 : "Advice of Charge (AoC)",
  34. 6 : "Capability Configuration Parameters (CCP)",
  35. 7 : "PLMN selector",
  36. 8 : "RFU",
  37. 9 : "MSISDN",
  38. 10 : "Extension1",
  39. 11 : "Extension2",
  40. 12 : "SMS Parameters",
  41. 13 : "Last Number Dialled (LND)",
  42. 14 : "Cell Broadcast Message Identifier",
  43. 15 : "Group Identifier Level 1",
  44. 16 : "Group Identifier Level 2",
  45. 17 : "Service Provider Name",
  46. 18 : "Service Dialling Numbers (SDN)",
  47. 19 : "Extension3",
  48. 20 : "RFU",
  49. 21 : "VGCS Group Identifier List (EFVGCS and EFVGCSS)",
  50. 22 : "VBS Group Identifier List (EFVBS and EFVBSS)",
  51. 23 : "enhanced Multi-Level Precedence and Pre-emption Service",
  52. 24 : "Automatic Answer for eMLPP",
  53. 25 : "Data download via SMS-CB",
  54. 26 : "Data download via SMS-PP",
  55. 27 : "Menu selection",
  56. 28 : "Call control",
  57. 29 : "Proactive SIM",
  58. 30 : "Cell Broadcast Message Identifier Ranges",
  59. 31 : "Barred Dialling Numbers (BDN)",
  60. 32 : "Extension4",
  61. 33 : "De-personalization Control Keys",
  62. 34 : "Co-operative Network List",
  63. 35 : "Short Message Status Reports",
  64. 36 : "Network's indication of alerting in the MS ",
  65. 37 : "Mobile Originated Short Message control by SIM ",
  66. 38 : "GPRS",
  67. 39 : "Image (IMG)",
  68. 40 : "SoLSA (Support of Local Service Area)",
  69. 41 : "USSD string data object supported in Call Control",
  70. 42 : "RUN AT COMMAND command",
  71. 43 : "User controlled PLMN Selector with Access Technology",
  72. 44 : "Operator controlled PLMN Selector with Access Technology",
  73. 45 : "HPLMN Selector with Access Technology",
  74. 46 : "CPBCCH Information",
  75. 47 : "Investigation Scan",
  76. 48 : "Extended Capability Configuration Parameters",
  77. 49 : "MExE",
  78. 50 : "RPLMN last used Access Technology",
  79. 51 : "PLMN Network Name",
  80. 52 : "Operator PLMN List",
  81. 53 : "Mailbox Dialling Numbers ",
  82. 54 : "Message Waiting Indication Status",
  83. 55 : "Call Forwarding Indication Status",
  84. 56 : "Service Provider Display Information",
  85. }
  86. class SIM(ISO7816):
  87. """
  88. define attributes, methods and facilities for ETSI / 3GPP SIM card
  89. check SIM specifications in ETSI TS 102.221 and 3GPP TS 51.011
  90. inherit methods and objects from ISO7816 class
  91. use self.dbg = 1 or more to print live debugging information
  92. """
  93. def __init__(self, atr = None):
  94. """
  95. initialize like an ISO7816-4 card with CLA=0xA0
  96. can also be used for USIM working in SIM mode,
  97. """
  98. ISO7816.__init__(self, atr, CLA=0xA0)
  99. if self.dbg >= 2:
  100. log(3, '(SIM.__init__) type definition: %s' % type(self))
  101. log(3, '(SIM.__init__) CLA definition: %s' % hex(self.CLA))
  102. @staticmethod
  103. def sw_status(sw1, sw2):
  104. """
  105. sw_status(sw1=int, sw2=int) -> string
  106. extends SW status bytes interpretation from ISO7816
  107. with ETSI / 3GPP SW codes
  108. helps to speak with the smartcard!
  109. """
  110. status = ISO7816.sw_status(sw1, sw2)
  111. if sw1 == 0x91: status = 'normal processing, with extra info ' \
  112. 'containing a command for the terminal: length of the ' \
  113. 'response data %d' % sw2
  114. elif sw1 == 0x9E: status = 'normal processing, SIM data download ' \
  115. 'error: length of the response data %d' % sw2
  116. elif sw1 == 0x9F: status = 'normal processing: length of the ' \
  117. 'response data %d' % sw2
  118. elif (sw1, sw2) == (0x93, 0x00): status = 'SIM application toolkit ' \
  119. 'busy, command cannot be executed at present'
  120. elif sw1 == 0x92 :
  121. status = 'memory management'
  122. if sw2 < 16: status += ': command successful but after %d '\
  123. 'retry routine' % sw2
  124. elif sw2 == 0x40: status += ': memory problem'
  125. elif sw1 == 0x94:
  126. status = 'referencing management'
  127. if sw2 == 0x00: status += ': no EF selected'
  128. elif sw2 == 0x02: status += ': out of range (invalid address)'
  129. elif sw2 == 0x04: status += ': file ID or pattern not found'
  130. elif sw2 == 0x08: status += ': file inconsistent with the command'
  131. elif sw1 == 0x98:
  132. status = 'security management'
  133. if sw2 == 0x02: status += ': no CHV initialized'
  134. elif sw2 == 0x04: status += ': access condition not fulfilled, ' \
  135. 'at least 1 attempt left'
  136. elif sw2 == 0x08: status += ': in contradiction with CHV status'
  137. elif sw2 == 0x10: status += ': in contradiction with ' \
  138. 'invalidation status'
  139. elif sw2 == 0x40: status += ': unsuccessful CHV verification, ' \
  140. 'no attempt left'
  141. elif sw2 == 0x50: status += ': increase cannot be performed, ' \
  142. 'max value reached'
  143. elif sw2 == 0x62: status += ': authentication error, ' \
  144. 'application specific'
  145. elif sw2 == 0x63: status += ': security session expired'
  146. return status
  147. def verify_pin(self, pin='', pin_type=1):
  148. """
  149. verify CHV1 (PIN code) or CHV2 with VERIFY APDU command
  150. call ISO7816 VERIFY method
  151. """
  152. if pin_type in [1, 2] and type(pin) is str and \
  153. len(pin) == 4 and 0 <= int(pin) < 10000:
  154. PIN = [ord(i) for i in pin] + [0xFF, 0xFF, 0xFF, 0xFF]
  155. self.coms.push( self.VERIFY(P2=pin_type, Data=PIN) )
  156. else:
  157. if self.dbg:
  158. log(2, '(verify_pin) bad input parameters')
  159. def disable_pin(self, pin='', pin_type=1):
  160. """
  161. disable CHV1 (PIN code) or CHV2 with DISABLE_CHV APDU command
  162. TIP: do it as soon as you can when you are working
  163. with a SIM / USIM card for which you know the PIN!
  164. call ISO7816 DISABLE method
  165. """
  166. if pin_type in [1, 2] and type(pin) is str and \
  167. len(pin) == 4 and 0 <= int(pin) < 10000:
  168. PIN = [ord(i) for i in pin] + [0xFF, 0xFF, 0xFF, 0xFF]
  169. self.coms.push( self.DISABLE_CHV(P2=pin_type, Data=PIN) )
  170. else:
  171. if self.dbg:
  172. log(2, '(disable_pin) bad input parameters')
  173. def enable_pin(self, pin='', pin_type=1):
  174. """
  175. enable CHV1 (PIN code) or CHV2 with ENABLE_CHV APDU command
  176. call ISO7816 ENABLE method
  177. """
  178. if pin_type in [1, 2] and type(pin) is str and \
  179. len(pin) == 4 and 0 <= int(pin) < 10000:
  180. PIN = [ord(i) for i in pin] + [0xFF, 0xFF, 0xFF, 0xFF]
  181. self.coms.push( self.ENABLE_CHV(P2=pin_type, Data=PIN) )
  182. else:
  183. if self.dbg:
  184. log(2, '(enable_pin) bad input parameters')
  185. def unblock_pin(self, pin_type=1, unblock_pin=''):
  186. """
  187. WARNING: not correctly implemented!!!
  188. and PUK are in general 8 nums...
  189. TODO: make it correctly!
  190. APDU Tx de-activated
  191. unblock CHV1 (PIN code) or CHV2 with UNBLOCK_CHV APDU command
  192. and set 0000 value for new PIN
  193. call ISO7816 UNBLOCK_CHV method
  194. """
  195. log(1, '(unblock_pin) not implemented: aborting')
  196. return
  197. #if pin_type == 1:
  198. # pin_type = 0
  199. if pin_type in [0, 2] and type(unblock_pin) is str and \
  200. len(unblock_pin) == 4 and 0 <= int(unblock_pin) < 10000:
  201. UNBL_PIN = [ord(i) for i in unblock_pin] + [0xFF, 0xFF, 0xFF, 0xFF]
  202. #self.coms.push( self.UNBLOCK_CHV(P2=pin_type, Lc=0x10, \
  203. # Data=UNBL_PIN + \
  204. # [0x30, 0x30, 0x30, 0x30, 0xFF, 0xFF, 0xFF, 0xFF]) )
  205. else:
  206. if self.dbg:
  207. log(2, '(unblock_pin) bad input parameters')
  208. #return self.UNBLOCK_CHV(P2=pin_type)
  209. def parse_file(self, Data=[]):
  210. """
  211. parse_file(Data=[0x12, 0x34, 0x56, 0x89]) -> dict(file)
  212. parses a list of bytes returned when selecting a file
  213. interprets the content of some informative bytes for right accesses,
  214. type / format of file... see TS 51.011
  215. works over the SIM file structure
  216. """
  217. fil = {}
  218. fil['Size'] = Data[2]*0x100 + Data[3]
  219. fil['File Identifier'] = Data[4:6]
  220. fil['Type'] = ('RFU', 'MF', 'DF', '', 'EF')[Data[6]]
  221. fil['Length'] = Data[12]
  222. if fil['Type'] == 'MF' or fil['Type'] == 'DF':
  223. fil['DF_num'] = Data[14]
  224. fil['EF_num'] = Data[15]
  225. fil['codes_num'] = Data[16]
  226. fil['CHV1'] = ('not initialized','initialized')\
  227. [Data[18] >> 7]\
  228. + ': %d attempts remain' % (Data[18] & 0x0F)
  229. fil['unblock_CHV1'] = ('not initialized','initialized')\
  230. [Data[19] >> 7]\
  231. + ': %d attempts remain' % (Data[19] & 0x0F)
  232. fil['CHV2'] = ('not initialized','initialized')\
  233. [Data[20] >> 7]\
  234. + ': %d attempts remain' % (Data[20] & 0x0F)
  235. fil['unblock_CHV2'] = ('not initialized','initialized')\
  236. [Data[21] >> 7]\
  237. + ': %d attempts remain' % (Data[21] & 0x0F)
  238. if len(Data) > 23:
  239. fil['Adm'] = Data[23:]
  240. elif fil['Type'] == 'EF':
  241. cond = ('ALW', 'CHV1', 'CHV2', 'RFU', 'ADM_4', 'ADM_5',
  242. 'ADM_6', 'ADM_7', 'ADM_8', 'ADM_9', 'ADM_A',
  243. 'ADM_B', 'ADM_C', 'ADM_D', 'ADM_E', 'NEW')
  244. fil['UPDATE'] = cond[Data[8] & 0x0F]
  245. fil['READ'] = cond[Data[8] >> 4]
  246. fil['INCREASE'] = cond[Data[9] >> 4]
  247. fil['INVALIDATE'] = cond[Data[10] & 0x0F]
  248. fil['REHABILITATE'] = cond[Data[10] >> 4]
  249. fil['Status'] = ('not read/updatable when invalidated',
  250. 'read/updatable when invalidated')\
  251. [byteToBit(Data[11])[5]] \
  252. + (': invalidated',': not invalidated')\
  253. [byteToBit(Data[11])[7]]
  254. fil['Structure'] = ('transparent', 'linear fixed', '', 'cyclic')\
  255. [Data[13]]
  256. if fil['Structure'] == 'cyclic':
  257. fil['INCREASE'] = byteToBit(Data[7])[1]
  258. if len(Data) > 14:
  259. fil['Record Length'] = Data[14]
  260. return fil
  261. def run_gsm_alg(self, RAND=16*[0x00]):
  262. """
  263. self.run_gsm_alg( RAND ) -> ( SRES, Kc )
  264. RAND : list of bytes, length 16
  265. SRES : list of bytes, length 4
  266. Kc : list of bytes, length 8
  267. runs GSM authentication algorithm:
  268. accepts any kind of RAND (old GSM fashion)
  269. feed with RAND 16 bytes value
  270. returns a list with SRES and Kc, or None on error
  271. """
  272. if len(RAND) != 16:
  273. if self.dbg:
  274. log(1, '(run_gsm_alg) bad RAND value: aborting')
  275. return None
  276. # select DF_GSM directory
  277. self.select([0x7F, 0x20])
  278. if self.coms()[2] != (0x90, 0x00):
  279. if self.dbg >= 2:
  280. log(3, '(run_gsm_alg) %s' % self.coms())
  281. return None
  282. # run authentication
  283. self.coms.push(self.INTERNAL_AUTHENTICATE(P1=0x00, P2=0x00, Data=RAND))
  284. if self.coms()[2][0] != 0x9F:
  285. if self.dbg >= 2:
  286. log(3, '(run_gsm_alg) %s' % self.coms())
  287. return None
  288. # get authentication response
  289. self.coms.push(self.GET_RESPONSE(Le=self.coms()[2][1]))
  290. if self.coms()[2] != (0x90, 0x00):
  291. if self.dbg >= 2:
  292. log(3, '(run_gsm_alg) %s' % self.coms())
  293. return None
  294. SRES, Kc = self.coms()[3][0:4], self.coms()[3][4:]
  295. return [ SRES, Kc ]
  296. def get_imsi(self):
  297. """
  298. self.get_imsi() -> string(IMSI)
  299. reads IMSI value at address [0x6F, 0x07]
  300. returns IMSI string on success or None on error
  301. """
  302. # select DF_GSM for SIM card
  303. self.select([0x7F, 0x20])
  304. if self.coms()[2] != (0x90, 0x00):
  305. if self.dbg >= 2:
  306. log(3, '(get_imsi) %s' % self.coms())
  307. return None
  308. # select IMSI file
  309. imsi = self.select([0x6F, 0x07])
  310. if self.coms()[2] != (0x90, 0x00):
  311. if self.dbg >= 2:
  312. log(3, '(get_imsi) %s' % self.coms())
  313. return None
  314. # and parse the received data into the IMSI structure
  315. if 'Data' in imsi.keys() and len(imsi['Data']) == 9:
  316. return decode_BCD(imsi['Data'])[3:]
  317. # if issue with the content of the DF_IMSI file
  318. if self.dbg >= 2:
  319. log(3, '(get_imsi) %s' % self.coms())
  320. return None
  321. def get_services(self):
  322. """
  323. self.get_services() -> None
  324. reads SIM Service Table at address [0x6F, 0x38]
  325. returns list of services allowed / activated
  326. """
  327. # select DF_GSM for SIM card
  328. self.select([0x7F, 0x20])
  329. if self.coms()[2] != (0x90, 0x00):
  330. if self.dbg >= 2:
  331. log(3, '(get_services) %s' % self.coms())
  332. return None
  333. # select SST file
  334. sst = self.select([0x6F, 0x38])
  335. if self.coms()[2] != (0x90, 0x00):
  336. if self.dbg >= 2:
  337. log(3, '(get_services) %s' % self.coms())
  338. return None
  339. # parse data and prints corresponding services
  340. if 'Data' in sst.keys() and len(sst['Data']) >= 2:
  341. return self.get_services_from_sst(sst['Data'])
  342. def read_services(self):
  343. """
  344. self.read_services() -> None
  345. reads SIM Service Table at address [0x6F, 0x38]
  346. prints services allowed / activated
  347. returns None
  348. """
  349. serv = self.get_services()
  350. for s in serv:
  351. print(s)
  352. def get_services_from_sst(self, sst=[0, 0]):
  353. services = []
  354. cnt = 0
  355. for B in sst:
  356. # 2 bits per service -> 4 services per byte
  357. for i in range(0, 7, 2):
  358. cnt += 1
  359. if B & 2**i:
  360. info = 'allocated'
  361. if B & (2**i+1):
  362. info += ' | activated'
  363. if cnt in SIM_service_table:
  364. services.append('%i : %s : %s' \
  365. % (cnt, SIM_service_table[cnt], info))
  366. else:
  367. services.append('%i : %s' % (cnt, info))
  368. return services
  369. def explore_fs(self, filename='sim_fs', depth=True, emul=False):
  370. """
  371. self.explore_fs(self, filename='sim_fs') -> void
  372. filename: file to write in information found
  373. depth: depth in recursivity, True=infinite
  374. brute force all file addresses from MF recursively
  375. (until no more DF are found)
  376. write information on existing DF and file in the output file
  377. """
  378. simfs_entries = MF_FS.keys()
  379. if not emul:
  380. self.explore_DF([], None, depth)
  381. fd = open(filename, 'w')
  382. fd.write('\n### MF ###\n')
  383. f = self.select()
  384. write_dict(f, fd)
  385. fd.write('\n')
  386. #
  387. for f in self.FS:
  388. path = tuple(f['Absolut Path'])
  389. if path in simfs_entries:
  390. f['Name'] = MF_FS[path]
  391. write_dict(f, fd)
  392. fd.write('\n')
  393. fd.close()
  394. def get_ICCID(self):
  395. # select MF
  396. self.select([0x3F, 0x0])
  397. if self.coms()[2] != (0x90, 0x00):
  398. if self.dbg >= 2:
  399. log(3, '(get_ICCID) %s' % self.coms())
  400. return None
  401. # select IMSI file
  402. iccid = self.select([0x2F, 0xE2])
  403. if self.coms()[2] != (0x90, 0x00):
  404. if self.dbg >= 2:
  405. log(3, '(get_ICCID) %s' % self.coms())
  406. return None
  407. # and parse the received data into the IMSI structure
  408. if 'Data' in iccid.keys() and len(iccid['Data']) >= 10:
  409. return decode_BCD(iccid['Data'])
  410. # if issue with the content of the ICCID file
  411. if self.dbg >= 2:
  412. log(3, '(get_ICCID) %s' % self.coms())
  413. return None