草庐IT

python渗透测试入门——流量嗅探器

yaolingyu 2023-03-28 原文

1.代码及代码讲解。

代码编写工具:VsCode

(1)socket嗅探器

首先第一个脚本是最简单的原始socket嗅探器,它只会读一个数据包,然后直接退出:

import socket
import os

#host to listen on
HOST = ''
#这里输入自己的IP地址
def main(): #create raw socket, bin to public interface if os.name == 'nt': socket_protocol = socket.IPPROTO_IP else: socket_protocol = socket.IPPROTO_ICMP sniffer = socket.socket(socket.AF_INET,socket.SOCK_RAW.socket_protocol) sniffer.bind((HOST,0)) #include the IP header in the capture sniffer.setsockopt(socket.IPPROTO_IP,socket.IP_HDRINCL,1) if os.name =='nt': sniffer.ioctl(socket.SIO_RCVALL,socket.RCVALL_ON) #read one packet print(sniffer.recvfrom(65565)) if os.name == 'nt': sniffer.ioctl(socket.SIO_RCVALL,socket.RCVALL_OFF) if __name__ == '__main__': main()

注意:这里Windows和Linux的区别是,前者允许我们嗅探任何协议的所有流入数据,而后者强制我们指定一个协议来嗅探,这里指定的是ICMP。

上面这只是一个非常简单的嗅探器,那我们将对它的功能进行进一步的拓展。

(2)ip解码器

import ipaddress
import os 
import socket
import struct
import sys


#定义了一个Python结构,把数据包的前20个字节映射到IP头对象中。展示目前的通信协议和通信双方的IP地址
class IP:
    def __init__(self, buff=None):
        header = struct.unpack('<BHHHBBH4s4s', buff)
        self.ver = header[0] >> 4
        self.ihl = header[0] & 0xF

        self.tos = header[1]
        self.len = header[2]
        self.id = header[3]
        self.offset = header[4]
        self.ttl = header[5]
        self.protocol = header[6]
        self.sum = header[7]
        self.src = header[8]
        self.dst = header[9]


        # human readable IP addresses
        #使用新打造的IP头结构,将抓包逻辑改成持续抓包和解析

        self.src_address = ipaddress.ip_address(self.src)
        self.dst_address = ipaddress.ip_address(self.dst)

        # map protocol constants to their names
        
        self.protocol_map = {1: "ICMP", 6: "TCP", 17: "UDP"}
        try:
            self.protocol = self.protocol_map[self.protocol_num]
        except Exception as e:
            print('%s No protocol for %s' % (e, self.protocol_num))
            self.protocol = str(self.protocol_num)

    def sniff(host):
        # should look familiar from previous example
        if os.name == 'nt':
            socket_protocol = socket.IPPROTO_IP
        else:
            socket_protocol = socket.IPPROTO_ICMP

        sniffer = socket.socket(socket.AF_INET,socket.SOCK_RAW,socket_protocol)
        sniffer.bind((host,0))
        sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

        if os.name == 'nt':
            sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

        try:
            while True:
                # read a packet
                #将前20字节转换成IP头对象
                raw_buffere = sniffer.recvfrom(65535)[0]
                # create an IP header from the first 20 bytes
                ip_header = IP(raw_buffere[0:20])
                # print the detected protocol and hosts
                #打印抓取信息
                print('Protocol: %s %s -> %s' % (ip_header.protocol, ip_header.src_address, ip_header.dst_address))

        except KeyboardInterrupt:
            # if we're on Windows, turn off promiscuous mode
            if os.name == 'nt':
                sniffer.ioctl(socket.SIO_RCVALL,socket.RCVALL_OFF)
            sys.exit()

if __name__ == '__main':
    if len(sys.argv) == 2:
        host = sys.argv[1]
    else:
        host = ''
    sniffer(host)

在理解这一部分的内容之前,我们首先要了解一个python库。struct库:struct库提供了一些格式字符,用来定义二进制数据的结构。这个库在解码,密码学方面经常会用到。

注:在struct库里,不存在对应于nybble格式(即4个二进制位组成的数据块,也叫作nibble)的格式字符。

self.ver = header[0] >> 4

对于IP头的第一个字节,我们只想取高位nybble(整个字节里的第一个nybble)作为ver的值。取某字节高位nybble的常规方法是将其向右位移4位,相当于在该字节的开头填4个0,把其尾部的4位挤出去。这样我们就得到了原字节的第一个nybble。

self.ihl = header[0] & 0xF

我们想把低位nybble(或者说原字节的最后4个二进制位)填进hdrlen里,取某个字节低位nybble的常规方法是将其与数字0xF(00001111)进行按位与运算。它利用了0 AND 1 = 0的特性(0代表假,1代表真)。想要AND表达式为真,表达式两边都必须为真。所以这个操作相当于删除前4个二进制位,因为任何数AND 0都得0;它保持了最后4个二进制位不变,因为任何数AND 1还是原数字。

