Important!

Blog moved to https://blog.apdu.fr/

I moved my blog from https://ludovicrousseau.blogspot.com/ to https://blog.apdu.fr/ . Why? I wanted to move away from Blogger (owne...

Wednesday, February 24, 2021

Writing a SIM card phone book in Python

In the previous article "Reading a SIM card phone book in Python" I presented a program to read the phone book from a SIM card. I will now present the writing part.

 

Source code

The source code is in 2 parts: usim.py and usim_write.py files.

usim.py is the exact same file as in the previous article.

usim_write.py is:

#!/usr/bin/env python3

from smartcard.util import toBytes, padd
import random
import usim


def get_name():
    # List of firstnames from https://www.data.gouv.fr/fr/datasets/liste-de-prenoms/
    with open(random.choice(['f', 'm'])) as fd:
        lines = list(map(str.strip, fd.readlines()))
    return " ".join([random.choice(lines), random.choice(lines),
                    random.choice(lines)])


def get_number():
    numbers = "0123456789"
    phone = list()
    phone.append(random.choice(numbers) + random.choice(numbers))
    phone.append(random.choice(numbers) + random.choice(numbers))
    phone.append(random.choice(numbers) + random.choice(numbers))
    phone.append(random.choice(numbers) + random.choice(numbers))
    phone.append(random.choice(numbers) + random.choice(numbers))
    return " ".join(phone)


def new_record(size):
    # size-14 characters for the name
    name = get_name()[0:size-14]
    phone = get_number()
    print("name:", name)
    print("phone:", phone)
    record = padd(list(map(ord, name)), size-14) \
        + padd(toBytes("06 A1 " + phone), 14)
    return record


def usim_write(reader_nb):
    # Select the EF ADN
    (size, connection) = usim.usim(reader_nb)

    for nbr in range(1, 250):
        record = new_record(size)
        #  Update record
        header = [0xA0, 0xDC]
        record_idx = nbr
        cmd = header + [record_idx, 0x04, size] + record
        data, sw1, sw2 = connection.transmit(cmd)
        if (sw1, sw2) != (0x90, 0x00):
            return


if __name__ == "__main__":
    import sys
    if 2 == len(sys.argv):
        reader_nb = int(sys.argv[1])
    else:
        reader_nb = 0
    usim_write(reader_nb)


Comments

I wanted to have reasonable names and phone numbers in my phone book. So I generate names by randomly selecting 3 first names from 2 lists: the 50 first (by frequency of use) male first names in French stored in the m file, and the 50 first female first names in French stored in the f file. I got the data from https://www.data.gouv.fr/fr/datasets/liste-de-prenoms/. The original list also contains first names from other languages.

The first 10 lines of m are:

Pierre
Juste
Julien
Olivier
Henri
Jacques
Philippe
Nicolas
Aime
Antoine
[...]

The first 10 lines of f are:

Marie
Victoire
Claire
Marine
Reine
Virginie
Vienne
Solange
Jolie
Marguerite
[...]

For the phone number I just select a random 10-digits number.

Maybe the entries I created are non functional in a real phone. First check it works for you if you want to reuse this code. Also if you plan to reuse my source code you must read "My blog source code license" first.


Output

$ ./usim_write.py 
Available readers:
- Gemalto PC Twin Reader
Using: Gemalto PC Twin Reader
connecting to Gemalto PC Twin Reader
Select MF
> A0 A4 00 00 02 3F 00
<  [] 9F 22
Select DF Telecom
> A0 A4 00 00 02 7F 10
<  [] 9F 22
Select EF ADN
> A0 A4 00 00 02 6F 3A
<  [] 9F 0F
Get Response
> A0 C0 00 00 0F
< 00 00 21 34 6F 3A 04 00 11 FF 22 01 02 01 22 90 00
name: Juliette Claire Fran
phone: 50 47 00 17 44
> A0 DC 01 04 22 4A 75 6C 69 65 74 74 65 20 43 6C 61 69 72 65 20 46 72 61 6E 06 A1 50 47 00 17 44 FF FF FF FF FF FF FF
<  [] 90 00
name: Luc Nicolas Regis
phone: 83 46 67 10 73
> A0 DC 02 04 22 4C 75 63 20 4E 69 63 6F 6C 61 73 20 52 65 67 69 73 FF FF FF 06 A1 83 46 67 10 73 FF FF FF FF FF FF FF
<  [] 90 00
name: Julien Jeremie Serge
phone: 89 07 17 20 07
> A0 DC 03 04 22 4A 75 6C 69 65 6E 20 4A 65 72 65 6D 69 65 20 53 65 72 67 65 06 A1 89 07 17 20 07 FF FF FF FF FF FF FF
<  [] 90 00
[...]

The output is truncated. I do not want to include all the 255 phone numbers.

If I use usim_read.py (with debug disabled) I get:

