Skip to content

Commit 6a0a985

Browse files
authored
Merge pull request Pennyw0rth#629 from Pennyw0rth/neff-update-ruff
Update Ruff and Fix Linting
2 parents b1f0368 + 3fcb377 commit 6a0a985

65 files changed

Lines changed: 246 additions & 281 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

nxc/connection.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -293,8 +293,7 @@ def call_modules(self):
293293
module.on_admin_login(context, self)
294294

295295
def inc_failed_login(self, username):
296-
global global_failed_logins
297-
global user_failed_logins
296+
global global_failed_logins, user_failed_logins
298297

299298
if username not in user_failed_logins:
300299
user_failed_logins[username] = 0
@@ -304,16 +303,15 @@ def inc_failed_login(self, username):
304303
self.failed_logins += 1
305304

306305
def over_fail_limit(self, username):
307-
global global_failed_logins
308-
global user_failed_logins
306+
global global_failed_logins, user_failed_logins
309307

310308
if global_failed_logins == self.args.gfail_limit:
311309
return True
312310

313311
if self.failed_logins == self.args.fail_limit:
314312
return True
315313

316-
if username in user_failed_logins and self.args.ufail_limit == user_failed_logins[username]:
314+
if username in user_failed_logins and self.args.ufail_limit == user_failed_logins[username]: # noqa: SIM103
317315
return True
318316

319317
return False

nxc/database.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ def initialize_db():
111111
# Even if the default workspace exists, we still need to check if every protocol has a database (in case of a new protocol)
112112
init_protocol_dbs("default")
113113

114+
114115
def format_host_query(q, filter_term, HostsTable):
115116
"""One annoying thing is that if you search for an ip such as '10.10.10.5',
116117
it will return 10.10.10.5 and 10.10.10.52, so we have to check if its an ip address first
@@ -141,6 +142,7 @@ def format_host_query(q, filter_term, HostsTable):
141142

142143
return q
143144

145+
144146
class BaseDB:
145147
def __init__(self, db_engine):
146148
self.db_engine = db_engine

nxc/helpers/args.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from argparse import ArgumentDefaultsHelpFormatter, SUPPRESS, OPTIONAL, ZERO_OR_MORE
22
from argparse import Action
33

4+
45
class DisplayDefaultsNotNone(ArgumentDefaultsHelpFormatter):
56
def _get_help_string(self, action):
67
help_string = action.help

nxc/helpers/even6_parser.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from datetime import datetime
77

8+
89
class Substitution:
910
def __init__(self, buf, offset):
1011
(sub_token, sub_id, sub_type) = struct.unpack_from("<BHB", buf, offset)
@@ -46,6 +47,7 @@ def xml(self, template=None):
4647
else:
4748
print("Unknown value type", hex(value.type))
4849

50+
4951
class Value:
5052
def __init__(self, buf, offset):
5153
token, string_type, length = struct.unpack_from("<BBH", buf, offset)
@@ -56,6 +58,7 @@ def __init__(self, buf, offset):
5658
def xml(self, template=None):
5759
return self._val
5860

61+
5962
class Attribute:
6063
def __init__(self, buf, offset):
6164
struct.unpack_from("<B", buf, offset)
@@ -75,13 +78,15 @@ def xml(self, template=None):
7578
val = self._value.xml(template)
7679
return None if val is None else f'{self._name.val}="{val}"'
7780

81+
7882
class Name:
7983
def __init__(self, buf, offset):
8084
hashs, length = struct.unpack_from("<HH", buf, offset)
8185

8286
self.val = buf[offset + 4:offset + 4 + length * 2].decode("utf16")
8387
self.length = 4 + (length + 1) * 2
8488

89+
8590
class Element:
8691
def __init__(self, buf, offset):
8792
token, dependency_id, length = struct.unpack_from("<BHI", buf, offset)
@@ -151,6 +156,7 @@ def xml(self, template=None):
151156
children = (x.xml(template) for x in self._children)
152157
return "<{}{}>{}</{}>".format(self._name.val, attrs, "".join(children), self._name.val)
153158

159+
154160
class ValueSpec:
155161
def __init__(self, buf, offset, value_offset):
156162
self.length, self.type, value_eof = struct.unpack_from("<HBB", buf, offset)
@@ -159,6 +165,7 @@ def __init__(self, buf, offset, value_offset):
159165
if self.type == 0x21:
160166
self.template = BinXML(buf, value_offset)
161167

168+
162169
class TemplateInstance:
163170
def __init__(self, buf, offset):
164171
token, unknown0, guid, length, next_token = struct.unpack_from("<BB16sIB", buf, offset)
@@ -179,6 +186,7 @@ def __init__(self, buf, offset):
179186
def xml(self, template=None):
180187
return self._xml.xml(self)
181188

189+
182190
class BinXML:
183191
def __init__(self, buf, offset):
184192
header_token, major_version, minor_version, flags, next_token = struct.unpack_from("<BBBBB", buf, offset)
@@ -195,6 +203,7 @@ def __init__(self, buf, offset):
195203
def xml(self, template=None):
196204
return self._element.xml(template)
197205

206+
198207
class ResultSet:
199208
def __init__(self, buf):
200209
total_size, header_size, event_offset, bookmark_offset, binxml_size = struct.unpack_from("<IIIII", buf)

nxc/helpers/misc.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from ipaddress import ip_address
88

9+
910
def identify_target_file(target_file):
1011
with open(target_file) as target_file_handle:
1112
for i, line in enumerate(target_file_handle):
@@ -23,7 +24,7 @@ def gen_random_string(length=10):
2324

2425

2526
def validate_ntlm(data):
26-
allowed = re.compile("^[0-9a-f]{32}", re.IGNORECASE)
27+
allowed = re.compile(r"^[0-9a-f]{32}", re.IGNORECASE)
2728
return bool(allowed.match(data))
2829

2930

@@ -79,6 +80,7 @@ def _access_check(fn, mode):
7980
if _access_check(name, mode):
8081
return name
8182

83+
8284
def get_bloodhound_info():
8385
"""
8486
Detect which BloodHound package is installed (regular or CE) and its version.
@@ -136,6 +138,7 @@ def get_bloodhound_info():
136138

