Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__pycache__
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

## Features
- Automatic hash type identification
- Supports MD5, SHA1, SHA256, SHA384, SHA512
- Supports MD5, NTLM, SHA1, SHA256, SHA384, SHA512
- Can extract & crack hashes from a file
- Can find hashes from a directory, recursively
- Multi-threading
Expand All @@ -40,6 +40,16 @@ After the installation, you will be able to access it with `buster` command.
You don't need to specify the hash type. Hash Buster will identify and *crack* it under 3 seconds.

**Usage:** `buster -s <hash>`

> **NTLM:** NTLM hashes are 32 characters long, exactly like MD5, so they can't be told apart automatically. For any 32-char hash, Hash Buster looks it up as **both** MD5 and NTLM and reports both results:
> ```
> [!] Hash function : MD5 / NTLM
> MD5 : (not found)
> NTLM : password
> ```
> You can also force a single type with `-m` (md5, ntlm, sha1, sha256, sha384, sha512):
> `buster -s <hash> -m ntlm`

### Finding hashes from a directory

Yep, just specify a directory and Hash Buster will go through all the files and directories present in it, looking for hashes.
Expand Down
125 changes: 125 additions & 0 deletions database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import requests
import re
import json
import random
import string
import time
from websocket import create_connection

class Database:

def md5decrypt(hashvalue, hashtype):
"""
md4, md5, sha1, sha256, sha384, sha512
"""
response = requests.post('https://api.md5decrypt.net/', json={
"action": "lookup",
"hashes": [
hashvalue
]
}, headers={
'Authorization': 'Bearer md5d_live_e4453e32_hGr9mR0aETTHqWra6eaz2Q_B6SOYnVvCkLVz1NeRxo0'
}, timeout=15)
if response.status_code == 200:
res = response.json()['results'][0]
# The API auto-detects the hash type; keep only a match whose
# detected type equals the requested one so md5 and ntlm (both
# 32 chars) don't cross over.
if hashtype and res.get('type') and res.get('type') != hashtype:
return False
return res.get('plaintext') or False
else:
return False

def gromweb(hashvalue, hashtype):
"""
md5, sha1
"""
if hashtype == 'md5':
mode = 'md5'
else:
mode = 'hash'
response = requests.get(f'https://{hashtype}.gromweb.com/?{mode}={hashvalue}', headers={'User-Agent': 'Mozilla/5.0'}, timeout=15)
if response.text.find('successfully reversed into the string') > 0:
plain = re.findall(r'<a class="String" href=".+">(.*?)<\/a>', response.text)[0]
return plain
else:
return False

def md5hashing(hashvalue, hashtype):
"""
md5hashing.net via its Meteor DDP-over-SockJS websocket.
Supported: md2, md4, md5, sha1, sha224, sha256, sha384, sha512,
ripemd128/160/256/320, whirlpool, gost, tiger, and more.
"""
deadline = time.monotonic() + 20 # md5hashing.net can be slow to answer
server = random.randint(100, 999)
session = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
url = f'wss://md5hashing.net/sockjs/{server}/{session}/websocket'
try:
ws = create_connection(url, timeout=20,
origin='https://md5hashing.net',
header=['User-Agent: Mozilla/5.0'])
except Exception:
return False
try:
ws.send(json.dumps(['{"msg":"connect","version":"1","support":["1","pre2","pre1"]}']))
ws.send(json.dumps([json.dumps({
'msg': 'method', 'method': 'hash.get',
'params': [hashtype, hashvalue], 'id': '1',
})]))
while True:
remaining = deadline - time.monotonic()
if remaining <= 0:
return False
ws.settimeout(remaining)
frame = ws.recv()
if not frame or frame[0] == 'c': # SockJS close / stream end
break
if frame[0] != 'a': # 'o' open / 'h' heartbeat
continue
for raw in json.loads(frame[1:]):
data = json.loads(raw)
if data.get('msg') == 'result' and data.get('id') == '1':
return (data.get('result') or {}).get('value') or False
if data.get('msg') == 'error':
return False
except Exception:
return False
finally:
ws.close()
return False


def crackcrypt(hashvalue, hashtype):
"""
md5, sha1, sha256, sha512
"""
response = requests.post('https://crackcrypt.com/api/v1/lookup',
json={'hash': hashvalue, 'alg': hashtype}, timeout=15)
if response.status_code == 200:
data = response.json()
if data.get('found'):
return data.get('plaintext')
return False

