Skip to content

Commit bdc4f73

Browse files
committed
Fix name resolution error for Presence module
1 parent 231dde0 commit bdc4f73

1 file changed

Lines changed: 120 additions & 35 deletions

File tree

nxc/modules/presence.py

Lines changed: 120 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,27 @@ class NXCModule:
1919
def options(self, context, module_options):
2020
"""There are no module options."""
2121

22-
def on_admin_login(self, context, connection):
23-
try:
24-
admin_users = self.enumerate_admin_users(context, connection)
25-
if not admin_users:
26-
context.log.fail("No admin users found.")
27-
return
22+
# --- helper: build/bind DCE-RPC while pinning TCP to IP and using safe server name ---
23+
def get_dce_rpc(self, tcp_host, string_binding, dce_binding, connection, remote_name=None):
24+
"""
25+
tcp_host: the TCP destination (IP)
26+
string_binding: ncacn_np binding (FQDN for Kerberos SPN; IP for NTLM is fine)
27+
remote_name: SMB server name for auth ('*SMBSERVER' for NTLM; FQDN for Kerberos)
28+
"""
29+
rpctransport = transport.DCERPCTransportFactory(string_binding)
2830

29-
# Update user objects to check if they are in tasklist, users directory or in scheduled tasks
30-
self.check_users_directory(context, connection, admin_users)
31-
self.check_tasklist(context, connection, admin_users)
32-
self.check_scheduled_tasks(context, connection, admin_users)
31+
# Pin the actual TCP destination to the IP we intend to hit.
32+
rpctransport.setRemoteHost(tcp_host)
3333

34-
# print grouped/logged results nicely
35-
self.print_grouped_results(context, admin_users)
36-
except Exception as e:
37-
context.log.fail(str(e))
38-
context.log.debug(traceback.format_exc())
34+
# Choose the SMB "server name" used by the session.
35+
if remote_name is None:
36+
remote_name = (
37+
f"{connection.hostname}.{connection.domain}"
38+
if connection.kerberos
39+
else "*SMBSERVER"
40+
)
41+
rpctransport.setRemoteName(remote_name)
3942

40-
def get_dce_rpc(self, target, string_binding, dce_binding, connection):
41-
# Create a DCE/RPC transport object with the specified string binding
42-
rpctransport = transport.DCERPCTransportFactory(string_binding)
43-
rpctransport.setRemoteHost(target)
4443
rpctransport.set_credentials(
4544
connection.username,
4645
connection.password,
@@ -51,7 +50,6 @@ def get_dce_rpc(self, target, string_binding, dce_binding, connection):
5150
)
5251
rpctransport.set_kerberos(connection.kerberos, connection.kdcHost)
5352

54-
# Connect to the DCE/RPC endpoint
5553
dce = rpctransport.get_dce_rpc()
5654
if connection.kerberos:
5755
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
@@ -61,12 +59,44 @@ def get_dce_rpc(self, target, string_binding, dce_binding, connection):
6159
dce.bind(dce_binding)
6260
return dce
6361

62+
def on_admin_login(self, context, connection):
63+
try:
64+
admin_users = self.enumerate_admin_users(context, connection)
65+
if not admin_users:
66+
context.log.fail("No admin users found.")
67+
return
68+
69+
# Update user objects to check if they are in tasklist, users directory or in scheduled tasks
70+
self.check_users_directory(context, connection, admin_users)
71+
self.check_tasklist(context, connection, admin_users)
72+
self.check_scheduled_tasks(context, connection, admin_users)
73+
74+
# print grouped/logged results nicely
75+
self.print_grouped_results(context, admin_users)
76+
except Exception as e:
77+
context.log.fail(str(e))
78+
context.log.debug(traceback.format_exc())
79+
6480
def enumerate_admin_users(self, context, connection):
6581
admin_users = []
6682

