python网络渗透之:扫描探测篇
本篇文章主要介绍Sacpy的内容通过Scapy如何编写一个简单的ping扫描工具,基于存活的主机通过socket编写一个TCP端口扫描器进行端口扫描探测,看看python在网络渗透的信息收集中起到的作用。
神器介绍:Scapy
1.Scapy的简介
关于Scapy,Scapy是一个python程序,它允许用户发送、嗅探、解析和伪造网络数据包。此程序允许构建可以探测、扫描、或攻击网络的工具。
换句话说,Scapy是一个强大的交互式数据包操作程序。它能够伪造或解码多种协议的数据包,在网上发送数据包,捕获数据包,匹配请求和回复,等等。Scapy可以轻松处理大多数经典任务,如:扫描、跟踪路由、探测、单元测试、攻击或网络发现。他可以取代hping、arpsopof、arp-sk、arping、p0f,甚至是nmap、tcpdump和tshark的某些部分。可以说Scapy是我们网络渗透中的一个神器
2.Scapy的安装
在具有python的环境下使用pip来安装:pip install scapy
3.开启Scapy
在命令模式下键入Scapy
来启动Scapy的交互式界面,该界面是一个基于python命令行的界面,可以看到显示效果如下:
4.初步尝试
- 前面介绍过了,Scapy是一个强大的数据包操作程序,它可以允许用户发送、嗅探、解析和伪造网络数据包。
那么我们先来尝试构造一个IP数据包,我们尝试让一个data变量来接收:
接下来我们使用show()
方法来显示该数据包:
IP数据包的格式如图:
根据上面ip数据包的格式图以及解释我们大致就可以知道这个IP数据包的基本内容了。
查看了我们构造的ip数据包可以看到,我们并没有向数据包中添加任何的数据,但Scapy会会帮助我们自动填充数据据内容。
接下来我们向data的IP数据包中添加目的地址,我们不用尝试添加源地址因为Scapy会帮我们填入,并且再加上一个ICMP的数据包(因为ICMP 数据包是用于诊断网络问题的,当我发送了ICMP数据包该服务器就会发送ICMP响应数据包给我)使用sr1()
来发送用send_receive_1来接收:
查看该响应数据包:
我们可以使用getlayer()
方法来提取IP数据包里的数据,同时还可以查看fields
里的字典数据内容:
-
解释发送数据包的方法:
sr(send_receive):发送一个三层数据包,等待接收一个或多个数据包的响应 sr1(send_receive_1):发送三层数据包,等待接收一个数据包回应,不论给了我多少个回应我就只收1个回应 srp:发送二层数据包,并等待接收一个或多个数据包的响应 send:仅仅发送三层的数据包 sendp:仅仅发送二层数据包
编写一个简单的ping扫描工具
前面我们已经介绍了一个神器:Scapy,并且简单的讲解了Scapy如何构造一个数据包将数据包进行发送。
那么我们现在就使用python来编写一个简单的ping扫描工具。
ping扫描的介绍
ping扫描是一种简单且有效的网络扫描技术用于确定网络中哪些主句处于存活状态。它通过向目标主机发送ICMP请求数据包来工作。如果该目标主句收到数据包并且响应,则该主机处于活动状态。如果目标主机没有收到数据包或没有响应,则该主机处于非活动状态。
编写代码:
- 可以看到我们首先构造了一个IP/ICMP的数据包它们是工作在第三层也就是网络层协议上的。
- 在IP()数据包中,我们填写了src源地址、id标识符、dst目的地址、ttl生命周期值
- 在ICMP()数据包中,我们填写了id标识符、seq序列号
- 我们将构造好的数据包赋值给了data_package
- 接下来我们使用sr1来发送第三层数据包,并且只接收一个响应
执行效果:
可以看到,当我们将数据包发送给目的地址192.168.64.131,很快192.168.64.131就发送了响应数据包,说明我们ping成功了。
接下来我们对该脚本进行一个改造,使其达到一个更好的效果
编写代码:
from scapy.layers.inet import IP, ICMP, sr1
import time
import sys
import random
import ipaddress
def scapy_ping_scan(ip_dst):
time.sleep(1)
for i in range(5):
start_time =time.time()
#构造随机的数值
random_IP_id = random.randint(1, 9999)
random_IP_ttl = random.randint(1, 64)
random_ICMP_id = random.randint(1, 9999)
random_ICMP_seq = random.randint(1, 9999)
data_package = IP(dst=ip_dst, id=random_IP_id, ttl=random_IP_ttl) / ICMP(id=random_ICMP_id,seq=random_ICMP_seq, ) / b'hello' # 空格数据发送解析失败引发异常
ping_data_package = sr1(data_package,timeout=5,verbose=False)
if ping_data_package:
time.sleep(1)
IP_dst = (ping_data_package.getlayer(IP)).fields['src']
IP_ttl = ping_data_package.getlayer(IP).fields['ttl']
ICMP_seq = ping_data_package.getlayer(ICMP).fields['seq']
else:
print(f"[-] An exception occurred during the request, possibly due to the target IP: {ip_dst} not surviving !" + "\r\n")
time.sleep(1)
break
stop_time = time.time()
times = stop_time-start_time #计算延时
print(f"[+] dst --> {IP_dst} ttl is {IP_ttl} seq is {ICMP_seq} delayed is {times}"+"\r\n")
time.sleep(1)
if __name__ == '__main__':
try:
if sys.argv[1] == '-d':
scapy_ping_scan(sys.argv[2])
elif sys.argv[1] == '-n' and sys.argv[3] == '-H':
scan_ip = sys.argv[2]
scan_ip_list = ipaddress.ip_network(str(scan_ip)+".0/24") #拼接网络号开启争对255个IP的扫描
print("[+] ping start ok !" + "\r\n")
batch_ip_list = []
for src_ip in scan_ip_list:
batch_ip_list.append(src_ip)#将该网络下的所有IP填入IP列表
#提取用户输入的x/y中的x与y转换成整型之后片切IP列表提取指定范围的IP
for src_ip in batch_ip_list[int(sys.argv[4].split("/")[0]):int(sys.argv[4].split("/")[1])+1]:
scapy_ping_scan(str(src_ip)) #开启扫描
elif sys.argv[1] == '-h':
raise TypeError
else:
print("[*] please input scapy_ping.py in show help !")
except:
print("""
|---------------------------------+
| SCAPY_PING |
|---------------------------------+
| -d | appoint IP scan |
|---------------------------------+
| -n [net] -H host | batch IP scan|
| demo : -n 192.168.1 -H 0/255 |
| [key net ip and host] |
|---------------------------------+
""")
- 先来解释一下:
这段代码是一个基于Scapy库的Ping扫描器,可以用于扫描指定IP地址或者指定网段下的所有IP地址是否存活。以下是代码的详细解释和注释:
导入所需的库
import time
import sys
import random
import ipaddress
from scapy.layers.inet import IP, ICMP, sr1
定义Ping扫描函数
def scapy_ping_scan(ip_dst):
time.sleep(1) # 等待1秒钟
for i in range(5): # 发送5个Ping包
start_time =time.time() # 记录开始时间
# 构造随机的IP ID和TTL,以及ICMP ID和序列号
random_IP_id = random.randint(1, 9999)
random_IP_ttl = random.randint(1, 64)
random_ICMP_id = random.randint(1, 9999)
random_ICMP_seq = random.randint(1, 9999)
# 构造Ping包
data_package = IP(dst=ip_dst, id=random_IP_id, ttl=random_IP_ttl) / ICMP(id=random_ICMP_id,seq=random_ICMP_seq, ) / b'hello'
# 发送Ping包并等待响应,时时间为5秒
ping_data_package = sr1(data_package,timeout=5,verbose=False)
if ping_data_package: # 如果收到响应
time.sleep(1) # 等待1秒钟
IP_dst = (ping_data_package.getlayer(IP)).fields['src'] # 获取响应的源IP地址
IP_ttl = ping_data_package.getlayer(IP).fields['ttl'] # 获取响应的TTL值
ICMP_seq = ping_data_package.getlayer(ICMP).fields['seq'] # 获取响应的ICMP序列号
else: # 如果未收到响应
print(f"[-] An exception occurred during the request, possibly due to the target IP: {ip_dst} not surviving !" + "\r\n")
time.sleep(1) # 等待1秒钟
break # 跳出循环
stop_time = time.time() # 记录结束时间
times = stop_time-start_time # 计算延时
# 输出Ping结果
print(f"[+] dst --> {IP_dst} ttl is {IP_ttl} seq is {ICMP_seq} delayed is {times}"+"\r\n")
time.sleep(1) # 等待1秒钟
判断命令行参数并执行相应的操作
if __name__ == '__main__':
try:
if sys.argv[1] == '-d': # 如果命令行参数为-d,表示指定IP地址进行扫描
scapy_ping_scan(sys.argv[2])
elif sys.argv[1] == '-n' and sys.argv[3] == '-H': # 如果命令行参数为-n和-H,表示指定网段进行扫描
scan_ip = sys.argv[2]
scan_ip_list = ipaddress.ip_network(str(scan_ip)+".0/24") # 构造IP地址列表
print("[+] ping start ok !" + "\r\n")
batch_ip_list = []
for src_ip in scan_ip_list:
batch_ip_list.append(src_ip) # 将该网络下的所有IP填入IP列表
# 以"/"为分隔符提取用户输入的x/y中的x与y转换成整型之后片切IP列表提取指定范围的IP,遍历该片切IP列表中的IP传给src_ip来进行扫描
for src_ip in batch_ip_list[int(sys.argv[4].split("/")[0]):int(sys.argv[4].split("/")[1])+1]:
scapy_ping_scan(str(src_ip)) # 开启扫描
elif sys.argv[1] == '-h': # 如果命令行参数为-h,显示帮助信息
raise TypeError
else: # 如果命令行参数不正确,输出提示信息
print("[*] please input scapy_ping.py in show help !")
except: # 如果发生异常,显示帮助信息
print("""
|---------------------------------+
| SCAPY_PING |
|---------------------------------+
| -d | appoint IP scan |
|---------------------------------+
| -n [net] -H host | batch IP scan|
| demo : -n 192.168.1 -H 0/255 |
| [key net ip and host] |
|---------------------------------+
""")
运行效果:
可以看到,程序正常扫描出了指定的IP存活主机和IP网段主机,并显示了扫描结果。那么存活主机扫描完了,我们就可以对存活的主机进行端口扫描,看看目标主机开放了哪些端口。
编写一个简单的TCP端口扫描器
其实TCP端口扫描器也可以用Scapy来进行编写,但这次我们使用Socket来编写。
Socket简介:
所谓的socket,中文翻译就是“套接字”,是计算机网络中进程间通信的基本数据结构,它是应用层与传输层之间的抽象层。套接字在应用层提供一个接口,应用程序可以通过套接字进行网络通信。
socket 的类型有以下几种:
- AF_INET:这表示使用 IPv4 协议。
- AF_INET6:这表示使用 IPv6 协议。
- AF_UNIX:这表示使用 Unix 域协议。
socket 的协议有以下几种:
- SOCK_STREAM:这表示使用面向连接的 socket。
- SOCK_DGRAM:这表示使用无连接的 socket。
- SOCK_RAW:这表示使用原始 socket
以下是socket的一个描述图:
开始编写代码之前我们先列出具体的方案:
-
这个端口扫描器,可以扫描指定 IP 地址的指定端口或所有端口。
-
首先,我们需要导入 socket、sys、threading 和 time 模块。socket 模块提供了网络编程的接口,sys 模块提供了系统相关的函数,threading模块提供了多线程服务,time 模块提供了时间相关的函数。
-
然后,我们定义一个
Scan_Port
类,这样方便我们维护管理,这个类包含了扫描端口的所有方法,以及显示使用信息和log界面的方法。 -
在
Scan_Port
类中,我们定义show_log
和help_option
两个方法。show_log
方法用于显示程序的log界面,help_option
方法用于显示程序的使用方法。 -
在
Scan_Port
类中,我们定义scan
方法。scan
方法用于扫描指定 IP 地址的指定端口或所有端口。 -
在
main
函数中,我们创建一个Scan_Port
对象。然后,我们根据用户的输入,调用不同的方法来扫描端口。 -
我们在
main
函数中捕获异常,并在异常发生时显示错误信息。
注:程序log界面我们使用figlet
来生成
编写代码
import socket
import sys
import time
from threading import Thread
class Scan_Port():
def __init__(self):
self.sock_tcp = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
def show_log(self):
LOG_ = """
____ ____ _
/ ___| ___ __ _ _ __ | _ \ ___ _ __| |_
\___ \ / __/ _` | '_ \ | |_) / _ \| '__| __|
___) | (_| (_| | | | | | __/ (_) | | | |_
|____/ \___\__,_|_| |_| |_| \___/|_| \__|
"""
return LOG_
def help_option(self):
HELP_ = """
++++++++++++++++++++++++++++++++
-p : appoint scan | -p [IP:PORT]
--------------------------------
-P : batch scan | -P [IP]
++++++++++++++++++++++++++++++++
"""
return HELP_
def scan(self,sc_ip,sc_port,options):
self.__init__() #创建套接字
try:
self.sock_tcp.connect((sc_ip,sc_port))
print(f"[+] {sc_ip}--->{sc_port} is start !")
except:
if options == "p": #只有指定的扫描端口不开放才显示端口不开放信息
print(f"[-] {sc_ip}--->({sc_port}) is stop !")
time.sleep(1)
else: #避免批量扫描产生过量的垃圾信息,所以批量扫描不显示端口不开放信息,只显示端口开放时的信息
pass
self.sock_tcp.close() #关闭套接字
def main():
SP = Scan_Port()
try:
if sys.argv[1] == "-p":
SP.scan(sys.argv[2].split(":")[0],int(sys.argv[2].split(":")[1]),"p")
elif sys.argv[1] == "-P":
thread_pool = [] #创建批量扫描线程池
for sc_port in range(0,65536):
sc_thread = Thread(target=SP.scan,args=(sys.argv[2],int(sc_port),"P",))
sc_thread.start()
thread_pool.append(sc_thread) #将开启的线程添加至线程池
for sc_thread in thread_pool:
sc_thread.join() #等待所有的子线程扫描结束后主线再结束
elif sys.argv[1] == '-h':
show_options = SP.help_option()
print(show_options)
else:
print("[-]please input correct options ! or input -h show options !")
except:
if len(sys.argv) == 1:
show_log = SP.show_log()
print(show_log)
else:
print("[-]please input correct options ! or input -h show options !")
if __name__ == '__main__':
main()
运行效果: