-
Notifications
You must be signed in to change notification settings - Fork 1
Description
前言
最近在看《Python绝技:运用Python成为顶级黑客》一书,被这种技术深深吸引住,但由于年代久远,一些例子已经没有攻击力了,但是了解其思想是很有趣的。书中的第一章主要讲解口令破解技术(也就是密码破解),方法是使用口令字典暴力破解,简单粗暴,来分析一下程序的逻辑并顺便学习一下python。
分析
UNIX密码破解
主程序代码(加了一部分我自己的写法)
# coding:utf-8
# UNIX密码破解脚本
# hash加密口令文件位置: /etc/shadow
# TODO:升级脚本,增加破解SHA-512 hash的功能(hashlib)
import crypt
import os
current_path = os.path.dirname(os.path.abspath(__file__))
passwords_path = os.path.abspath(os.path.join(current_path, 'passwords.txt'))
dictionary_path = os.path.abspath(os.path.join(current_path, 'dictionary.txt'))
def testPass(cryptPass):
global dictionary_path
# 将加密口令的前两个字符视为salt
salt = cryptPass[0:2]
dictFile = open(dictionary_path, 'r')
for word in dictFile.readlines():
word = word.strip('\n')
cryptWord = crypt.crypt(word, salt)
if (cryptWord == cryptPass):
print('Found Password: ' + word + '\n')
return
print('Password Not Found')
def main():
global passwords_path
passwordFile = open(passwords_path)
for line in passwordFile.readlines():
if ':' in line:
user = line.split(':')[0]
cryptPass = line.split(':')[1].strip(' ')
cryptPass = cryptPass.strip('\n')
print("Checking Password For: " + user)
testPass(cryptPass)
if __name__ == "__main__":
main()
运行这个脚本需要一个待破解的加密口令文件与口令字典文件的配合,自己模拟创建两个来方便测试就行。先从主函数 main() 入手,打开加密口令文件,逐行遍历(加密口令文件的结构要一致username:password),并取出加密口令,使用 testPass 函数去尝试暴力破解它。
Unix 口令是使用 crypt 函数加密,它是一种单向不可逆的加密过程,所以并没有对应的解密函数,crypt 函数需要的参数是待加密的明文和 salt 密钥。由于 crtpt 在相同参数下加密的结果是一样的,所以根据这一特性可以使用暴力破解,一个一个去试,很麻烦就是了。但在计算机计算性能越来越高的当下,如果破解的价值非常高的话,花点时间去暴力破解也是值得的,所以还是不安全。
salt密钥默认使用DES加密方法。DES加密时,salt只能取两个字符,多出的字符会被丢弃(百度原话)。此脚本就是默认salt是使用DES加密来暴力破解的,取加密口令的前两个字符为salt,所以还是有局限性。
看回 testPass 函数,从口令字典文件中逐一取出口令并 crypt 加密然后对比加密口令来找出正确的口令。注意逐行读取字典文件要过滤掉换行符 \n 和空格。
zip口令破解
zip压缩包口令的暴力破解,基本原理和上面一样,一样读取口令字典去一个个试。不同的是这个脚本加入了解析命令行的标志和参数来动态配置口令字典文件和加密zip文件,而且还使用多线程去解密,节省时间。主程序如下:
# coding:utf-8
# zip口令破解脚本
# -f 待破解zip文件
# -d 口令字典文件
import optparse
import zipfile
import sys
import os
from threading import Thread
# 解析标志和参数
parser = optparse.OptionParser("usage prog " + "-f <zipfile> -d <dictionary>")
parser.add_option('-f', dest='zname', type='string', help='specify zip file')
parser.add_option('-d', dest='dname', type='string', help='specify zip file')
(options, args) = parser.parse_args()
if (options.zname == None) | (options.dname == None):
print(parser.usage)
exit(0)
else:
zname = options.zname
dname = options.dname
# 检测文件是否存在
if not os.path.isfile(zname):
print('filename: ' + zname + ' does not exist')
exit(0)
if not os.path.isfile(dname):
print('filename: ' + dname + ' does not exist')
exit(0)
current_path = os.path.dirname(os.path.abspath(__file__))
zip_path = os.path.abspath(os.path.join(current_path, zname))
dictionary_path = os.path.abspath(os.path.join(current_path, dname))
def extractFile(zFile, password):
try:
zFile.extractall(pwd=password)
print('Found Password = ' + password + '\n')
except Exception:
pass
def main():
global zip_path, dictionary_path
zFile = zipfile.ZipFile(zip_path)
passFile = open(dictionary_path)
for line in passFile.readlines():
password = line.strip('\n')
t = Thread(target=extractFile, args=(zFile, password))
t.start()
if __name__ == '__main__':
main()
解密过程主要是引用 zipfile 包的 extractall 方法,略过。
解析命令行输入的参数,可以直接使用 sys 模块来实现,sys.argv 是一个列表,存储着命令行执行程序时输入的参数,这样可以根据固定的顺序来获取需要的数据,如第一个参数是要破解的 zip 文件,第二个参数是字典文件,则取相应的 sys.argv[0] 或 sys.argv[1] 就好了。不好的地方是如果顺序传错了,第一个参数传的是字典文件就会解析不了,容错率很低。所以使用带标志的传参则灵活得多,如 -f 文件名,就是指待破解的zip文件,-d 则代表字典文件,这样就无关顺序,没那么容易出错了,当然,硬是传 -f 字典文件名,出错也没办法了。方法是使用 optparse 模块,具体实现见程序代码。
使用 threading 模块实现多线程解密,从字典文件中每读一行口令就创建一个线程来执行解密操作,资源利用最大化。一个线程读文件的同时另一个线程解密,解密速度大大提高,对于这种大规模计算的程序来说必不可少。
(以上例子的源码文件可以在此项目的 Hacker 文件夹下找到,链接:Unix密码破解 , zip密码破解)
总结
暴力破解这种技术历史的确悠久,但至今仍是一种比较实用的破解方式。所以许多网站或程序为了防止暴力破解而衍生出验证码,限制错误次数等应对的方式。技术在竞争和对抗中进步,不要停止思考,keep hacking!