Psudohash 密码列表生成器

Psudohash 密码列表生成器

Psudohash简介

psudohash 是一个用于编排暴力攻击的密码列表生成器。它模仿了人类常用的某些密码创建模式,例如用符号或数字替换单词的字母、使用字符大小写变体、在单词之前或之后添加常见的填充等等。它基于关键字且高度可定制。

使用示例

企业环境渗透

系统管理员和其他员工经常使用公司名称的变异版本来设置密码(例如 Am@z0n_2022)。这通常是网络设备(Wi-Fi 接入点、交换机、路由器等)、应用程序甚至域帐户的情况。使用最基本的选项,psudohash 可以根据常见的字符替换模式(可定制)、大小写变化、通常用作填充的字符串等生成一个包含一个或多个关键字的所有可能突变的词表。看看下面的例子:

Psudohash 密码列表生成器

该脚本包括一个基本的字符替换模式。您可以通过编辑源代码并遵循以下数据结构逻辑(默认)来添加/修改字符替换模式:

transformations = [
	{'a' : '@'},
	{'b' : '8'},
	{'e' : '3'},
	{'g' : ['9', '6']},
	{'i' : ['1', '!']},
	{'o' : '0'},
	{'s' : ['$', '5']},
	{'t' : '7'}
]

针对个人渗透

说到人,我认为我们都(或多或少)使用一个或多个对我们有意义的词的突变来设置密码,例如,我们的名字或妻子/孩子/宠物/乐队的名字,坚持我们的那一年出生在最后,或者可能是像“!@#”

Psudohash 密码列表生成器

下载地址

直接保存为Psudohash.py即可

#!/bin/python3
#
# Created by Panagiotis Chartas (t3l3machus)
# https://github.com/t3l3machus

import argparse, sys, itertools

# Colors
MAIN = '\033[38;5;50m'
LOGO = '\033[38;5;41m'
LOGO2 = '\033[38;5;42m'
GREEN = '\033[38;5;82m'
ORANGE = '\033[0;38;5;214m'
PRPL = '\033[0;38;5;26m'
PRPL2 = '\033[0;38;5;25m'
RED = '\033[1;31m'
END = '\033[0m'
BOLD = '\033[1m'

# -------------- Arguments & Usage -------------- #
parser = argparse.ArgumentParser(
	formatter_class=argparse.RawTextHelpFormatter,
	epilog='''
Usage examples:
  Basic:
      python3 psudohash.py -w <keywords> -cpa
	
  Thorough:
      python3 psudohash.py -w <keywords> -cpa -cpb -an 3 -y 1990-2022
'''
	)

parser.add_argument("-w", "--words", action="store", help = "Comma seperated keywords to mutate", required = True)
parser.add_argument("-an", "--append-numbering", action="store", help = "Append numbering range at the end of each word mutation (before appending year or common paddings).\nThe LEVEL value represents the minimum number of digits. LEVEL must be >= 1. \nSet to 1 will append range: 1,2,3..100\nSet to 2 will append range: 01,02,03..100 + previous\nSet to 3 will append range: 001,002,003..100 + previous.\n\n", type = int, metavar='LEVEL')
parser.add_argument("-nl", "--numbering-limit", action="store", help = "Change max numbering limit value of option -an. Default is 50. Must be used with -an.", type = int, metavar='LIMIT')
parser.add_argument("-y", "--years", action="store", help = "Singe OR comma seperated OR range of years to be appended to each word mutation (Example: 2022 OR 1990,2017,2022 OR 1990-2000)")
parser.add_argument("-ap", "--append-padding", action="store", help = "Add comma seperated values to common paddings (must be used with -cpb OR -cpa)", metavar='VALUES')
parser.add_argument("-cpb", "--common-paddings-before", action="store_true", help = "Append common paddings before each mutated word") 
parser.add_argument("-cpa", "--common-paddings-after", action="store_true", help = "Append common paddings after each mutated word") 
parser.add_argument("-cpo", "--custom-paddings-only", action="store_true", help = "Use only user provided paddings for word mutations (must be used with -ap AND (-cpb OR -cpa))") 
parser.add_argument("-o", "--output", action="store", help = "Output filename (default: output.txt)", metavar='FILENAME')
parser.add_argument("-q", "--quiet", action="store_true", help = "Do not print the banner on startup")

args = parser.parse_args()

def exit_with_msg(msg):
	parser.print_help()
	print(f'\n[{RED}Debug{END}] {msg}\n')
	sys.exit(1)	



def unique(l):
  
	unique_list = []

	for i in l:
		if i not in unique_list:
			unique_list.append(i)
    
	return unique_list