137139
return package_name, version, is_ce
138140

141+
139142
def detect_if_ip(target):
140143
try:
141144
ip_address(target)

nxc/helpers/pfx.py

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import secrets
3131
import hashlib
3232
import datetime
33-
import logging
3433
import random
3534
import base64
3635

@@ -47,8 +46,7 @@
4746
from minikerberos.pkinit import PKINIT, DirtyDH
4847
from minikerberos.protocol.constants import NAME_TYPE, PaDataType
4948
from minikerberos.protocol.encryption import Enctype, _enctype_table, Key
50-
from minikerberos.protocol.asn1_structs import KDC_REQ_BODY, PrincipalName, KDCOptions, EncASRepPart, AS_REQ, PADATA_TYPE, \
51-
PA_PAC_REQUEST
49+
from minikerberos.protocol.asn1_structs import KDC_REQ_BODY, PrincipalName, KDCOptions, EncASRepPart, AS_REQ, PADATA_TYPE, PA_PAC_REQUEST
5250
from minikerberos.protocol.rfc4556 import PKAuthenticator, AuthPack, PA_PK_AS_REP, KDCDHKeyInfo, PA_PK_AS_REQ
5351

5452
from pyasn1.codec.der import decoder, encoder
@@ -70,6 +68,7 @@
7068
from impacket.krb5.ccache import CCache as impacket_CCache
7169

7270
from nxc.paths import NXC_PATH
71+
from nxc.logger import nxc_logger
7372

7473

7574
class myPKINIT(PKINIT):
@@ -304,8 +303,8 @@ def truncate_key(value, keysize):
304303

305304
key = Key(cipher.enctype, t_key)
306305
enc_data = as_rep["enc-part"]["cipher"]
307-
logging.info("AS-REP encryption key (you might need this later):")
308-
logging.info(hexlify(t_key).decode("utf-8"))
306+
nxc_logger.info("AS-REP encryption key (you might need this later):")
307+
nxc_logger.info(hexlify(t_key).decode("utf-8"))
309308
dec_data = cipher.decrypt(key, 3, enc_data)
310309
encasrep = EncASRepPart.load(dec_data).native
311310
cipher = _enctype_table[int(encasrep["key"]["keytype"])]
@@ -327,34 +326,27 @@ def printPac(self, data, key=None):
327326
for _bufferN in range(pacType["cBuffers"]):
328327
infoBuffer = PAC_INFO_BUFFER(buff)
329328
data = pacType["Buffers"][infoBuffer["Offset"] - 8:][:infoBuffer["cbBufferSize"]]
330-
if logging.getLogger().level == logging.DEBUG:
331-
print("TYPE 0x%x" % infoBuffer["ulType"])
329+
nxc_logger.debug(f"TYPE 0x{infoBuffer['ulType']}")
332330
if infoBuffer["ulType"] == 2:
333331
found = True
334332
credinfo = PAC_CREDENTIAL_INFO(data)
335-
if logging.getLogger().level == logging.DEBUG:
336-
credinfo.dump()
337333
newCipher = _enctype_table[credinfo["EncryptionType"]]
338334
out = newCipher.decrypt(key, 16, credinfo["SerializedData"])
339335
type1 = TypeSerialization1(out)
340336
# I'm skipping here 4 bytes with its the ReferentID for the pointer
341337
newdata = out[len(type1) + 4:]
342338
pcc = PAC_CREDENTIAL_DATA(newdata)
343-
if logging.getLogger().level == logging.DEBUG:
344-
pcc.dump()
345339
for cred in pcc["Credentials"]:
346340
credstruct = NTLM_SUPPLEMENTAL_CREDENTIAL(b"".join(cred["Credentials"]))
347-
if logging.getLogger().level == logging.DEBUG:
348-
credstruct.dump()
349341

