How to send commands to smart card reader (and not to the smart card) while no card present?

13,851

When you stop "Smart Card" service, does the tool still return a firmware version? If yes, then the tool might use raw IOCTL commands (DeviceIoControl) to communicate with the driver.


Also take a look at this question. The author says you have to set SCARD_PROTOCOL_UNDEFINED as protocol parameter:

SCardConnect(hSC,
             readerState.szReader,
             SCARD_SHARE_DIRECT,
             SCARD_PROTOCOL_UNDEFINED,
             &hCH,
             &dwAP
            );

I just tried it and it seems to work for Windows 10 at least. Communication was possible with no card inserted. I did not test for other Windows versions though.

Share:
13,851

Related videos on Youtube

Ebrahim Ghasemi
Author by

Ebrahim Ghasemi

Passionate Java Card programmer with +6 years of experience in different security related topics, including cryptography, web application and network penetration testing and also reverse engineering. Having strong background in network traffic analysis, deep packet inspection,networking protocols and high-performance system programming.

Updated on June 04, 2022

Comments

  • Ebrahim Ghasemi
    Ebrahim Ghasemi about 2 years

    Preface:

    I have a dual interface smart card reader that has some extended capabilities (other than sending APDU commands to card and receiving APDU responses).

    For example in its document it is mentioned that you can get firmware version of your reader using following command:

    GET_FIRMWARE_VERSION: FF 69 44 42 05 68 92 00 05 00

    In its tool, there is a button for this function and it works fine:

    enter image description here

    I even sniffed USB port to see what exactly exchanged in the connection between my PC and my reader for this function:

    Command: enter image description here

    Response: enter image description here

    Problem:

    I want to get my reader version (and maybe send other extended commands) using other tools or via code, but I must insert a card in the card reader to be able sending commands, otherwise I receive No Card Present exception, while I don't want to send commands to card! (The reader tool answers successfully to GET_FIRMWARE_VERSION without any card available in the reader's slots)

    What I did so far:

    1.I tried some tools, including OpenSCTool , PyAPDUTool, and another reader's tool. 2.I wrote following python script to send extended commands.

    #--- Importing required modules.
    import sys
    import time
    sys.path.append("D:\\PythonX\\Lib\\site-packages")
    from smartcard.scard import *
    import smartcard.util
    from smartcard.System import readers
    
    
    #---This is the list of commands that we want to send device
    cmds =[[,0xFF,0x69,0x44,0x42,0x05,0x68,0x92,0x00,0x04,0x00],]
    
    
    #--- Let's to make a connection to the card reader
    r=readers()
    print "Available Readers :",r
    print
    target_reader = input("--- Select Reader (0, 1 , ...): ")
    print
    
    while(True):
        try:
            print "Using :",r[target_reader]
            reader = r[target_reader]
            connection=reader.createConnection()
            connection.connect()
            break
        except:
            print "--- Exception occured! (Wrong reader or No card present)"
            ans = raw_input("--- Try again? (0:Exit/1:Again/2:Change Reader)")
            if int(ans)==0:
                exit()
            elif int(ans)==2:
                target_reader = input("Select Reader (0, 1 , ...): ")
    
    #--- An struct for APDU responses consist of Data, SW1 and SW2
    class stru:
        def __init__(self):
            self.data = list()
            self.sw1 = 0
            self.sw2 = 0
    
    resp = stru()
    
    def send(cmds):
        for cmd in cmds:
    
            #--- Following 5 line added to have a good format of command in the output.
            temp = stru() ;
            temp.data[:]=cmd[:]
            temp.sw1=12
            temp.sw2=32
            modifyFormat(temp)
            print "req: ", temp.data
    
            resp.data,resp.sw1,resp.sw2 = connection.transmit(cmd)
            modifyFormat(resp)
            printResponse(resp)
    
    def modifyFormat(resp):
        resp.sw1=hex(resp.sw1)
        resp.sw2=hex(resp.sw2)   
        if (len(resp.sw2)<4):
            resp.sw2=resp.sw2[0:2]+'0'+resp.sw2[2]
        for i in range(0,len(resp.data)):
            resp.data[i]=hex(resp.data[i])
            if (len(resp.data[i])<4):
                resp.data[i]=resp.data[i][0:2]+'0'+resp.data[i][2]
    
    def printResponse(resp):
        print "res: ", resp.data,resp.sw1,resp.sw2
    
    
    send(cmds)
    connection.disconnect()
    

    Output:

    >>> ================================ RESTART ================================
    Available Readers : ['CREATOR CRT-603 (CZ1) CCR RF 0', 'CREATOR CRT-603 (CZ1) CCR SAM 0']
    
    --- Select Reader (0, 1 , ...): 0
    
    Using : CREATOR CRT-603 (CZ1) CCR RF 0
    --- Exception occured! (Wrong reader or No card present)
    --- Try again? (0:Exit/1:Again/2:Change Reader)
    
    >>> ================================ RESTART ================================
    Available Readers : ['CREATOR CRT-603 (CZ1) CCR RF 0', 'CREATOR CRT-603 (CZ1) CCR SAM 0']
    
    --- Select Reader (0, 1 , ...): 1
    
    Using : CREATOR CRT-603 (CZ1) CCR SAM 0
    --- Exception occured! (Wrong reader or No card present)
    --- Try again? (0:Exit/1:Again/2:Change Reader)
    

    But both have the mentioned problem!

    Questions:

    1- How to send extended commands to reader while there is no card available?

    2- Why I can't see command header in the sniffed data? (Note that, as Header is a pre-designated fixed value for all extended commands, I think the reader tool doesn't send the header with GET_FIRMWARE_VERSION command and it send only the data! but how does it works?)


    Update:

    Using trial and error I found something useful.

    Assumptions:

    • Pseudo-APDUs fixed header = FF 69 44 42
    • Pseudo-APDU data field for GET_READER_FIRMWARE_VERSION = 68 92 00 04 00
    • Pseudo-APDU data field for CHANGE_SAM_SLOT = 68 92 01 00 03 XX 00 00 (My reader has two SAM slots, so XX can be 01 or 02)
    • SELECT APDU command = 00 A4 04 00 00

    Ok, I wrote the following Java Program:

    import java.util.List;
    import java.util.Scanner;
    import javax.smartcardio.Card;
    import javax.smartcardio.CardChannel;
    import javax.smartcardio.CardException;
    import javax.smartcardio.CardTerminal;
    import javax.smartcardio.CommandAPDU;
    import javax.smartcardio.ResponseAPDU;
    import javax.smartcardio.TerminalFactory;
    import javax.xml.bind.DatatypeConverter;
    
    public class TestPCSC {
    
        public static void main(String[] args) throws CardException {
    
            TerminalFactory tf = TerminalFactory.getDefault();
            List< CardTerminal> terminals = tf.terminals().list();
            System.out.println("Available Readers:");
            System.out.println(terminals + "\n");
    
            Scanner scanner = new Scanner(System.in);
            System.out.print("Which reader do you want to send your commands to? (0 or 1 or ...): ");
            String input = scanner.nextLine();
            int readerNum = Integer.parseInt(input);
            CardTerminal cardTerminal = (CardTerminal) terminals.get(readerNum);
            Card connection = cardTerminal.connect("DIRECT");
            CardChannel cardChannel = connection.getBasicChannel();
    
            System.out.println("Write your commands in Hex form, without '0x' or Space charaters.");
            System.out.println("\n---------------------------------------------------");
            System.out.println("Pseudo-APDU Mode:");
            System.out.println("---------------------------------------------------");
            while (true) {
                System.out.println("Pseudo-APDU command: (Enter 0 to send APDU command)");
                String cmd = scanner.nextLine();
                if (cmd.equals("0")) {
                    break;
                }
                System.out.println("Command  : " + cmd);
                byte[] cmdArray = hexStringToByteArray(cmd);
                byte[] resp = connection.transmitControlCommand(CONTROL_CODE(), cmdArray);
                String hex = DatatypeConverter.printHexBinary(resp);
                System.out.println("Response : " + hex + "\n");
            }
    
            System.out.println("\n---------------------------------------------------");
            System.out.println("APDU Mode:");
            System.out.println("---------------------------------------------------");
    
            while (true) {
                System.out.println("APDU command: (Enter 0 to exit)");
                String cmd = scanner.nextLine();
                if (cmd.equals("0")) {
                    break;
                }
                System.out.println("Command  : " + cmd);
                byte[] cmdArray = hexStringToByteArray(cmd);
                ResponseAPDU resp = cardChannel.transmit(new CommandAPDU(cmdArray));
                byte[] respB = resp.getBytes();
                String hex = DatatypeConverter.printHexBinary(respB);
                System.out.println("Response : " + hex + "\n");
            }
    
            connection.disconnect(true);
    
        }
    
        public static int CONTROL_CODE() {
            String osName = System.getProperty("os.name").toLowerCase();
            if (osName.indexOf("windows") > -1) {
                /* Value used by both MS' CCID driver and SpringCard's CCID driver */
                return (0x31 << 16 | 3500 << 2);
            } else {
                /* Value used by PCSC-Lite */
                return 0x42000000 + 1;
            }
    
        }
    
        public static byte[] hexStringToByteArray(String s) {
            int len = s.length();
            byte[] data = new byte[len / 2];
            for (int i = 0; i < len; i += 2) {
                data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                        + Character.digit(s.charAt(i + 1), 16));
            }
            return data;
        }
    
    }
    

    In the above program, I can send commands to my reader using both connection.transmitControlCommand and cardChannel.transmit() methods. The point is that, all the commands that send to the reader using first method, are assumed as Pseudo-APDU command and I should not use the Psedo-APDU header for them! And all the commands that send to the reader using second method, are assumed as regular APDU commands, so if I need to send Pseudo-APDU commands via second method, I must add the Pseudo-APDU header to it.

    Let see output for the contact-less reader:

    run:
    Available Readers:
    [PC/SC terminal ACS ACR122 0, 
    PC/SC terminal CREATOR CRT-603 (CZ1) CCR RF 0,
    PC/SC terminal CREATOR CRT-603 (CZ1) CCR SAM 0]
    
    Which reader do you want to send your commands to? (0 or 1 or ...): 1
    Write your commands in Hex form, without '0x' or Space charaters.
    
    ---------------------------------------------------
    Pseudo-APDU Mode:
    ---------------------------------------------------
    Pseudo-APDU command: (Enter 0 to send APDU command)
    00A4040000
    Command  : 00A4040000
    Response : 6800
    //Based on reader's documents, 0x6800 means "Class byte is not correct"
    //As I have a regular java card in the RF field of my  reader, I conclude that 
    //this response is Reader's response (and not card response)
    
    Pseudo-APDU command: (Enter 0 to send APDU command)
    6892000400
    Command  : 6892000400
    Response : 433630335F435A375F425F31353038323100039000
    
    Pseudo-APDU command: (Enter 0 to send APDU command)
    FF694442056892000400
    Command  : FF694442056892000400
    Response : 6800
    //Pseudo-APDU commands doesn't work in Pseudo-APDU mode if I add the Pseudo-APDU header to them. 
    
    Pseudo-APDU command: (Enter 0 to send APDU command)
    00A4040000
    Command  : 00A4040000
    Response : 6800
    
    Pseudo-APDU command: (Enter 0 to send APDU command)
    0
    
    ---------------------------------------------------
    APDU Mode:
    ---------------------------------------------------
    APDU command: (Enter 0 to exit)
    00A4040000
    Command  : 00A4040000
    Response : 6F198408A000000018434D00A50D9F6E061291921101009F6501FF9000
    
    APDU command: (Enter 0 to exit)
    6892000400
    Command  : 6892000400
    Response : 6E00
    //This is the response of my card. I can't receive Firmware version in APDU mode using this command without Pseudo-APDU header. 
    
    APDU command: (Enter 0 to exit)
    FF694442056892000400
    Command  : FF694442056892000400
    Response : 433630335F435A375F425F31353038323100099000
    //I successfully received Firmware version in APDU mode using the fixed Pseudo-APDU header.
    
    APDU command: (Enter 0 to exit)
    00A4040000
    Command  : 00A4040000
    Response : 6F198408A000000018434D00A50D9F6E061291921101009F6501FF9000
    
    APDU command: (Enter 0 to exit)
    0
    BUILD SUCCESSFUL (total time: 1 minute 36 seconds)
    

    Is there any problem still?

    Yes, two problems!:

    1-the above program works fine only for its first run. I mean, if I stop running and rerun it, the second method throw an exception:

    run:
    Available Readers:
    [PC/SC terminal ACS ACR122 0, PC/SC terminal CREATOR CRT-603 (CZ1) CCR RF 0, PC/SC terminal CREATOR CRT-603 (CZ1) CCR SAM 0]
    
    Which reader do you want to send your commands to? (0 or 1 or ...): 1
    Write your commands in Hex form, without '0x' or Space charaters.
    
    ---------------------------------------------------
    Pseudo-APDU Mode:
    ---------------------------------------------------
    
    Pseudo-APDU command: (Enter 0 to send APDU command)
    00A4040000
    Command  : 00A4040000
    Response : 6800
    
    Pseudo-APDU command: (Enter 0 to send APDU command)
    FF694442056892000400
    Command  : FF694442056892000400
    Response : 6800
    
    Pseudo-APDU command: (Enter 0 to send APDU command)
    6892000400
    Command  : 6892000400
    Response : 433630335F435A375F425F31353038323100049000
    
    Pseudo-APDU command: (Enter 0 to send APDU command)
    00A4040000
    Command  : 00A4040000
    Response : 6800
    
    Pseudo-APDU command: (Enter 0 to send APDU command)
    0
    
    ---------------------------------------------------
    APDU Mode:
    ---------------------------------------------------
    APDU command: (Enter 0 to exit)
    00A4040000
    Command  : 00A4040000
    Exception in thread "main" javax.smartcardio.CardException: sun.security.smartcardio.PCSCException: Unknown error 0x16
        at sun.security.smartcardio.ChannelImpl.doTransmit(ChannelImpl.java:219)
        at sun.security.smartcardio.ChannelImpl.transmit(ChannelImpl.java:90)
        at TestPCSC.main(TestPCSC.java:58)
    Caused by: sun.security.smartcardio.PCSCException: Unknown error 0x16
        at sun.security.smartcardio.PCSC.SCardTransmit(Native Method)
        at sun.security.smartcardio.ChannelImpl.doTransmit(ChannelImpl.java:188)
        ... 2 more
    Java Result: 1
    BUILD SUCCESSFUL (total time: 39 seconds)
    

    As you see above, I can't use second method anymore and I need to power-off the reader and power on it again to make it work fine again.

    2-The contact interface (I mean the SAM reader) throw previous exception always! I mean the second method doesn`t work at all (neither first run, nor second and third .... )

    Note that I tried different readers, it seems that this is not limited only ti this reader. Some ACS readers also have a similar or exactly the same problem with rerun

    Does anyone have any idea?

    And as a side question, does Python have any equal methods for sending Pseudo-APDU like Java?

    And finally, from the view of Reader, what's the difference between connection.transmitControlCommand and cardChannel.transmit() methods?

    • Ebrahim Ghasemi
      Ebrahim Ghasemi
      @MaartenBodewes Thank you dear Mr Bodewes. I updated the question. About the reader specific library, I think it is not the point in my case. All the tools that my reader has is a portable executable file (without any .sys file or .dll library). Any way I used tasklist \M command in windows command line and it return me ` ntdll.dll, wow64.dll, wow64win.dll, wow64cpu.dll` for this executable file process.
    • Maarten Bodewes
      Maarten Bodewes
      You should be able to check with a dependency viewer which libraries are used by the different tools.
  • Ebrahim Ghasemi
    Ebrahim Ghasemi over 7 years
    Dear Amin, Unfortunately I have no more access to the reader to check it. :)
  • arminb
    arminb over 7 years
    Oh, what a pity.
  • arminb
    arminb about 7 years
    @Abraham I updated my answer. I was able to successfully communicate to the reader without a card inserted by passing SCARD_PROTOCOL_UNDEFINED as protocol parameter.
  • Ebrahim Ghasemi
    Ebrahim Ghasemi about 7 years
    Great! Thank you for updating the answer and sharing your experience dear Armin.