This post returns to the problem of capturing traffic between a Windows and a smart card and looks at the nuts-and-bolts of logging such communication. A simplifying assumption is that all applications go through PC/SC API to communicate with the card. This is partially enforced by the smart card driver architecture in Windows, as each driver is given a handle to an existing card connection when invoked. But there also exist hardware such as USB tokens, which do not advertise themselves as CCID devices, and therefore are not considered “smart cards” according to the OS. Typically these devices are accessed via proprietary vendor code using a different USB class such human-interface device (HID), by passing the OS stack. The approach described here will not capture such communication.
MSDN documentation on PC/SC API indicates that the functionality is implemented in the winscard.dll binary. Looking at the list of function, a few entry points stand out: connect to the card, transceive (send a command APDU and receive a response APDU in return) and close the connection. For ad hoc experiments, setting breakpoints on these functions and dumping the memory regions corresponding to the input/output buffers would suffice. A sketch of the process if one were walking through it manually:
- Set breakpoint on SCardTransmit.
- When breakpoint is hit, dump the request APDU passed in by caller. This source buffer and its byte count are among the parameters passed into the function, and can be referenced from ESP/RSP offsets on entry.
- Run the function until its return or equivalently set another breakpoint on the final exit point. Typically code compiled by MSFT Visual C++ code has exactly one function prologue and return instruction, regardless of how many return paths exist.
- Dump the response APDU. This destination buffer and its count are also parameters passed into the function. The reason for waiting until function exit is that on entry the buffer is uninitialized. It is only when transceive operation completes that the response APDU has been copied here.
While cdb and windbg can be scripted to automate these steps (for example, by associating actions taken when breakpoints are hit) a more systematic approach is useful to deal with more complex cases. First it may not be known in advance when/which process will invoke the smart card stack. For example the smart card discovery process runs as a service inside svchost.exe. Second case is when attaching a debugger to a process is non trivial– the case of debugging Windows logon process from user mode for instance.
A wrapper DLL can help in these cases. The idea is creating a new DLL exporting all of the same entry points as the one it is trying to mimic. When located in the load path, the new binary can transparently substitute for the original, while the original will be still available to allow calling the unmodified implementation. In the case of winscard, the majority of APIs in the new DLL can be pass-through: they directly call the corresponding API in the original with same parameters, without any processing. (MSFT Visual C++ compiler makes this easy, with a one-line pragma statement to forward a function to another DLL, without having to write manual stubs.) Only for a few select APIs more processing is required. For example the transceive function will emulate the debugging steps described above: log the command APDU, invoke the “real” transceive function in the renamed DLL, then log the response APDU from the card before finally returning control to the caller.
A proof of concept (binaries for x86 and x64) logs APDUs into a hard-coded directory. It is important for this directory to be ACLed as world-writable as some features such as discovery access the smart card run from service accounts. As shown below, each file is identified by process name and crude timestamp, created on demand when the first transceive happens. In this case there were four processes observed: certutil, logonUI, lsass and svchost.
Each entry in the log is a time-stamped sequence of pairs, command APDU followed by corresponding response APDU. For example, selection of PIV applet during logon produces this snippet:
Request APDU [20:30:01.169] -- 14 bytes: 00 a4 04 00 09 a0 00 00 03 08 00 00 10 00 Response APDU [20:30:01.188] -- 30 bytes: 4f 0a a0 00 00 03 08 00 00 10 00 01 00 79 0c 4f 0a a0 00 00 03 08 00 00 10 00 01 00 90 00
(For completeness, the log could also include messages when a connection is closed, to demarcate the boundary between multiple card sessions from the same process.)
A word of caution: Smart card traffic contains sensitive information such as user PIN going to the card or decrypted sessions keys returned by the card. As such logging should only be used for research purposes, and not enabled on production systems.
For comparison, this is very similar to APDU View, which also uses a wrapper DLL. Main difference is that APDUview requires wrapper DLL to be copied the same directory as the application being targeted, as a private copy. The proof of concept sketched here replaces the global copy instead, and relies on locating the original under a different name. Part of the challenge with private copies is that many of the users of smart card stack are core OS components as opposed to third-party applications. These all live in the same system directory so it is not possible to use load paths to differentiate. The wrapper DLL can implement additional controls around when the extra logging functionality is invoked, based on name of the containing process.