Even within organizations that have achieved a mature security posture, targeted NTLM relay attacks are still incredibly effective after all these years of abuse. Leveraging several of these NTLM relay primitives, specifically ones that require coercing SMB-based authentication, come with additional challenges to overcome while operating over command and control (C2). This technique will the ease abuse of several popular NTLM relay primitives by allowing attackers to control inbound 445/tcp traffic without loading a driver, loading a module into LSASS, or requiring a reboot of the target Windows machine.
When conducting a penetration test or red team from a device that can directly route into a target network, there is often a straightforward path to control inbound SMB-based traffic on port 445/tcp. Common scenarios include a Windows laptop plugged directly into ethernet, a deployed Linux virtual machine, VPN accessibility, etc.
In the case of Windows, the `LanmanServer` service can be disabled, followed by a reboot, and the Windows kernel is no longer bound to the target port. In the case of a Linux machine, having escalated privileges on your testing device will allow for binding to the target port.
However, conducting your offensive assessment through a C2 agent includes a few additional hurdles to overcome. A commonly problematic step is gaining is gaining control of inbound SMB-based authentication attempts on port 445/tcp from a compromised Windows host.
If you’re interested in skipping the technical analysis and getting straight to the solution, see the Implementation Summary section.
The WinDivert driver is described as a “a user-mode packet interception library that enables user-mode capturing/modifying/dropping of network packets sent to/from the Windows network stack”. Many popular open-source projects have been created to leverage this driver to redirect inbound SMB-based authentication, such as PortBender, SharpRelay, StreamDivert, DivertTCPconn, hwfwbypass, and more.
The lsarelayx by @_EthicalChaos_ is a “system wide NTLM relay tool designed to relay incoming NTLM based authentication to the host it is running on” by leveraging “a fake LSA authentication provider to hook the NTLM and Negotiate packages and facilitate redirecting authentication requests”.
The LanmanServer service can simply be set to a ‘disabled’ start type. When the Windows machine is rebooted, 445/tcp will no longer be bound by the kernel.
Leveraging a driver for post-exploitation involves several considerations, such as potential for BSOD. This is a risk we cannot afford to take in certain situations. Especially when conducting activities on high-uptime, critical infrastructure. Loading a driver, especially one publicly associated with popular abuse primitives, can also be a single point of failure regarding detection and prevention.
Loading a customer LSA authentication provider can come with similar risks, as it can affect the stability of the LSASS process. You could be one incorrectly handled error away from a forced reboot depending on your code. As a Microsoft-specific limitation of how LSA plugins work, the provider also cannot be unloaded until a reboot occurs (without getting creative).
Disabling the LanmanServer service also requires either forcing, or waiting for, a reboot of the target machine. This often isn’t an option due to time constraints or high-uptime needs of a production environment.
Ideally, we would be able to control traffic inbound on the target port without loading a driver, loading a module into LSASS, or rebooting the target machine.
As previously mentioned, configuring the `LanmanServer` service to a start type of `disabled` and rebooting Windows will result in the machine no longer being bound to 445/tcp by default. Another important note — when reconfiguring the `LanmanServer` service back to the default start type of `auto start` and manually starting the service, the Windows machine will again bind to 445/tcp and reloading all the necessary resources to resume normal SMB-based functionality. Remember, the goal here is to do something to release this port without requiring a reboot, loading a driver, or loading a module into LSASS. Being able to repeat and debug the same thing in reverse (i.e., binding to the port) will be helpful for understanding the potential associated code path(s) for our desired result.
To start, let’s verify what is binding to the port we are interested in. Here’s one way to do this quickly, using PowerShell:
PS C:\> Get-NetTCPConnection -LocalPort 445 | ForEach-Object { Get-Process -Id $_.OwningProcess }
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
- - - - - - - - - - - - - - - - - - - - - - - -
6600 0 224 7424 9,175.72 4 0 System
So we know the process with a handle to the socket bound on 445/tcp is `System`, and we can begin looking at loaded modules/drivers associated with opening and closing sockets. Using System Informer to obtain a list of loaded modules is a good starting point for this enumeration. After reading through driver names, descriptions, and definitely not using ChatGPT, the initial list for inspection was narrowed down to:
NOTE: Winbindex was used to ensure the same binaries were being analyzed on the remote machine during dynamic analysis and locally during static analysis.
The next objective was to identify function(s) within these drivers used to bind to the SMB-related port. IDA Free was used to conduct initial inspection for potentially related functions, and thanks to Microsoft symbols, several were found. Searching function names for related keywords such as “port”, “socket”, and “bind”, some of the functions initially identified included:
The target Windows VM was configured to enable kernel debugging. To make the action of binding to 445/tcp a repeatable behavior, the VM was also configured with the `LanmanServer` service to a start type of `disabled` and rebooted (mentioned in Prerequisite Notes). Once the machine was no longer listening on the port in question, the VM was snapshotted for easily repeatable behavior. This was coupled with a simple PowerShell one-liner (below) to quickly iterate over the action of rebinding to 445/tcp while using WinDbg.
Set-Service -Name "lanmanserver" -StartupType Automatic; Start-Service -Name "lanmanserver"
Breakpoints were set on many of these interesting functions, which eventually led to the inspection of `tcpip!InetAcquirePort`. A breakpoint set for this function was reliably hit when `LanmanServer` was restarted (i.e., when the port was being bound). To ensure this activity was associated with the binding of port 445, I wanted to see the port number passed in function call parameters. Early in the logic of the `tcpip!InetAcquirePort` function, there was a call to another function, `tcpip!IsPortInExclusion`.
ExAcquireResourceExclusiveLite(a1, v16);
v68 = (unsigned __int16)__ROR2__(*a6, 8);
v69 = IsPortInExclusion(*(_QWORD *)(a1 + 136), v68);
if ( v69 && (*(_BYTE *)(v69 + 16) & 0x12) == 2 )
As seen above, the ‘tcpip!IsPortInExclusion’ function took two parameters. The second parameter was an `unsigned __int16`, which could likely represent a port number between 0–65535. Using the standard fastcall calling convention, this parameter should appear in the RDX register. Stepping through execution of `tcpip!InetAcquirePort` until `tcpip!IsPortInExclusion` was called and obtaining the RDX register value looked like this:
1: kd> p
tcpip!InetAcquirePort+0xbae:
fffff806`3e93c646 e8f9bd0100 call tcpip!IsPortInExclusion (fffff806`3e958444)1: kd> ? rdx
Evaluate expression: 445 = 00000000`000001bd
So we know this function call is associated with the binding of port 445 when starting the `LanmanServer` service. What information can we gather from the call stack (below) and how we got to this function call? Where can we start in terms of attempting to unbind this port while considering our given prerequisites?
[0x0] tcpip!InetAcquirePort+0xbae
[0x1] tcpip!TcpBindEndpointRequestInspectComplete+0x2cc
[0x2] tcpip!TcpIoControlEndpoint+0x2e9
[0x3] tcpip!TcpTlEndpointIoControlEndpointCalloutRoutine+0x74
[0x4] nt!KeExpandKernelStackAndCalloutInternal+0x78
[0x5] nt!KeExpandKernelStackAndCalloutEx+0x1d
[0x6] tcpip!TcpTlEndpointIoControlEndpoint+0x6e
[0x7] afd!WskProIRPBind+0x11e
[0x8] afd!AfdWskDispatchInternalDeviceControl+0x3c
[0x9] nt!IofCallDriver+0x55
[0xa] afd!WskProAPIBind+0x47
[0xb] srvnet!SrvNetWskOpenListenSocket+0x3ef
[0xc] srvnet!SrvNetAllocateEndpointCommon+0x34a
[0xd] srvnet!SrvNetAllocateEndpoint+0x3e02
[0xe] srvnet!SrvNetAddServedName+0x564
[0xf] srvnet!SvcXportAdd+0x14e
[0x10] srvnet!SrvAdminProcessFsctlFsp+0xbe
[0x11] nt!IopProcessWorkItem+0x93
[0x12] nt!ExpWorkerThread+0x105
[0x13] nt!PspSystemThreadStartup+0x55
[0x14] nt!KiStartSystemThread+0x28
We have several places we can continue on from this point. My first thought was to identify functionality exposed by these drivers, through device I/O control codes (IOCTLs) for example. Something more straightforward was identified first, though.
Starting with the `srvnet.sys` driver, I attempted to identify similar functions to what was previously identified when debugging 445/tcp being bound. Referencing our call stack from before, we see the `srvnet!SrvNetWskOpenListenSocket` function. Stepping back through the cross-references, we see another function call that is comparable to the functions used to bind to the target port. In this case we see `srvnet!SrvNetCloseEndpoint` calling `srvnet!SrvNetWskCloseListenSocket`, similarly to `srvnet!SrvNetAllocateEndpoint` calling `srvnet!SrvNetWskOpenListenSocket` previously observed.
Checking the cross-references for `srvnet!SrvNetAllocateEndpoint` yields several more results. After manual triage, it was identified that one of those several cross-references (`srvnet!SrvNetCleanupDeviceExtensionPreScavengerTermination`) was called by `srvnet!DriverUnload`.
This is the part where I thought to myself… “no way it’ll be this easy”. If we can use the Service Control Manager (SCM) to stop the service associated with the srvnet.sys driver, would the target code path leading to the release of port 445 be reached?
The `LanmanServer` service is configured with a start type of `auto start` by default. We will first configure this to `disabled` to prevent it from restarting, for testing purposes.
Stopping the `srvnet` service, responsible for loading the `srvnet.sys` driver should make use of the Service Control Manager (SCM) to ultimately call `srvnet!DriverUnload`. However, using System Informer we can quickly determine that `srvnet` has two dependent services:
So both of these services must first be stopped before attempting to stop the target service. The `srv2` service also has a dependency of `LanmanServer`. Stopping these services in the following order (based on the previously mentioned dependencies) should allow for all three of the services in question to be stopped:
We now see the first major indicator that our assumption might be correct. The port 445/tcp is no longer locally bound.
PS C:\Windows\system32> Get-NetTCPConnection -LocalPort 445
Get-NetTCPConnection : No MSFT_NetTCPConnection objects found with property 'LocalPort' equal to '445'. Verify the
value of the property and retry.
At line:1 char:1
+ Get-NetTCPConnection -LocalPort 445
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (445:UInt16) [Get-NetTCPConnection], CimJobException
+ FullyQualifiedErrorId : CmdletizationQuery_NotFound_LocalPort,Get-NetTCPConnection
To further validate our assumptions, we can return to the kernel debugger. When we were previously getting a better understanding of the binding process, we set a breakpoint on `tcpip!InetAcquirePort`. Similarly, there is a function in the same driver that will likely reveal what we are looking for when unbinding, `tcpip!InetReleasePort`. We can reconfigure the services to their original state (or just revert the VM) and set the appropriate breakpoint using WinDbg.
Upon repeating reconfiguring and disabling of target services, the breakpoint is hit:
0: kd> g
Breakpoint 2 hit
tcpip!InetReleasePort:
fffff807`7d92a3fc 4c8bdc mov r11,rsp
1: kd> r
rax=ffffcf8d773ed190 rbx=ffffcf8d7a1eacb0 rcx=ffffcf8d77475000
rdx=000000000000bd01 rsi=ffffcf8d7a782770 rdi=0000000000000000
rip=fffff8077d92a3fc rsp=fffffe8bfb1ea0b8 rbp=fffffe8bfb1ea3a0
r8=ffffcf8d7a1ead28 r9=0000000000000000 r10=fffff80779cd2250
r11=fffffe8bfb1ea178 r12=0000000000000001 r13=0000000000000000
r14=ffffcf8d7a80ad98 r15=fffff807901ee040
We don’t immediately see “445” as an argument passed to the function, so to make sure this is our activity that cause the breakpoint let’s take a quick look at the function’s pseudocode in IDA free.
__int64 __fastcall InetReleasePort(__int64 a1, __int64 a2, __int64 a3, __int64 a4)
{
unsigned __int16 v4; // r14
…
__int128 v21; // [rsp+20h] [rbp-48h] BYREF
__int64 v22; // [rsp+30h] [rbp-38h]
v4 = __ROR2__(a2, 8);
v21 = 0i64;
…
v13 = IsPortInExclusion(*(__int64 **)(a1 + 136), v4);
if ( (unsigned __int8)IsEmptyAssignment(v12, v13) )
…
The variable of `v4` is declared as an `__int16`, which helped us previously identify the port being used in the `tcpip!InetAcquirePort` call during the binding process. This variable is used shortly thereafter when calling the `tcpip!IsPortInExclusion` function, where it should appear in the `rdx` register (as the second parameter for that function). We set another breakpoint for `tcpip!IsPortInExclusion`, hit the additional breakpoint, and see that port 445 is the target of this activity.
0: kd> g
Breakpoint 1 hit
tcpip!IsPortInExclusion:
fffff807`7d918444 6690 nop
0: kd> ? rdx
Evaluate expression: 445 = 00000000`000001bd
Now we have our validation that the following behavior, from simply interacting with the Service Control Manager (SCM), is achieved:
I’ve published two tools (Python and BOF format) to automate abuse of this technique, and the code can be found on Github. They both include simple commands of “check”, “stop”, and “start” to automate the Service Control Manager interactions discussed in the previous section.
You don’t need to use my PoCs, as you can just use your favorite tool to manage services remotely or locally. Below I’ve included some example commands of using ‘sc.exe’ proxied into a network from remote Windows machine, as well as ‘wmiexec-Pro’:
sc.exe
wmiexec-Pro
NOTE: Disabling these services effectively hinders the target machine’s ability to facilitate named pipe / SMB communication. This is important to know for two reasons:
1. If the target machine is a server that is, let’s say, a large file share server, it will no longer be able to serve its function.
2. If you disable these services on a remote machine, and the tools you’re using rely on RPC over named pipes (ncacn_np) for Service Control Manager interactions, you will not be able to re-enable them remotely. The examples I’ve given above, as well as my PoCs, make use of RPC over TCP (ncacn_ip_tcp), which should not be affected.
A big perk of this technique is that re-enabling SMB functionality to its default state is a straightforward task and takes effect immediately. You can just set the LanmanServer service to a start-type of “auto-start” again, and a service trigger will soon reenable the service itself which will reload all the necessary drivers and resources to resume normal functionality. If you don’t want to wait, you can just manually start the LanmanServer service again.
My hope is that this technique provides a “lower-touch” alternative to existing solutions for taking control of port 445/tcp on compromised Windows hosts while operating over C2. My code to automate this can be found on GitHub and if you’re using other tools, be sure to determine if they use ‘ncacn_np’ vs ‘ncacn_ip_tcp’ to avoid issues with re-enabling remotely.
Relay Your Heart Away: An OPSEC-Conscious Approach to 445 Takeover was originally published in Posts By SpecterOps Team Members on Medium, where people are continuing the conversation by highlighting and responding to this story.
*** This is a Security Bloggers Network syndicated blog from Posts By SpecterOps Team Members - Medium authored by Nick Powers. Read the original post at: https://posts.specterops.io/relay-your-heart-away-an-opsec-conscious-approach-to-445-takeover-1c9b4666c8ac?source=rss----f05f8696e3cc---4