Skip to content

Commit 86ac49c

Browse files
committed
Merge branch 'main' into masky
2 parents 88684dc + ac85127 commit 86ac49c

5 files changed

Lines changed: 103 additions & 1 deletion

File tree

nxc/modules/example_module.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def options(self, context, module_options):
1818
"""Required.
1919
Module options get parsed here. Additionally, put the modules usage here as well
2020
"""
21+
# Put "No options available" in the docstring if there are no options for the module
2122

2223
def on_login(self, context, connection):
2324
"""Concurrent.

nxc/modules/lockscreendoors.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from io import BytesIO
2+
import pefile
3+
4+
5+
class NXCModule:
6+
"""
7+
Module for detecting Windows lock screen backdoors
8+
Module by @E1A
9+
"""
10+
11+
name = "lockscreendoors"
12+
description = "Detect Windows lock screen backdoors by checking FileDescriptions of accessibility binaries."
13+
supported_protocols = ["smb"]
14+
15+
def __init__(self):
16+
# List of exe names with expected descriptions
17+
self.expected_descriptions = {
18+
"utilman.exe": ["Utility Manager"],
19+
"narrator.exe": ["Screen Reader", "Narrator"],
20+
"sethc.exe": ["Accessibility shortcut keys"],
21+
"osk.exe": ["Accessibility On-Screen Keyboard"],
22+
"magnify.exe": ["Microsoft Screen Magnifier"],
23+
"EaseOfAccessDialog.exe": ["Ease of Access Dialog Host"],
24+
"voiceaccess.exe": ["Voice access"], # Only on Windows 11 / Server 2025+
25+
"displayswitch.exe": ["Display Switch"],
26+
"atbroker.exe": ["Windows Assistive Technology Manager", "Transitions Accessible technologies between desktops"],
27+
}
28+
29+
# If description matches one of these it's almost certainly backdoored
30+
self.backdoor_descriptions = [
31+
"Windows Command Processor",
32+
"Windows PowerShell"
33+
]
34+
35+
def options(self, context, module_options):
36+
"""No options available"""
37+
38+
def get_description(self, binary_data):
39+
# Extract the file description from version info
40+
try:
41+
pe = pefile.PE(data=binary_data, fast_load=True)
42+
pe.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_RESOURCE"]])
43+
for fileinfo in pe.FileInfo:
44+
for entry in fileinfo:
45+
if entry.Key.decode() == "StringFileInfo":
46+
for st in entry.StringTable:
47+
desc = st.entries.get(b"FileDescription")
48+
if desc:
49+
return desc.decode().strip()
50+
except Exception as e:
51+
self.context.log.debug(f"Failed to extract PE info: {e}")
52+
return None
53+
54+
def on_admin_login(self, context, connection):
55+
target_path = "\\Windows\\System32"
56+
tampered = False
57+
58+
for exe, expected_descs in self.expected_descriptions.items():
59+
try:
60+
# Grab the binary from the share
61+
buf = BytesIO()
62+
connection.conn.getFile("C$", f"{target_path}\\{exe}", buf.write)
63+
binary = buf.getvalue()
64+
65+
# Extract and normalize the file description
66+
file_desc = self.get_description(binary)
67+
if not file_desc:
68+
context.log.fail(f"{exe}: could not extract FileDescription")
69+
continue
70+
71+
# Check if the description is as expected
72+
if file_desc not in expected_descs:
73+
tampered = True
74+
if file_desc in self.backdoor_descriptions:
75+
context.log.highlight(f"BACKDOOR DETECTED: {exe} has FileDescription '{file_desc}'")
76+
else:
77+
if len(expected_descs) == 1:
78+
expected_str = f"'{expected_descs[0]}'"
79+
else:
80+
expected_str = ", ".join(f"'{d}'" for d in expected_descs)
81+
expected_str = f"one of: {expected_str}"
82+
context.log.highlight(f"SUSPICIOUS: {exe} has unexpected FileDescription '{file_desc}' (expected {expected_str})")
83+
except Exception as e:
84+
context.log.debug(f"Failed to process {exe}: {e}")
85+
86+
if not tampered:
87+
context.log.display("All lock screen executable descriptions are consistent with the expected values")

poetry.lock

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ dependencies = [
3030
"minikerberos>=0.4.1",
3131
"neo4j>=5.0.0",
3232
"paramiko>=3.3.1",
33+
"pefile (>=2024.8.26,<2025.0.0)",
3334
"pyasn1-modules>=0.3.0",
3435
"pylnk3>=0.4.3",
3536
"pypsrp>=0.8.1",

tests/e2e_commands.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M winscp
161161
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M zerologon
162162
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M change-password -o NEWPASS=Password123
163163
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M change-password -o NEWNTHASH=58A478135A93AC3BF058A5EA0E8FDB71
164+
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M lockscreendoors
164165
# test for multiple modules at once
165166
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M spooler -M petitpotam -M zerologon -M nopac -M enum_av -M enum_dns -M gpp_autologin -M gpp_password -M lsassy -M impersonate -M install_elevated -M ioxidresolver -M ms17-010 -M ntlmv1 -M runasppl -M uac -M webdav -M wifi -M coerce_plus
166167
##### SMB Anonymous Auth

0 commit comments

Comments
 (0)