FIDO2 lock screen on removal of a USB-key

Ok, here is a real-world problem:

My company decided to force users to use smartcards and Feitian FIDO-keys to logon on our domain computers with no option to use passwords. I will not mention all the other problems that came up, especially with IT-stuff who couldn’t help remotely any of the regular users without smartcards connected to accounts with local-admin privilege. But that’s another story.

My company bought a couple of thousand Feitian FIDO2 compatible keys that are supposed to be used by our domain users for MFA. Nice. Nobody thought about how those keys are treated by Windows. They are NOT smart cards, if you remove the key nothing will happen, you're still logged on until the screensaver kicks and locks the screen, all depending on your group policy.

The problem is that people who ordered this solution took for granted that Feitian FIDO-keys (or any other USB-based solution like YUBI-key) would lock the computer as soon as the user pulled out the key out of the computer. This of course doesn’t work, there is a Group Policy for Smartcard removal that works well when you pull out your smartcard because there is a service in Windows specially constructed for that task, it is called “ScPolicySvc”.

It is easy to activate this, just go to your Group Policy for clients and change Interactive logon: Smart card removal behavior to Lock Workstation.

Pull out your smartcard and your Windows will go to lock-screen.

This works ONLY for smartcards and that is a different hardware per definition for Windows OS.

With USB-based security keys you could glare on your Desktop until Windows inactivity threshold kicks the screensaver with locking, 2 minutes or 1 hour or 7 days depending on your company’s policy.

I thought that it should be some kind of Microsoft policy for this but there isn’t. YUBICO has published a software that you can download for free, it is a standard MSI-package but this works only for their products (Yubikey) and I guess that deviceID-s are hardcoded in software because I’ve tested Feitian FIDO key and it didn’t work.

I got some knowledge about WMI subscriptions, you could watch for events on your computer and do some deeds accordingly, send an e-mail, run a program, create an event... Too bad that this part of WMI is usually a tool that hackers use for compromising computers.

But I thought that it could be used for something that it was constructed for in the first place. You can read about it on following link. Unfortunately, you won’t be able to open any of the links on that page because Microsoft decided to pull the plug from the http://blogs.technet.com.

https://devblogs.microsoft.com/scripting/an-insiders-guide-to-using-wmi-events-and-powershell/

My first test worked like a charm, I registered an event in Powershell and my computer got locked as soon as I pulled out the Feitian-key. Observe that I used FIDO as a manufacturer to catch event for any USB key with this property. This is the command that I used for that:

# I am using Manufacturer FIDO for detection and 15 seconds polling, I don’t want to force computer to work too hard with WMI, 15 seconds should be ok or anything that you find suitable

Register-WMIEvent -Query "Select * FROM __InstanceDeletionEvent WITHIN 15 WHERE TargetInstance ISA 'win32_PNPEntity' and TargetInstance.Manufacturer = 'FIDO'" -Action {rundll32.exe user32.dll,LockWorkStation} -SourceIdentifier deviceChange 

After that I tried to pull out this with GPO as a logon script, it didn’t work. The problem is that I misunderstood the statement that this command is valid for user logon-session, it is not, it is valid only for the Powershell session. The PS1 script is executed when user logs in and it ends directly, no subscription left, nothing happens.

Then I had to think over. I knew that there is a way to make permanent subscriptions with WMI. Let’s try that:

The first thing I tried Is to make a subscription that would react directly on __InstanceDeletionEvent and execute rundll32.exe user32.dll,LockWorkStation as soon as FIDO-key was removed. The problem is, this command works only in user’s security context, SYSTEM cannot send this command to the current user, it only locks SYSTEM Desktop that does not exist.

OK, we’ll have to think more, I will not execute something, I’m gonna create an event in Windows built-in Event Log instead. Then I’ll have to create a Scheduled Task that triggers rundll32.exe on that event, but only in users’ security context.

It ended with a Powershell script that creates 4 different instances:

EventFilter

NTEventLogEventConsumer

FilterToConsumerBinding

Sheduled Task

Now I have some bullets here to explain what happens:

Register a custom event when Feitian USB-key is pulled out with 15-30 seconds polling via WMI

Create a Scheduled Task that watches for this event and do the stuff, format your hard drive, mail someone or lock the f*** screen, whatever…

If you have another type of key just try to find out those important names of your key, just insert your key into USB-port and run this query and find your own safety-key:

Get-WmiObject -Query "Select * FROM win32_PNPEntity" | Select Name, Manufacturer, Description | ogv

Of course, you could use some other parameters in your Event registration, just run the same query without picking only name, manufacturer and description:

Get-WmiObject -Query "Select * FROM win32_PNPEntity" | sort Manufacturer | ogv

You could choose PNPDeviceID, HardwareID or DeviceID for your query to be more specific, in my case I was interested only in FIDO-keys in general. Maybe we have 3 different firmware on those keys, I want it to work on any FIDO-key.

And this is what I got now, Powershelled, no strings attached:

