From 93ec5f3566a70b14e9a79d706018964392cb7dc2 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Sun, 26 Dec 2010 00:11:19 +0100 Subject: Initial add of 27C3 HLR management tools Signed-off-by: Sylvain Munaut --- ccc-gen.py | 230 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ccc.py | 193 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 423 insertions(+) create mode 100755 ccc-gen.py create mode 100644 ccc.py diff --git a/ccc-gen.py b/ccc-gen.py new file mode 100755 index 0000000..46ea2dd --- /dev/null +++ b/ccc-gen.py @@ -0,0 +1,230 @@ +#!/usr/bin/env python + +# +# Utility to generate the HLR +# +# +# Copyright (C) 2010 Sylvain Munaut +# +# 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 . +# + +from optparse import OptionParser + +from ccc import StateManager, CardParametersGenerator, isnum +from pySim.utils import h2b + + +# +# OpenBSC HLR Writing +# + +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 hlr_write_cards(filename, network, cards): + + import sqlite3 + + conn = sqlite3.connect(filename) + + for card in cards: + c = conn.execute( + 'INSERT INTO Subscriber ' + + '(imsi, name, extension, authorized, created, updated) ' + + 'VALUES ' + + '(?,?,?,1,datetime(\'now\'),datetime(\'now\'));', + [ + card.imsi, + '%s #%d' % (network.name, card.num), + '9%05d' % card.num, + ], + ) + sub_id = c.lastrowid + c.close() + + c = conn.execute( + 'INSERT INTO AuthKeys ' + + '(subscriber_id, algorithm_id, a3a8_ki)' + + 'VALUES ' + + '(?,?,?)', + [ sub_id, 2, sqlite3.Binary(_dbi_binary_quote(h2b(card.ki))) ], + ) + c.close() + + conn.commit() + conn.close() + + +# +# CSV Writing +# + +def csv_write_cards(filename, network, cards): + import csv + fh = open(filename, 'a') + cw = csv.writer(fh) + cw.writerows(cards) + fh.close() + + +# +# Main stuff +# + +def parse_options(): + + parser = OptionParser(usage="usage: %prog [options]") + + # Network parameters + parser.add_option("-n", "--name", dest="name", + help="Operator name [default: %default]", + default="CCC Event", + ) + parser.add_option("-c", "--country", dest="country", type="int", metavar="CC", + help="Country code [default: %default]", + default=49, + ) + parser.add_option("-x", "--mcc", dest="mcc", type="int", + help="Mobile Country Code [default: %default]", + default=262, + ) + parser.add_option("-y", "--mnc", dest="mnc", type="int", + help="Mobile Network Code [default: %default]", + default=42, + ) + parser.add_option("-m", "--smsp", dest="smsp", + help="SMSP [default: '00 + country code + 5555']", + ) + + # Autogen + parser.add_option("-z", "--secret", dest="secret", metavar="STR", + help="Secret used for ICCID/IMSI autogen", + ) + parser.add_option("-k", "--count", dest="count", type="int", metavar="CNT", + help="Number of entried to generate [default: %default]", + default=1000, + ) + + # Output + parser.add_option("--state", dest="state_file", metavar="FILE", + help="Use this state file", + ) + 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", + ) + + (options, args) = parser.parse_args() + + if args: + parser.error("Extraneous arguments") + + # Check everything + if 1 < len(options.name) > 16: + parser.error("Name must be between 1 and 16 characters") + + if 0 < options.country > 999: + parser.error("Invalid country code") + + if 0 < options.mcc > 999: + parser.error("Invalid Mobile Country Code (MCC)") + if 0 < options.mnc > 999: + parser.error("Invalid Mobile Network Code (MNC)") + + if options.smsp is not None: + if not isnum(options.smsp): + parser.error("Invalid SMSP Number") + else: + options.smsp = '00%d' % options.country + '5555' + + return options + + +def main(): + + # Parse options + opts = parse_options() + + # Load state + sm = StateManager(opts.state_file, opts) + sm.load() + + # Instanciate generator + np = sm.network + cpg = CardParametersGenerator(np.cc, np.mcc, np.mnc, sm.get_secret()) + + # Generate cards + imsis = set() + cards = [] + while len(cards) < opts.count: + # Next number + i = sm.next_gen_num() + + # Generate card number + cp = cpg.generate(i) + + # Check for dupes + if cp.imsi in imsis: + continue + imsis.add(cp.imsi) + + # Collect + cards.append(cp) + + # Save cards + if opts.write_hlr: + hlr_write_cards(opts.write_hlr, np, cards) + + if opts.write_csv: + csv_write_cards(opts.write_csv, np, cards) + + # Save state + sm.save() + + +if __name__ == '__main__': + main() diff --git a/ccc.py b/ccc.py new file mode 100644 index 0000000..09426b5 --- /dev/null +++ b/ccc.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python + +# +# CCC Event HLR management common stuff +# +# +# Copyright (C) 2010 Sylvain Munaut +# +# 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 . +# + +import hashlib +import os +import random + +from collections import namedtuple + +try: + import json +except Importerror: + # Python < 2.5 + import simplejson as json + +# +# Various helpers +# + +def isnum(s, l=-1): + return s.isdigit() and ((l== -1) or (len(s) == l)) + + +# +# Storage tuples +# + +CardParameters = namedtuple("CardParameters", "num iccid imsi ki") +NetworkParameters = namedtuple("NetworkParameters", "name cc mcc mnc smsp") + + +# +# State management +# + +class StateManager(object): + + def __init__(self, filename=None, options=None): + # Filename for state storage + self._filename = filename + + # Params from options + self._net_name = options.name if options else None + self._net_cc = options.country if options else None + self._net_mcc = options.mcc if options else None + self._net_mnc = options.mnc if options else None + self._net_smsp = options.smsp if options else None + + self._secret = options.secret if options else None + + # Default + self._num_gen = 0 + self._num_write = 0 + + def load(self): + # Skip if no state file + if self._filename is None: + return + + # Skip if doesn't exist yet + if not os.path.isfile(self._filename): + return + + # Read + fh = open(self._filename, 'r') + data = fh.read() + fh.close() + + # Decode json and merge + dd = json.loads(data) + + self._net_name = dd['name'] + self._net_cc = dd['cc'] + self._net_mcc = dd['mcc'] + self._net_mnc = dd['mnc'] + self._net_smsp = dd['smsp'] + self._secret = dd['secret'] + self._num_gen = dd['num_gen'] + self._num_write = dd['num_write'] + + def save(self): + # Skip if no state file + if self._filename is None: + return + + # Serialize + data = json.dumps({ + 'name': self._net_name, + 'cc': self._net_cc, + 'mcc': self._net_mcc, + 'mnc': self._net_mnc, + 'smsp': self._net_smsp, + 'secret': self._secret, + 'num_gen': self._num_gen, + 'num_write': self._num_write, + }) + + # Save in json + fh = open(self._filename, 'w') + fh.write(data) + fh.close() + + @property + def network(self): + return NetworkParameters( + self._net_name, + self._net_cc, + self._net_mcc, + self._net_mnc, + self._net_smsp, + ) + + def get_secret(self): + return self._secret + + def next_gen_num(self): + n = self._num_gen + self._num_gen += 1 + return n + + def next_write_num(self): + n = self._num_write + self._num_write += 1 + return n + +# +# Card parameters generation +# + +class CardParametersGenerator(object): + + def __init__(self, cc, mcc, mnc, secret): + # Digitize country code (2 or 3 digits) + self._cc_digits = ('%03d' if cc > 100 else '%02d') % cc + + # Digitize MCC/MNC (5 or 6 digits) + self._plmn_digits = ('%03d%03d' if mnc > 100 else '%03d%02d') % (mcc, mnc) + + # Store secret + self._secret = secret + + def _digits(self, usage, len_, num): + s = hashlib.sha1(self._secret + usage + '%d' % num) + d = ''.join(['%02d'%ord(x) for x in s.digest()]) + return d[0:len_] + + def _gen_iccid(self, num): + iccid = ( + '89' + # Common prefix (telecom) + self._cc_digits + # Country Code on 2/3 digits + self._plmn_digits # MCC/MNC on 5/6 digits + ) + ml = 20 - len(iccid) + iccid += self._digits('ccid', ml, num) + return iccid + + def _gen_imsi(self, num): + ml = 15 - len(self._plmn_digits) + msin = self._digits('imsi', ml, num) + return ( + self._plmn_digits + # MCC/MNC on 5/6 digits + msin # MSIN + ) + + def _gen_ki(self): + return ''.join(['%02x' % random.randrange(0,256) for i in range(16)]) + + def generate(self, num): + return CardParameters( + num, + self._gen_iccid(num), + self._gen_imsi(num), + self._gen_ki(), + ) -- cgit v1.2.3