6783
try:
68-
string_binding = fr"ncacn_np:{connection.kdcHost}[\pipe\samr]"
69-
dce = self.get_dce_rpc(connection.kdcHost, string_binding, samr.MSRPC_UUID_SAMR, connection)
84+
tcp_ip = connection.host # e.g., 10.10.11.181
85+
# Binding name: FQDN for Kerberos SPN; IP is fine for NTLM.
86+
bind_name = (
87+
f"{connection.hostname}.{connection.domain}"
88+
if connection.kerberos
89+
else tcp_ip
90+
)
91+
string_binding = fr"ncacn_np:{bind_name}[\pipe\samr]"
92+
dce = self.get_dce_rpc(
93+
tcp_host=tcp_ip,
94+
string_binding=string_binding,
95+
dce_binding=samr.MSRPC_UUID_SAMR,
96+
connection=connection,
97+
remote_name=(f"{connection.hostname}.{connection.domain}"
98+
if connection.kerberos else "*SMBSERVER"),
99+
)
70100
except Exception as e:
71101
context.log.fail(f"Failed to connect to SAMR: {e}")
72102
context.log.debug(traceback.format_exc())
@@ -77,7 +107,9 @@ def enumerate_admin_users(self, context, connection):
77107
domain = samr.hSamrEnumerateDomainsInSamServer(dce, server_handle)["Buffer"]["Buffer"][0]["Name"]
78108
resp = samr.hSamrLookupDomainInSamServer(dce, server_handle, domain)
79109
self.domain_sid = resp["DomainId"].formatCanonical()
80-
domain_handle = samr.hSamrOpenDomain(dce, server_handle, samr.DOMAIN_LOOKUP | samr.DOMAIN_LIST_ACCOUNTS, resp["DomainId"])["DomainHandle"]
110+
domain_handle = samr.hSamrOpenDomain(
111+
dce, server_handle, samr.DOMAIN_LOOKUP | samr.DOMAIN_LIST_ACCOUNTS, resp["DomainId"]
112+
)["DomainHandle"]
81113
context.log.debug(f"Resolved domain SID for {domain}: {self.domain_sid}")
82114
except Exception as e:
83115
context.log.fail(f"Failed to open domain {domain}: {e!s}")
@@ -100,14 +132,26 @@ def enumerate_admin_users(self, context, connection):
100132
rid = int.from_bytes(member.getData(), byteorder="little")
101133
try:
102134
user_handle = samr.hSamrOpenUser(dce, domain_handle, samr.MAXIMUM_ALLOWED, rid)["UserHandle"]
103-
username = samr.hSamrQueryInformationUser2(dce, user_handle, samr.USER_INFORMATION_CLASS.UserAllInformation)["Buffer"]["All"]["UserName"]
135+
username = samr.hSamrQueryInformationUser2(
136+
dce, user_handle, samr.USER_INFORMATION_CLASS.UserAllInformation
137+
)["Buffer"]["All"]["UserName"]
104138

105139
# If user already exists, append group name
106140
if any(u["sid"] == f"{self.domain_sid}-{rid}" for u in admin_users):
107141
user = next(u for u in admin_users if u["sid"] == f"{self.domain_sid}-{rid}")
108142
user["group"].append(group_name)
109143
else:
110-
admin_users.append({"username": username, "sid": f"{self.domain_sid}-{rid}", "domain": domain, "group": [group_name], "in_tasks": False, "in_directory": False, "in_scheduled_tasks": False})
144+
admin_users.append(
145+
{
146+
"username": username,
147+
"sid": f"{self.domain_sid}-{rid}",
148+
"domain": domain,
149+
"group": [group_name],
150+
"in_tasks": False,
151+
"in_directory": False,
152+
"in_scheduled_tasks": False,
153+
}
154+
)
111155
context.log.debug(f"Found user: {username} with RID {rid} in group {group_name}")
112156
except Exception as e:
113157
context.log.debug(f"Failed to get user info for RID {rid}: {e!s}")
@@ -151,19 +195,30 @@ def check_users_directory(self, context, connection, admin_users):
151195

152196
def check_tasklist(self, context, connection, admin_users):
153197
"""Checks tasklist over rpc."""
198+
# Choose a stable server name for the RPC layer (matches our SMB session)
199+
remote_name = (
200+
f"{connection.hostname}.{connection.domain}"
201+
if connection.kerberos else "*SMBSERVER"
202+
)
154203
try:
155-
with TSTS.LegacyAPI(connection.conn, connection.remoteName, kerberos=connection.kerberos) as legacy:
204+
# LegacyAPI reuses the existing SMB connection; give it the right name
205+
with TSTS.LegacyAPI(connection.conn, remote_name, kerberos=connection.kerberos) as legacy:
156206
handle = legacy.hRpcWinStationOpenServer()
157207
processes = legacy.hRpcWinStationGetAllProcesses(handle)
158208
except Exception as e:
209+
msg = str(e)
210+
# If the WinStation/TSTS pipe or object isn't there, just skip this check
211+
if "STATUS_OBJECT_NAME_NOT_FOUND" in msg or "0xc0000034" in msg.lower():
212+
context.log.debug("TSTS/WinStation endpoint not present; skipping tasklist enumeration")
213+
return []
214+
# Other errors: log and continue without failing the whole module
159215
context.log.fail(f"Error in check_tasklist RPC method: {e}")
160216
return []
161217

162218
context.log.debug(f"Enumerated {len(processes)} processes on {connection.host}")
163219

