Smart card container name
Pavlo Myroniuk April 22, 2024 #debugging #windows #scardBefore I start
Goals
- Explain how the Windows minidriver treats the smart card container name.
- Tell how a bad container name can break scard auth.
- Fun ðĨģ.
Non-goals
- Explain every piece of smart card architecture in Windows.
- Explain every smart card minidriver function.
Getting Started
In my previous article, I explained the different smart card cache items (files), their format, and purpose. The current one is some continuation of talking about PIV smart cards in Windows. The setup is the same: Windows smart card minidriver + PIV smartcard. But in this case, we'll use a fully emulated smart card ðĨ.
Note. The current article is full of technical things related to debugging (WinDbg) and reversing (IDA). Depending on your purpose, choose one of the following paths:
- If you want to reproduce each step with me, then perform all actions from the Debugging environment section and continue reading the article.
- If you don't want to set up the whole environment but want to reproduce each step on your machine, then download my TTD recoding and log files. But I still recommend reading the Debugging environment section (at least to know how I came up with it).
- If you only want to read my findings and smart card container name structure, then jump to the What? section.
else: read it fully.
Happy reading ð. Enjoy ð.
Debugging environment
My goal is to simulate the data signing using an emulated smart card. To achieve this I need two key things:
- Some program signs the data using the smart card.
- Hook somehow the
winscard.dllto use an emulated smart card.
A brief explanation of our debugging environment:
- Test VM + certificate with private key suitable for the data signing.
- We'll have a small program written in C#. The only thing this program does is sign the hardcoded data using the smart card.
- We'll have a
bad.dllthat hooks the originalwinscard.dllwith our one. - And the last but not least thing is a "launcher": another program that creates a signing process, then loads the
bad.dllinto it to hook thewinscard.dll, and then continues the execution.
Pheww, it starts looking like some kind of Frankenstein ðĪŠ
Yes. You are right ð.
Compile the data signer
The code is very simple. It uses some high level C# API for the data signing. The full code can be found here. Just clone the project and build it with Visual Studio. Note: it required net6.0.
The overall algorithm is pretty simple: we create a CSP that matches our smart card and then sign the data using the RSA crypto provider. The simplified code looks like this:
// Create needed CSP.
CspParameters csp = ;
csp.KeyPassword = pwd;
// Sign the data.
RSACryptoServiceProvider rsaCsp = ;
byte signature = rsaCsp.;
As a result, you should get SignDataTmp.exe + SignDataTmp.dll.
Compile your own version of winscard.dll
Of course, during my first debugging, I had a dev version of the sspi-rs. But now all developed functionality has been merged, so we need to pick a specific commit. After that you will have sspi.dll in the target/debug directory. You can rename it to winscard.dll if you want (but it's not required).
Compile bad.dll
This bad.dll represents how I do the hooking. There are many hooking techniques. My choice criteria were that it should work and not be hard to implement. So, I chose the MinHook library. It is easy to use and does its job well. Project source code can be found here. Just clone the project and build it with Visual Studio. Note: do not forget to properly link it with MinHook.
But here is a surprise. When you try to read the code you'll find out that I hook some weird functions instead of the winscard.dll. The reason for that is delayed loading. We can not simply hook the winscard.dll because the basecsp.dll loads it via delayed loading ð.
But it is still possible to hook. Thanks to @awakecoding I know how to do it. He had researched and implemented it in the MsRdpEx: fix winscard.dll delay-loading interception from basecsp.dll. I reused some code and make it work for my debugging program ð .
So, as a result, you should get bad.dll. Do not forget to set your own paths in the code!
Compile the launcher
The "launcher" is named as "HookLibrary" due to my own historical reasons ðĪŠ. The full source code can be found here. Important: do not forget to change hardcoded paths to your ones. It works as follows:
- Runs the data signer executable in a separate process, but does not run it.
- Injects the
bad.dllinto the process memory forcing it to load this dll. In turn, thebad.dllwill hook all needed methods. - Continues the execution.
So, as a result, you should get HookLoadLibrary.exe.
Run it
Before trying to run this machinery we need to configure the emulated smart card. To do this set up the following environment variables:
| Name | Value | Example |
|---|---|---|
SSPI_LOG_PATH | Path to the log file | D:\test_data\sspi.log |
SSPI_LOG_LEVEL | Log level | trace |
WINSCARD_PIN | Smart card PIN code | 214653 |
WINSCARD_CERT_PATH | Path to the .cer file containing the smart card certificate | D:\test_data\user.cer |
WINSCARD_PK_PATH | Path to the .key file containing the smart card certificate | D:\test_data\user.key |
WINSCARD_CONTAINER | Container name | 1b22c362-46ba-4889-ad5c-01f7abcabcabedw |
WINSCARD_READER | Reader name | Microsoft Virtual Smart Card 2 |
And finally, run HookLoadLibrary.exe.
Debugging
The first run
Let's try to sign the data using the emulated smart card.

Expectedly (otherwise, this article would not exist ð), we got an exception. Our goal for the rest of the article is to figure out the cause of the error and fix it.
Error location
At this point, I don't know what caused the error. So, let's use WinDbg and make a TTD recording. Finding a function that returns an error in this way will be easier. With TTD recording I can walk through the execution without rerunning the app.
Note. Because the HookLoadLibrary.exe runs the SignDataTpm.exe in a separate process, you should attach WinDbg to this process instead of running the HookLoadLibrary.exe in the WinDbg.

Good. Now we can start debugging.

At this point, I can assume only something: some function in msclmd.dll or basecsp.dll fails and breaks the signing. Let's see the last sspi-rs FII calls in the logs to have at least some orientation on where to dig. If we catch a moment of the last winscard calls, we'll retrieve a call stack and then analyze those functions. These are the last meaningful records in the log file:

We can see the SCardTransmit function call. I'll search for them in the WinDbg. But because I use the custom winscard.dll, all WinSCard-related functions have a Rust_ prefix. Example:

I have omitted some manipulations so that this article is not too boring. In short, on the screenshot below we can see the last SCardTransmit call and part of the call stack:

As I expected, we see the msclmd.dll and basecsp.dll functions. The next algorithm is the following: I take the highest function from the call stack and check the resulting status code in the WinDbg. If it succeeds, I take a lower function from the call stack and check its status code. When I finally find a failing function, I'll use WinDbg and IDA to see the exact failing point. I expect something like return 0x80100004; or any other status code.
Hmmm ðĪ. All functions up to the C#-related ones were succeeded. Perhaps the last calls to SCardTransmit occurred after a signing error and Windows was trying to finalize the error/gather some information OR the signing process is not even started yet. I need another function to start with. Let's try SCardReadCacheW. I had a "great" experience with smart card caches in the past, so maybe it'll help me this time. Thanks to TTD I don't need to rerun it and can walk through the recording again and again.
After some time I finally found something that looks like data signing ð.

Let's see the status codes of the highlighted functions:

Cool! Now we know what the msclmd!I_PIVCardSignData function returns the SCARD_E_INVALID_PARAMETER status code.
I need to find where in this function body the error has been thrown. Walking through every ASM instruction is boring and time-consuming, so I took an IDA and reversed this function as much as I could. After that, I marked some places (other function calls, ifs, and so on) that could fail and checked them with WinDbg.

Okaaay, so, the key algorithm and id extraction have failed ðĪ. Reversed pseudo-code:
return_code = ;
I dug into it and found exact place where the fail happens. I skipped this part for you and show you the result:

Here is the reversed part of the ASM code above. We can see that invalid cert file tag causes the SCARD_E_INVALID_PARAMETER error:

On the screenshot above the part of the msclmd!I_GetPIVKeyIDFromPIVCertFileTag function is shown. The msclmd!I_IsValidPIVCertFileTag function has returned 0 (false). It means that the cert file tag is not valid.

So, the cert_file_tag must meet highlighted conditions to be valid. In our case it's equal to 0xaaaaaa which is obviously not valid. To fix it we need to figure out how the cert_file_tag is constructed and how we can change it to met one of the needed values. I did it for you, so take a look at the following screenshot:

The screenshot above contains a part of the msclmd!I_GetKeyAndAlgFromMapIndex function. This piece of code gives us answers to all our questions. First of all, the cert_file_tag value is extracted from the cmap_record using the swscanf_s function. In simple words, it takes the cmap_record buffer pointer, skips the first 30 characters (bytes), scans the following 6 characters (bytes), converts them from hex to decimal, and, finally, assigns it to the cert_file_tag variable. This is our cert_file_tag which is not valid. In turn, the cmap_record is a pointer to the CONTAINER_MAP_RECORD structure where the first field is a container name:
typedef struct _CONTAINER_MAP_RECORD
CONTAINER_MAP_RECORD, *PCONTAINER_MAP_RECORD;
According to the Microsoft's specification:
The
wszGuidmember consists of a UNICODE character string representation of an identifier that CAPI assigned to the container. This is usually, but not always, a GUID string. Identifier names cannot contain the special character â\â. When read-only cards are provisioned, the provisioning process must follow the same guidelines for identifier names.
What?
ðŪâðĻ Let's summarize it all.
The CONTAINER_MAP_RECORD structure contains a smart card container name. The container map record value is extracted from the smart card cache. This container name (wszGuid) is not a random GUID value. This is a special value that must contain one of the allowed certificate file tags. The smart card driver (msclmd.dll) decides how to sign the data based on this cer_file_tag. All possible certificate file tags are defined and can be found in the PIV smart card specification and open-source projects that follow this spec.

Soooo, if we just change the container name, then data signing should work? ðð
It seems like yes, it should. We have nothing to lose. Let's try it.
The fix
Change the WINSCARD_CONTAINER environment variable.
| Name | Value | Example |
|---|---|---|
WINSCARD_CONTAINER | Container name | 1b22c362-46ba-4889-ad5c-01f7aa5fc10awww |
ðĪðĪðĪ

ð It works! ð Ignore the error at the bottom of the terminal. The only important thing is the resulting signature. It means, that the data signing using the emulated smart card works well ð. Even in the sspi-rs logs we can see SCardTransmit calls with input padded digest and output signature:

ððð I don't know why this container name format is not documented anywhere (at least I didn't find any mention about it on the Internet).
Conclusion
There are a lot of ways of debugging programs. In this article, I told you about one of my cases where I need to be creative and patient. In the end, I could tell that Windows is bad and so on, but I'll say the following instead:
Everything is possible. Any bug and problem is solvable. Be creative and have patience.
Wish you more interesting bugs and fun debugging! ð