(3)解码ICMP

import ipaddress
import os 
import socket
import struct
import sys


#定义了一个Python结构,把数据包的前20个字节映射到IP头对象中。展示目前的通信协议和通信双方的IP地址
class IP:
    def __init__(self, buff=None):
        header = struct.unpack('<BHHHBBH4s4s', buff)
        self.ver = header[0] >> 4
        self.ihl = header[0] & 0xF

        self.tos = header[1]
        self.len = header[2]
        self.id = header[3]
        self.offset = header[4]
        self.ttl = header[5]
        self.protocol = header[6]
        self.sum = header[7]
        self.src = header[8]
        self.dst = header[9]


        # human readable IP addresses
        #使用新打造的IP头结构,将抓包逻辑改成持续抓包和解析

        self.src_address = ipaddress.ip_address(self.src)
        self.dst_address = ipaddress.ip_address(self.dst)

        # map protocol constants to their names
        
        self.protocol_map = {1: "ICMP", 6: "TCP", 17: "UDP"}
        try:
            self.protocol = self.protocol_map[self.protocol_num]
        except Exception as e:
            print('%s No protocol for %s' % (e, self.protocol_num))
            self.protocol = str(self.protocol_num)

class ICMP:
    def __init__(self, buff):
        header = struct.unpack('<BBHHH', buff)
        self.type = header[0]
        self.code = header[1]
        self.sum = header[2]
        self.id = header[3]
        self.seq = header[4]

def sniff(host):
        # should look familiar from previous example
        if os.name == 'nt':
            socket_protocol = socket.IPPROTO_IP
        else:
            socket_protocol = socket.IPPROTO_ICMP

        sniffer = socket.socket(socket.AF_INET,socket.SOCK_RAW,socket_protocol)
        sniffer.bind((host,0))
        sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

        if os.name == 'nt':
            sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

        try:
            while True:
                # read a packet
                #将前20字节转换成IP头对象
                raw_buffere = sniffer.recvfrom(65535)[0]
                # create an IP header from the first 20 bytes
                ip_header = IP(raw_buffere[0:20])
                # if it's ICMP, we want it
                if ip_header.protocol == 'ICMP':
                    print('Protocol: %s %s -> %s' % (ip_header.protocol,ip_header.src_address, ip_header.dst_address))
                    print(f'Version: {ip_header.ver}')
                    print(f'Header Length; {ip_header.ihl} TTL: {ip_header.ttl}')

                    # calculate where our ICMP packet starts
                    offset = ip_header.ihl * 4
                    buf = raw_buffere[offset:offset + 8]
                    # create our ICMP structure
                    icmp_header = ICMP(buf)
                    print('ICMP -> Yype: %s Code: %s\n' % (icmp_header.type, icmp_header.code))
        except KeyboardInterrupt:
            if os.name == 'nt':
                sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
            sys.exit() 
    

if __name__ == '__main':
    if len(sys.argv) == 2:
        host = sys.argv[1]
    else:
        host = '10.74.212.22'
    sniff(host)

这段代码在之前的IP结构下方又创建了一个ICMP结构。在负责接收数据包的主循环中,我们会判断接收到的数据包是否为ICMP数据包,然后计算出ICMP数据在原始数据包中的偏移,最后将数据按照ICMP结构进行解析。

(4)scanner.py

那前面我们已经完成了大部分工作,只剩下了一个任务——群发UDP数据包,并解析结果