def weakpass(hashvalue, hashtype):
"""
weakpass.com precomputed lookup. The API auto-detects the hash type and
reports it; we only accept a match whose detected type equals the
requested one, so md5 and ntlm (both 32 chars) don't cross over.
Supported: md5, ntlm, sha1, sha256.
"""
# The API only matches lowercase hex; NTLM hashes are often uppercase.
response = requests.get(f'https://weakpass.com/api/v1/search/{hashvalue.lower()}.json',
timeout=15)
if response.status_code == 200:
data = response.json()
# The API may return a single object or a list of matches.
if isinstance(data, list):
data = data[0] if data else {}
if isinstance(data, dict) and data:
if hashtype and data.get('type') and data.get('type') != hashtype:
return False
return data.get('pass') or False
return False
190 changes: 72 additions & 118 deletions hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@
import re
import urllib3
import os
import requests
import argparse
import concurrent.futures
import websocket
from database import Database

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
parser = argparse.ArgumentParser()
parser.add_argument('-s', help='hash', dest='hash')
parser.add_argument('-f', help='file containing hashes', dest='file')
parser.add_argument('-d', help='directory containing hashes', dest='dir')
parser.add_argument('-t', help='number of threads', dest='threads', type=int)
parser.add_argument('-m', help='force hash type (md5, ntlm, sha1, sha256, sha384, sha512)',
dest='type', choices=['md5', 'ntlm', 'sha1', 'sha256', 'sha384', 'sha512'])
args = parser.parse_args()

#flag
Expand Down Expand Up @@ -42,140 +43,93 @@
if directory:
if directory[-1] == '/':
directory = directory[:-1]
def alpha(hashvalue, hashtype):
cookies = {
'ASP.NET_SessionId': 'be2jpjuviqbaa2mmq1w4h5ci',
}
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0',
'Content-Type': 'application/x-www-form-urlencoded',
}
data = {
'__EVENTTARGET': 'Button1',
'__VIEWSTATE': '6fEUcEEj0b0eN1Obqeu4TSsOBdS0APqz...',
'ctl00$ContentPlaceHolder1$TextBoxInput': hashvalue,
'ctl00$ContentPlaceHolder1$InputHashType': hashtype,
'ctl00$ContentPlaceHolder1$Button1': 'decrypt',
}

response = requests.post('https://www.cmd5.org/', cookies=cookies, headers=headers, data=data)
match = re.search(r'<span id="LabelAnswer"[^>]+?>(.+)</span>', response.text)
if match:
return match.group(1)
return False

def send_message(ws, message):
pattern = r'"value\\":\\([^,]+)'
global found, hashv
ws.send(message)
response = ws.recv()
response2 = ws.recv()
match1 = re.search(pattern,response)

if match1:
x = match1.end()-2
found =1
hashv = response[148:x]
return response[148:x]

def beta(hashvalue, hashtype):
url = "wss://md5hashing.net/sockjs/697/etstxji0/websocket"
ws = websocket.create_connection(url)
connect_message = r'["{\"msg\":\"connect\",\"version\":\"1\",\"support\":[\"1\",\"pre2\",\"pre1\"]}"]'
send_message(ws, connect_message)

# Use str.replace for the method message
base_message = r'["{\"msg\":\"method\",\"method\":\"hash.get\",\"params\":[\"HASH_TYPE\",\"HASH_VALUE\"],\"id\":\"1\"}"]'
method_message = base_message.replace("HASH_TYPE", hashtype).replace("HASH_VALUE", hashvalue)
send_message(ws, method_message)
ls = r'["{\"msg\":\"sub\",\"id\":\"AZnxL9tsZpE6XMTDB\",\"name\":\"meteor_autoupdate_clientVersions\",\"params\":[]}"]'
send_message(ws, ls)
if found:
return hashv
else:
return False



def gamma(hashvalue, hashtype):
response = requests.get('https://www.nitrxgen.net/md5db/' + hashvalue, verify=False).text
if response:
return response
else:
return False

def theta(hashvalue, hashtype):
response = requests.get('https://md5decrypt.net/Api/api.php?hash=%s&hash_type=%s&email=noyile6983@lofiey.com&code=fa9e66f3c9e245d6' % (hashvalue, hashtype)).text
if len(response) != 0:
return response
else:
return False

print (f'''\033[1;97m_ _ ____ ____ _ _ ___ _ _ ____ ___ ____ ____
print ('''\033[1;97m_ _ ____ ____ _ _ ___ _ _ ____ ___ ____ ____
|__| |__| [__ |__| |__] | | [__ | |___ |__/
| | | | ___] | | |__] |__| ___] | |___ | \\ {red}v4.0\033[0m\n''' )

#md5 = [gamma, alpha, beta, theta, delta]
md5 = [alpha,beta,gamma,theta]
sha1 =[alpha,beta,theta]
sha256 = [alpha, beta, theta]
sha384 = [alpha, beta, theta]
sha512 = [alpha, beta, theta]
| | | | ___] | | |__] |__| ___] | |___ | \\ %sv3.0\033[0m\n''' % red)

md5 = [Database.weakpass, Database.md5decrypt, Database.gromweb, Database.md5hashing, Database.crackcrypt]
ntlm = [Database.weakpass, Database.md5decrypt]
sha1 = [Database.weakpass, Database.md5decrypt, Database.gromweb, Database.md5hashing, Database.crackcrypt]
sha256 = [Database.weakpass, Database.md5decrypt, Database.md5hashing, Database.crackcrypt]
sha384 = [Database.md5decrypt, Database.md5hashing]
sha512 = [Database.md5decrypt, Database.md5hashing, Database.crackcrypt]

