Hunting for obfuscation

During my summer holidays, I watched some of the great 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://')

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.

Split strings

  • Context

I worked recently on some Active Directory properties and needed to split a path.

$h = (
Get-ADUser UserName -Properties HomeDirectory
$h = '\\servername\homepath.username$'

The HomeDirectory is a System.String and my variable $h contains something like \\servername\homepath.username$ (there’s a dollar at the end to hide the share).

  • Problem

I’d like to split on the \ character and to do so, you need to escape it with another backslash character because it’s a special character for regular expressions.

The official documentation About Split says that the delimiter is evaluated as a regular expression if I use the highlighted 3rd syntax:
The Options parameter is optional in the syntax and defaults to the value RegexMatch
The doc says:

RegexMatch: Use regular expression matching to evaluate the delimiter.

If I use the split operator like this:

$h -split '\\'

$h -split '\\' | measure

[string]::Empty -eq ($h -split '\\')[0]
[string]::Empty -eq ($h -split '\\')[1]
[string]::Empty -eq ($h -split '\\')[2]
[string]::Empty -eq ($h -split '\\')[3]

I get 4 results. The first 2 results of the split operation are an empty system.string.
This is expected.

I want to capture the server name and the share name, not the backslashes and not the empty strings.

  • Solution(s)

My first solution followed the idea “just ignore and discard empty strings”:

# capture
$null,$null,$server,$share=$h -split '\\'
# display result

My second solution is based on the idea that “there’s probably a better way to skip these empty strings”:

# display result

If you’ve another great idea and want to share another (better?) way to skin the cat, please post it in the comments 🙂 .

[Quick post] Exchange (Online) team’s changes to the default policy about blocked file types

The Exchange (Online) team is about to change the default policy about blocked file types, meaning that OWA (a.k.a Outlook on the web) users will be impacted by this change.

Their message is quite clear and was published on this page

Almost all the most common extensions related to PowerShell are concerned: .ps1, .ps1xml, .ps2, .ps2xml, .psc1, .psc2, .psd1, .psdm1, .cdxml, .pssc. That said, neither .ps2, ps2xml nor .psc2 are used and recognized by PowerShell or pwsh. We just wonder where they got this list of extensions? 🙄 (from telemetry?)

If you’ve the habit to send these files by email, your recipient may not be able to download and open the sent files if he uses Exchange Online (EXO) (i.e. OWA / Outlook on the web).

If you are an EXO admin, you can override the default policy and set your own policy using the guidance in the above article mentioned

As a user, you can zip the files before sending them. Or, if you’ve a OneDrive (or a similar online storage) you can store these blocked files there.

The kind of change doesn’t add anything to the security of your endpoints. On any Windows, these file types have always been associated by default with notepad and not PowerShell .

If any user gets a PowerShell script (a .ps1) by email, when he downloads and opens it, the .ps1 script doesn’t get executed but it’s instead opened by notepad.exe and its content displayed in notepad.exe.

The attack surface of notepad is extremely tiny. As far as I can remember, there’s only 1 known vulnerability that was discovered by a Google Project Zero security researcher over the last few years. It isn’t notepad.exe that was actually vulnerable but a more subtle sub-component CTF (a.k.a. Windows Text Services Framework) used by notepad.

Get full control of Windows Update

  • Context

I’ve been invited to a strange meeting where 2 companies were talking about Windows Updates. The software manufacturer wanted to patch the computers once a year while the maintenance provider wanted to be never ever disturbed by an update or its reboot during a full week of production. I told the software company that this bad practice is called “patch and pray”. I told the maintenance company that’s hard to achieve nowadays due to “Windows Update as a Service” and that I’ll have a look.

While I understand the business needs of these companies, it seems that the behavior described by Brian Krebs in his recent blog post has already negatively impacted their subconscious mind.
Please allow me to quote his post:

Most Microsoft Windows (ab)users probably welcome the monthly ritual of applying security updates about as much as they look forward to going to the dentist: It always seems like you were there just yesterday, and you never quite know how it’s all going to turn out.


Nevertheless, it is frustrating when being diligent about applying patches introduces so many unfixable problems that you’re forced to completely reinstall the OS and all of the programs that ride on top of it.


So, three words of advice. First off, don’t let Microsoft decide when to apply patches and reboot your computer.


Secondly, it doesn’t hurt to wait a few days to apply updates.


Finally, please have some kind of system for backing up your files before applying any updates.

I usually recommend the same as Brian Krebs to any user who’s not involved in patch management.

  • Solution

To regain control on Windows Update (a.k.a. WU), the following solution allows you to turn WU on and off on demand.

All the screenshots below have been done on a Windows 10 Enterprise 1903 provisioned in Azure:

  • How to install

Right-click the Start menu, select “Windows PowerShell (Admin)”,
Copy/paste in your browser the link to the above gist or click on it. Once you’re on the gist, you should use the ‘Raw’ button and then copy the content of the page and paste it in a PowerShell script file. I used a.ps1 as a name below:

Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force
notepad a.ps1

  • Demo

Let’s see what it looks like once it’s deployed.
You get 2 scripts on the desktop and the following settings in the system settings tiles:
The active hours are set to 6:00 AM to 11:00 PM.

It also defines a period of 185 days to defer upgrades and 25 days to defer updates. If that’s not what you want, you’ll need to modify the content of the script but that’s beyond the scope of this blog post.

If you use the Off.cmd script on the desktop, checking for updates fails (that’s expected)

If you use the On.cmd script on the desktop and hit “Retry”, checking for updates succeeds:

  • Known limits

The above solution doesn’t work in a corporate environment where the above WU settings are controlled by an administrator.
The above solution works only for an Ethernet connection because it’s set as a metered connection. That will also stop Office 365 updates (it honors the metered flag). Unfortunately the above solution isn’t working for a wireless connection.