# Append numbering
if args.numbering_limit and not args.append_numbering:
	exit_with_msg('Option -nl must be used with -an.')

if args.append_numbering:
	if args.append_numbering <= 0:
		exit_with_msg('Numbering level must be > 0.')

_max = args.numbering_limit + 1 if args.numbering_limit and isinstance(args.numbering_limit, int) else 51


# Create years list		
if args.years:
	
	years = []
	
	if args.years.count(',') == 0 and args.years.count('-') == 0 and args.years.isdecimal() and int(args.years) >= 1000 and int(args.years) <= 3200:
		years.append(str(args.years))

	elif args.years.count(',') > 0:
		for year in args.years.split(','):
			if year.strip() != '' and year.isdecimal() and int(year) >= 1000 and int(year) <= 3200: 
				years.append(year)
			else:
				exit_with_msg('Illegal year(s) input. Acceptable years range: 1000 - 3200.')

	elif args.years.count('-') == 1:
		years_range = args.years.split('-')
		start_year = years_range[0]
		end_year = years_range[1]
		
		if (start_year.isdecimal() and int(start_year) < int(end_year) and int(start_year) >= 1000) and (end_year.isdecimal() and int(end_year) <= 3200):
			for y in range(int(years_range[0]), int(years_range[1])+1):
				years.append(str(y))
		else:
			exit_with_msg('Illegal year(s) input. Acceptable years range: 1000 - 3200.')
	else:
		exit_with_msg('Illegal year(s) input. Acceptable years range: 1000 - 3200.')
			

def banner():
	padding = '  '

	P = [[' ', '┌', '─', '┐'], [' ', '├','─','┘'], [' ', '┴',' ',' ']]
	S = [[' ', '┌','─','┐'], [' ', '└','─','┐'], [' ', '└','─','┘']]
	U = [[' ', '┬',' ','┬'], [' ', '│',' ','│'], [' ', '└','─','┘']]
	D = [[' ', '┌','┬','┐'], [' ', ' ','│','│'], [' ', '─','┴','┘']]
	O =	[[' ', '┌','─','┐'], [' ', '│',' ','│'], [' ', '└','─','┘']]
	H = [[' ', '┐', ' ', '┌'], [' ', '├','╫','┤'], [' ', '┘',' ','└']]	
	A = [[' ', '┌','─','┐'], [' ', '├','─','┤'], [' ', '┴',' ','┴']]
	S = [[' ', '┌','─','┐'], [' ', '└','─','┐'], [' ', '└','─','┘']]
	H = [[' ', '┬',' ','┬'], [' ', '├','─','┤'], [' ', '┴',' ','┴']]

	banner = [P,S,U,D,O,H,A,S,H]
	final = []
	print('\r')
	init_color = 37
	txt_color = init_color
	cl = 0

	for charset in range(0, 3):
		for pos in range(0, len(banner)):
			for i in range(0, len(banner[pos][charset])):
				clr = f'\033[38;5;{txt_color}m'
				char = f'{clr}{banner[pos][charset][i]}'
				final.append(char)
				cl += 1
				txt_color = txt_color + 36 if cl <= 3 else txt_color

			cl = 0

			txt_color = init_color
		init_color += 31

		if charset < 2: final.append('\n   ')

	print(f"   {''.join(final)}")
	print(f'{END}{padding}                        by t3l3machus\n')


# ----------------( Base Settings )---------------- #
mutations_cage = []
basic_mutations = []
outfile = args.output if args.output else 'output.txt'
trans_keys = []

transformations = [
	{'a' : '@'},
	{'b' : '8'},
	{'e' : '3'},
	{'g' : ['9', '6']},
	{'i' : ['1', '!']},
	{'o' : '0'},
	{'s' : ['$', '5']},
	{'t' : '7'}
]

for t in transformations:
	for key in t.keys():
		trans_keys.append(key)

# Paddings
if (args.custom_paddings_only or args.append_padding) and not (args.common_paddings_before or args.common_paddings_after):
	exit_with_msg('Options -ap and -cpo must be used with -cpa or -cpb.')
	
	
