A few days ago bluechill was talking in irc about an IRCd he is developing, at the time I mentioned he should use an IRC fuzzer, well today I was bored so I decided to make one myself.
It's very primitive, however it was only made due to boredom, so what can you expect
Before I go any further, for those of you who don't know, a fuzzer just sends unexpected data to an application with the intention of causing crashes and memory leaks. For more information see
Wikipedia and
OWASPThe script loads tests to be performed from a file called "tests.txt". This is all the commands you want fuzzed. <FUZZ> will be replaced by fuzzing payloads, such as random characters and very long strings. An example tests.txt is shown below:
/JOIN #test
<FUZZ>
/NICK <FUZZ>
/PART #test
/JOIN <FUZZ>
/MSG NickServ <FUZZ>
Notice that you can execute commands without <FUZZ> in them, for example to join a channel. In this example, once we have joined the channel #test we fuzz the messages sent to the channel then leave it.
The payloads used by the fuzzer can be edited in the source code, the ones included are just there as an example, mostly taken from
https://www.owasp.org/index.php/OWASP_Testing_Guide_Appendix_C:_Fuzz_VectorsAll networking is logged in "log.txt", the log includes connections made, data sent and any errors. If the IRCd crashes it's easy to find what crashed it this way.
Here's a screenshot:
And finally, here's the code:
#!/usr/bin/python
# This is a basic IRCd Fuzzer created out of boredom
# Go to line 100 to edit the payloads used
# All tests to be performed are loaded from tests.txt
# All logs are written to log.txt
# Created by Thor from EvilZone.org
import os
import errno
import re
import time
from optparse import OptionParser
from socket import *
# Deals with all the networking in the application,
# connecting to hosts, sending data etc
class Networking():
def __init__(self):
self.host = "127.0.0.1"
self.port = "6667"
self.s = socket(AF_INET,SOCK_STREAM)
self.logger = Logger()
self.logger.open()
# Set the current host
def setHost(self,host):
self.host = host
# Set the current port after chacking the value is valid
def setPort(self,port):
if port < 1 or port > 65535:
print("invalid port. Value must be > 0 and <= 65535!")
return 0
if port < 1024:
print("<WARNING> Targeting ports 1-1023 can be dangerous!")
self.port = port
return 1
# Connect to the host on the specified port
def connect(self):
try:
self.s.connect((self.host, self.port))
self.logger.write("Connected to %s on %d" % (self.host,self.port))
except error, e:
errorcode = e[0]
if errorcode == errno.ECONNREFUSED:
print("Connection Refused!")
elif errorcode == errno.ETIMEDOUT:
print("Connection Timed Out!")
else:
print("IOError")
return 0
return 1
# Authenticate with IRC server
def authenticate(self):
nick = "fuzzer"
hostname = "fuzzer"
username = "fuzzer"
realname = "fuzzer"
self.sendData("NICK %s\r\n" % nick)
self.sendData("USER %s %s evilzone :%s\r\n" % (username, hostname, realname))
# Send data to the host
def sendData(self, data):
# Ensure we don't flood the server
time.sleep(3)
try:
self.s.send(data)
self.logger.write("Sent: %s" % data)
except error, e:
self.logger.writeError(e)
errorcode=e[0]
if errorcode==errno.ECONNREFUSED:
print("Connection Refused!")
elif errorcode==errno.ETIMEDOUT:
print("Connection Timed Out!")
else:
print("An unknown error occured")
#print(e)
# Handles all fuzzing. Loads tests, generates payloads,
# combines them and formats them for the irc daemon.
class Fuzzer():
def __init__(self, host, port):
self.testFile = "tests.txt"
self.fuzzPayloads = []
self.tests = []
self.testCount = 0
self.fuzzCount = 0
self.host = host
self.port = port
self.sas = 1
self.commands = ["JOIN", "PART", "LEAVE", "QUIT", "WHOIS", "WHOWAS", "WHO", "NAMES",\
"MSG", "QUERY", "NICK", "ME", "AWAY", "LIST", "INVITE", "IGNORE",\
"KICK", "MODE"]
# Generate fuzzing payloads and add them to fuzzPayloads list
# Feel free to edit these
# More info can be found here: https://www.owasp.org/index.php/OWASP_Testing_Guide_Appendix_C:_Fuzz_Vectors
def generateFuzzPayloads(self):
self.fuzzPayloads.append("ABCDE")
self.fuzzPayloads.append("A"*513)
self.fuzzPayloads.append("A"*1025)
self.fuzzPayloads.append("http://google.com")
self.fuzzPayloads.append("http://g" + "o"*512 + ".com/")
self.fuzzPayloads.append("%#0123456x%08x%x%s%p%d%n%o%u%c%h%l%q%j%z%Z%t%i%e%g%f%a%C%S%08x%%")
self.fuzzPayloads.append("%x"*1025)
self.fuzzPayloads.append("-1")
self.fuzzPayloads.append("0")
self.fuzzPayloads.append(os.urandom(513))
self.fuzzPayloads.append(os.urandom(1025))
self.fuzzPayloads.append("\x00\x00\x00\x00")
# Return a test to be performed eg "/msg <FUZZ>"
def getTest(self):
if self.testCount < len(self.tests):
self.testCount+=1
return self.tests[self.testCount-1]
else:
return 0
# Set the current file the tests will be loaded from
def setTestFile(self,testFile):
if os.path.exists(testFile):
self.testFile = testFile
else:
print("Invalid file")
# Load tests to be performed from testFile
def loadTests(self):
for line in open(self.testFile,"r").read().split("\n"):
self.tests.append(line)
# Return a fuzzing payload
def getFuzzPayload(self):
if self.fuzzCount < len(self.fuzzPayloads):
self.fuzzCount+=1
return self.fuzzPayloads[self.fuzzCount-1]
else:
self.fuzzCount = 0
return 0
# Connects to host, generates fuzzing payloads, loads tests to be
# performed and then begins fuzzing.
def fuzz(self):
# Flag to display if fuzzer has joined a channel
joinFlag = 0
currentChannel = ""
# Configure networking
net = Networking()
net.setHost(self.host)
net.setPort(self.port)
# If connection successful, run every test individually, only
# moving to another test after every fuzz payload has been used
if net.connect():
net.authenticate()
self.loadTests()
self.generateFuzzPayloads()
test = self.getTest()
while test != 0:
# Remove "/" at start as "/" is only used by irc clients
if test.startswith("/"):
test = test[1:]
# Replace <FUZZ> with fuzz payload
if test.find("<FUZZ>") != -1:
payload = self.getFuzzPayload()
while payload != 0:
fuzzedTest = re.sub("<FUZZ>", payload, test)
if joinFlag == 0:
net.sendData(fuzzedTest+"\r\n")
# If we're in a channel, check if we're sending a command,
# or are we sending a message to the channel
elif joinFlag == 1:
for x in self.commands:
if fuzzedTest.startswith(x):
net.sendData(fuzzedTest+"\r\n")
break
# No command found, assume data is a message for joined channel
else:
net.sendData("PRIVMSG %s %s\r\n" % (currentChannel,fuzzedTest))
payload = self.getFuzzPayload()
# Else no fuzz data required so send raw request
else:
# Joining a channel so set joinFlag
if test.startswith("JOIN"):
joinFlag = 1
channel = re.split(" ", test)
currentChannel = channel[1]
# Leaving a channel so reset joinFlag
elif test.startswith("PART") or test.startswith("LEAVE"):
joinFlag = 0
net.sendData(test+"\r\n")
test = self.getTest()
# Logs all connections and data sent as well as errors.
class Logger():
def __init__(self):
self.logFile = None
def open(self, fileName="log.txt"):
try:
self.logFile = open(fileName, "w")
except:
print("An error occured when opening the log file!")
exit()
def write(self, text):
self.logFile.write("%s\n" % text)
def writeError(self, error):
self.logFile.write("\n\n~~ERROR~~ >> %s\n\n" % error)
def close(self):
self.logFile.close()
while __name__ == "__main__":
print '''
_ _____ _____ _ _______ _ _ _______ _______
| | __ \/ ___| | | ____| | | ||____ /|____ /
| | |__| | / | | |____| | | | / / / /
| | ___/ | ____| | ____| | | | / / / /
| | |\ \ | / __ | | | | | | / / / /
| | | \ \ \___| |__| | | | |___| | / /___ / /____
|_|_| \_\_____|______|__| |_______|/______|/_______|
'''
parser = OptionParser()
parser.add_option("-H", "--host", type="string", dest="host", default="127.0.0.1", help="Host the irc daemon is running on (default 127.0.0.1)")
parser.add_option("-p", "--port", type="int", dest="port", default="6667", help="Port the irc daemon is listening on (default 6667)")
(options, args) = parser.parse_args()
fuzz = Fuzzer(options.host, options.port)
print("Attempting to fuzz %s on port %d" % (options.host, options.port))
print("Logs are being written to logs.txt")
print("This may take a while..")
fuzz.fuzz()
print("Fuzzing complete!")
quit()
TO DO:
- Add SSL support
- Respond to PINGs to keep session going (shouldn't be a problem if you control the IRCd)
- Possibly allow users to edit sleep time between requests and filenames using cli arguments