Python 网络扫描器

端口扫描可以定义为一种监视技术,用于定位特定主机上可用的开放端口。网络管理员、渗透测试人员或黑客可以使用此技术。我们可以根据需要配置端口扫描器,以从目标系统获取最大信息。

现在,考虑一下运行端口扫描后我们可以获得的信息 −

  • 有关开放端口的信息。

  • 有关每个端口上运行的服务的信息。

  • 有关目标主机的操作系统和 MAC 地址的信息。

端口扫描就像一个小偷想进入一所房子,检查每一扇门和窗户,看看哪些是开着的。如前所述,用于通过互联网进行通信的 TCP/IP 协议套件由两种协议组成,即 TCP 和 UDP。两种协议都有 0 到 65535 个端口。我们总是建议关闭系统中不必要的端口,因此实际上有超过 65000 个门(端口)需要锁定。这 65535 个端口可分为以下三个范围 −

  • 系统或知名端口:从 0 到 1023

  • 用户或注册端口:从 1024 到 49151

  • 动态或私有端口:全部 > 49151

使用套接字的端口扫描器

在上一章中,我们讨论了什么是套接字。现在,我们将使用套接字构建一个简单的端口扫描器。以下是使用套接字进行端口扫描的 Python 脚本 −

from socket import *
import time
startTime = time.time()

if __name__ == '__main__':
   target = input('Enter the host to be scanned: ')
   t_IP = gethostbyname(target)
   print ('Starting scan on host: ', t_IP)
   
   for i in range(50, 500):
      s = socket(AF_INET, SOCK_STREAM)
      
      conn = s.connect_ex((t_IP, i))
      if(conn == 0) :
         print ('Port %d: OPEN' % (i,))
      s.close()
print('Time taken:', time.time() - startTime)

当我们运行上述脚本时,它会提示输入主机名,您可以提供任何主机名,例如任何网站的名称,但要小心,因为端口扫描可能被视为或被解释为犯罪。我们绝不应该在没有获得目标服务器或计算机所有者的明确书面许可的情况下对任何网站或 IP 地址执行端口扫描。端口扫描类似于去某人的家里检查他们的门窗。这就是为什么建议在本地主机或您自己的网站(如果有)上使用端口扫描器的原因。

输出

上述脚本生成以下输出 −

Enter the host to be scanned: localhost
Starting scan on host: 127.0.0.1
Port 135: OPEN
Port 445: OPEN
Time taken: 452.3990001678467

输出显示,在 50 到 500 的范围内(如脚本中提供的),此端口扫描器发现两个端口 - 端口 135 和 445,打开。我们可以更改此范围并检查其他端口。

使用 ICMP 的端口扫描器(网络中的实时主机)

ICMP 不是端口扫描,但它用于 ping 远程主机以检查主机是否正常运行。当我们必须检查网络中的多个实时主机时,此扫描非常有用。它涉及向主机发送 ICMP ECHO 请求,如果该主机处于实时状态,它将返回 ICMP ECHO 回复。

使用 ICMP 的端口扫描器

上述发送 ICMP 请求的过程也称为 ping 扫描,由操作系统的 ping 命令提供。

Ping 扫描的概念

实际上,从某种意义上讲,ping 扫描也称为 ping 扫描。唯一的区别是 ping 扫描是在特定网络范围内查找多台机器可用性的过程。例如,假设我们想要测试完整的 IP 地址列表,那么通过使用 ping 扫描(即操作系统的 ping 命令),逐个扫描 IP 地址将非常耗时。这就是为什么我们需要使用 ping 扫描脚本。以下是使用 ping 扫描查找活动主机的 Python 脚本 −

import os
import platform

from datetime import datetime
net = input("Enter the Network Address: ")
net1= net.split('.')
a = '.'

net2 = net1[0] + a + net1[1] + a + net1[2] + a
st1 = int(input("Enter the Starting Number: "))
en1 = int(input("Enter the Last Number: "))
en1 = en1 + 1
oper = platform.system()

if (oper == "Windows"):
   ping1 = "ping -n 1 "
elif (oper == "Linux"):
   ping1 = "ping -c 1 "
else :
   ping1 = "ping -c 1 "
t1 = datetime.now()
print ("Scanning in Progress:")

for ip in range(st1,en1):
   addr = net2 + str(ip)
   comm = ping1 + addr
   response = os.popen(comm)
   
   for line in response.readlines():
      if(line.count("TTL")):
         break
      if (line.count("TTL")):
         print (addr, "--> Live")
         
t2 = datetime.now()
total = t2 - t1
print ("Scanning completed in: ",total)

上述脚本分为三个部分。首先,它通过将 IP 地址拆分为多个部分来选择要 ping 扫描的 IP 地址范围。然后使用函数,该函数将根据操作系统选择 ping 扫描命令,最后给出有关主机的响应以及完成扫描过程所需的时间。

输出

上述脚本生成以下输出 −

Enter the Network Address: 127.0.0.1
Enter the Starting Number: 1
Enter the Last Number: 100

