WNF and Task Scheduler

As I was investing another subject, I came across a scheduled task with a custom trigger and when I looked at the XML, we can see WnfStateChangeTrigger as the trigger type. I began to look for documentation but nothing valuable.

Until I came across a blackhat conference by Alex Ionescu and Gabrielle Viala where they explain what WNF is.

The interesting thing here is that WNF stand for Windows Notification Facility and is the notification system within the Windows OS. So it seems that the Task Scheduler is capable of subscribing to event and launch task against it.

How WNF works ?

In a nutshell and as 20k feet view: it’s notification system where processes can subscribe and publish without the need to wait for the other processes to be there. For example, if you want to be notified that wifi is connected then you subscribe to the event and the system will notify you that it did start and also can gives you more information like SSID name etc. The system or a process can also publish information to it.

For (wayyyy) more information, watch the conference and read these:

WnfStateChangeTrigger

When you look at this attribute, it’s the same length as a WNF state name which is a 64 Bit int but doesn’t correspond to any WNF state names either well-known, permanent or volatile, when you used directly. When trying to XOR it with the magic constant WNF_STATE_KEY, it doesn’t output anything meaningful, so it’s definitely not a usual WNF number.

After having a quick look in the PDB of taskschd.dll, I could find information about WnfStateChangeTriggerImpl but nothing with regards to the state number conversion. So, I let that go (Frozen style).

Finally, after staring for hours at the Task Scheduler state name and usual WNF numbers, I noticed something interesting, the task scheduler state name, I was looking at, end with 41 and start with 75 and WNF ones start with 41 and end with 75 for a lot of them.

Light bulb Moment

It appears that the StateName attribute is indeed a correct WNF number but in the wrong order. Each 8 bits or 2 characters are backward. I understood after, that this change is due to Endianess, one is using Big Endian the other Little Endian.

So 7550bea33e06830d = 0d83063ea3be5075

I’ve created 2 powershell functions to translate the name, it works in both direction, from and to. The first one is using the string and switching around the characters while the second one is doing it at the byte level.

function Translate-StateName {
    param (
        [ValidateLength(16,16)] [String] $StateName
    )
    
    process {
        $CharArray = (&{for ($i = 0;$i -lt $StateName.length;$i += 2) { $StateName.substring($i,2) }})
        $TranslatedStateName = (&{for ( $i = 7; $i -ge 0; $i-- ) { $CharArray[$i] }}) -join ''
        return $TranslatedStateName
    }
}

function Convert-StateNameEndianness {
    param (
        [ValidateLength(16,16)] [String] $StateName
    )
    process {
        [byte[]] $ByteArray = [System.BitConverter]::GetBytes([Convert]::ToInt64($StateName,16))
        if ([System.BitConverter]::IsLittleEndian -eq $false) { [System.Array]::Reverse($ByteArray) }
        return ([System.BitConverter]::ToString($ByteArray)).Replace('-','')
    }
}

Testing

So now that we have the key to unlock, let’s test it.
I’ve used WNF_SHEL_DESKTOP_APPLICATION_STARTED same as what Gabrielle used in her blog post as I knew this would work (superstition sometimes…)
Here’s the scheduled task that I’ve used

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.6" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Author>Camille Debay</Author>
    <Description>Test WNF Subscription to Start Desktop Application</Description>
    <URI>\TEST\WNFSub</URI>
  </RegistrationInfo>
  <Principals>
    <Principal id="LocalSystem">
      <UserId>S-1-5-18</UserId>
    </Principal>
  </Principals>
  <Settings>
    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
    <MultipleInstancesPolicy>Queue</MultipleInstancesPolicy>
    <StartWhenAvailable>true</StartWhenAvailable>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
    <UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>
  </Settings>
  <Triggers>
    <WnfStateChangeTrigger>
      <StateName>7550BEA33E06830D</StateName>
    </WnfStateChangeTrigger>
  </Triggers>
  <Actions Context="LocalSystem">
    <Exec>
      <Command>powershell</Command>
      <Arguments>-ExecutionPolicy Bypass -WindowStyle hidden -NonInteractive -NoLogo -file "C:\temp\test.ps1"</Arguments>
    </Exec>
  </Actions>
</Task>

The task is running a powershell script which create a new line with the time of execution.

Get-date | out-file C:\temp\test.txt -NoClobber -Append

To import the task as we need to be admin and to use powershell, won’t work in the GUI, with the following command:

Register-ScheduledTask -Xml (Get-Content -Path C:\temp\WNFTaskTrigger.xml | Out-String) -TaskName TestWNFStartDesktopApp -TaskPath '\TEST\' -force

Next step is to open any desktop app.

Bingo! it’s works!!

Wrap up

So the Windows Task Scheduler is capable of subscribing to WNF events and trigger tasks, the event I’ve used is a system wide event but there is WNF in user mode which could be reused for other purposes.


References: