【py网安武器库】第二期:开发简易ping工具【原创】

任务目标

1、理解ICMP协议的原理

2、实现代码,尽可能多的实现探测主机是否存活的功能

3.扩展任务:使用多线程技术提升探测速度

实验环境:python3.9 pycharm

前言

来到了的系列分享的第二篇,这次我们还是要从认识一个新的网络协议出发,开发相应的python脚本,并且学习如何使用多线程来提高效率.

ICMP协议

ICMP(Internet Control Message Protocol)Internet控制报文协议。它是TCP/IP协议簇的一个子协议,用于在IP主机路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。ICMP使用IP的基本支持,就像ICMP是一个更高级别的协议,但是,ICMP实际上是IP的一个组成部分,必须由每个IP模块实现。

img

图文来源:百度百科

链接:https://baike.baidu.com/pic/ICMP/572452/0/dbf554ed9529cbccb31cb1d0?fr=lemma&ct=single#aid=0&pic=dbf554ed9529cbccb31cb1d0

icmp工作流程简介

让我们通过手绘的ICMP图了解一下ICMP协议的流程

未命名文件.png

ICMP报文情况

ICMP报文包含在IP数据报中,IP报头在ICMP报文的最前面。一个ICMP报文包括IP报头(至少20字节)、ICMP报头(至少八字节)和ICMP报文(属于ICMP报文的数据部分)。当IP报头中的协议字段值为1时,就说明这是一个ICMP报文。ICMP报头如下图所示。
如下图:

未命名文件 _1_.png

ICMP报文格式具体由RFC 777 [4] ,RFC 792 [2] 规范。

ICMP大概分为两类报文:
一类是通知出错原因 ;一类是用于诊断查询

以下是icmp基本报文类型表:

类型(十进制) 内容
0 回送应答
3 目标不可达
4 原点抑制
5 重定向或改变路由
8 回送请求
9 路由器公告
10 路由器请求
11 超时
17 地址子网请求
18 地址子网应答

常见的icmp报文

相应请求

我们用的ping操作中就包括了相应请求(类型字段值为8)和应答(类型字段值为0)ICMP报文。
过程:
一台主机向一个节点发送一个类型字段值为8的ICMP报文,如果途中没有异常(如果没有被路由丢弃,目标不回应ICMP或者传输失败),则目标返回类型字段值为0的ICMP报文,说明这台主机存在。

目标不可达,源抑制和超时报文

这三种报文的格式是一样的。
(1)目标不可到达报文(类型值为3)在路由器或者主机不能传递数据时使用。
例如:我们要连接对方一个不存在的系统端口(端口号小于1024)时,将返回类型字段值3、代码字段值为3的ICMP报文。
常见的不可到达类型还有网络不可到达(代码字段值为0)、主机不可达到(代码字段值为1)、协议不可到达(代码字段值为2)等等。
(2)源抑制报文(类型字段值为4,代码字段值为0)则充当一个控制流量的角色,通知主机减少数据报流量。由于ICMP没有回复传输的报文,所以只要停止该报文,主机就会逐渐恢复传输速率。
(3)无连接方式网络的问题就是数据报会丢失,或者长时间在网络游荡而找不到目标,或者拥塞导致主机在规定的时间内无法重组数据报分段,这时就要触发ICMP超时报文的产生。
超时报文(类型字段值为11)的代码域有两种取值:代码字段值为0表示传输超时,代码字段值为1表示分段重组超时。

时间戳请求

时间戳请求报文(类型值字段13)和时间戳应答报文(类型值字段14)用于测试两台主机之间数据报来回一次的传输时间。
传输时,主机填充原始时间戳,接受方收到请求后填充接受时间戳后以类型值字段14的报文格式返回,发送方计算这个时间差。
(有些系统不响应这种报文)

脚本开发:实现探测主机存活

我们可以利用python自带的scapy模块中的内容实现ICMP协议的请求和答复

Scapy学习

Scapy是一个可以让用户发送、侦听和解析并伪装网络报文的python程序。这些功能可以用于制作侦测、扫描和攻击网络的工具。

换言之,Scapy 是一个强大的操纵报文的交互程序。它可以伪造或者解析多种协议的报文,还具有发送、捕获、匹配请求和响应这些报文以及更多的功能。Scapy 可以轻松地做到像扫描(scanning)、路由跟踪(tracerouting)、探测(probing)、单元测试(unit tests)、攻击(attacks)和发现网络(network discorvery)这样的传统任务。它可以代替hping,arpspoof,arp-sk,arping,p0f 甚至是部分的Namp,tcpdumptshark 的功能。

scapy信息

官网:scapy

用户手册:scapy docs

函数汇总