Scanning in Progress:
Scanning completed in: 0:00:02.711155

上述输出显示没有活动端口,因为防火墙已打开并且 ICMP 入站设置也已禁用。更改这些设置后,我们可以获得输出中提供的 1 到 100 范围内的活动端口列表。

使用 TCP 扫描的端口扫描程序

要建立 TCP 连接,主机必须执行三次握手。请按照以下步骤执行操作 −

步骤 1 − 带有 SYN 标志的数据包

在此步骤中,尝试发起连接的系统从设置了 SYN 标志的数据包开始。

步骤 2 − 带有 SYN-ACK 标志的数据包

在此步骤中,目标系统返回设置了 SYN 和 ACK 标志的数据包。

步骤 3 − ACK 标志已设置的数据包

最后,发起系统将向原始目标系统返回一个设置了 ACK 标志的数据包。

然而,这里出现的问题是,如果我们可以使用 ICMP 回显请求和回复方法(ping 扫描扫描仪)进行端口扫描,那么为什么我们需要 TCP 扫描?其背后的主要原因是,如果我们关闭 ICMP ECHO 回复功能或对 ICMP 数据包使用防火墙,那么 ping 扫描扫描仪将不起作用,我们需要 TCP 扫描。

import socket
from datetime import datetime
net = input("Enter the IP address: ")
net1 = net.split('.')
a = '.'

net2 = net1[0] + a + net1[1] + a + net1[2] + a
st1 = int(input("Enter the Starting Number: "))
en1 = int(input("Enter the Last Number: "))
en1 = en1 + 1
t1 = datetime.now()

def scan(addr):
   s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
   socket.setdefaulttimeout(1)
   result = s.connect_ex((addr,135))
   if result == 0:
      return 1
   else :
      return 0

def run1():
   for ip in range(st1,en1):
      addr = net2 + str(ip)
      if (scan(addr)):
         print (addr , "is live")
         
run1()
t2 = datetime.now()
total = t2 - t1
print ("Scanning completed in: " , total)

上述脚本分为三个部分。它通过将 IP 地址拆分为多个部分来选择要 ping 扫描的 IP 地址范围。然后使用一个函数来扫描地址,该函数进一步使用套接字。之后,它会给出有关主机的响应以及完成扫描过程所需的时间。result = s. connect_ex((addr,135)) 语句返回错误指示器。如果操作成功,则错误指示器为 0,否则,它是 errno 变量的值。这里,我们使用了端口 135;此扫描仪适用于 Windows 系统。另一个将在这里工作的端口是 445(Microsoft-DSActive Directory),通常是打开的。

输出

上述脚本生成以下输出 −

Enter the IP address: 127.0.0.1
Enter the Starting Number: 1
Enter the Last Number: 10

127.0.0.1 is live
127.0.0.2 is live
127.0.0.3 is live
127.0.0.4 is live
127.0.0.5 is live
127.0.0.6 is live
127.0.0.7 is live
127.0.0.8 is live
127.0.0.9 is live
127.0.0.10 is live
Scanning completed in: 0:00:00.230025

线程化端口扫描器可提高效率

正如我们在上述案例中所见,端口扫描可能非常慢。例如,您可以看到使用套接字端口扫描器扫描端口 50 到 500 所需的时间为 452.3990001678467。为了提高速度,我们可以使用线程。以下是使用线程的端口扫描器的示例 −

import socket
import time
import threading

from queue import Queue
socket.setdefaulttimeout(0.25)
print_lock = threading.Lock()

target = input('Enter the host to be scanned: ')
t_IP = socket.gethostbyname(target)
print ('Starting scan on host: ', t_IP)

def portscan(port):
   s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
   try:
      con = s.connect((t_IP, port))
      with print_lock:
         print(port, 'is open')
      con.close()
   except:
      pass

def threader():
   while True:
      worker = q.get()
      portscan(worker)
      q.task_done()
      
q = Queue()
   startTime = time.time()
   
for x in range(100):
   t = threading.Thread(target = threader)
   t.daemon = True
   t.start()
   
for worker in range(1, 500):
   q.put(worker)
   
q.join()
print('Time taken:', time.time() - startTime)

在上述脚本中,我们需要导入 Python 包中内置的 threading 模块。我们使用线程锁定概念 thread_lock = threading.Lock() 来避免一次进行多次修改。基本上,threading.Lock() 将允许单个线程一次访问变量。因此,不会发生双重修改。

稍后,我们定义一个 threader() 函数,该函数将从 worker for 循环中获取工作(端口)。然后调用 portscan() 方法连接到端口并打印结果。端口号作为参数传递。任务完成后,将调用 q.task_done() 方法。

现在运行上述脚本后,我们可以看到扫描 50 到 500 个端口的速度差异。它只花费了 1.3589999675750732 秒,远少于套接字端口扫描器扫描本地主机相同数量端口所花费的 452.3990001678467 秒。

输出

上述脚本生成以下输出 −

Enter the host to be scanned: localhost
Starting scan on host: 127.0.0.1
135 is open
445 is open
Time taken: 1.3589999675750732