The crypto provider being shipped with the SafeNet client is accessing the USB-Token using the SmardCardAPI (winscard.dll). Because SmartCards are also used for authentication/login purposes, the RDP stack will always redirect any access to the RDP client computer.
https://learn.microsoft.com/en-us/windows/security/identity-protection/smart-cards/smart-card-and-remote-desktop-services
For scenarios like code signing this behavior can be quite cumbersome. We are using a dedicated virtual machine for signing purposes and each developer having access to this machine should be able to perform the signing process. With COVID-19 and all developers working at home the idea to use the local USB port is not feasible. Our USB-Token is attached to the dedicated machine. Each time you connect via RDP to this machine the dongle is no longer accessible, because the SmartCardAPI will redirect the access.
There is a solution to this problem however:
The SmartCard stack uses the API-Calls
ProcessIdToSessionId
WinStationGetCurrentSessionCapabilities
to determine if the current process is running in a RDP session. By injecting a DLL into the signtool and using the Detours framework you can hook these API calls and report a local session, so the SmardCardAPI will access the dongle connected to the remote virtual machine.
https://github.com/microsoft/Detours
The detoured functions are quite simple (code reduced to important stuff, so you can get the idea)
DWORD WINAPI ProcessIdToSessionIdLocal(DWORD dwProcessId, DWORD *pSessionId)
{
OutputDebugString("Detoured ProcessIdToSessionId\r\n");
if (pSessionId)
pSessionId = 0;
return TRUE;
}
BOOL WINAPI WinStationGetCurrentSessionCapabilitiesLocal(DWORD flags, DWORD *pOutBuffer)
{
BOOL bResult;
OutputDebugString("Detoured WinStationGetCurrentSessionCapabilities\r\n");
bResult = TrueGetCurStationCapabilities(flags,pOutBuffer);
if (bResult)
*pOutBuffer = 0;
return bResult;
}
BOOL WINAPI DllMain (haDLL, dwReason, lpReserved)
HANDLE haDLL;
DWORD dwReason;
LPVOID lpReserved;
{
LONG error;
TCHAR cBuffer[160];
wsprintf(cBuffer,"DllMain Entry %08x\r\n",dwReason);
OutputDebugString(cBuffer);
if (DetourIsHelperProcess()) {
return TRUE;
}
if (dwReason == DLL_PROCESS_ATTACH) {
OutputDebugString("Starting Detour API Calls \r\n");
hStaDLL = LoadLibrary("WINSTA.DLL");
TrueGetCurStationCapabilities = GetProcAddress(hStaDLL,"WinStationGetCurrentSessionCapabilities");
DetourRestoreAfterWith();
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach((PVOID*)&TrueProcessIdToSessionId, ProcessIdToSessionIdLocal);
DetourAttach((PVOID*)&TrueGetCurStationCapabilities, WinStationGetCurrentSessionCapabilitiesLocal);
error = DetourTransactionCommit();
if (error == NO_ERROR) {
OutputDebugString("Successfully Detoured API Calls \r\n");
}
else {
return FALSE;
}
}
else if (dwReason == DLL_PROCESS_DETACH) {
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach((PVOID*)&TrueProcessIdToSessionId, ProcessIdToSessionIdLocal);
DetourDetach((PVOID*)&TrueGetCurStationCapabilities, WinStationGetCurrentSessionCapabilitiesLocal);
error = DetourTransactionCommit();
FreeLibrary(hStaDLL);
}
return TRUE;
}
Injecting a DLL into the signtool is also easy: just add a new entry to the IMPORTs section that will load the DLL with the detoured functions. This can be done using a tool named "Lord PE" (for 32bit executeables).