import ipaddress
import os 
import
socket import struct import sys import threading import time #subnet to target SUBNET = '192.168.0.1/24' # magic string we'll check ICMP responses for
#添加的这点代码应该很好理解。我们定义了一个简单的字符串作为“签名”,用于确认收到的ICMP响应是否是由我们发送的UDP包所触发的
MESSAGE = 'PYTHONRULES!' #定义了一个Python结构,把数据包的前20个字节映射到IP头对象中。展示目前的通信协议和通信双方的IP地址 class IP: def __init__(self, buff=None): header = struct.unpack('<BHHHBBH4s4s', buff) self.ver = header[0] >> 4 self.ihl = header[0] & 0xF self.tos = header[1] self.len = header[2] self.id = header[3] self.offset = header[4] self.ttl = header[5] self.protocol = header[6] self.sum = header[7] self.src = header[8] self.dst = header[9] # human readable IP addresses #使用新打造的IP头结构,将抓包逻辑改成持续抓包和解析 self.src_address = ipaddress.ip_address(self.src) self.dst_address = ipaddress.ip_address(self.dst) # map protocol constants to their names self.protocol_map = {1: "ICMP", 6: "TCP", 17: "UDP"} try: self.protocol = self.protocol_map[self.protocol_num] except Exception as e: print('%s No protocol for %s' % (e, self.protocol_num)) self.protocol = str(self.protocol_num) class ICMP: def __init__(self, buff): header = struct.unpack('<BBHHH', buff) self.type = header[0] self.code = header[1] self.sum = header[2] self.id = header[3] self.seq = header[4] def udp_sender(): with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sender: for ip in ipaddress.ip_network(SUBNET).hosts(): sender.sendto(bytes(MESSAGE, 'utf8'), (str(ip), 65212)) class Scanner: def __init__(self, host): self.host = host if os.name == 'nt': socket_protocol = socket.IPPROTO_IP else: socket_protocol = socket.IPPROTO_ICMP self.socket = socket.socket(socket.AF_INET,socket.SOCK_RAW, socket_protocol) self.socket.bind((host, 0)) self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) if os.name == 'nt': self.socket.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) def sniff(self): hosts_up = set([f'{str(self.host)} *']) try: while True: # read a packet raw_buffer = self.socket.recvfrom(65535)[0] # create an IP header from the first 20 bytes ip_header = IP(raw_buffer[0:20]) # if it's ICMP, we went it if ip_header.protocol == "ICMP": offset = ip_header.ihl1 * 4 buf = raw_buffer[offset:offset + 8] icmp_header = ICMP(buf) if icmp_header.code == 3 and icmp_header.type == 3: if ipaddress.ip_address(ip_header.src_address) in ipaddress.IPv4Network(SUBNET): # make sure it has our magic message if raw_buffer[len(raw_buffer) - len(MESSAGE):] == bytes(MESSAGE, 'utf8'): tgt = str(ip_header.src_address) if tgt !=self.host and tgt not in hosts_up: hosts_up.add(str(ip_header.src_address)) print(f'Host Up: {tgt}') except KeyboardInterrupt: if os.name == 'nt': self.socket.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) print('\nUser interrupted') if hosts_up: print(f'\n\nSummary: Hosts up on {SUBNET}') for host in sorted(hosts_up): print(f'{host}') print('') sys.exit() if __name__ == '__main': if len(sys.argv) == 2: host = sys.argv[1] else: host = '' s = Scanner(host) time.sleep(5) t = threading.Thread(target=udp_sender) t.start() s.sniff()

其中我们引入ipaddress库,这样就能对整个子网进行主机发现扫描。

sniff函数会嗅探网络上的数据,步骤跟之前的例子差不多,唯一的区别就是这次它会把在线的主机记录下来。接收到预期的ICMP消息时,我们首先检查这个响应是不是来自我们正在扫描的子网,然后检查ICMP消息里有没有我们自定义的签名。如果所有检查都通过了,就把发送这条ICMP消息的主机IP地址打印出来。如果使用Ctrl+C组合键中断扫描过程的话,程序就会关闭网卡混杂模式(如果是Windows平台),并且把迄今为止扫描出来的主机都打印到屏幕上。

那到这里我们的流量嗅探工具已经算编写完成了,希望大家有所收获。

有关python渗透测试入门——流量嗅探器的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  3. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  4. ruby - Ruby 的 Hash 在比较键时使用哪种相等性测试? - 2

    我有一个围绕一些对象的包装类,我想将这些对象用作散列中的键。包装对象和解包装对象应映射到相同的键。一个简单的例子是这样的:classAattr_reader:xdefinitialize(inner)@inner=innerenddefx;@inner.x;enddef==(other)@inner.x==other.xendenda=A.new(o)#oisjustanyobjectthatallowso.xb=A.new(o)h={a=>5}ph[a]#5ph[b]#nil,shouldbe5ph[o]#nil,shouldbe5我试过==、===、eq?并散列所有无济于事。

  5. ruby - RSpec - 使用测试替身作为 block 参数 - 2

    我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

  6. ruby - Sinatra:运行 rspec 测试时记录噪音 - 2

    Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/

  7. ruby-on-rails - 迷你测试错误 : "NameError: uninitialized constant" - 2

    我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test

  8. ruby - 即使失败也继续进行多主机测试 - 2

    我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r

  9. ruby-on-rails - 如何使辅助方法在 Rails 集成测试中可用? - 2

    我在app/helpers/sessions_helper.rb中有一个帮助程序文件,其中包含一个方法my_preference,它返回当前登录用户的首选项。我想在集成测试中访问该方法。例如,这样我就可以在测试中使用getuser_path(my_preference)。在其他帖子中,我读到这可以通过在测试文件中包含requiresessions_helper来实现,但我仍然收到错误NameError:undefinedlocalvariableormethod'my_preference'.我做错了什么?require'test_helper'require'sessions_hel

  10. ruby-on-rails - Cucumber 是否只是 rspec 的包装器以帮助将测试组织成功能? - 2

    只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您

随机推荐