# Maps a forced hash type (-m) to its service list. NTLM and MD5 share the
# same 32-char length, so it cannot be auto-detected -- use -m ntlm to force it.
apis = {
'md5': md5, 'ntlm': ntlm, 'sha1': sha1,
'sha256': sha256, 'sha384': sha384, 'sha512': sha512,
}
LABELS = {'md5': 'MD5', 'ntlm': 'NTLM', 'sha1': 'SHA1',
'sha256': 'SHA-256', 'sha384': 'SHA-384', 'sha512': 'SHA-512'}

def lookup(api_list, hashvalue, hashtype):
"""Run each service for a hash type, returning the first plaintext found."""
for api in api_list:
r = api(hashvalue, hashtype)
if r:
return r
return False

def crack(hashvalue):
result = False
if len(hashvalue) == 32:
if not file:
print ('%s Hash function : MD5' % info)
for api in md5:
r = api(hashvalue, 'md5')
if r:
return r
elif len(hashvalue) == 40:
if not file:
print ('%s Hash function : SHA1' % info)
for api in sha1:
r = api(hashvalue, 'sha1')
if r:
return r
elif len(hashvalue) == 64:
"""
Return a dict mapping a hash-type label -> cracked plaintext for every
match found, or False if nothing was cracked. A 32-char hash is looked up
as BOTH md5 and ntlm (same length, indistinguishable) so both results are
reported. -m forces a single type.
"""
# -m forces the hash type (e.g. ntlm, which is 32 chars like md5).
if args.type:
if not file:
print ('%s Hash function : SHA-256' % info)
for api in sha256:
r = api(hashvalue, 'sha256')
if r:
return r
elif len(hashvalue) == 96:
print ('%s Hash function : %s (forced)' % (info, LABELS[args.type]))
r = lookup(apis[args.type], hashvalue, args.type)
return {LABELS[args.type]: r} if r else False

length = len(hashvalue)
if length == 32:
if not file:
print ('%s Hash function : SHA-384' % info)
for api in sha384:
r = api(hashvalue, 'sha384')
if r:
return r
elif len(hashvalue) == 128:
print ('%s Hash function : MD5 / NTLM' % info)
# 32-char hashes are ambiguous (md5 vs ntlm) -- report both attempts.
results = {LABELS[ht]: lookup(apis[ht], hashvalue, ht) for ht in ('md5', 'ntlm')}
return results if any(results.values()) else False
elif length in (40, 64, 96, 128):
ht = {40: 'sha1', 64: 'sha256', 96: 'sha384', 128: 'sha512'}[length]
if not file:
print ('%s Hash function : SHA-512' % info)
for api in sha512:
r = api(hashvalue, 'sha512')
if r:
return r
print ('%s Hash function : %s' % (info, LABELS[ht]))
r = lookup(apis[ht], hashvalue, ht)
return {LABELS[ht]: r} if r else False
else:
if not file:
print ('%s This hash type is not supported.' % bad)
quit()
else:
return False
return False

def display_result(res):
"""Multi-line rendering for a single hash on screen (shows every attempt)."""
if len(res) == 1:
return next(iter(res.values()))
return '\n'.join('%s : %s' % (label, plain if plain else '(not found)')
for label, plain in res.items())

def inline_result(res):
"""One-line rendering for file output / multi-hash mode (found results only)."""
found = [(label, plain) for label, plain in res.items() if plain]
if len(res) == 1:
return found[0][1] if found else ''
return ', '.join('%s (%s)' % (plain, label) for label, plain in found)

result = {}

def threaded(hashvalue):
resp = crack(hashvalue)
if resp:
print (hashvalue + ' : ' + resp)
result[hashvalue] = resp
line = inline_result(resp)
print (hashvalue + ' : ' + line)
result[hashvalue] = line

def grepper(directory):
os.system('''grep -Pr "[a-f0-9]{128}|[a-f0-9]{96}|[a-f0-9]{64}|[a-f0-9]{40}|[a-f0-9]{32}" %s --exclude=*.{png,jpg,jpeg,mp3,mp4,zip,gz} |
os.system('''grep -Pr "[a-f0-9]{128}|[a-f0-9]{96}|[a-f0-9]{64}|[a-f0-9]{40}|[a-f0-9]{32}" %s --exclude=\\*.{png,jpg,jpeg,mp3,mp4,zip,gz} |
grep -Po "[a-f0-9]{128}|[a-f0-9]{96}|[a-f0-9]{64}|[a-f0-9]{40}|[a-f0-9]{32}" >> %s/%s.txt''' % (directory, cwd, directory.split('/')[-1]))
print ('%s Results saved in %s.txt' % (info, directory.split('/')[-1]))

Expand All @@ -200,7 +154,7 @@ def miner(file):
def single(args):
result = crack(args.hash)
if result:
print (good ,result)
print (display_result(result))
else:
print ('%s Hash was not found in any database.' % bad)

Expand Down
Loading