函数 作用
数据包 生成数据包
IP IP数据包
TCP TCP数据包,基于IP
ICMP ICMP数据包,基于IP
srp(pkt) 发送二层数据包,并等待响应。
srp1(pkt) 发送第二层数据包,并返回响应的数据包
sr(pkt) 发送三层数据包,返回两个结果,分别是接收到响应的数据包和未收到响应的数据包。
send 发送数据包,三层
sendp 发送数据包,两层
sr1 发送三层数据包并接收一个数据包

scapy源码

>>> packet =Ether()/IP(src='192.168.1.113',dst='192.168.1.1')/ICMP()
>>> packet.show()
###[ Ethernet ]### 
  dst= 88:25:93:d2:6e:fa
  src= 00:0c:29:62:44:de
  type= 0x800
###[ IP ]### 
     version= 4
     ihl= None
     tos= 0x0
     len= None
     id= 1
     flags= 
     frag= 0
     ttl= 64
     proto= icmp
     chksum= None
     src= 192.168.1.113
     dst= 192.168.1.1
     \options\
###[ ICMP ]### 
        type= echo-request
        code= 0
        chksum= None
        id= 0x0
        seq= 0x0

>>> p = srp1(packet)
Begin emission:
.Finished sending 1 packets.
*
Received 2 packets, got 1 answers, remaining 0 packets
>>> p.show()
###[ Ethernet ]### 
  dst= 00:0c:29:62:44:de
  src= 88:25:93:d2:6e:fa
  type= 0x800
###[ IP ]### 
     version= 4
     ihl= 5
     tos= 0x0
     len= 28
     id= 36140
     flags= 
     frag= 0
     ttl= 64
     proto= icmp
     chksum= 0x69f2
     src= 192.168.1.1
     dst= 192.168.1.113
     \options\
###[ ICMP ]### 
        type= echo-reply
        code= 0
        chksum= 0xffff
        id= 0x0
        seq= 0x0
###[ Padding ]### 
           load= '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

>>> p.getlayer(ICMP)  #获取ICMP层
<ICMP  type=echo-reply code=0 chksum=0xffff id=0x0 seq=0x0 |<Padding  load='\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' |>>
>>> p.getlayer(ICMP).fields['type'] #获取type的值
0

ipaddress模块

同时,python还自带ipaddress模块.不得不说python真是脚本开发yyds

>>> import ipaddress
>>> ip = list(ipaddress.ip_network('192.168.1.0/30'))
>>> ip
[IPv4Address('192.168.1.0'),
 IPv4Address('192.168.1.1'),
 IPv4Address('192.168.1.2'),
 IPv4Address('192.168.1.3')]
>>> for i in ip:
...:     print(i)
...:
192.168.1.0
192.168.1.1
192.168.1.2
192.168.1.3
>>> ip = ipaddress.ip_network('192.168.1.1')
>>> for i in ip:
...:     print(i)
...:
192.168.1.1

以上是我们需要使用到的自带模块的介绍,站在巨人的肩膀上,我们的 脚本编写工作就大大的简化了。

首先定义两个函数,实现icmp请求和主机存活探索

def icmp_rqst(ip_dst,iface=None)
    pkt=Ether()/IP(dst=ip_dst)/ICMP(type=8):
    res=srp1(pkt,timeout=2,verbose=False)
    return res

这个函数实现了icmp请求

pkt变量用于利用scary获取icmp报文.采用srp1的方式返回响应包赋值给res变量,最终将res变量返回。

def icmp_scan(ip_dst)
    req=icmp_request(ip_dst)
    if req:
        type =req.getlayer('ICMP').fields['type']
        print('[+]',ip_dst,':',type,'  Host is up')
    else:
        pass

这个函数实现了主机存活探索。首先利用编写好的icmp_rqst函数获取icmp报文,然后通过if选择结构。存活的主机则输出host is up 。

扩展:增加扫描子网功能

def main(network)
    network= list(ipaddress.ip_network(network))
    for ip in network:
        icmp_scan(ip)

使用list函数将包含所有子网地址字符串变成每一个子网为一个元素的列表。然后枚举扫描。

list()函数

list()函数 将字符串变成列表

eg:

a = (1,2) #tuple
b = {"1":2,"3":3} #dict
c = {1,2,3}  #set
d = range(2,10,2) #range
print(list(a))
print(list(b))
print(list(c))
print(list(d))

输出:

[1, 2]
['1', '3']
[1, 2, 3]
[2, 4, 6, 8]

扩展:实现多线程提高效率

Python 命令行工具 argparse

argparse在我现在的理解中有三大功能

1用来实现通过命令行传参

2用来实现通过命令行定义函数

3用来定义参数

最基本的用法
import argparse

parser = argparse.ArgumentParser()
parser.parse_args()