elif (args.common_paddings_before or args.common_paddings_after) and not args.custom_paddings_only:

	common_paddings = [
		'!', '@', '#', '$', '%', '^', '&', '*', ',', '.', '?', '-' \
		'123', '234', '345', '456', '567', '678', '789', '890',\
		'!@', '@#', '#$', '$%', '%^', '^&', '&*', '*(', '()', \
		'!@#', '@#$', '#$%', '$%^', '%^&', '^&*', '&*(', '*()', ')_+',\
		'1!1', '2@2', '3#3', '4$4', '5%5', '6^6', '7&7', '8*8', '9(9', '0)0',\
		'@2@', '#3#', '$4$', '%5%', '^6^', '&7&', '*8*', '(9(', \
		'!@!', '@#@', '!@#$%', '1234', '12345', '123456', '123!@#', \
		'!!!', '@@@', '###', '$$$', '%%%', '^^^', '&&&', '***', '(((', ')))', '---', '+++'
	]

elif (args.common_paddings_before or args.common_paddings_after) and (args.custom_paddings_only and args.append_padding):
	common_paddings = []

elif not (args.common_paddings_before or args.common_paddings_after):
	common_paddings = []

else:
	exit_with_msg('\nIllegal padding settings.\n')		

if args.append_padding:
	for val in args.append_padding.split(','):
		if val.strip() != '' and val not in common_paddings: 
			common_paddings.append(val)


if (args.common_paddings_before or args.common_paddings_after):
	common_paddings = unique(common_paddings)

# ----------------( Functions )---------------- #
def evalTransformations(w):
	
	trans_chars = []
	total = 1
	c = 0	
	w = list(w)
	
	for char in w:
		for t in transformations:
			if char in t.keys():
				trans_chars.append(c)
				if isinstance(t[char], list):
					total *= 3
				else:
					total *= 2
		c += 1
			
	return [trans_chars, total]

		

def mutate(tc, word):
	
	global trans_keys, mutations_cage, basic_mutations
	
	i = trans_keys.index(word[tc].lower())
	trans = transformations[i][word[tc].lower()]
	limit = len(trans) * len(mutations_cage)
	c = 0
	
	for m in mutations_cage:
		w = list(m)			

		if isinstance(trans, list):
			for tt in trans:
				w[tc] = tt
				transformed = ''.join(w)
				mutations_cage.append(transformed)
				c += 1
		else:
			w[tc] = trans
			transformed = ''.join(w)
			mutations_cage.append(transformed)
			c += 1
		
		if limit == c: break
		
	return mutations_cage
	


def mutations_handler(kword, trans_chars, total):
	
	global mutations_cage, basic_mutations
	
	container = []
	
	for word in basic_mutations:
		mutations_cage = [word.strip()]	
		for tc in trans_chars:
			results = mutate(tc, kword)
		container.append(results)
	
	for m_set in container:
		for m in m_set:
			basic_mutations.append(m)
	
	basic_mutations = unique(basic_mutations)

	with open(outfile, 'a') as wordlist:		
		for m in basic_mutations:
			wordlist.write(m + '\n')



def mutateCase(word):
	trans = list(map(''.join, itertools.product(*zip(word.upper(), word.lower()))))
	return trans



def caseMutationsHandler(word, mutability):
	
	global basic_mutations
	case_mutations = mutateCase(word)

	for m in case_mutations:
		basic_mutations.append(m)

	if not mutability:
		
		basic_mutations = unique(basic_mutations)
		
		with open(outfile, 'a') as wordlist:		
			for m in basic_mutations:
				wordlist.write(m + '\n')



def append_numbering():
	
	global _max
	first_cycle = True
	previous_list = []
	lvl = args.append_numbering
	
	with open(outfile, 'a') as wordlist:
		for word in basic_mutations:
			for i in range(1, lvl+1):		
				for k in range(1, _max):
					if first_cycle:
						wordlist.write(f'{word}{str(k).zfill(i)}\n')
						wordlist.write(f'{word}_{str(k).zfill(i)}\n')
						previous_list.append(f'{word}{str(k).zfill(i)}')
						
					else:
						if previous_list[k - 1] != f'{word}{str(k).zfill(i)}':
							wordlist.write(f'{word}{str(k).zfill(i)}\n')
							wordlist.write(f'{word}_{str(k).zfill(i)}\n')
							previous_list[k - 1] = f'{word}{str(k).zfill(i)}'

				first_cycle = False
	del previous_list
	


def mutate_years():
	
	current_mutations = basic_mutations.copy()
	
	with open(outfile, 'a') as wordlist:
		for word in current_mutations:
			for y in years:				
				wordlist.write(f'{word}{y}\n')
				wordlist.write(f'{word}_{y}\n')
				wordlist.write(f'{word}{y[2:]}\n')
				basic_mutations.append(f'{word}{y}')
				basic_mutations.append(f'{word}_{y}')
				basic_mutations.append(f'{word}{y[2:]}')		
	
	del current_mutations



def check_underscore(word, pos):
	if word[pos] == '_':
		return True
	else:
		return False
		