# FIDO Lock Screen on removal by Denis Sahuric 2024 # Detects if FIDO Event Subscription exists # If not it creates all 4 elements: # 1 - Filter, 2 - Consumer, 3 - Binding, 4 - Scheduled task # Polling time 30 sec $WMI_Event_Name = "FIDO_removed" # Check if Scheduled Task exists $taskExists = Get-ScheduledTask | Where-Object {$_.TaskName -like $WMI_Event_Name} # Construct respective filters $FLT_Event_Filter = 'Name="'+$WMI_Event_Name+'"' $FLT_Binding_Filter = 'Filter="__EventFilter.Name=\"'+$WMI_Event_Name+'\""' # Event Filter $1_Event = Get-WmiObject -Namespace 'root/subscription' -Class '__EventFilter' -Filter $FLT_Event_Filter #Get-WmiObject -Namespace 'root/subscription' -Class '__EventFilter' -Filter 'Name="$WMI_Event_Name"' | Remove-WmiObject # Event Consumer for Event $2_Event = Get-WmiObject -Namespace 'root/subscription' -Class 'NTEventLogEventConsumer' -Filter $FLT_Event_Filter # Binding between Filter and Consumer $3_Event = Get-WmiObject -Namespace 'root/subscription' -Class '__FilterToConsumerBinding' -Filter $FLT_Binding_Filter if ($taskExists -and $1_Event -and $2_Event -and $3_Event) { Exit } $WMI_Poll_Time = 30 $Task_Exec = "C:\Windows\System32\rundll32.exe" $Task_Exec_Args = "user32.dll, LockWorkStation" $Task_Event_Name = "FIDO" $Task_Event_ID = "12345" $Event_Template = @( $WMI_Event_Name, 'Lock screen recommended' ) $props = @{ Name = $WMI_Event_Name Category = [UInt16]0 EventType = [UInt32]4 EventID = [UInt32]$Task_Event_ID SourceName = $Task_Event_Name NumberOfInsertionStrings = [UInt32]$Event_Template.Length InsertionStringTemplates = $Event_Template } ## 1 - create consumer $WMIEventConsumer = New-CimInstance -Namespace root\subscription -ClassName NtEventLogEventConsumer -Property $props Start-Sleep -Seconds 2 ## 2 - create subscription $query = "Select * FROM __InstanceDeletionEvent WITHIN $WMI_Poll_Time WHERE TargetInstance ISA 'win32_PNPEntity' and TargetInstance.Manufacturer = '$Task_Event_Name'" $WMIEventFilter = Set-WmiInstance -Class __EventFilter -Namespace "root\subscription" -Arguments @{Name=$WMI_Event_Name;EventNameSpace="root\cimv2";QueryLanguage="WQL";Query=$query} Start-Sleep -Seconds 2 ## 3 - create binding between consumer and subscription [object]$Filter = (Get-WMIObject -Namespace root\Subscription -Class __EventFilter | where name -eq $WMI_Event_Name) [object]$Consumer = (Get-WMIObject -Namespace root\Subscription -Class __EventConsumer | where name -eq $WMI_Event_Name) $instanceBinding = ([wmiclass]"\\.\root\subscription:__FilterToConsumerBinding").CreateInstance() $instanceBinding.Filter = $Filter $instanceBinding.Consumer = $Consumer $instanceBinding.Put() Start-Sleep -Seconds 2 ## Finally create Scheduled Task that is triggered by Event ## Get localized Users name $Task_GroupID = (Get-LocalGroup -SID "S-1-5-32-545").Name $Trigger_Subscription = "<QueryList><Query Id='0' Path='Application'><Select Path='Application'>*[System[Provider[@Name='" + $Task_Event_Name + "'] and (EventID=" + $Task_Event_ID + ")]]</Select></Query></QueryList>" #Create trigger on event number $CIMTriggerClass = Get-CimClass -ClassName MSFT_TaskEventTrigger -Namespace Root/Microsoft/Windows/TaskScheduler:MSFT_TaskEventTrigger $Trigger = New-CimInstance -CimClass $CIMTriggerClass -ClientOnly $Trigger.Subscription = $Trigger_Subscription $Trigger.Enabled = $True #create Scheduled task $Action = New-ScheduledTaskAction -Execute $Task_Exec -Argument $Task_Exec_Args $Settings = New-ScheduledTaskSettingsSet �AllowStartIfOnBatteries �DontStopIfGoingOnBatteries -DisallowDemandStart $RunUnder = New-ScheduledTaskPrincipal -GroupId $Task_GroupID Register-ScheduledTask -TaskName $WMI_Event_Name -Action $Action -Settings $Settings -Trigger $trigger -Principal $RunUnder

About polling time:

https://learn.microsoft.com/en-us/windows/win32/wmisdk/within-clause

About using built-in events:

Microsoft > Windows > DriverFrameworks-UserMode > Operational won’t work, it detects drives but I couldn’t see any traces of FIDO events.



Comments