350-
logging.info("Recovered NT Hash")
351-
logging.info(hexlify(credstruct["NtPassword"]).decode("utf-8"))
342+
nxc_logger.info("Recovered NT Hash")
343+
nxc_logger.info(hexlify(credstruct["NtPassword"]).decode("utf-8"))
352344
nthash = hexlify(credstruct["NtPassword"]).decode("utf-8")
353345

354346
buff = buff[len(infoBuffer):]
355347

356348
if not found:
357-
logging.info("Did not find the PAC_CREDENTIAL_INFO in the PAC. Are you sure your TGT originated from a PKINIT operation?")
349+
nxc_logger.info("Did not find the PAC_CREDENTIAL_INFO in the PAC. Are you sure your TGT originated from a PKINIT operation?")
358350
return nthash
359351

360352
def __init__(self, username, domain, kdcHost, key, tgt):
@@ -399,10 +391,8 @@ def dump(self):
399391
authenticator["cusec"] = now.microsecond
400392
authenticator["ctime"] = KerberosTime.to_asn1(now)
401393

402-
if logging.getLogger().level == logging.DEBUG:
403-
logging.debug("AUTHENTICATOR")
404-
print(authenticator.prettyPrint())
405-
print("\n")
394+
nxc_logger.debug("AUTHENTICATOR")
395+
nxc_logger.debug(authenticator.prettyPrint() + "\n")
406396

407397
encodedAuthenticator = encoder.encode(authenticator)
408398

@@ -452,23 +442,18 @@ def dump(self):
452442

453443
myTicket = ticket.to_asn1(TicketAsn1())
454444
seq_set_iter(reqBody, "additional-tickets", (myTicket,))
455-
if logging.getLogger().level == logging.DEBUG:
456-
logging.debug("Final TGS")
457-
print(tgsReq.prettyPrint())
458-
if logging.getLogger().level == logging.DEBUG:
459-
logging.debug("Final TGS")
460-
print(tgsReq.prettyPrint())
445+
nxc_logger.debug("Final TGS")
446+
nxc_logger.debug(tgsReq.prettyPrint())
461447

462448
message = encoder.encode(tgsReq)
463-
logging.info("Requesting ticket to self with PAC")
449+
nxc_logger.info("Requesting ticket to self with PAC")
464450

465451
r = sendReceive(message, self.__domain, self.__kdcHost)
466452

467453
tgs = decoder.decode(r, asn1Spec=TGS_REP())[0]
468454

469-
if logging.getLogger().level == logging.DEBUG:
470-
logging.debug("TGS_REP")
471-
print(tgs.prettyPrint())
455+
nxc_logger.debug("TGS_REP")
456+
nxc_logger.debug(tgs.prettyPrint())
472457

473458
cipherText = tgs["ticket"]["enc-part"]["cipher"]
474459

nxc/helpers/powershell.py

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
obfuscate_ps_scripts = False
1414

15+
1516
def replace_singles(s):
1617
"""Replaces single quotes with a double quote
1718
We do this because quoting is very important in PowerShell, and we are doing multiple layers:
@@ -27,6 +28,7 @@ def replace_singles(s):
2728
"""
2829
return s.replace("'", r"\"")
2930

31+
3032
def get_ps_script(path):
3133
"""Generates a full path to a PowerShell script given a relative path.
3234
@@ -56,19 +58,6 @@ def encode_ps_command(command):
5658
return b64encode(command.encode("UTF-16LE")).decode()
5759

5860

59-
def is_powershell_installed():
60-
"""
61-
Check if PowerShell is installed.
62-
63-
Returns
64-
-------
65-
bool: True if PowerShell is installed, False otherwise.
66-
"""
67-
if which("powershell"):
68-
return True
69-
return False
70-
71-
7261
def obfs_ps_script(path_to_script):
7362
"""
7463
Obfuscates a PowerShell script.
@@ -90,7 +79,7 @@ def obfs_ps_script(path_to_script):
9079
obfs_script_dir = os.path.join(NXC_PATH, "obfuscated_scripts")
9180
obfs_ps_script = os.path.join(obfs_script_dir, ps_script)
9281