def append_paddings_before():

	current_mutations = basic_mutations.copy()
	
	with open(outfile, 'a') as wordlist:
		for word in current_mutations:
			for val in common_paddings:
				wordlist.write(f'{val}{word}\n')
				if not check_underscore(val, -1):
					wordlist.write(f'{val}_{word}\n')
				
					
	del current_mutations



def append_paddings_after():

	current_mutations = basic_mutations.copy()
	
	with open(outfile, 'a') as wordlist:
		for word in current_mutations:
			for val in common_paddings:	
				wordlist.write(f'{word}{val}\n')			
				if not check_underscore(val, 0):
					wordlist.write(f'{word}_{val}\n')
						
	del current_mutations



def calculate_output(keyw):
	
	global trans_keys
	
	c = 0
	total = 1
	basic_total = 1
	basic_size = 0
	size = 0
	numbering_count = 0
	numbering_size = 0
	
	# Basic mutations calc
	for char in keyw:
		if char in trans_keys:
			i = trans_keys.index(keyw[c].lower())
			trans = transformations[i][keyw[c].lower()]
			basic_total *= (len(trans) + 2)		
		else:
			basic_total = basic_total * 2 if char.isalpha() else basic_total
			
		c += 1
	
	total = basic_total 
	basic_size = total * (len(keyw) + 1)
	size = basic_size
	
	# Words numbering mutations calc
	if args.append_numbering:
		global _max
		word_len = len(keyw) + 1
		first_cycle = True
		previous_list = []
		lvl = args.append_numbering
			
		for w in range(0, total):
			for i in range(1, lvl+1):		
				for k in range(1, _max):
					n = str(k).zfill(i)
					if first_cycle:					
						numbering_count += 2						
						numbering_size += (word_len * 2) + (len(n) * 2) + 1
						previous_list.append(f'{w}{n}')
						
					else:
						if previous_list[k - 1] != f'{w}{n}':
							numbering_size += (word_len * 2) + (len(n) * 2) + 1
							numbering_count += 2
							previous_list[k - 1] = f'{w}{n}'

				first_cycle = False

		del previous_list
		
	# Adding years mutations calc
	if args.years:
		patterns = 3
		year_chars = 4
		_year = 5
		year_short = 2
		yrs = len(years)
		size += (basic_size * patterns * yrs) + (basic_total * year_chars * yrs) + (basic_total * _year * yrs) + (basic_total * year_short * yrs)
		total += total * len(years) * 3
		basic_total = total
		basic_size = size
	
	# Common paddings mutations calc
	patterns = 2
	
	if args.common_paddings_after or args.common_paddings_before:
		paddings_len = len(common_paddings)
		pads_wlen_sum = sum([basic_total*len(w) for w in common_paddings])
		_pads_wlen_sum = sum([basic_total*(len(w)+1) for w in common_paddings])
		
		if args.common_paddings_after and args.common_paddings_before:		
			size += ((basic_size * patterns * paddings_len) + pads_wlen_sum + _pads_wlen_sum) * 2
			total += (total * len(common_paddings) * 2) * 2
		
		elif args.common_paddings_after or args.common_paddings_before:
			size += (basic_size * patterns * paddings_len) + pads_wlen_sum + _pads_wlen_sum
			total += total * len(common_paddings) * 2
	
	return [total + numbering_count, size + numbering_size]



def check_mutability(word):
	
	global trans_keys
	m = 0
	
	for char in word:
		if char in trans_keys:
			m += 1
	
	return m



def chill():
	pass