164220
for process in processes:
165221
context.log.debug(f"ImageName: {process['ImageName']}, UniqueProcessId: {process['SessionId']}, pSid: {process['pSid']}")
166-
# Check if process SID matches any admin user SID
167222
for user in admin_users:
168223
if process["pSid"] == user["sid"]:
169224
user["in_tasks"] = True
@@ -172,9 +227,21 @@ def check_tasklist(self, context, connection, admin_users):
172227
def check_scheduled_tasks(self, context, connection, admin_users):
173228
"""Checks scheduled tasks over rpc."""
174229
try:
175-
target = connection.remoteName if connection.kerberos else connection.host
176-
stringbinding = f"ncacn_np:{target}[\\pipe\\atsvc]"
177-
dce = self.get_dce_rpc(target, stringbinding, tsch.MSRPC_UUID_TSCHS, connection)
230+
tcp_ip = connection.host
231+
target_name = (
232+
f"{connection.hostname}.{connection.domain}"
233+
if connection.kerberos
234+
else tcp_ip
235+
)
236+
stringbinding = f"ncacn_np:{target_name}[\\pipe\\atsvc]"
237+
dce = self.get_dce_rpc(
238+
tcp_host=tcp_ip,
239+
string_binding=stringbinding,
240+
dce_binding=tsch.MSRPC_UUID_TSCHS,
241+
connection=connection,
242+
remote_name=(f"{connection.hostname}.{connection.domain}"
243+
if connection.kerberos else "*SMBSERVER"),
244+
)
178245

179246
# Also extract non admins where we can get the password
180247
self.non_admins = []
@@ -198,18 +265,36 @@ def check_scheduled_tasks(self, context, connection, admin_users):
198265
non_admin_sids.add(sid.group(1))
199266

200267
if non_admin_sids:
201-
string_binding = fr"ncacn_np:{connection.kdcHost}[\pipe\samr]"
202-
dce = self.get_dce_rpc(connection.kdcHost, string_binding, samr.MSRPC_UUID_SAMR, connection)
268+
# Re-use SAMR with pinned TCP to IP and correct server name.
269+
tcp_ip = connection.host
270+
bind_name = (
271+
f"{connection.hostname}.{connection.domain}"
272+
if connection.kerberos
273+
else tcp_ip
274+
)
275+
string_binding = fr"ncacn_np:{bind_name}[\pipe\samr]"
276+
dce = self.get_dce_rpc(
277+
tcp_host=tcp_ip,
278+
string_binding=string_binding,
279+
dce_binding=samr.MSRPC_UUID_SAMR,
280+
connection=connection,
281+
remote_name=(f"{connection.hostname}.{connection.domain}"
282+
if connection.kerberos else "*SMBSERVER"),
283+
)
203284

204285
# Get Domain Handle
205286
server_handle = samr.hSamrConnect2(dce)["ServerHandle"]
206287
domain = samr.hSamrEnumerateDomainsInSamServer(dce, server_handle)["Buffer"]["Buffer"][0]["Name"]
207288
domain_sid = samr.hSamrLookupDomainInSamServer(dce, server_handle, domain)["DomainId"]
208-
domain_handle = samr.hSamrOpenDomain(dce, server_handle, samr.DOMAIN_LOOKUP | samr.DOMAIN_LIST_ACCOUNTS, domain_sid)["DomainHandle"]
289+
domain_handle = samr.hSamrOpenDomain(
290+
dce, server_handle, samr.DOMAIN_LOOKUP | samr.DOMAIN_LIST_ACCOUNTS, domain_sid
291+
)["DomainHandle"]
209292

210293
for sid in non_admin_sids:
211294
user_handle = samr.hSamrOpenUser(dce, domain_handle, samr.MAXIMUM_ALLOWED, int(sid.split("-")[-1]))["UserHandle"]
212-
username = samr.hSamrQueryInformationUser2(dce, user_handle, samr.USER_INFORMATION_CLASS.UserAllInformation)["Buffer"]["All"]["UserName"]
295+
username = samr.hSamrQueryInformationUser2(
296+
dce, user_handle, samr.USER_INFORMATION_CLASS.UserAllInformation
297+
)["Buffer"]["All"]["UserName"]
213298
self.non_admins.append(username)
214299

215300
except Exception as e:
@@ -241,7 +326,7 @@ def print_grouped_results(self, context, admin_users):
241326
context.log.success("Found admins in scheduled tasks:")
242327
for user in scheduled_tasks_users:
243328
context.log.highlight(f"{user['username']} ({', '.join(user['group'])})")
244-
if self.non_admins:
329+
if getattr(self, "non_admins", None):
245330
context.log.info(f"Found {len(self.non_admins)} non-admin scheduled tasks:")
246331
for sid in self.non_admins:
247332
context.log.info(sid)

0 commit comments

Comments
 (0)