$ ./usim_read.py 
Available readers:
- Gemalto PC Twin Reader
Using: Gemalto PC Twin Reader
Select MF
Select DF Telecom
Select EF ADN
Get Response
1: Name: Juliette Claire Fra, phone: 0574007144
2: Name: Luc Nicolas Regis.., phone: 3864760137
3: Name: Julien Jeremie Serg, phone: 9870710270
[...]

Note that the phone numbers are reversed by group of 2 digits. 50 47 00 17 44 becomes 0574007144.


Conclusion

It is as easy to write than to read a SIM phone book.

My goal here was to be able to write "realistic" phone book entries so that the usim_read.py has "real" data to read and display.

usim_read.py will be used again in the next episode.

Monday, February 22, 2021

Reading a SIM card phone book in Python

I already wrote a SIM phone book dumper program in 2004. This first version was in Perl and was presented in "SIM card phone book listing".

You can also find more advanced tools in articles with the SIM label, like cardpeek, monosim or PSSI.

I now present a version in Python using the PySCard wrapper

 

Source code

The source code is in 2 parts: usim.py and usim_read.py files.

usim.py is:

#!/usr/bin/env python3

from smartcard.System import readers
from smartcard.util import toBytes
from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver

debug = True

def usim(reader_nb):
    # get all the available readers
    r = readers()
    print("Available readers:")
    for reader in r:
        print("-", reader)

    reader = r[reader_nb]
    print("Using:", reader)

    connection = reader.createConnection()
    if debug:
        observer = ConsoleCardConnectionObserver()
        connection.addObserver(observer)
    connection.connect()

    SELECT = "A0 A4 00 00 02 "
    GET_RESPONSE = "A0 C0 00 00 "

    # Select MF
    print("Select MF")
    data, sw1, sw2 = connection.transmit(toBytes(SELECT + "3F 00"))
    if sw1 != 0x9F:
        raise(Exception("Error"))

    # Select DF Telecom
    print("Select DF Telecom")
    data, sw1, sw2 = connection.transmit(toBytes(SELECT + "7F 10"))
    if sw1 != 0x9F:
        raise(Exception("Error"))

    # Select EF ADN
    print("Select EF ADN")
    data, sw1, sw2 = connection.transmit(toBytes(SELECT + "6F 3A"))
    if (sw1, sw2) != (0x9F, 0x0F):
        raise(Exception("Error"))

    # Get Response
    print("Get Response")
    data, sw1, sw2 = connection.transmit(toBytes(GET_RESPONSE) + [sw2])
    if (sw1, sw2) != (0x90, 0x00):
        raise(Exception("Error"))

    size = data[-1]

    pin = None
    if pin:
        print(pin)
        pin = list(map(ord, pin))
        padd(pin, 8)

        # Verify CHV
        VERIFY = "A0 20 00 01 08"
        cmd = toBytes(VERIFY) + pin
        data, sw1, sw2 = connection.transmit(cmd)
        if (sw1, sw2) != (0x90, 0x00):
            raise(Exception("Wrong PIN:" + pin))

    return size, connection


if __name__ == "__main__":
    usim(0)

usim_read.py is:

#!/usr/bin/env python3

from smartcard.util import toBytes, toASCIIString
import usim


def decode_record(record):
    """
    decode_record(toBytes("43 75 73 74 6F 6D 65 72 20 43 61 72 65 FF 06 A1 80 00 07 70 00 FF FF FF FF FF FF FF"))
    >> ['Customer Care', '0800700700']
    """
    X = len(record) - 14
    name = toASCIIString(record[0:X - 1]).replace("ΓΏ", "")
    # number of bytes for the phone number
    tel_size = record[X]
    phone = record[X + 2:X + tel_size + 1]

    decoded = ""
    for n in phone:
        hex = "%02X" % n
        high = hex[0]
        low = hex[1]
        decoded += low + high
    # if the number of digits is odd we suppress the padding
    if decoded[-1] == "F":
        decoded = decoded[:-1]
    phone = decoded

    return name, phone


def usim_read(reader_nb):
    # Select the EF ADN
    (size, connection) = usim.usim(reader_nb)

    for nbr in range(1, 250):
        #  Read record
        header = [0xA0, 0xB2]
        record_idx = nbr
        cmd = header + [record_idx, 0x04, size]
        data, sw1, sw2 = connection.transmit(cmd)
        if (sw1, sw2) != (0x90, 0x00):
            return

        name, phone = decode_record(data)
        if name != "":
            print(f"{record_idx}: Name: {name}, phone: {phone}")


if __name__ == "__main__":
    import sys
    if 2 == len(sys.argv):
        reader_nb = int(sys.argv[1])
    else:
        reader_nb = 0
    usim_read(reader_nb)


Comments

The debug is enabled. So you can see the communication between the application and the card.
You ned to change only one line to remove the APDU log if needed. 