def main():
	
	banner() if not args.quiet else chill()
	
	global basic_mutations, mutations_cage
	keywords = []
	
	for w in args.words.split(','):
		if w.strip().isdecimal():
			exit_with_msg('Unable to mutate digit-only keywords.')
			
		elif w.strip() not in [None, '']:
			keywords.append(w.strip())
	
	# Calculate total words and size of output
	total_size = [0, 0]
	
	for keyw in keywords:
		count_size = calculate_output(keyw.strip().lower())
		total_size[0] += count_size[0]
		total_size[1] += count_size[1]
	
	size = round(((total_size[1]/1000)/1000), 1) if total_size[1] > 100000 else total_size[1]
	prefix = 'bytes' if total_size[1] <= 100000 else 'MB'
	fsize = f'{size} {prefix}'
	
	print(f'[{MAIN}Info{END}] Calculating total words and size...')
	concent = input(f'[{ORANGE}Warning{END}] This operation will produce {BOLD}{total_size[0]}{END} words, {BOLD}{fsize}{END}. Are you sure you want to proceed? [y/n]: ')
	
	if concent.lower() not in ['y', 'yes']:
		sys.exit(f'\n[{RED}X{END}] Aborting.')
		
	else:
		
		open(outfile, "w").close()
		
		for word in keywords:
			print(f'[{GREEN}*{END}] Mutating keyword: {GREEN}{word}{END} ')	
			mutability = check_mutability(word.lower())
					
			# Produce case mutations
			print(f' ├─ Producing character case-based transformations... ')
			caseMutationsHandler(word.lower(), mutability)	
			
			if mutability:
				# Produce char substitution mutations
				print(f' ├─ Mutating word based on commonly used char-to-symbol and char-to-number substitutions... ')
				trans = evalTransformations(word.lower())
				mutations_handler(word, trans[0], trans[1])
				
			else:
				print(f' ├─ {ORANGE}No character substitution instructions match this word.{END}')

			# Append numbering
			if args.append_numbering:
				print(f' ├─ Appending numbering to each word mutation... ')
				append_numbering()
			
			# Handle years
			if args.years:
				print(f' ├─ Appending year patterns after each word mutation... ')
				mutate_years()
			
			# Append common paddings		
			if args.common_paddings_after or args.custom_paddings_only:
				print(f' ├─ Appending common paddings after each word mutation... ')
				append_paddings_after()
				
			if args.common_paddings_before:
				print(f' ├─ Appending common paddings before each word mutation... ')
				append_paddings_before()
			
			basic_mutations = []
			mutations_cage = []
			print(f' └─ Done!')
		
		print(f'\n[{MAIN}Info{END}] Completed! List saved in {outfile}\n')
			

if __name__ == '__main__':
	main()

安装方法

git clone https://github.com/t3l3machus/psudohash
cd ./psudohash
chmod +x psudohash.py
python3 psudohash.py

使用方法

Psudohash 密码列表生成器
用法: psudohash.py [-h] -w WORDS [-an LEVEL] [-nl LIMIT] [-y YEARS] [-ap VALUES] [-cpb] [-cpa] [-cpo] [-o FILENAME] [-q]

选项:
  -h, --help            显示此帮助消息并退出
  -w WORDS, --words WORDS
                        要变异的关键字-逗号分隔
  -an LEVEL, --append-numbering LEVEL
                        在每个单词变异的末尾追加编号范围(在追加年份或公共填充之前)。
                        LEVEL值表示最小位数。LEVEL必须为>= 1。
                        设置为1将附加范围:1,2,3..100
                        设置为2将附加范围:01,02,03..100+之前
                        设置为3将附加范围:001,002,003..100+之前。
                        
  -nl LIMIT, --numbering-limit LIMIT
                        更改选项-an的最大编号限制值。默认是50。必须与-an连用。
  -y YEARS, --years YEARS
                        每一个单词变异后,用逗号分隔的年份或范围(例如:2022 OR 1990,2017,2022 OR 1990-2000)
  -ap VALUES, --append-padding VALUES
                        在普通填充中添加逗号分隔的值(必须与-cpb或-cpa一起使用)
  -cpb, --common-paddings-before
                        在每个变异的单词前添加普通填充
  -cpa, --common-paddings-after
                        在每个变异的单词后添加普通填充
  -cpo, --custom-paddings-only
                        仅对突变使用用户提供的单词填充符(必须与-ap AND (-cpb OR -cpa)一起使用)
  -o FILENAME, --output FILENAME
                        输出文件名(默认值:Output .txt)
  -q, --quiet           启动时不打印横幅

用法示例:

  基本:
      python3 psudohash.py -w <keywords> -cpa

  彻底的:
      python3 psudohash.py -w <keywords> -cpa -cpb -an 3 -y 1990-2022

使用技巧

  1. 由于工具实现的突变模式,组合选项--years--append-numbering用一个--numbering-limit ≥任何年份输入的后两位数字很可能会产生重复的单词.
  2. 如果您在源代码中添加自定义填充值或修改预定义的公共填充值,并结合多个可选参数,则出现重复单词的可能性很小。psudohash 包括单词过滤控件,但为了速度,这些是有限的。

测试截图

我们单纯使用admin来进行生成测试,命令如下:

python3 psudohash.py -w 'admin' -cpa

共计生成了14304个字典列表

Psudohash 密码列表生成器

让我们看看有哪些组合:

Psudohash 密码列表生成器

有点多了,可能缺点就是太全了

项目地址:

https://github.com/t3l3machus/psudohash

转载请注明出处及链接

Leave a Reply

您的邮箱地址不会被公开。 必填项已用 * 标注