当执行了 parse_args() 之后默认情况类似于这样:

img

在执行 parse_args() 之前,所有追加到命令行的参数都不会生效。所以说命令行工具argparse的使用声明一定要在所有需要这个我们通过命令行传入的函数之前

设置默认参数函数
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("echo")
args = parser.parse_args()
print args.echo
定义可选参数
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("-v", "--verbosity", help="increase output verbosity")
args = parser.parse_args()
if args.verbosity:
    print "verbosity turned on"

如果用省略方法 -v赋值的话,最终–verbosity 值会传递到这个完整的参数中去,如果没有后面的 –verbosity 只有 -v 的话,那么值会可以通过 args.v 得到。

Python多线程编程 threading 模块

什么是线程

线程是操作系统能够进行运算调度最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务

线程与进程

Python自己没有线程和进程,Python中调用的操作系统的线程和进程。

  1. 操作系统帮助开发者操作硬件。

    1. 程序员写好代码在操作系统上运行(依赖解释器)。

    进程与线程的区别:

    进程是cpu资源分配的最小单元,线程是cpu计算的最小单元。

    一个进程中可以有多个线程。

    对于python来说他的进程和线程和其他语言有差异是有GIL锁。GIL锁保证一个进程中同一时刻只有一个线程被cpu调度。

    python threading模块学习

    本文我们学习创建thread类,传递一个参数的基本方法。

    三部曲:

    首先实例化thread类

    其次start方法开始线程

    最后join方法可以让主线程等待所有的线程执行完毕

    1. def icmp_scan(network):
          threads = []
          length =len(network)
          for ip in network:
              t=threading.Thread(target=icmp_rqst,arg=(str(ip),))
              threads.append(t) #实例化2个Thread类,传递函数及其参数,并将线程对象放入一个列表中
          for i in range(length):
              threads[i].start() #循环 开始线程
          for i in range(length):
              threads[i].join() ##循环 join()方法可以让主线程等待所有的线程都执行完毕。
    

最终代码

本代码的多线程优化采取的是创建Thread类,传递一个函数的方法,我们先实例化thread类,然后执行线程。此时icmp_rqst 完成了生成icmp报告并且判断主机是否存活的双重功能。而scan函数用于实例化多个线程并且采用多线程执行。具体请看注释

# -*- coding:utf-8 -*-
from scapy.all import IP, ICMP, srp1,Ether
import threading
import argparse
import ipaddress
import os
import sys
#判断主机是否存活
def icmp_rqst(ip_dst,iface=None):
    pkt=Ether()/IP(dst=ip_dst)/ICMP(type=8)
    res=srp1(pkt,timeout=2,verbose=False)
    if res:
        print('[+]',ip_dst,':',type,'  Host is up')
def icmp_scan(network):
    threads = []
    length =len(network)
    for ip in network:
        t=threading.Thread(target=icmp_rqst,args=(str(ip),))
        threads.append(t)
    for i in range(length):
        threads[i].start()
    for i in range(length):
        threads[i].join()
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('network', help='eg:192.168.1.0/24')
    args = parser.parse_args()
    network= list(ipaddress.ip_network(args.network))
    icmp_scan(network)
if __name__ == '__main__':
  main()

报错解决:安装npcap

测试程序时出现了这个好讨厌的bug

image.png

上网搜了一波 winpcap早已不更新了,解决的办法很简单,安装Npcap

下载地址:

https://nmap.org/npcap/#download

进入官网后,win系统下载红线标注的即可

image.png

下载之后安装即可

安装完重新运行就没有报错啦。

测试结果

找到源程序文件夹。打开设置,找到路径

image.png

在命令行中进入该目录

image.png

由于pycharm运行不知道咋带参数,cmd又没装scapy,所以又跑去装了一波scapy,这里记录一下

scapy安装教程

在github上下载压缩包

下载路径:https://github.com/secdev/scapy

解压后 cd进入其目录,运行(我的python3默认为python。python2为python2)

Python setup.py install

等待安装即可

安装完成后,输入scapy出现下述页面即为安装成功。

image.png

突然想起上一次做红队任务时对同程进行了信息搜集,那就用同程的服务器测试一下我的工具把(嘻嘻嘻同程dbqdbq)

image-20210802160103181.png

先确认服务器能够ping通,再使用我们的工具。返回host is up。

后记

通过这次学习,我们可以深入的了解icmp协议的原理,并且利用icmp协议实现了 简单主机存活工具的开发。

预告:下一篇教大家利用python自定义开发简单的端口扫描工具,敬请期待。


小白学堂 » 【py网安武器库】第二期:开发简易ping工具【原创】

就聊挣钱,一个带着你做副业的社群。

立即查看 了解详情