93-
if is_powershell_installed() and obfuscate_ps_scripts:
82+
if bool(which("powershell")) and obfuscate_ps_scripts:
9483
if os.path.exists(obfs_ps_script):
9584
nxc_logger.display("Using cached obfuscated Powershell script")
9685
with open(obfs_ps_script) as script:
@@ -116,12 +105,11 @@ def obfs_ps_script(path_to_script):
116105
and debug statements from a PowerShell source file.
117106
"""
118107
# strip block comments
119-
stripped_code = re.sub(re.compile("<#.*?#>", re.DOTALL), "", script.read())
108+
stripped_code = re.sub(re.compile(r"<#.*?#>", re.DOTALL), "", script.read())
120109
# strip blank lines, lines starting with #, and verbose/debug statements
121110
return "\n".join([line for line in stripped_code.split("\n") if ((line.strip() != "") and (not line.strip().startswith("#")) and (not line.strip().lower().startswith("write-verbose ")) and (not line.strip().lower().startswith("write-debug ")))])
122111

123112

124-
125113
def create_ps_command(ps_command, force_ps32=False, obfs=False, custom_amsi=None, encode=True):
126114
"""
127115
Generates a PowerShell command based on the provided `ps_command` parameter.
@@ -139,7 +127,7 @@ def create_ps_command(ps_command, force_ps32=False, obfs=False, custom_amsi=None
139127
str: The generated PowerShell command.
140128
"""
141129
nxc_logger.debug(f"Creating PS command parameters: {ps_command=}, {force_ps32=}, {obfs=}, {custom_amsi=}, {encode=}")
142-
130+
143131
if custom_amsi:
144132
nxc_logger.debug(f"Using custom AMSI bypass script: {custom_amsi}")
145133
with open(custom_amsi) as file_in:
@@ -154,7 +142,7 @@ def create_ps_command(ps_command, force_ps32=False, obfs=False, custom_amsi=None
154142
command = amsi_bypass + f"$functions = {{function Command-ToExecute{{{amsi_bypass + ps_command}}}}}; if ($Env:PROCESSOR_ARCHITECTURE -eq 'AMD64'){{$job = Start-Job -InitializationScript $functions -ScriptBlock {{Command-ToExecute}} -RunAs32; $job | Wait-Job | Receive-Job }} else {{IEX '$functions'; Command-ToExecute}}"
155143
else:
156144
command = f"{amsi_bypass} {ps_command}"
157-
145+
158146
nxc_logger.debug(f"Generated PS command:\n {command}\n")
159147

160148
if obfs:
@@ -163,7 +151,7 @@ def create_ps_command(ps_command, force_ps32=False, obfs=False, custom_amsi=None
163151
while True:
164152
nxc_logger.debug(f"Obfuscation attempt: {obfs_attempts + 1}")
165153
obfs_command = invoke_obfuscation(command)
166-
154+
167155
command = f'powershell.exe -exec bypass -noni -nop -w 1 -C "{replace_singles(obfs_command)}"'
168156
if len(command) <= 8191:
169157
break
@@ -176,11 +164,11 @@ def create_ps_command(ps_command, force_ps32=False, obfs=False, custom_amsi=None
176164
# if we arent encoding or obfuscating anything, we quote the entire powershell in double quotes, otherwise the final powershell command will syntax error
177165
command = f"-enc {encode_ps_command(command)}" if encode else f'"{command}"'
178166
command = f"powershell.exe -noni -nop -w 1 {command}"
179-
167+
180168
if len(command) > 8191:
181169
nxc_logger.error(f"Command exceeds maximum length of 8191 chars (was {len(command)}). exiting.")
182170
exit(1)
183-
171+
184172
nxc_logger.debug(f"Final command: {command}")
185173
return command
186174

@@ -429,4 +417,3 @@ def invoke_obfuscation(script_string):
429417
obfuscated_script = choice(invoke_options)
430418
nxc_logger.debug(f"Script after obfuscation: {obfuscated_script}")
431419
return obfuscated_script
432-

nxc/loaders/moduleloader.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,7 @@ def module_is_sane(self, module, module_path):
4646
self.logger.fail(f"{module_path} missing the on_login/on_admin_login function(s)")
4747
module_error = True
4848

49-
if module_error:
50-
return False
51-
return True
49+
return not module_error
5250

5351
def load_module(self, module_path):
5452
"""Load a module, initializing it and checking that it has the proper attributes"""

0 commit comments

Comments
 (0)