From 46bc6d25d62321b5bac0c6b8615e684f14ae293f Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Fri, 27 Mar 2020 00:28:53 +0100 Subject: [PATCH] HACK: support for SIM factory file formats Change-Id: I23cd37fc06b6e2d21964fd4f2694d9ee3c6012d4 --- execute_ipr.py | 141 +++++++++++++++++++++++++++++++++++++++++++++++ format_ipr.py | 55 ++++++++++++++++++ format_ldr.py | 74 +++++++++++++++++++++++++ ldr_to_ipr.py | 84 ++++++++++++++++++++++++++++ script_format.py | 13 +++++ 5 files changed, 367 insertions(+) create mode 100755 execute_ipr.py create mode 100644 format_ipr.py create mode 100644 format_ldr.py create mode 100755 ldr_to_ipr.py create mode 100644 script_format.py diff --git a/execute_ipr.py b/execute_ipr.py new file mode 100755 index 00000000..715ad18f --- /dev/null +++ b/execute_ipr.py @@ -0,0 +1,141 @@ +#!/usr/bin/python3 + +from pySim.transport.pcsc import PcscSimLink +from pySim.utils import enc_iccid, enc_imsi + +from smartcard.Exceptions import NoCardException, CardRequestTimeoutException +from smartcard.System import readers + +from lark import Lark, Transformer, Token, Tree +import sys + +from format_ipr import ScriptFormatIPR + +class DataTransform(): + ''' Transform raw/logical input data into the format use on the SIM card, + like encoding the PIN from '1234' -> 3132334 or IMSI encoding''' + def transform(self, inp): + outp = {} + for k in inp.keys(): + f = getattr(self, 'xfrm_'+k, None) + if f != None: + outp[k] = f(inp[k]) + else: + outp[k] = inp[k] + return outp + + def xfrm_PIN(self, pin): + ret = '' + for c in str(pin): + ret += '3%c' % c + return ret + def xfrm_PIN1(self, pin): + return self.xfrm_PIN(pin) + def xfrm_PIN2(self, pin): + return self.xfrm_PIN(pin) + def xfrm_PUK1(self, pin): + return self.xfrm_PIN(pin) + def xfrm_PUK2(self, pin): + return self.xfrm_PIN(pin) + def xfrm_ADM1(self, pin): + return self.xfrm_PIN(pin) + def xfrm_ADM2(self, pin): + return self.xfrm_PIN(pin) + def xfrm_IMSI(self, imsi): + return enc_imsi(imsi) + def xfrm_ICCID(self, iccid): + # TODO: calculate luhn check digit + return enc_iccid(iccid) + + +def expand_cmd_template(cmd, templates): + ''' Take a single command, supstituting all [] template keys with data from 'template' ''' + ret = "" + for e in cmd: + if e[0] == 'hexstr': + ret += e[1] + if e[0] == 'key': + ret += templates[e[1]] + return ret + +def match_sw(actual_sw, sw_match): + ''' Check if actual_sw matches any of the templates given in sw_match''' + def match_sw_single(actual_sw, match): + match = match.lower() + if 'x' in match: + FIXME + else: + if actual_sw.lower() == match: + return True + return False + + if sw_match == []: + return True + + for m in sw_match: + if match_sw_single(actual_sw, m): + return True + + return False + + +def execute_ipr_raw(s, sl, dynamic_data_raw = {}): + """ translate a single LDR statement to IPR format. """ + if s == None: + None + elif s == 'reset': + print("RESET") + sl.reset_card() + elif s[0] == 'rem': + print("REM %s" % (s[1])) + elif s[0] == 'cmd': + d = s[1] + req = expand_cmd_template(d['req'], dynamic_data_raw) + rsp = d['rsp'] + print("\tREQ: %s, EXP: %s" % (req, rsp)) + (data, sw) = sl.send_apdu_raw(req) + if not match_sw(sw, rsp): + raise ValueError("SW %s doesn't match expected %s" % (sw, rsp)) + print("\tRSP: %s\n" % (sw)) + +def execute_ipr(s, sl, dynamic_data = {}): + """ translate a single LDR statement to IPR format; optionally substitute dynamic_data. """ + xf = DataTransform() + return execute_ipr_raw(s, sl, xf.transform(dynamic_data)) + + +'''Dictionaries like this must be generated for each card to be programmed''' +demo_dict = { + 'PIN1': '1234', + 'PIN2': '1234', + 'PUK1': '12345678', + 'PUK2': '12345678', + 'ADM1': '11111111', + + 'KIC1': '100102030405060708090a0b0c0d0e0f', + 'KID1': '101102030405060708090a0b0c0d0e0f', + 'KIK1': '102102030405060708090a0b0c0d0e0f', + + 'KIC2': '200102030405060708090a0b0c0d0e0f', + 'KID2': '201102030405060708090a0b0c0d0e0f', + 'KIK2': '202102030405060708090a0b0c0d0e0f', + + 'KIC3': '300102030405060708090a0b0c0d0e0f', + 'KID3': '301102030405060708090a0b0c0d0e0f', + 'KIK3': '302102030405060708090a0b0c0d0e0f', + + 'ICCID': '012345678901234567', + 'IMSI': '001010123456789', + 'ACC': '0200', + 'KI': '000102030405060708090a0b0c0d0e0f', + 'OPC': '101112131415161718191a1b1c1d1e1f', + 'VERIFY_ICCID': '0001020304050608090a0b0c0d0e0f', + } + + +sl = PcscSimLink(0) + +infile_name = sys.argv[1] + +fmt = ScriptFormatIPR() +fmt.parse_process_file(infile_name, execute_ipr, {'sl':sl, 'dynamic_data':demo_dict}) diff --git a/format_ipr.py b/format_ipr.py new file mode 100644 index 00000000..940e3185 --- /dev/null +++ b/format_ipr.py @@ -0,0 +1,55 @@ +from lark import Lark, Transformer, Token, Tree +from script_format import ScriptFormat +from format_ldr import LdrXfrm + +class IprXfrm(LdrXfrm): + """ transform the parse tree into a more easily consumable form """ + def key(self, items): + return ('key', ''.join(list(items))) + def req(self, items): + return items[:-1] + def rsp(self, items): + return items[:-1] + #def NEWLINE(self, items): + #return None + + +class ScriptFormatIPR(ScriptFormat): + # parser for the IPR file format as used by the SIM card factory + ipr_parser = Lark(r""" + script: statement* + ?statement: cmd | rst | rem | NEWLINE + + NONL: /[^\n]/+ + rem: "//" NONL? NEWLINE + + ALNUM: DIGIT | LETTER | "_" + key: "[" ALNUM+ "]" + + cmd: req rsp + + req: "I:" [hexstr|key]+ NEWLINE + hexstr: HEX_ITEM+ + HEX_ITEM: HEXDIGIT ~ 2 + + rsp: "O:" swpattern? NEWLINE + swpattern: HEX_OR_X ~ 4 + HEX_OR_X: HEXDIGIT | "X" | "x" + + rst: "RESET" NEWLINE + + %import common.ESCAPED_STRING -> STRING + %import common.WS_INLINE + %import common.HEXDIGIT + %import common.DIGIT + %import common.LETTER + %import common.NEWLINE + %ignore WS_INLINE + + """, start='script', parser='lalr')#, lexer='standard') + + def parse_xform(self, text): + tree = self.ipr_parser.parse(text) + #print(tree.pretty()) + p = IprXfrm().transform(tree) + return p diff --git a/format_ldr.py b/format_ldr.py new file mode 100644 index 00000000..7b4bb1f9 --- /dev/null +++ b/format_ldr.py @@ -0,0 +1,74 @@ +from lark import Lark, Transformer, Token, Tree +from script_format import ScriptFormat + +class LdrXfrm(Transformer): + """ transform the parse tree into a more easily consumable form """ + def rst(self, items): + return ('reset') + def NONL(self, items): + return ''.join([i for i in items.value]) + def rem(self, items): + return ('rem', items[0]) + def swmatch(self, items): + return ('swmatch', items) + def cmd(self, items): + return ('cmd', {'req': items[0], 'rsp': items[1]}) + + def todigit(self, item): + """ convert from Token/Tree to raw hex-digit """ + if isinstance(item, Token): + return item.value + elif isinstance(item, Tree): + return item.data + def hex_item(self, items): + """ return one byte as two-digit HEX string """ + return "%s%s" % (items[0].value, items[1].value) + def hexstr(self, items): + """ return list of two-digit HEX strings """ + return ('hexstr', ''.join(list(items))) + def swpattern(self, items): + """ return list of four HEX nibbles (or 'x' as wildcard) """ + arr = [self.todigit(x) for x in items] + return ''.join(arr) + +class ScriptFormatLDR(ScriptFormat): + # parser for the LDR file format as generated by Simulity Profile Editor + ldr_parser = Lark(r""" + script: statement* + ?statement: cmd | rst | rem | NEWLINE + + BSLASH: "\\\n" + %ignore BSLASH + + NONL: /[^\n]/+ + rem: "REM" NONL? NEWLINE + + ALNUM: DIGIT | LETTER | "_" + key: "[" ALNUM+ "]" + + cmd: "CMD" hexstr [swmatch] NEWLINE + cmd_item: hexstr | key + hexstr: hex_item+ + hex_item: HEXDIGIT HEXDIGIT + + swmatch: "(" swpattern ("," swpattern)* ")" + swpattern: hex_or_x hex_or_x hex_or_x hex_or_x + ?hex_or_x: HEXDIGIT | "X" -> x | "x" -> x + + rst: "RST" NEWLINE + + %import common.ESCAPED_STRING -> STRING + %import common.WS_INLINE + %import common.HEXDIGIT + %import common.DIGIT + %import common.LETTER + %import common.NEWLINE + %ignore WS_INLINE + + """, start='script') + + def parse_xform(self, text): + tree = self.ldr_parser.parse(text) + #print(tree.pretty()) + p = LdrXfrm().transform(tree) + return p diff --git a/ldr_to_ipr.py b/ldr_to_ipr.py new file mode 100755 index 00000000..dbbd9116 --- /dev/null +++ b/ldr_to_ipr.py @@ -0,0 +1,84 @@ +#!/usr/bin/python3 + +from lark import Lark, Transformer, Token, Tree +import sys + +from format_ldr import ScriptFormatLDR +from format_ipr import ScriptFormatIPR + +def split_hex(value): + """ split a string of hex digits into groups (bytes) of two digits. """ + return ' '.join(value[i:i+2] for i in range(0, len(value), 2)) + +def expand_cmd(cmd): + ret = "" + for e in cmd: + if e[0] == 'hexstr': + ret += e[1] + else: + raise ValueError("Unsupported '%s'" % (e[0])) + return ret + + +def ldr_stmt_to_ipr(s): + """ translate a single LDR statement to IPR format. """ + if s == None: + None + elif s == 'reset': + print("RESET") + print("") + elif s[0] == 'rem': + print("//\t%s" % s[1]) + elif s[0] == 'cmd': + cmd = s[1] + req = cmd['req'] + rsp = cmd['rsp'] + print("I: %s" % split_hex(expand_cmd([req]))) + if rsp != None and len(rsp) != 1: + if rsp[0] != 'swmatch' or len(rsp[1]) != 1: + raise ValueError("Unsupported '%s'" % (rsp)) + print("O: %s" % rsp[1][0]) + else: + print("O:") + print("") + else: + print("Unknown %s" % (s.pretty())) + raise ValueError() + + +test_text = ''' +RST +CMD E0 CA DF 1F 13 +CMD E0 CA DF 1F (90 00) +CMD E0 CA DF 1F (61 XX, 90 00) +REM foo bar +CMD E4 DA DF 20 09 EA 53 F8 D7 64 1E D9 88 00 \\ + (90 00 , 6B 00) +''' + + +def run_statement(s): + print(s) + +def fii(s): + if s.data == 'rst': + print("=> RESET") + # FIXME: actually perform card reset + elif s.data == 'rem': + print(s) + elif s.data == 'cmd': + #print(s) + cmd = s.children[0] + print(s.pretty()) + # FIXME: if swmatch: match all contained swpattern + else: + print("Unknown %s" % (s.pretty())) + raise ValueError() + + +#process_ldr(test_text, run_statement) +#process_ldr(test_text, ldr_stmt_to_ipr) + +fmt = ScriptFormatLDR() +fmt.parse_process_file(sys.argv[1], ldr_stmt_to_ipr) +#fmt.parse_process_file(sys.argv[1], run_statement) diff --git a/script_format.py b/script_format.py new file mode 100644 index 00000000..18d4c1c6 --- /dev/null +++ b/script_format.py @@ -0,0 +1,13 @@ + +class ScriptFormat(): + + def parse_process(self, text, stmt_cb, stmt_cb_kwargs={}): + p = self.parse_xform(text) + #print(p.pretty()) + for stmt in p.children: + stmt_cb(stmt, **stmt_cb_kwargs) + + def parse_process_file(self, fname, stmt_cb, stmt_cb_kwargs={}): + f = open(fname, "r") + text = f.read() + return self.parse_process(text, stmt_cb, stmt_cb_kwargs)