The phone book record size is not fixed for all the SIM cards. The record size is returned in the last byte of the GET RESPONSE command after the SELECT EF (Elementary File) ADN (Abbreviated dialling numbers).

You can get more details about the EF ADN and the record coding in the document ETSI TS 131 102 V16.6.0 (2021-01) chapter "4.4.2.3 EF_ADN (Abbreviated dialling numbers)". It is important to note that ETSI (European Telecommunications Standards Institute) standards are public and free.

By default the first PC/SC reader is used. But you can select another reader by passing a number as argument to the program. We will use this feature later.

No PIN is defined and verified. I am using a sysmocom sysmoUSIM-SJS1 card. This card has the user PIN disabled by default.
You can enable PIN verification by defining a PIN in usim.py. The code is already present.

USIM (Universal Subscriber Identity Module) is a new application introduced for GSM version 3 (3G) to replace/improve the SIM (subscriber identity/identification) application. The code should work the same with a SIM card or a USIM card (but untested).

Output

$ ./usim_read.py 
Available readers:
- Gemalto PC Twin Reader
Using: Gemalto PC Twin Reader
connecting to Gemalto PC Twin Reader
Select MF
> A0 A4 00 00 02 3F 00
<  [] 9F 22
Select DF Telecom
> A0 A4 00 00 02 7F 10
<  [] 9F 22
Select EF ADN
> A0 A4 00 00 02 6F 3A
<  [] 9F 0F
Get Response
> A0 C0 00 00 0F
< 00 00 21 34 6F 3A 04 00 11 FF 22 01 02 01 22 90 00
> A0 B2 01 04 22
< 4C 61 75 72 65 20 46 72 61 6E E7 6F 69 73 65 20 59 76 6F 6E 06 A1 24 66 85 10 22 FF FF FF FF FF FF FF 90 00
1: Name: Laure Francoise Yvo, phone: 4266580122
> A0 B2 02 04 22
< 4C 75 63 69 65 6E 6E 65 20 48 65 6C 65 6E 65 20 4C 75 63 69 06 A1 76 45 65 28 77 FF FF FF FF FF FF FF 90 00
2: Name: Lucienne Helene Luc, phone: 6754568277
> A0 B2 03 04 22
< 55 72 62 61 69 6E 20 47 69 6C 6C 65 73 20 4A 75 73 74 65 FF 06 A1 80 38 74 16 54 FF FF FF FF FF FF FF 90 00
3: Name: Urbain Gilles Juste, phone: 0883476145
> A0 B2 04 04 22
< 4A 65 72 6F 6D 65 20 50 61 73 63 61 6C 20 46 65 72 6E 61 6E 06 A1 80 53 42 10 86 FF FF FF FF FF FF FF 90 00
4: Name: Jerome Pascal Ferna, phone: 0835240168
[...]

The output is truncated. I do not want to include all the 255 phone numbers. 

Note that the names and numbers are random. More on that later.


Conclusion

It is easy to dump the phone book from a SIM card.

The SIM phone book is very limited (no birthday, no email address). The real phone book is, in general, in the phone itself and synchronised using CardDav with a server like Nextcloud.

Wednesday, February 17, 2021

GitHub Sponsors: first year

In January 2020 I engaged in the GitHub Sponsors project. I got my first payment in May 2020. After a full year I think it is time for a public status. 

 

Results

My gains give a total of 296,82€ for 12 months. Of course I will have to pay some French taxes (employee and employer social security contributions URSSAF) of around 25% and also some income taxes.

Description
Revenue 296,82 €
Social Contributions
-74,20 €
Remaining
225,62 €

That is 18,55€/month.

 

Bills

That is not much (I can't live with that yet) but enough to pay some of the infrastructure used by my smart card projects: a Virtual Private Server to host the web sites and the domain name for https://muscle.apdu.fr/, https://pcsclite.apdu.fr/ and https://ccid.apdu.fr/.

Description
Virtual Private Server 100,66 €
Domain name .apdu.fr
8,39 €
Total
109,05 €

That is 9,09€/month.

 

Conclusion

Thank you to the generous sponsors.

Tuesday, February 16, 2021

New version of pcsc-lite: 1.9.1

I just released a new version of pcsc-lite 1.9.0.
pcsc-lite is a Free Software implementation of the PC/SC (or WinSCard) API for Unix systems.

 Changes:

1.9.1: Ludovic Rousseau
16 February 2021

  • Do not (possibly) lock a reader if allocating hCard fails
  • Fix a hang in SCardTransmit()
  • Do not report an error if the wrong interface is used by the driver
  • Update reader state when a card is removed during an exchange
  • readerfactory: Make sure a freed Reader Context is not accessed
  • PHSetProtocol(): supports T=0&1 cards on T=0 reader
  • hotplug-libusb:
    • support CCIDCLASSDRIVER
    • add interface name to reader name
    • remove obsolete libhal scheme
  • Some other minor improvements