Detecting Obfuscated PowerShell

I was recently looking at a sample that was encoded using MSF’s basic template obfuscation (stolen without attribution from Matt Graeber of course):

 

https://github.com/rapid7/metasploit-framework/blob/b206de77081069dd53b1f90f57bfaccd0ecbb0d8/data/templates/scripts/to_mem_pshreflection.ps1.template

For example:

 

function mtKZ {

       Param ($l7PpJu1SE4VO, $qhnBBk5lHo)            

       $pcE6VKGt = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')

      

       return $pcE6VKGt.GetMethod('GetProcAddress').Invoke($null, @([System.Runtime.InteropServices.HandleRef](New-Object System.Runtime.InteropServices.HandleRef((New-Object IntPtr), ($pcE6VKGt.GetMethod('GetModuleHandle')).Invoke($null, @($l7PpJu1SE4VO)))), $qhnBBk5lHo))

}

 

For detection purposes, attempted obfuscation like this (i.e.: the variable names) are themselves an indicator to malicious activity.

 

PowerShell’s AST APIs make detection of stuff like this a breeze. For example, here’s a way to get all of the variables in $Path:

 

$tokens = @()

$null = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref] $tokens, [ref] $null)

$tokens | ? VariablePath | % { $_.VariablePath.UserPath }

 

With that, we can start to do some variable analysis. Basic entropy is a pretty good start. When you combine that with letter frequency distribution, this creates a pretty good obfuscation metric:

14 [C:\temp]
>> dir *.ps1 | % { Measure-VariableObfuscation.ps1 $_.FullName } | sort ObfuscationMetric

Path                                                  Entropy    TopFourLetters ObfuscationMetric
----                                                  -------    -------------- -----------------
C:\temp\hello.ps1                                           0                 0                 0
C:\temp\foo2.ps1                                            0                 1                 0
C:\temp\2.ps1                                               0                 0                 0
C:\temp\1.ps1                                               0                 0                 0
C:\temp\3.ps1                                               0                 0                 0
C:\temp\verbose.ps1                          3.17281073351987 0.666666666666667  1.05760357783996
C:\temp\msf_template.ps1                     3.07084709362252 0.631578947368421  1.13136471870303
C:\temp\foo.ps1                              3.65719253292414 0.588235294117647  1.50590280767465
C:\temp\pester.temp.tests.ps1                3.47972685963298            0.5625  1.52238050108943
C:\temp\configtest.ps1                       3.75004181130572 0.495934959349594  1.89026497805654
C:\temp\sendmailmessagetest.ps1              3.79012121177685  0.48780487804878  1.94128159627595
C:\temp\Repro.ps1                            4.16910660776366  0.46583850931677  2.22697620042034
C:\temp\sttest.ps1                           4.27537906345103 0.469194312796209  2.26939552183183
C:\temp\TranscriptTest.ps1                   4.17394102071541 0.425629290617849  2.39738946498757
C:\temp\Invoke-ActiveScriptEventConsumer.ps1 4.21710521416516 0.415384615384615  2.46538458674271
C:\temp\mywatch-command.ps1                   4.2973293816282  0.42159383033419  2.48560182741991
C:\temp\Burn-Console.ascii.ps1               4.45029315016471 0.352501867064974  2.88155650574519
C:\temp\Burn-Console.ps1                     4.45029315016471 0.352501867064974  2.88155650574519
C:\temp\Invoke-TokenManipulation.ps1         4.91011096435002 0.384693390598902  3.02122372925736
C:\temp\Invoke-TokenManipulationNonAdmin.ps1 4.91011096435002 0.384693390598902  3.02122372925736
C:\temp\stager.ps1                           5.32866566677047 0.244131455399061  4.02777076220679

MSF could of course adapt to this, but its algorithm would continue to have predictable and detectable output. All you’ve got to do is look J

And of course, Measure-VariableObfuscation:

001

002

003

004

005

006

007

008

009

010

011

012

013

014

015

016

017

018

019

020

021

022

023

024

025

026

027

028

#requires -Module PowerShellArsenal

[CmdletBinding()]

param(

    [Parameter(Mandatory)]

    $Path

)

$tokens = @(); $null = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref] $tokens, [ref] $null)

$bytes = [byte[]][char[]]-join ($tokens | ? VariablePath | % { $_.VariablePath.UserPath })

$entropy = 0

$top4 = 0

if($bytes)

{

    $entropy = Get-Entropy $bytes

    $letterFrequency = Measure-LetterFrequency (-join ($tokens | ? VariablePath | % { $_.VariablePath.UserPath })) -Raw

    $top4 = $letterFrequency[1..4] | Measure-Object -Sum Percent | % Sum

}

[PSCustomObject] @{

    Path = $Path

    Entropy = $entropy

    TopFourLetters = $top4

    ObfuscationMetric = $entropy * (1 - $top4)

}

3 Responses to “Detecting Obfuscated PowerShell”

  1. Dew Drop – November 16, 2015 (#2133) | Morning Dew writes:

    […] Detecting Obfuscated PowerShell (Lee Holmes) […]

  2. Peter Monadjemi writes:

    Where does Measure-LetterFrequency comes from? I couldn’t find in either in the PowerShellArsenal nor in the PowershellCookbook module.

  3. More Detecting Obfuscated PowerShell | Precision Computing writes:

    […] a recent post, we talked a little bit about detecting obfuscated PowerShell through the use of PowerShell’s […]

Leave a Reply