fusil-1.5/0000775000175000017500000000000012305626161012767 5ustar haypohaypo00000000000000fusil-1.5/ChangeLog0000664000175000017500000001430312305626026014542 0ustar haypohaypo00000000000000Changelog ========= Fusil 1.5 (2013-03-05) ---------------------- * experimental Python 3.3 support with the same code base; python 2.5 is no more supported * fusil-python: generate buffer objects and Unicode strings with surrogate characters * Change the default process memory limit from 100 MB to 500 MB Fusil 1.4 (2011-02-16) ---------------------- * Python 3 support * fusil-python: - improve function listing all Python modules: use sys.builtin_module_names and pkgutil.iter_modules() - blacklist more modules, classes and functions Fusil 1.3.2 (2010-01-09) ------------------------ * replay.py: set sys.path to ease the usage of Fusil without installing it * Fix fusil-gettext: ignore strace errors in locateMO() * fusil-python: - hide Python warnings - listAllModules() includes builtin modules - new option --only-c to test only modules written in C - fix memory leak: unload tested modules - fix getFunctions(): use also isclass() to detect classes * Disable Fusil process maximum memory limit Fusil 1.3.1 (2009-11-09) ------------------------ * fusil-python: autodiscover all modules instead of using a static list of modules, catch any exception when loading a module, only fuzz public functions (use module.__all__) * FileWatch: ignore duplicate parts on session rename * Remove session name parts duplicate (eg. "pickle-error-error" => "picke-error") * replay.py: don't redirect stdin to /dev/null if --ptrace is used * CPU probe: set max duration from 3 to 10 seconds (and rename the session on success) Fusil 1.3 (2009-09-18) ---------------------- * Create fusil-gimp * Remove charset from WriteCode: use builtin open() instead codecs.open() because files created by open() are much faster * Optimize FileWatch: don't recompile patterns at each session * fusil now depends on python-ptrace 0.6 * Don't use close_fds argument of subprocess.Popen() on Windows * Fix configuration reader: normal_calm_load, normal_calm_sleep, slow_calm_load, slow_calm_sleep keys global options are float, not integer * Project website moved to http://bitbucket.org/haypo/fusil/wiki/Home * FileWatch uses the pattern to rename the session Fusil 1.2.1 (2009-02-06) ------------------------ * Fix mangle agent of the Image Magick fuzzer * Fix AttachProcessPID() probe: stop the probe at process exit Fusil 1.2 (2009-02-04) ---------------------- User visible changes: * Fusil now requires Python 2.5 * Documentation: write an index (index.rst) and an user guide (usage.rst) * Replay script: copy HOME environment for GDB and catch setuid() error * fusil-firefox: support more file formats (bmp, gif, ico, png, svg), create --test command line option, write the HTML page into index.html file * fusil-python: write errors to stderr (instead of stdout) to avoid unicode error (especially with Python3) * FileWatch: rename the session with "long_output" if the program wrote more than max_nbline lines * fusil-python: blacklist posix.fork() to avoid false positive * If the process is killed by a signal, rename the session using the signal name (already worked if the debugger was disabled) Developer changes: * MangleAgent supports multiple input files * Create DummyMangle: agent with MangleFile API but don't touch file content to test the fuzzer * Network: close() method of NetworkClient and ServerClient use shutdown(SHUT_RDWR) * NetworkServer uses a backlog of 5 clients for socket.listen() (instead of 1) Bugfixes: * Fix Directory.rmtree() and replay script for Python 3.0 * Fix ServerClient.sendBytes(): use socket.send() result to get the next data offset Fusil 1.1 (2008-10-22) ---------------------- User visible changes: * replay.py: ask confirmation if the fuzzer will not be running under a different user or as root * Even with --force-unsafe, show safety warning if the fuzzer is running as the root user * Close files for child processes (close_fds=True) * Fix directory.rmtree() for Python 3.0 final Developer changes: * Create IntegerRangeGenerator in fusil.unicode_generator * Create EnvVarIntegerRange in fusil.process.env * Create fusil-wizzard fuzzer * Write timestamp in session.log * Add session() method to ProjectAgent * Add NAME attribute to a fuzzer, reused to choose the project directory name Bugfixes: * Fix Debugger.processSignal(): use the process agent to send the message (session_rename) since the debugger agent may be disabled * Fix replay.py: quote gdb arguments escape quote and antislash characters (eg. "text=\"Hello\\n\".") * replay.py uses /dev/null for stdin as Fusil does * FileWatch: open file in binary mode to use bytes in Python3 Fusil 1.0 final (2008-09-13) ---------------------------- Visible changes: * Create fusil-zzuf fuzzer (use the zzuf library) * Create fusil-vlc fuzzer (VLC media player) * For each session, generate a Python script (replay.py) to replay the session. The script can run the target in gdb, valgrind or gdb.py (python-ptrace debugger), with many options (--user, --limit, etc.) * Create --force-unsafe option, like --unsafe without the confirmation * CreateProcess is now a probe (with a score): if the debugger catchs a fatal signal, the session stops * Always use a null device as stdin for child processes to avoid blocking the fuzzer if the process reads stdin (eg. call getchar()) * Write the created process identifier in the logs Developer: * Create EnvVarIntegerRange: environment variable with an integer value in a fixed range * Changes to get a minimal Windows support: disable "change user/group" feature on Windows; remove log file before removing the project directory; use ":NUL" instead of /dev/null for null input/output * On setupProject() error, make sure that the project is cleaned * Close stdout files (input and output) at process exit (fix needed by Windows) * Rename long2raw() to uint2bytes(), and bytes2long() to bytes2uint() * Normalize score that make sure that a probe score is in range [-1; +1] and so that score*weight is in range[-weight; +weight] * CodeC: remove method lines(), writeCode() is renamed writeIntoFile(), use unicode strings (instead of byte strings) * Remove StdoutFile class, code merged in CreateProcess fusil-1.5/README.windows.txt0000664000175000017500000000100712110032732016141 0ustar haypohaypo00000000000000Status of Windows support ========================= Windows support of Fusil is minimal. No fuzzer works because all depends on programs missing on Windows (eg. strace for fusil-gettext). TODO ==== * SUPPORT_UID: Run child process as a different user/group * Create replay.bat (instead of replay.sh) * CreateProcess: write workaround for preexec_fn * Implement functions in fusil.process.tools (eg. limitMemory()) * Implement AttachProcessPID.checkAlive() * Implement searching a process for AttachProcessPID fusil-1.5/fusil.egg-info/0000775000175000017500000000000012305626161015603 5ustar haypohaypo00000000000000fusil-1.5/fusil.egg-info/SOURCES.txt0000664000175000017500000000611012305626161017465 0ustar haypohaypo00000000000000AUTHORS COPYING ChangeLog IDEAS INSTALL MANIFEST.in README README.windows.txt TODO graph.sh lsall.sh pyflakes.sh setup.py test_doc.py doc/Makefile doc/agent.rst doc/architecture.rst doc/c_tools.rst doc/configuration.rst doc/events.rst doc/file_watch.rst doc/howto_write_fuzzer.rst doc/index.rst doc/linux_process_limits.rst doc/mangle.rst doc/mas.rst doc/network.rst doc/process.rst doc/safety.rst doc/score.rst doc/time.rst doc/usage.rst examples/good-bye-world examples/hello-world examples/xterm fusil/__init__.py fusil/aggressivity.py fusil/application.py fusil/application_logger.py fusil/auto_mangle.py fusil/bits.py fusil/bytes_generator.py fusil/c_tools.py fusil/cmd_help_parser.py fusil/config.py fusil/directory.py fusil/dummy_mangle.py fusil/error.py fusil/file_tools.py fusil/file_watch.py fusil/fixpng.py fusil/incr_mangle.py fusil/incr_mangle_op.py fusil/mangle.py fusil/mangle_agent.py fusil/mangle_op.py fusil/mockup.py fusil/project.py fusil/project_agent.py fusil/project_directory.py fusil/python_tools.py fusil/score.py fusil/session.py fusil/session_agent.py fusil/session_directory.py fusil/system_calm.py fusil/terminal_echo.py fusil/time_watch.py fusil/tools.py fusil/unicode_generator.py fusil/unsafe.py fusil/version.py fusil/write_code.py fusil/x11.py fusil/xhost.py fusil/zzuf.py fusil.egg-info/PKG-INFO fusil.egg-info/SOURCES.txt fusil.egg-info/dependency_links.txt fusil.egg-info/requires.txt fusil.egg-info/top_level.txt fusil/linux/__init__.py fusil/linux/cpu_load.py fusil/linux/syslog.py fusil/mas/__init__.py fusil/mas/agent.py fusil/mas/agent_id.py fusil/mas/agent_list.py fusil/mas/application_agent.py fusil/mas/mailbox.py fusil/mas/message.py fusil/mas/mta.py fusil/mas/univers.py fusil/network/__init__.py fusil/network/client.py fusil/network/http_request.py fusil/network/http_server.py fusil/network/server.py fusil/network/server_client.py fusil/network/tcp_client.py fusil/network/tcp_server.py fusil/network/tools.py fusil/network/unix_client.py fusil/process/__init__.py fusil/process/attach.py fusil/process/cmdline.py fusil/process/cpu_probe.py fusil/process/create.py fusil/process/debugger.py fusil/process/env.py fusil/process/mangle.py fusil/process/prepare.py fusil/process/replay_python.py fusil/process/stdout.py fusil/process/time_watch.py fusil/process/tools.py fusil/process/watch.py fuzzers/fusil-clamav fuzzers/fusil-firefox fuzzers/fusil-gettext fuzzers/fusil-gimp fuzzers/fusil-gstreamer fuzzers/fusil-imagemagick fuzzers/fusil-libc-printf fuzzers/fusil-mplayer fuzzers/fusil-ogg123 fuzzers/fusil-php fuzzers/fusil-poppler fuzzers/fusil-python fuzzers/fusil-vlc fuzzers/fusil-wizzard fuzzers/fusil-zzuf fuzzers/notworking/fusil-apache fuzzers/notworking/fusil-libc-env fuzzers/notworking/fusil-libexif fuzzers/notworking/fusil-linux-ioctl fuzzers/notworking/fusil-linux-proc fuzzers/notworking/fusil-linux-syscall fuzzers/notworking/fusil-mysql fuzzers/notworking/fusil-rpm tests/cmd_help_parser.rst tests/file_watch_ignore.rst tests/file_watch_read.rst tests/cmd_help/gcc.help tests/cmd_help/identify.help tests/cmd_help/ls.help tests/cmd_help/ping.help tests/cmd_help/python.helpfusil-1.5/fusil.egg-info/dependency_links.txt0000664000175000017500000000000112305626161021651 0ustar haypohaypo00000000000000 fusil-1.5/fusil.egg-info/top_level.txt0000664000175000017500000000000612305626161020331 0ustar haypohaypo00000000000000fusil fusil-1.5/fusil.egg-info/requires.txt0000664000175000017500000000002212305626161020175 0ustar haypohaypo00000000000000python-ptrace>=0.7fusil-1.5/fusil.egg-info/PKG-INFO0000664000175000017500000002677412305626161016720 0ustar haypohaypo00000000000000Metadata-Version: 1.1 Name: fusil Version: 1.5 Summary: Fuzzing framework Home-page: http://bitbucket.org/haypo/fusil/wiki/Home Author: Victor Stinner Author-email: UNKNOWN License: GNU GPL v2 Download-URL: http://bitbucket.org/haypo/fusil/wiki/Home Description: Fusil is a Python library used to write fuzzing programs. It helps to start process with a prepared environment (limit memory, environment variables, redirect stdout, etc.), start network client or server, and create mangled files. Fusil has many probes to detect program crash: watch process exit code, watch process stdout and syslog for text patterns (eg. "segmentation fault"), watch session duration, watch cpu usage (process and system load), etc. Fusil is based on a multi-agent system architecture. It computes a session score used to guess fuzzing parameters like number of injected errors to input files. Available fuzzing projects: ClamAV, Firefox (contains an HTTP server), gettext, gstreamer, identify, libc_env, libc_printf, libexif, linux_syscall, mplayer, php, poppler, vim, xterm. Website: http://bitbucket.org/haypo/fusil/wiki/Home Usage ===== Fusil is a library and a set of fuzzers called "fusil-...". To run a fuzzer, call it by its name. Example: :: $ fusil-gettext Fusil version 0.9.1 -- GNU GPL v2 http://bitbucket.org/haypo/fusil/wiki/Home (...) [0][session 13] Start session [0][session 13] ------------------------------------------------------------ [0][session 13] PID: 16989 [0][session 13] Signal: SIGSEGV [0][session 13] Invalid read from 0x0c1086e0 [0][session 13] - instruction: CMP EDX, [EAX] [0][session 13] - mapping: 0x0c1086e0 is not mapped in memory [0][session 13] - register eax=0x0c1086e0 [0][session 13] - register edx=0x00000019 [0][session 13] ------------------------------------------------------------ [0][session 13] End of session: score=100.0%, duration=3.806 second (...) Success 1/1! Project done: 13 sessions in 5.4 seconds (414.5 ms per session), total 5.9 seconds, aggresssivity: 19.0% Total: 1 success Keep non-empty directory: /home/haypo/prog/SVN/fusil/trunk/run-3 Features ======== Why using Fusil instead your own hand made C script? * Fusil limits child process environment: limit memory, use timeout, make sure that process is killed on session end * Fusil waits until system load is load before starting a fuzzing session * Fusil creates a session directory used as the process current working directory and Fusil only creates files in this directory (and not in /tmp) * Fusil stores all actions in fusil.log but also session.log for all actions related of a session * Fusil has multiple available probes to compute session score: guess if a sessions is a succes or not * Fusil redirects process output to a file and searchs bug text patterns in the stdout/stderr (Fusil contains many text patterns to detect crashes and problems) Installation ============ Read INSTALL documentation file. Documentation ============= Read doc/index.rst: documentation index. Changelog ========= Fusil 1.5 (2013-03-05) ---------------------- * experimental Python 3.3 support with the same code base; python 2.5 is no more supported * fusil-python: generate buffer objects and Unicode strings with surrogate characters * Change the default process memory limit from 100 MB to 500 MB Fusil 1.4 (2011-02-16) ---------------------- * Python 3 support * fusil-python: - improve function listing all Python modules: use sys.builtin_module_names and pkgutil.iter_modules() - blacklist more modules, classes and functions Fusil 1.3.2 (2010-01-09) ------------------------ * replay.py: set sys.path to ease the usage of Fusil without installing it * Fix fusil-gettext: ignore strace errors in locateMO() * fusil-python: - hide Python warnings - listAllModules() includes builtin modules - new option --only-c to test only modules written in C - fix memory leak: unload tested modules - fix getFunctions(): use also isclass() to detect classes * Disable Fusil process maximum memory limit Fusil 1.3.1 (2009-11-09) ------------------------ * fusil-python: autodiscover all modules instead of using a static list of modules, catch any exception when loading a module, only fuzz public functions (use module.__all__) * FileWatch: ignore duplicate parts on session rename * Remove session name parts duplicate (eg. "pickle-error-error" => "picke-error") * replay.py: don't redirect stdin to /dev/null if --ptrace is used * CPU probe: set max duration from 3 to 10 seconds (and rename the session on success) Fusil 1.3 (2009-09-18) ---------------------- * Create fusil-gimp * Remove charset from WriteCode: use builtin open() instead codecs.open() because files created by open() are much faster * Optimize FileWatch: don't recompile patterns at each session * fusil now depends on python-ptrace 0.6 * Don't use close_fds argument of subprocess.Popen() on Windows * Fix configuration reader: normal_calm_load, normal_calm_sleep, slow_calm_load, slow_calm_sleep keys global options are float, not integer * Project website moved to http://bitbucket.org/haypo/fusil/wiki/Home * FileWatch uses the pattern to rename the session Fusil 1.2.1 (2009-02-06) ------------------------ * Fix mangle agent of the Image Magick fuzzer * Fix AttachProcessPID() probe: stop the probe at process exit Fusil 1.2 (2009-02-04) ---------------------- User visible changes: * Fusil now requires Python 2.5 * Documentation: write an index (index.rst) and an user guide (usage.rst) * Replay script: copy HOME environment for GDB and catch setuid() error * fusil-firefox: support more file formats (bmp, gif, ico, png, svg), create --test command line option, write the HTML page into index.html file * fusil-python: write errors to stderr (instead of stdout) to avoid unicode error (especially with Python3) * FileWatch: rename the session with "long_output" if the program wrote more than max_nbline lines * fusil-python: blacklist posix.fork() to avoid false positive * If the process is killed by a signal, rename the session using the signal name (already worked if the debugger was disabled) Developer changes: * MangleAgent supports multiple input files * Create DummyMangle: agent with MangleFile API but don't touch file content to test the fuzzer * Network: close() method of NetworkClient and ServerClient use shutdown(SHUT_RDWR) * NetworkServer uses a backlog of 5 clients for socket.listen() (instead of 1) Bugfixes: * Fix Directory.rmtree() and replay script for Python 3.0 * Fix ServerClient.sendBytes(): use socket.send() result to get the next data offset Fusil 1.1 (2008-10-22) ---------------------- User visible changes: * replay.py: ask confirmation if the fuzzer will not be running under a different user or as root * Even with --force-unsafe, show safety warning if the fuzzer is running as the root user * Close files for child processes (close_fds=True) * Fix directory.rmtree() for Python 3.0 final Developer changes: * Create IntegerRangeGenerator in fusil.unicode_generator * Create EnvVarIntegerRange in fusil.process.env * Create fusil-wizzard fuzzer * Write timestamp in session.log * Add session() method to ProjectAgent * Add NAME attribute to a fuzzer, reused to choose the project directory name Bugfixes: * Fix Debugger.processSignal(): use the process agent to send the message (session_rename) since the debugger agent may be disabled * Fix replay.py: quote gdb arguments escape quote and antislash characters (eg. "text=\"Hello\\n\".") * replay.py uses /dev/null for stdin as Fusil does * FileWatch: open file in binary mode to use bytes in Python3 Fusil 1.0 final (2008-09-13) ---------------------------- Visible changes: * Create fusil-zzuf fuzzer (use the zzuf library) * Create fusil-vlc fuzzer (VLC media player) * For each session, generate a Python script (replay.py) to replay the session. The script can run the target in gdb, valgrind or gdb.py (python-ptrace debugger), with many options (--user, --limit, etc.) * Create --force-unsafe option, like --unsafe without the confirmation * CreateProcess is now a probe (with a score): if the debugger catchs a fatal signal, the session stops * Always use a null device as stdin for child processes to avoid blocking the fuzzer if the process reads stdin (eg. call getchar()) * Write the created process identifier in the logs Developer: * Create EnvVarIntegerRange: environment variable with an integer value in a fixed range * Changes to get a minimal Windows support: disable "change user/group" feature on Windows; remove log file before removing the project directory; use ":NUL" instead of /dev/null for null input/output * On setupProject() error, make sure that the project is cleaned * Close stdout files (input and output) at process exit (fix needed by Windows) * Rename long2raw() to uint2bytes(), and bytes2long() to bytes2uint() * Normalize score that make sure that a probe score is in range [-1; +1] and so that score*weight is in range[-weight; +weight] * CodeC: remove method lines(), writeCode() is renamed writeIntoFile(), use unicode strings (instead of byte strings) * Remove StdoutFile class, code merged in CreateProcess Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Operating System :: OS Independent Classifier: Natural Language :: English Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 fusil-1.5/fusil/0000775000175000017500000000000012305626161014111 5ustar haypohaypo00000000000000fusil-1.5/fusil/mas/0000775000175000017500000000000012305626161014671 5ustar haypohaypo00000000000000fusil-1.5/fusil/mas/agent.py0000664000175000017500000001102612253715322016341 0ustar haypohaypo00000000000000from __future__ import print_function from sys import stderr from fusil.mas.message import Message from fusil.mas.mailbox import Mailbox from weakref import ref as weakref_ref from ptrace.error import PTRACE_ERRORS, writeError from fusil.mas.agent_id import AgentID class AgentError(Exception): pass class Agent(object): def __init__(self, name, mta): self.agent_id = AgentID().generate() self.is_active = False self.name = name self.setupMTA(mta) def setupMTA(self, mta, logger=None): """ Create the mailbox and store a weak reference the message transfert agent (MTA). """ if mta: if logger: self.logger = logger else: self.logger = mta.logger self.mta = weakref_ref(mta) self.mailbox = Mailbox(self, mta) else: self.logger = None self.mta = None self.mailbox = None def __cmp__(self, other): return cmp(self.agent_id, other.agent_id) def __del__(self): try: if hasattr(self, 'is_active'): self.deactivate() if hasattr(self, "mailbox") and self.mailbox: self.mailbox.unregister() self.destroy() except KeyboardInterrupt: self.error("Agent destruction interrupted!") self.send('application_interrupt') except PTRACE_ERRORS as error: writeError(self, error, "Agent destruction error") def destroy(self): """ (Abstract method) Method called on agent destruction: cleanup data. """ pass def getEvents(self): """ List events which the agent listen on """ events = set() for attrname in dir(self): if attrname.startswith("on_"): events.add(attrname[3:]) return events def send(self, event, *arguments): """ send(event, *arguments): send a message (event) to other agents """ if not self.is_active: raise AgentError("Inactive agent are not allowed to send message!") message = Message(event, arguments) mta = self.mta() if mta is not None: mta.deliver(message) else: self.error("Unable to send %r: MTA is missing" % message) def readMailbox(self): """ Read the mailbox: "execute" each message. Return the number of executed messages. """ messages = self.mailbox.popMessages() for message in messages: message(self) return len(messages) def activate(self): """ Enable an agent: call init() method if the agent was disabled. """ if self.is_active: raise AgentError("%r is already activated!" % self) self.mailbox.clear() self.is_active = True self.init() def deactivate(self): """ Disable an agent: call deinit() method if the agent was enabled. """ if not self.is_active: return self.is_active = False self.deinit() def init(self): """ Abstract method called by activate() """ pass def live(self): """ Abstract method called at each univers step. """ pass def deinit(self): """ Abstract method called by deactivate() """ pass def _log(self, level, message): try: func = getattr(self.logger, level) except AttributeError: if level == "error": print("(no logger)", message, file=stderr) return func(message, sender=self) def debug(self, message): """ Write a message to the log with DEBUG level """ self._log('debug', message) def info(self, message): """ Write a message to the log with INFO level """ self._log('info', message) def warning(self, message): """ Write a message to the log with WARNING level """ self._log('warning', message) def error(self, message): """ Write a message to the log with ERROR level """ self._log('error', message) def __repr__(self): return '<%s id=%s, name=%r is_active=%s>' % ( self.__class__.__name__, self.agent_id, self.name, self.is_active) def __str__(self): return '<%s %r>' % (self.__class__.__name__, self.name) fusil-1.5/fusil/mas/message.py0000664000175000017500000000072112110032732016654 0ustar haypohaypo00000000000000class Message: def __init__(self, event, arguments): self.event = event self.arguments = arguments def __repr__(self): return '' % ( self.event, len(self.arguments)) def __call__(self, agent): try: function = "on_%s" % self.event function = getattr(agent, function) except AttributeError: return function(*self.arguments) fusil-1.5/fusil/mas/univers.py0000664000175000017500000000175512110032732016733 0ustar haypohaypo00000000000000from time import sleep from fusil.mas.application_agent import ApplicationAgent class Univers(ApplicationAgent): def __init__(self, application, mta, step_sleep): ApplicationAgent.__init__(self, "univers", application, mta) self.on_stop = None self.is_done = False self.step_sleep = step_sleep def executeAgent(self, agent): if not agent.is_active: return agent.readMailbox() agent.live() def execute(self, project): age = 0 self.is_done = False while True: age += 1 # Execute one univers step for agent in project.agents: self.executeAgent(agent) # Application is done? stop if self.is_done: return # Be nice with CPU: sleep some milliseconds sleep(self.step_sleep) def on_univers_stop(self): if self.on_stop: self.on_stop() self.is_done = True fusil-1.5/fusil/mas/mta.py0000664000175000017500000000354012110032732016013 0ustar haypohaypo00000000000000from fusil.mas.application_agent import ApplicationAgent from weakref import ref as weakref_ref class MTA(ApplicationAgent): """ Mail (message) transfer agent: - send(): store messages in a mailbox of message category - live(): deliver messages in agent mailboxes """ def __init__(self, application): ApplicationAgent.__init__(self, "mta", application, None) self.setupMTA(self, application.logger) self.mailing_list = {} self.queue = [] def hasMessage(self): return bool(self.queue) def clear(self): self.queue = [] def registerMailingList(self, mailbox, event): mailbox_ref = weakref_ref(mailbox) if event not in self.mailing_list: self.mailing_list[event] = [mailbox_ref] elif mailbox_ref not in self.mailing_list[event]: self.mailing_list[event].append(mailbox_ref) def unregisterMailingList(self, mailbox, event): if event not in self.mailing_list: return if mailbox not in self.mailing_list[event]: return self.mailing_list[event].remove(mailbox) def deliver(self, message): self.queue.append(message) def live(self): # Delive messages to agents including myself for message in self.queue: if message.event not in self.mailing_list: continue mailing_list = self.mailing_list[message.event] broken_refs = [] for mailbox_ref in mailing_list: mailbox = mailbox_ref() if mailbox is None: broken_refs.append(mailbox_ref) continue mailbox.deliver(message) # Remove broken references for mailbox_ref in broken_refs: mailing_list.remove(mailbox_ref) self.clear() fusil-1.5/fusil/mas/mailbox.py0000664000175000017500000000201712110032732016663 0ustar haypohaypo00000000000000from weakref import ref as weakref_ref class Mailbox: def __init__(self, agent, mta): self.messages = [] self.agent = weakref_ref(agent) self.mta = weakref_ref(mta) self.events = agent.getEvents() for event in self.events: mta.registerMailingList(self, event) def unregister(self): mta = self.mta() if not mta: return for event in self.events: mta.unregisterMailingList(self, event) def clear(self): self.messages = [] def deliver(self, message): agent = self.agent() if not agent: self.unregister() return if not agent.is_active: return self.messages.append(message) def popMessages(self): messages = self.messages self.messages = [] return messages def __repr__(self): agent = self.agent() if agent: return "" % agent else: return "" fusil-1.5/fusil/mas/agent_list.py0000664000175000017500000000166212253715322017401 0ustar haypohaypo00000000000000from ptrace.error import PTRACE_ERRORS, writeError class AgentList: def __init__(self): self.agents = [] def append(self, agent): if agent in self: raise KeyError("Agent %r already registred") self.agents.append(agent) def _destroy(self, agent): try: agent.deactivate() except PTRACE_ERRORS as error: writeError(None, error, "Agent deinit error") agent.unregister(False) def remove(self, agent, destroy=True): if agent not in self: return self.agents.remove(agent) if destroy: self._destroy(agent) def clear(self): while self.agents: agent = self.agents.pop() self._destroy(agent) def __del__(self): self.clear() def __contains__(self, agent): return agent in self.agents def __iter__(self): return iter(self.agents) fusil-1.5/fusil/mas/agent_id.py0000664000175000017500000000044412110032732017004 0ustar haypohaypo00000000000000class AgentID(object): instance = None counter = 0 def __new__(cls): if cls.instance is None: obj = object.__new__(cls) cls.instance = obj return cls.instance def generate(self): self.counter += 1 return self.counter fusil-1.5/fusil/mas/__init__.py0000664000175000017500000000000012110032732016755 0ustar haypohaypo00000000000000fusil-1.5/fusil/mas/application_agent.py0000664000175000017500000000074112110032732020713 0ustar haypohaypo00000000000000from fusil.mas.agent import Agent from weakref import ref as weakref_ref class ApplicationAgent(Agent): def __init__(self, name, application, mta): Agent.__init__(self, name, mta) self.application = weakref_ref(application) if application is not self: self.register() def register(self): self.application().registerAgent(self) def unregister(self, destroy=True): self.application().unregisterAgent(self, destroy) fusil-1.5/fusil/x11.py0000664000175000017500000000524312253715322015100 0ustar haypohaypo00000000000000from Xlib.X import NONE, KeyPress, KeyRelease, AnyPropertyType, CurrentTime from Xlib.display import Display from Xlib.protocol.event import (KeyPress as KeyPressEvent, KeyRelease as KeyReleaseEvent) from Xlib.protocol.request import InternAtom from re import compile as compileRegex, IGNORECASE def listWindows(root): children = root.query_tree().children for window in children: yield window for window in children: for window in listWindows(window): yield window def findWindowById(root, window_id): for window in listWindows(root): if window.id == window_id: return window raise KeyError("Unable to find Window 0x%08x" % window_id) def findWindowByNameRegex(root, name_regex, ignore_case=True): if ignore_case: flags = IGNORECASE else: flags = 0 match = compileRegex(name_regex, flags).search for window in listWindows(root): name = window.get_wm_name() if name and match(name): return window raise KeyError("Unable to find window with name regex: %r" % name_regex) def formatWindow(window): name = window.get_wm_name() info = [] if name: info.append(name) geometry = window.get_geometry() info.append('%sx%sx%s at (%s,%s)' % ( geometry.width, geometry.height, geometry.depth, geometry.x, geometry.y)) atom = InternAtom(display=window.display, name="_NET_WM_PID", only_if_exists=1) pid = window.get_property(atom.atom, AnyPropertyType, 0, 10) if pid: pid = int(pid.value.tolist()[0]) info.append('PID=%r' % pid) info.append("ID=0x%08x" % window.id) return '; '.join(info) def displayWindows(root, level=0): tree = root.query_tree() children = tree.children if not children: return indent = (" "*level) print(indent + "|== %s ===" % formatWindow(root)) parent = tree.parent if parent: print(indent + "|-- parent: %s" % formatWindow(parent)) for window in children: print(indent + "|-> %s" % formatWindow(window)) displayWindows(window, level+1) def sendKey(window, keycode, modifiers=0, released=True): if released: type = KeyRelease event_class = KeyReleaseEvent else: type = KeyPress event_class = KeyPressEvent event = event_class( type=type, detail=keycode, time=CurrentTime, root=NONE, window=window, child=NONE, root_x=0, root_y=0, event_x=0, event_y=0, state=modifiers, same_screen=1) window.send_event(event) window.display.flush() def getDisplay(): return Display() fusil-1.5/fusil/version.py0000664000175000017500000000016012253711645016151 0ustar haypohaypo00000000000000PACKAGE = "fusil" VERSION = "1.5" WEBSITE = "http://bitbucket.org/haypo/fusil/wiki/Home" LICENSE = "GNU GPL v2" fusil-1.5/fusil/project_directory.py0000664000175000017500000000365312261304517020223 0ustar haypohaypo00000000000000from os.path import basename from fusil.project_agent import ProjectAgent from fusil.directory import Directory from os import getcwd class ProjectDirectory(ProjectAgent, Directory): def __init__(self, project): # Create $PWD/run-0001 directory name Directory.__init__(self, getcwd()) name = project.application().NAME self.directory = self.uniqueFilename(name, save=False) # Initialize the agent and create the directory ProjectAgent.__init__(self, project, "directory:%s" % basename(self.directory)) self.warning("Create the directory: %s" % self.directory) self.mkdir() def keepDirectory(self, verbose=True): if not self.directory: return False # No session executed? Remove the directory project = self.project() application = self.application() # Fusil error? Keep the directory if application \ and application.exitcode: if verbose: self.warning("Fusil error: keep the directory %s" % self.directory) return True # Not session executed: remove the directory if project \ and not project.session_executed \ and (not application or not application.options.keep_sessions): return False # Keep generated files? if not self.isEmpty(True): # Project generated some extra files: keep the directory if verbose: self.error("Keep the non-empty directory %s" % self.directory) return True # Default: remove the directory return False def rmtree(self): if not self.directory: return self.info("Remove the directory: %s" % self.directory) Directory.rmtree(self) self.directory = None def destroy(self): keep = self.keepDirectory(verbose=False) if not keep: self.rmtree() fusil-1.5/fusil/unicode_generator.py0000664000175000017500000001033112254530314020152 0ustar haypohaypo00000000000000from fusil.bytes_generator import Generator from ptrace.six import text_type, unichr from ptrace.six.moves import range as xrange from random import choice, randint def createCharset(start, stop): return set(u''.join( unichr(code) for code in xrange(start, stop+1) )) # ASCII codes 0..255 ASCII8 = createCharset(0, 255) # ASCII codes 1..255 ASCII0 = createCharset(1, 255) # ASCII codes 0..127 ASCII7 = createCharset(0, 127) # ASCII codes 32..126 PRINTABLE_ASCII = createCharset(32, 126) # Unicode: codes 0..65535 UNICODE_65535 = createCharset(0, 65535) # Letters and digits UPPER_LETTERS = set(u'ABCDEFGHIJKLMNOPQRSTUVWXYZ') LOWER_LETTERS = set(u'abcdefghijklmnopqrstuvwxyz') LETTERS = UPPER_LETTERS | LOWER_LETTERS DECIMAL_DIGITS = set(u'0123456789') HEXADECIMAL_DIGITS = DECIMAL_DIGITS | set(u'abcdefABCDEF') PUNCTUATION = set(u' .,-;?!:(){}[]<>\'"/\\') class UnicodeGenerator(Generator): def __init__(self, min_length, max_length, charset=ASCII8): Generator.__init__(self, min_length, max_length) self.charset = list(charset) def _createValue(self, length): if 1 < len(self.charset): return u''.join( choice(self.charset) for index in xrange(length) ) else: return self.charset[0] * length class UnsignedGenerator(UnicodeGenerator): """Unsigned integer""" def __init__(self, max_length=20, charset=DECIMAL_DIGITS, min_length=1): # 2^32 length in decimal: 10 digits # 2^64 length in decimal: 20 digits # 2^128 length in decimal: 39 digits UnicodeGenerator.__init__(self, min_length, max_length, charset) # First digit charset self.first_digit = list(charset - set('0')) def _createValue(self, length): if 2 <= length: return choice(self.first_digit) \ + UnicodeGenerator._createValue(self, length - 1) else: return UnicodeGenerator._createValue(self, length) class IntegerGenerator(UnsignedGenerator): """Signed integer""" def __init__(self, max_length=21, charset=DECIMAL_DIGITS): # 2^32 length in decimal: 10 digits + sign = 11 # 2^64 length in decimal: 20 digits + sign = 21 # 2^128 length in decimal: 39 digits + sign = 40 UnsignedGenerator.__init__(self, max_length, charset=charset, min_length=2) def _createValue(self, length): value = UnsignedGenerator._createValue(self, length-1) if randint(0, 1) == 1: value = u"-" + value return value class IntegerRangeGenerator(Generator): """ Random signed integer in the specified range. """ def __init__(self, min, max): Generator.__init__(self, 1, 1) self.min = min self.max = max def createValue(self): value = randint(self.min, self.max) return text_type(value) class UnixPathGenerator(UnicodeGenerator): def __init__(self, max_length=None, absolute=False, charset=None): if not max_length: max_length = 5000 UnicodeGenerator.__init__(self, 1, max_length) if not charset: charset = UPPER_LETTERS | LOWER_LETTERS | DECIMAL_DIGITS | set('-_.') self.filename_length = 100 self.filename_generator = UnicodeGenerator(1, 1, charset) self.change_dir = (".", "..") self.absolute = absolute def _createValue(self, length): path = [] path_len = 0 while path_len < length: if not path: # Absolute path? (25%) use_slash = self.absolute or (randint(0, 4) == 0) elif path[-1] == u'/': # Add double slash, eg. /a/b// ? (10%) use_slash = (randint(0, 9) == 0) else: use_slash = True if use_slash: part = u'/' else: filelen = min(randint(1, length - path_len), self.filename_length) if randint(0, 9) != 0: # Filename part = self.filename_generator.createValue(length=filelen) else: # "." or ".." part = choice(self.change_dir) path.append(part) path_len += len(part) return u''.join(path) fusil-1.5/fusil/c_tools.py0000664000175000017500000001435312254530331016127 0ustar haypohaypo00000000000000from fusil.bytes_generator import BytesGenerator from fusil.process.tools import runCommand, locateProgram from fusil.write_code import WriteCode from os.path import basename from ptrace.os_tools import RUNNING_WINDOWS from ptrace.six import b, text_type, string_types, PY2 from random import choice, randint from struct import pack class CompilerError(Exception): pass GCC_PROGRAM = None MIN_INT32 = -2**31 MAX_INT32 = (2**21)-1 def encodeUTF32(text): data = [] for character in text: data.append( pack('I', ord(character)) ) return b('').join(data) def quoteString(text): data = [] for character in text: if character == '"': data.append('\\"') elif character == "\0": data.append('\\0') elif character == "\n": data.append('\\n') elif character == "\\": data.append('\\\\') elif 32 <= ord(character) <= 127: data.append(character) else: data.append("\\x%02X" % ord(character)) return '"'+''.join(data)+'"' def compileC(logger, c_filename, output_filename, options=None, debug=True, libraries=None): """ Compile a C script. Raise CompilerError on failure. """ global GCC_PROGRAM if not GCC_PROGRAM: if RUNNING_WINDOWS: program = u"gcc.exe" else: program = u"gcc" GCC_PROGRAM = locateProgram(program, raise_error=True) command = [GCC_PROGRAM, u"-o", output_filename, c_filename] if debug: command.extend((u"-Wall", u"-Wextra", u"-Werror")) if libraries: for library in libraries: command.append(u"-l%s" % library) if options: options = options.split() command.extend(options) try: runCommand(logger, command) except RuntimeError as err: raise CompilerError("Unable to compile %s: %s" % (basename(c_filename), err)) class FunctionC: def __init__(self, name, arguments=None, type="void"): self.name = text_type(name) if arguments: self.arguments = arguments else: self.arguments = tuple() self.type = type self.variables = [] self.code = [] self.footer = [] def lines(self): yield (0, u"%s %s(%s) {" % (self.type, self.name, ", ".join(self.arguments))) for variable in self.variables: yield (1, variable + u';') if self.variables: yield None for code in self.code: if isinstance(code, string_types): yield (1, code) else: level, code = code yield (level+1, code) if self.footer: yield None for line in self.footer: yield (1, line) yield (0, u'}') def callFunction(self, name, arguments, result_variable=None): if result_variable: name = u'%s = %s(' % (result_variable, name) else: name = u'%s(' % name if arguments: self.code.append(name) last_index = len(arguments)-1 for index, argument in enumerate(arguments): argument = text_type(argument) if index != last_index: argument += u',' self.code.append( (1, argument) ) self.code.append(u');') else: self.code.append(name + u');') def add(self, instr): self.code.append(instr + u';') def __repr__(self): return '' % (self.type, self.name) class CodeC(WriteCode): def __init__(self): WriteCode.__init__(self) self.includes = [] self.gnu_source = False self.functions = {} self.functions_list = [] def addFunction(self, function): self.functions[function.name] = function self.functions_list.append(function) return function def addMain(self, with_argv=False, type=u'int', footer=u'return 0;'): if with_argv: arguments = (u'int argc', u'char **argv') else: arguments = None main = FunctionC(u'main', arguments, type) if footer: footer = text_type(footer) main.footer.append(footer) self.addFunction(main) return main def __getitem__(self, name): return self.functions[name] def writeCode(self): if self.gnu_source: self.write(0, u"#define _GNU_SOURCE") for include in self.includes: self.write(0, u"#include %s" % include) if self.includes: self.emptyLine() for function in self.functions_list: for line in function.lines(): if line: level, text = line self.write(level, text) else: self.emptyLine() self.emptyLine() def writeIntoFile(self, filename): self.createFile(filename) self.writeCode() self.close() def compile(self, logger, c_filename, program_filename, **kw): self.writeIntoFile(c_filename) compileC(logger, c_filename, program_filename, **kw) class FuzzyFunctionC(FunctionC): def __init__(self, name, arguments=None, type="void", random_bytes=400): FunctionC.__init__(self, name, arguments, type) self.bytes_generator = BytesGenerator(random_bytes, random_bytes) self.buffer_count = 0 self.special_int32 = (0x8000, 0xffff, 0x80000000) def createInt32(self): state = randint(1, 3) if state == 1: return choice(self.special_int32) elif state == 2: return (0xffffff00 | randint(0, 255)) else: return randint(MIN_INT32, MAX_INT32) def createInt(self): return self.createInt32() def createRandomBytes(self): self.buffer_count += 1 name = "buffer%s" % self.buffer_count value = self.bytes_generator.createValue() size = len(value) if PY2: value = ', '.join("0x%02x" % ord(item) for item in value) else: value = ', '.join("0x%02x" % item for item in value) self.variables.append("const char %s[] = {%s}" % (name, value)) return (name, size) fusil-1.5/fusil/application_logger.py0000664000175000017500000000745712110032732020327 0ustar haypohaypo00000000000000from sys import stdout from weakref import ref as weakref_ref from logging import (getLogger, StreamHandler, Formatter, DEBUG, INFO, WARNING, ERROR) from os import unlink LOG_FILENAME = 'fusil.log' class ApplicationLogger: def __init__(self, application): self.application = weakref_ref(application) self.timestamp_format = '%(asctime)s: %(message)s' # Get the logger self.logger = getLogger() self.logger.setLevel(ERROR) # Create stdout logger handler = StreamHandler(stdout) self.stdout = self.addHandler(handler, ERROR) # File logger is not set yet self.filename = None self.file_handler = None def applyOptions(self, options): # Choose log levels if options.debug: stdout_level = INFO file_level = DEBUG elif options.verbose: stdout_level = WARNING file_level = INFO elif not options.quiet: stdout_level = ERROR file_level = WARNING else: stdout_level = ERROR file_level = INFO logger_level = min(INFO, stdout_level, file_level) # Update log levels self.logger.setLevel(logger_level) self.stdout.setLevel(stdout_level) # fusil.log file self.filename = LOG_FILENAME self.file_handler = self.addFileHandler(self.filename, file_level) def addFileHandler(self, filename, level=None, mode='w', formatter_class=Formatter): if level is None: if self.application().options.verbose: level = DEBUG else: level = INFO handler = StreamHandler(open(filename, mode)) formatter = formatter_class(self.timestamp_format) handler.setFormatter(formatter) self.addHandler(handler, level) return handler def addHandler(self, handler, level=None): handler.setLevel(level) self.logger.addHandler(handler) return handler def removeFileHandler(self, handler): handler.stream.close() self.removeHandler(handler) def removeHandler(self, handler): handler.close() self.logger.removeHandler(handler) def unlinkFile(self): if self.file_handler: self.removeFileHandler(self.file_handler) self.file_handler = None if self.filename: unlink(self.filename) self.filename = None def formatMessage(self, message, sender): message = str(message) application = self.application() prefix = [] if application: if application.options: debug = application.options.debug else: debug = False project = application.project if project and project.session_index: prefix.append('[%s]' % project.nb_success) session = project.session if session: prefix.append('[%s]' % session.name) if debug and project.step: prefix.append('[step %s]' % project.step) else: debug = False if debug and sender is not None: prefix.append('[%s]' % sender.name) if prefix: message = ''.join(prefix)+' '+message return message def debug(self, message, sender): self.log(self.logger.debug, message, sender) def info(self, message, sender): self.log(self.logger.info, message, sender) def warning(self, message, sender): self.log(self.logger.warning, message, sender) def error(self, message, sender): self.log(self.logger.error, message, sender) def log(self, func, message, sender): message = self.formatMessage(message, sender) func(message) fusil-1.5/fusil/mangle.py0000664000175000017500000000733112254530347015735 0ustar haypohaypo00000000000000from array import array from fusil.mangle_agent import MangleAgent from fusil.mangle_op import SPECIAL_VALUES, MAX_INCR from fusil.tools import minmax from ptrace.six.moves import range as xrange from random import randint, choice class MangleConfig: def __init__(self, min_op=1, max_op=100, operations=None): """ Number of operations: min_op..max_op Operations: list of function names (eg. ["replace", "bit"]) """ self.min_op = min_op self.max_op = max_op self.max_insert_bytes = 4 self.max_delete_bytes = 4 self.max_incr = MAX_INCR self.first_offset = 0 self.change_size = False if operations: self.operations = operations else: self.operations = None class Mangle: def __init__(self, config, data): self.config = config self.data = data def generateByte(self): return randint(0, 255) def offset(self, last=1): first = self.config.first_offset last = len(self.data)-last if last < first: raise ValueError( "Invalid first_offset value (first=%s > last=%s)" % (first, last)) return randint(first, last) def mangle_replace(self): self.data[self.offset()] = self.generateByte() def mangle_bit(self): offset = self.offset() bit = randint(0, 7) if randint(0, 1) == 1: value = self.data[offset] | (1 << bit) else: value = self.data[offset] & (~(1 << bit) & 0xFF) self.data[offset] = value def mangle_special_value(self): text = choice(SPECIAL_VALUES) offset = self.offset(len(text)) self.data[offset:offset+len(text)] = array("B", text) def mangle_increment(self): incr = randint(1, self.config.max_incr) if randint(0, 1) == 1: incr = -incr offset = self.offset() self.data[offset] = minmax(0, self.data[offset] + incr, 255) def mangle_insert_bytes(self): offset = self.offset() count = randint(1, self.config.max_insert_bytes) for index in xrange(count): self.data.insert(offset, self.generateByte()) def mangle_delete_bytes(self): offset = self.offset(2) count = randint(1, self.config.max_delete_bytes) count = min(count, len(self.data)-offset) del self.data[offset:offset+count] def run(self): """ Mangle data and return number of applied operations """ operation_names = self.config.operations if not operation_names: operation_names = ["replace", "bit", "special_value"] if self.config.change_size: operation_names.extend(("insert_bytes", "delete_bytes")) operations = [] for name in operation_names: operation = getattr(self, "mangle_" + name) operations.append(operation) if self.config.max_op <= 0: return 0 count = randint(self.config.min_op, self.config.max_op) for index in xrange(count): operation = choice(operations) operation() return count class MangleFile(MangleAgent): """ Inject errors in a valid file ("mutate" or "mangle" a file) to create new files. Use the config attribute (a MangleConfig instance) to configure the mutation parameters. """ def __init__(self, project, source, nb_file=1): MangleAgent.__init__(self, project, source, nb_file) self.config = MangleConfig() def mangleData(self, data, file_index): # Mangle bytes count = Mangle(self.config, data).run() self.info("Mangle operation: %s" % count) return data fusil-1.5/fusil/bytes_generator.py0000664000175000017500000000466012253713423017665 0ustar haypohaypo00000000000000r""" Bytes generators: - BytesGenerator - LengthGenerator Byte sets: - ASCII8: 0..255 - ASCII0: 1..255 - ASCII7: 0..127 - PRINTABLE_ASCII: 32..126 - UPPER_LETTERS: 'A'..'Z' - LOWER_LETTERS: 'a'..'z' - LETTERS: UPPER_LETTERS | LOWER_LETTERS - DECIMAL_DIGITS: "0".."9" - HEXADECIMAL_DIGITS: DECIMAL_DIGITS | 'a'..'f' | 'A'..'F' - PUNCTUATION: --> .,-;?!:(){}[]<>'"/\<-- """ from random import choice, randint from ptrace.os_tools import RUNNING_PYTHON3 from ptrace.six import b def createBytesSet(start, stop): if RUNNING_PYTHON3: return set(range(start, stop+1)) else: return set(chr(code) for code in xrange(start, stop+1)) # ASCII codes 0..255 ASCII8 = createBytesSet(0, 255) # ASCII codes 1..255 ASCII0 = createBytesSet(1, 255) # ASCII codes 0..127 ASCII7 = createBytesSet(0, 127) # ASCII codes 32..126 PRINTABLE_ASCII = createBytesSet(32, 126) # Letters and digits UPPER_LETTERS = set(b('ABCDEFGHIJKLMNOPQRSTUVWXYZ')) LOWER_LETTERS = set(b('abcdefghijklmnopqrstuvwxyz')) LETTERS = UPPER_LETTERS | LOWER_LETTERS DECIMAL_DIGITS = set(b('0123456789')) HEXADECIMAL_DIGITS = DECIMAL_DIGITS | set(b('abcdefABCDEF')) PUNCTUATION = set(b(' .,-;?!:(){}[]<>\'"/\\')) class Generator: def __init__(self, min_length, max_length): self.min_length = min_length self.max_length = max_length def createLength(self): return randint(self.min_length, self.max_length) def _createValue(self, length): raise NotImplementedError() def createValue(self, length=None): if length is None: length = self.createLength() return self._createValue(length) class BytesGenerator(Generator): def __init__(self, min_length, max_length, bytes_set=ASCII8): Generator.__init__(self, min_length, max_length) self.bytes_set = bytes_set def _createValue(self, length): bytes_list = list(self.bytes_set) if len(bytes_list) != 1: if RUNNING_PYTHON3: return bytes(choice(bytes_list) for index in range(length)) else: return ''.join(choice(bytes_list) for index in xrange(length)) else: value = bytes_list[0] if RUNNING_PYTHON3: value = bytes((value,)) return value * length class LengthGenerator(BytesGenerator): def __init__(self, min_length, max_length): BytesGenerator.__init__(self, min_length, max_length, set(b('A'))) fusil-1.5/fusil/mockup.py0000664000175000017500000000271012253715322015761 0ustar haypohaypo00000000000000""" Classes mockup used for unit tests. """ from weakref import ref class Logger: def __init__(self, show=False): self.show = show def debug(self, message, sender=None): self.display(message) def info(self, message, sender=None): self.display(message) def warning(self, message, sender=None): self.display(message) def error(self, message, sender=None): self.display(message) def display(self, message): if not self.show: return print(message) class MTA: def __init__(self, logger=None): if not logger: logger = Logger() self.logger = logger def registerMailingList(self, mailbox, event): pass def deliver(self, message): pass class Options: def __init__(self): self.debug = False class Application: def __init__(self): self.options = Options() def initX11(self): pass class Config: def __getattr__(self, name): return None class Debugger: def tracePID(self, agent, pid): pass class Project: def __init__(self, logger=None): self._mta = MTA(logger) self.mta = ref(self._mta) self._application = Application() self.application = ref(self._application) self.debugger = Debugger() self.config = Config() def registerAgent(self, agent): pass def unregisterAgent(self, agent): pass fusil-1.5/fusil/directory.py0000664000175000017500000000531712254530362016475 0ustar haypohaypo00000000000000from os import mkdir, listdir, chmod, umask from os.path import basename, join as path_join, exists as path_exists from ptrace.six import text_type from shutil import rmtree from sys import getfilesystemencoding class Directory: def __init__(self, directory): self.directory = directory # Filenames generated by uniqueFilename() method self.files = set() def ignore(self, filename): try: self.files.remove(filename) except KeyError: pass def mkdir(self): old_umask = umask(0) mkdir(self.directory, 0o775) umask(old_umask) def isEmpty(self, ignore_generated=False): for filename in listdir(self.directory): if filename in ('.', '..'): continue if filename in self.files and ignore_generated: continue return False return True def rmtree(self): filename = self.directory if isinstance(filename, text_type): # Convert to byte strings because rmtree() doesn't support mixing # byte and unicode strings charset = getfilesystemencoding() filename = filename.encode(charset) rmtree(filename, onerror=self.rmtree_error) def rmtree_error(self, operation, argument, stack): # Try to change file permission (allow write) and retry try: chmod(argument, 0o777) except OSError: pass operation(argument) def uniqueFilename(self, name, count=None, count_format="%d", save=True): # Test with no count suffix name = basename(name) if not name: raise ValueError("Empty filename") if count is None and not self._exists(name): if save: self.files.add(name) return path_join(self.directory, name) # Create filename pattern: "archive.tar.gz" => "archive-%04u.tar.gz" name_pattern = name.split(".", 1) if count is None: count = 2 count_format = "-" + count_format if 1 < len(name_pattern): name_pattern = name_pattern[0] + count_format + '.' + name_pattern[1] else: name_pattern = name_pattern[0] + count_format # Try names and increment count at each step while True: name = name_pattern % count if not self._exists(name): if save: self.files.add(name) return path_join(self.directory, name) count += 1 def _exists(self, name): if name in self.files: return True filename = path_join(self.directory, name) return path_exists(filename) fusil-1.5/fusil/application.py0000664000175000017500000003770112253715322016776 0ustar haypohaypo00000000000000from optparse import OptionParser, OptionGroup from fusil.unsafe import SUPPORT_UID from sys import exit, stdout if SUPPORT_UID: from os import getuid, getgid from fusil.process.tools import runCommand from fusil.project import Project from fusil.mas.application_agent import ApplicationAgent from fusil.mas.mta import MTA from fusil.mas.univers import Univers from ptrace.error import PTRACE_ERRORS, writeError from ptrace.os_tools import RUNNING_PYTHON3 from fusil.application_logger import ApplicationLogger from fusil.process.tools import limitMemory, beNice from fusil.file_tools import relativePath from fusil.version import VERSION, LICENSE, WEBSITE from fusil.mas.agent_list import AgentList from fusil.config import FusilConfig, ConfigError from fusil.xhost import xhostCommand if SUPPORT_UID: from pwd import getpwnam, getpwuid from grp import getgrnam, getgrgid try: # Use readline to get better raw_input() import readline except ImportError: pass def formatLimit(limit): if 0 < limit: return str(limit) else: return "unlimited" class Application(ApplicationAgent): """ Application class is responsible to execute a fuzzer using Fusil: - parse the command line - setup logging - create the project - execute the project - cleanup on exit """ # Fuzzer name: short alphanumeric string NAME = "fusil" # Command line usage USAGE = "%prog [options]" # Number of command line arguments: fixed value or a range (min, max). # Use (min, None) to only check the minimum number of arguments. NB_ARGUMENTS = 0 def __init__(self): self.agents = AgentList() ApplicationAgent.__init__(self, "application", self, None) self.setup() def registerAgent(self, agent): self.agents.append(agent) def unregisterAgent(self, agent, destroy=True): if agent not in self.agents: return self.agents.remove(agent, destroy) def createFuzzerOptions(self, parser): """ Create command line options specific to a fuzzer """ return None def createOptionParser(self): """ Create all command line options including Fusil options. """ parser = OptionParser(usage=self.USAGE) parser.add_option("--version", help="Display Fusil version (%s) and exit" % VERSION, action="store_true") options = self.createFuzzerOptions(parser) if options: parser.add_option_group(options) fuzzer = OptionGroup(parser, "Fuzzer") fuzzer.add_option("--success", help="Maximum number of success sessions (default: %s)" % formatLimit(self.config.fusil_success), type="int", default=self.config.fusil_success) fuzzer.add_option("--fast", help="Run as fast as possible (opposite of --slow)", action="store_true") fuzzer.add_option("--slow", help="Try to keep system load low: be nice with CPU (opposite of --fast)", action="store_true") fuzzer.add_option("--sessions", help="Maximum number of session (default: %s)" % formatLimit(self.config.fusil_session), type="int", default=self.config.fusil_session) fuzzer.add_option("--keep-generated-files", help="Keep a session directory if it contains generated files", action="store_true") fuzzer.add_option("--keep-sessions", help="Do not remove session directories", action="store_true") fuzzer.add_option("--aggressivity", help="Initial aggressivity factor in percent, value in -100.0..100.0 (default: 0.0%%)", type="float", default=None) fuzzer.add_option("--unsafe", help="Don't change user or group for child processes", action="store_true", default=False) fuzzer.add_option("--force-unsafe", help="Similar to --unsafe option but don't ask confirmation", action="store_true", default=False) parser.add_option_group(fuzzer) log = OptionGroup(parser, "Logging") log.add_option('-v', "--verbose", help="Enable verbose mode (set log level to WARNING)", action="store_true") log.add_option("--quiet", help="Be quiet (lowest log level), don't create log file", action="store_true") parser.add_option_group(log) debug = OptionGroup(parser, "Development") debug.add_option("--debug", help="Enable debug mode (set log level to DEBUG)", action="store_true") debug.add_option("--profiler", help="Enable Python profiler", action="store_true") parser.add_option_group(debug) return parser def parseOptions(self): parser = self.createOptionParser() self.options, self.arguments = parser.parse_args() # Just want to know the version? if self.options.version: print("Fusil version %s" % VERSION) print("License: %s" % LICENSE) print("Website: %s" % WEBSITE) print("") exit(0) if self.options.quiet: self.options.debug = False self.options.verbose = False if self.options.debug: self.options.verbose = True self.processOptions(parser, self.options, self.arguments) def processOptions(self, parser, options, arguments): # Check the number of arguments nb_arg = len(arguments) if isinstance(self.NB_ARGUMENTS, tuple): # Range (min, max) min_arg, max_arg = self.NB_ARGUMENTS need_arg = False if nb_arg < min_arg: need_arg = True elif (max_arg is not None) \ and (max_arg < nb_arg): need_arg = True else: # Fixed number of arguments need_arg = (nb_arg != self.NB_ARGUMENTS) if need_arg: parser.print_help() exit(1) # --force-unsafe enables --unsafe if options.force_unsafe: options.unsafe = True def setup(self): # Application objects self.exitcode = 0 self.interrupted = False self.project = None self._setup_x11 = False self.options = None # Create the logger self.logger = ApplicationLogger(self) # Read configuration try: self.config = FusilConfig() except ConfigError as err: self.fatalError(u"Configuration error: %s" % err) # Read command line options self.parseOptions() # Setup the logger and display Fusil version, license and website self.logger.applyOptions(self.options) self.error("Fusil version %s -- %s" % (VERSION, LICENSE)) self.error(WEBSITE) # Check the configuration try: self.processConfig() except ConfigError as err: self.fatalError(u"Configuration error: %s" % err) # Limit Fusil environment if not self.options.fast: beNice(True) if 0 < self.config.fusil_max_memory: self.info("Limit memory to %s bytes" % self.config.fusil_max_memory) limitMemory(self.config.fusil_max_memory) # Create multi agent system self.createMAS() def processConfig(self): config = self.config if not SUPPORT_UID: config.process_user = None config.process_group = None return # Use --unsafe? if self.options.unsafe: config.process_user = None config.process_group = None # Get user name and identifier errors = [] user = config.process_user if user: try: try: # user is the user identifier (as string) config.process_uid = int(user) config.process_user = getpwuid(config.process_uid).pw_name except ValueError: # user is the user name config.process_uid = getpwnam(user).pw_uid except KeyError: errors.append("the user %r" % user) # Get group name and identifier group = config.process_group if group: try: try: # group is the group identifier (as string) config.process_gid = int(group) config.process_user = getgrgid(config.process_gid).gr_name except ValueError: # group is the group name config.process_gid = getgrnam(group).gr_gid except KeyError: errors.append("the group %r" % group) # Display error if any if errors: message = u"Unable to get the identifier of " message += u" and ".join(errors) message += u" (create missing user/group or use --unsafe option)" raise ConfigError(message) # Display the safety warning (if needed) self.safetyWarning() # Display second warning about force unsafee if self.options.force_unsafe: self.error("") self.error("!!!WARNING!!! You choosed --force-unsafe, so don't cry if you lost any file or process!") self.error("") def safetyWarning(self): if not SUPPORT_UID: return # uid or gid is None? uid = self.config.process_uid gid = self.config.process_gid if (uid is not None) and (gid is not None): return # Don't show the warning running_root = (uid is None) and (getuid() == 0) if self.options.force_unsafe \ and not running_root: return # Display huge error message if uid is None: uid = getuid() if gid is None: gid = getgid() self.error("") self.error("!!!WARNING!!! The fuzzer will run as user %s and group %s," % (uid, gid)) self.error("!!!WARNING!!! and may remove arbitrary files and kill arbitrary processes.") if not self.options.unsafe: self.error("!!!WARNING!!! Change your Fusil configuration (%s)" % self.config.filename) self.error("!!!WARNING!!! to use different user and group, or use --unsafe command") self.error("!!!WARNING!!! line option to use current user and group (%s:%s)." % (getuid(), getgid())) if not running_root: # always show the warning when running as root! self.error("!!!WARNING!!! Use --force-unsafe to avoid this warning.") self.error("") # Ask confirmation try: answer = None while answer not in (u"yes", u"no", u""): if answer: prompt = 'Please answer "yes" or "no": ' else: prompt = 'Do you want to continue? (yes/NO) ' if RUNNING_PYTHON3: answer = input(prompt) else: answer = raw_input(prompt) answer = answer.strip().lower() confirm = (answer == 'yes') except (KeyboardInterrupt, EOFError): stdout.write("\n") confirm = False if not confirm: self.fatalError() def createMAS(self): # Create mail transfer agent (MTA) self.mta = None mta = MTA(self) # Create univers if self.options.fast: # note: without sleep, the fuzzer is slower than sleep(0.001) step_sleep = 0.001 elif not self.options.slow: step_sleep = 0.010 else: step_sleep = 0.050 self.univers = Univers(self, mta, step_sleep) # Finish to setup application self.setupMTA(mta, self.logger) self.registerAgent(self) # Activate agents mta.activate() self.activate() self.univers.activate() def interrupt(self, message): self.interrupted = True self.error(message) def exit(self, keep_log=True): """ Cleanup on exiting: destroy agents """ self.warning("Exit Fusil") if not keep_log: self.logger.unlinkFile() elif self.logger.filename: self.error("Fusil log written into %s" % relativePath(self.logger.filename)) self.mta = None self.univers = None try: self.agents.clear() except KeyboardInterrupt: self.interrupt("Application cleanup interrupted!") except PTRACE_ERRORS as error: writeError(None, error, "AGENT DEINIT ERRORT") self.exitcode = 1 self.deinitX11() self.config = None def fatalError(self, message=None): """ Fatal error: display a message (if message is set) and exit the fuzzer. """ self.exit_code = 1 if message: self.error(message) self.exit(keep_log=False) exit(self.exitcode) def executeProject(self): """ Execute the fuzzer: create a session, execute the session, destroy the session, create a second session, etc. """ self.project.activate() try: if self.options.profiler: from ptrace.profiler import runProfiler runProfiler(self, self.univers.execute, (self.project,)) else: self.univers.execute(self.project) except KeyboardInterrupt: self.interrupt("Fuzzer execution interrupted!") except PTRACE_ERRORS as error: writeError(self, error, "Fuzzer execution error") self.exitcode = 1 self.project.deactivate() def setupProject(self): """ (Abstract method) Setup the project: create project agents, prepare the environment, create some files or directories, etc. """ raise NotImplementedError() def runProject(self): """ Load, execute and destroy the project. """ # Load project self.project = Project(self) try: # Create the project self.setupProject() self.registerAgent(self.project) # Execute project self.executeProject() self.unregisterAgent(self.project) finally: # Destroy project self.project.destroy() self.project = None def on_application_interrupt(self): self.error("User interrupt!") self.send('univers_stop') def on_application_error(self, message): self.error(message) self.exitcode = 1 self.send('univers_stop') def main(self, exit_at_end=True): """ Main function of a fuzzer using Fusil: call runProject(), catch errors, and exit (if exit_at_end is True) with 0 on success or 1 on error. """ try: self.runProject() except KeyboardInterrupt: self.interrupt("Project interrupted!") except PTRACE_ERRORS as error: writeError(self, error) self.exitcode = 1 if exit_at_end: self.exit() exit(self.exitcode) def initX11(self): """ X11 initialization: allow the fusil user to use X11 using xhost program. """ if self._setup_x11: return self._xhost(self.config, True) self._setup_x11 = True def deinitX11(self): """ X11 deinitialization: disallow the fusil user to use X11 using xhost program. """ if not self._setup_x11: return self._setup_x11 = False self._xhost(self.config, False) def _xhost(self, config, allow): if config.process_uid is None: return command = xhostCommand(config.fusil_xhost_program, config.process_uid, allow) runCommand(self, command, stdout=None) fusil-1.5/fusil/write_code.py0000664000175000017500000000201212254530373016604 0ustar haypohaypo00000000000000from os import chmod from ptrace.six import text_type class WriteCode: def __init__(self): self.indent = u' ' * 4 self.base_level = 0 def useStream(self, stream): self.output = stream def createFile(self, filename, mode=None): self.output = open(filename, 'w') if mode: chmod(filename, mode) def close(self): if not self.output: return self.output.close() self.output = None def emptyLine(self): self.output.write("\n") def addLevel(self, delta): level = self.base_level self.base_level += delta return level def restoreLevel(self, level): self.base_level = level def indentLine(self, level, text): if not isinstance(text, text_type): text = unicode(text, 'ASCII') return self.indent * (self.base_level + level) + text def write(self, level, text): line = self.indentLine(level, text) self.output.write(line + "\n") fusil-1.5/fusil/file_tools.py0000664000175000017500000000255112253715322016625 0ustar haypohaypo00000000000000from errno import EEXIST from os import mkdir, getpid, fstat, getcwd from os.path import basename from datetime import datetime from ptrace.os_tools import RUNNING_LINUX if RUNNING_LINUX: from ptrace.linux_proc import readProcessLink def safeMkdir(path): try: mkdir(path) except OSError as err: if err.errno == EEXIST: return else: raise def filenameExtension(filename): ext = basename(filename) if '.' in ext: return '.'+ext.rsplit('.', 1)[-1] else: return None def dumpFileInfo(logger, file_obj): try: fileno = file_obj.fileno() except AttributeError: logger.info("File object class: %s" % file_obj.__class__.__name__) return if RUNNING_LINUX: filename = readProcessLink(getpid(), 'fd/%s' % fileno) logger.info("File name: %r" % filename) logger.info("File descriptor: %s" % fileno) stat = fstat(fileno) logger.info("File user/group: %s/%s" % (stat.st_uid, stat.st_gid)) logger.info("File size: %s bytes" % stat.st_size) logger.info("File mode: %04o" % stat.st_mode) mtime = datetime.fromtimestamp(stat.st_mtime) logger.info("File modification: %s" % mtime) def relativePath(path, cwd=None): if not cwd: cwd = getcwd() if path.startswith(cwd): path = path[len(cwd)+1:] return path fusil-1.5/fusil/system_calm.py0000664000175000017500000000327112110032732016773 0ustar haypohaypo00000000000000from fusil.error import FusilError from fusil.linux.cpu_load import SystemCpuLoad from time import time, sleep class SystemCalm: def __init__(self, max_load, sleep_second): self.load = SystemCpuLoad() self.max_load = max_load self.sleep = sleep_second self.first_message = 3.0 self.repeat_message = 5.0 self.max_wait = 60*5 # seconds (5 minutes) def wait(self, agent): first_message = False start = time() next_message = time() + self.first_message while True: load = self.load.get(estimate=False) if load <= self.max_load: break duration = time() - start if next_message < time(): first_message = True next_message = time() + self.repeat_message agent.error("Wait until system load is under %.1f%% since %.1f seconds (current: %.1f%%)..." % (self.max_load*100, duration, load*100)) elif not first_message: first_message = True agent.info("Wait until system load is under %.1f%% (current: %.1f%%)..." % (self.max_load*100, load*100)) if self.max_wait <= duration: raise FusilError( 'Unable to calm down system load after ' \ '%.1f seconds (current load: %.1f%% > max: %.1f%%)' % ( duration, load*100, self.max_load*100)) sleep(self.sleep) if first_message: duration = time() - start agent.info("System is now calm after %.1f seconds (current load: %.1f%%)" % ( duration, load*100)) fusil-1.5/fusil/mangle_op.py0000664000175000017500000000143512110032732016414 0ustar haypohaypo00000000000000from ptrace.six import b def generateSpecialValues(): values = ( # Special values in big endian # SPECIAL_VALUES will contains value in big endian and little endian b("\x00"), b("\x00\x00"), b("\x01"), b("\x00\x01"), b("\x7f"), b("\x7f\xff"), b("\x7f\xff\xff\xff"), b("\x80"), b("\x80\x00"), b("\x80\x00\x00\x00"), b("\xfe"), b("\xfe\xff"), b("\xfe\xff\xff\xff"), b("\xff"), b("\xff\xff"), b("\xff\xff\xff\xff"), ) result = [] for item in values: result.append(item) itemb = item[::-1] if item != itemb: result.append(itemb) return result SPECIAL_VALUES = generateSpecialValues() MAX_INCR = 8 fusil-1.5/fusil/tools.py0000664000175000017500000000317312254530401015621 0ustar haypohaypo00000000000000from ptrace.six import text_type from ptrace.six.moves import zip as izip import re def minmax(min_value, value, max_value): """ Restrict value to [min_value; max_value] >>> minmax(-2, -3, 10) -2 >>> minmax(-2, 27, 10) 10 >>> minmax(-2, 0, 10) 0 """ return min(max(min_value, value), max_value) def listDiff(old, new): """ Difference of two lists item by item. >>> listDiff([4, 0, 3], [10, 0, 50]) [6, 0, 47] """ return [ item[1]-item[0] for item in izip(old, new) ] def timedeltaSeconds(delta): """ Convert a datetime.timedelta() objet to a number of second (floatting point number). >>> from datetime import timedelta >>> timedeltaSeconds(timedelta(seconds=2, microseconds=40000)) 2.04 >>> timedeltaSeconds(timedelta(minutes=1, milliseconds=250)) 60.25 """ return delta.microseconds / 1000000.0 + delta.seconds \ + delta.days * 3600 * 24 def makeUnicode(text): if isinstance(text, text_type): return text try: return text_type(text, "utf8") except UnicodeError: pass return text_type(text, "ISO-8859-1") def makeFilename(text): """ >>> makeFilename('Fatal error!') 'fatal_error' """ if isinstance(text, text_type): text = text.lower() text = re.sub(u'[^a-z_-]', '_', text) text = re.sub(u'_{2,}', '_', text) text = re.sub(u'_$', '', text) else: # byte string text = text.lower() text = re.sub(b'[^a-z_-]', '_', text) text = re.sub(b'_{2,}', '_', text) text = re.sub(b'_$', '', text) return text fusil-1.5/fusil/python_tools.py0000664000175000017500000000022412110032732017207 0ustar haypohaypo00000000000000from sys import version RUNNING_PYPY = ("pypy" in version.lower()) # Kept for backward compatibility from ptrace.os_tools import RUNNING_PYTHON3 fusil-1.5/fusil/dummy_mangle.py0000664000175000017500000000020712110032732017125 0ustar haypohaypo00000000000000from fusil.mangle import MangleAgent class DummyMangle(MangleAgent): def mangleData(self, data, file_index): return data fusil-1.5/fusil/bits.py0000664000175000017500000000541312110032732015414 0ustar haypohaypo00000000000000""" Convert bytes string to integer, and integer to bytes string. """ from struct import calcsize, unpack, error as struct_error from itertools import chain, repeat BIG_ENDIAN = "ABCD" LITTLE_ENDIAN = "DCBA" def uint2bytes(value, endian, size=None): r""" Convert an unsigned integer to a bytes string in the specified endian. If size is given, add nul bytes to fill to size bytes. >>> uint2bytes(0x1219, BIG_ENDIAN) '\x12\x19' >>> uint2bytes(0x1219, BIG_ENDIAN, 4) # 32 bits '\x00\x00\x12\x19' >>> uint2bytes(0x1219, LITTLE_ENDIAN, 4) # 32 bits '\x19\x12\x00\x00' """ assert (not size and 0 < value) or (0 <= value) assert endian in (LITTLE_ENDIAN, BIG_ENDIAN) text = [] while (value != 0 or text == ""): byte = value % 256 text.append( chr(byte) ) value >>= 8 if size: need = max(size - len(text), 0) else: need = 0 if need: if endian is BIG_ENDIAN: text = chain(repeat("\0", need), reversed(text)) else: text = chain(text, repeat("\0", need)) else: if endian is BIG_ENDIAN: text = reversed(text) return "".join(text) def _createStructFormat(): """ Create a dictionnary (endian, size_byte) => struct format used by bytes2uint() to convert raw data to positive integer. """ format = { BIG_ENDIAN: {}, LITTLE_ENDIAN: {}, } for struct_format in "BHILQ": try: size = calcsize(struct_format) format[BIG_ENDIAN][size] = '>%s' % struct_format format[LITTLE_ENDIAN][size] = '<%s' % struct_format except struct_error: pass return format _struct_format = _createStructFormat() def bytes2uint(data, endian): r""" Convert a bytes string into an unsigned integer. >>> chr(bytes2uint('*', BIG_ENDIAN)) '*' >>> bytes2uint("\x00\x01\x02\x03", BIG_ENDIAN) == 0x10203 True >>> bytes2uint("\x2a\x10", LITTLE_ENDIAN) == 0x102a True >>> bytes2uint("\xff\x14\x2a\x10", BIG_ENDIAN) == 0xff142a10 True >>> bytes2uint("\x00\x01\x02\x03", LITTLE_ENDIAN) == 0x3020100 True >>> bytes2uint("\xff\x14\x2a\x10\xab\x00\xd9\x0e", BIG_ENDIAN) == 0xff142a10ab00d90e True >>> bytes2uint("\xff\xff\xff\xff\xff\xff\xff\xff", BIG_ENDIAN) == (2**64-1) True """ assert 1 <= len(data) <= 32 # arbitrary limit: 256 bits try: return unpack(_struct_format[endian][len(data)], data)[0] except KeyError: pass assert endian in (BIG_ENDIAN, LITTLE_ENDIAN) shift = 0 value = 0 if endian is BIG_ENDIAN: data = reversed(data) for character in data: byte = ord(character) value += (byte << shift) shift += 8 return value fusil-1.5/fusil/file_watch.py0000664000175000017500000002023112254530410016560 0ustar haypohaypo00000000000000from errno import EBADF from fusil.project_agent import ProjectAgent from fusil.score import scoreLogFunc from fusil.tools import makeFilename from os.path import basename from ptrace.six import b from ptrace.six import text_type, iteritems from time import time import re VALID_POS = ('zero', 'end', 'current') class FileWatch(ProjectAgent): def __init__(self, project, file_obj, name, start=None): if not start: start = 'zero' if start not in VALID_POS: raise ValueError('start position (%r) have to be in %s' % (start, VALID_POS)) ProjectAgent.__init__(self, project, name) self.file_obj = file_obj self.ignore = [] self.start = start self.regexs = [] self.compiled_patterns = None self._need_compile = True # Minimum number of lines: # eg. (10, -0.2) to add -20% to score if there is fewer than 10 lines self.min_nb_line = None self.max_nb_line = (100, 1.0) self.last_seed = None self.log_not_matching = False self.show_matching = False self.show_not_matching = self.application().options.debug self.read_size = 4096 self.cleanup_func = None self.max_process_time = 0.250 # second # FIXME: write methods instead of direct access to self.words # to be able to update self._need_compile self.words = { # Notice and warning u"too large": 0.10, u"unknown": 0.10, u"can't": 0.10, u"could not": 0.10, u"not allowed": 0.10, u'invalid': 0.10, u'not valid': 0.10, u'failed': 0.10, u'failure': 0.10, u'warning': 0.10, # Non fatal errors u'oops': 0.30, # Linux kernel Oops: "Oops: 0000" u'bug': 0.30, u'pointer': 0.30, u'error': 0.30, u'allocate': 0.40, u'memory': 0.40, u'permission': 0.40, u'overflow': 0.40, # Fatal errors u'fatal': 1.0, u'assert': 1.0, u'assertion': 1.0, u'critical': 1.0, u'exception': 1.0, u'panic': 1.0, u'glibc detected': 1.0, u'segfault': 1.0, u'segmentation fault': 1.0, } @staticmethod def fromFilename(project, filename, start=None): input_file = open(filename, 'rb') return FileWatch(project, input_file, basename(filename), start) def ignoreRegex(self, regex, flags=0): if isinstance(regex, text_type): regex = regex.encode("ASCII") regex = re.compile(regex, flags) self.ignore.append(regex.search) def addRegex(self, regex, score, flags=0): if isinstance(regex, text_type): regex = regex.encode("ASCII") match = re.compile(regex, flags).search self.regexs.append((regex, score, match)) self._need_compile = True def compilePatterns(self): for text, score, match in self.regexs: yield (text, score, match) for text, score in iteritems(self.words): text = text.lower() if isinstance(text, text_type): text = text.encode("ASCII") regex = re.escape(text) regex = b(r'(?:^|\W)') + regex + b(r'(?:$|\W)') match = re.compile(regex, re.IGNORECASE).search yield (text, score, match) def setFileObject(self, file_obj): self.file_obj = file_obj self.prepareFile() def init(self): if self.start == "zero": self.last_seed = None self.total_line = 0 self.nb_line = 0 self.score = 0 if (self.compiled_patterns is None) or self._need_compile: self._need_compile = False self.compiled_patterns = list(self.compilePatterns()) self.buffer = [] if self.file_obj: self.prepareFile() def prepareFile(self): oldpos = self.file_obj.tell() if self.start == 'zero': self.file_obj.seek(0) elif self.start == 'end': self.file_obj.seek(0, 2) self.file_seed = self.file_obj.tell() self.file_obj.seek(oldpos) def splitlines(self, data): lines = data.splitlines(1) for index, line in enumerate(lines): if index == len(lines)-1 and line[-1] not in b('\n\r'): self.buffer.append(line) return if index == 0 and self.buffer: self.buffer.append(line) line = b('').join(self.buffer) self.buffer = [] yield line.rstrip() def processLine(self, line): # Total number of line self.total_line += 1 if self.max_nb_line \ and self.max_nb_line[0] <= self.total_line: score = self.max_nb_line[1] log = scoreLogFunc(self, score) log("More than %s lines written: increment score by %.1f%%" % (self.total_line, score*100)) self.score += score self.max_nb_line = None self.send('session_rename', 'long_output') # Ignore this line? if self.cleanup_func: if not line: return line = self.cleanup_func(line) if not line: return for ignore_func in self.ignore: if ignore_func(line): return # Number of line self.nb_line += 1 # Search the matching pattern with the highest score found = None for pattern, score, match in self.compiled_patterns: if found and abs(score) < abs(found[1]): continue if not match(line): continue found = (pattern, score) if not found: message = "Not matching line: %r" % line if self.show_not_matching: self.error(message) elif self.log_not_matching: self.info(message) return pattern, score = found if self.show_matching: log = self.error else: log = self.warning log("Match pattern %r (score %.1f%%) in %r" % ( pattern, score*100, line)) self.score += score name = makeFilename(pattern) self.send('session_rename', name) def readlines(self): try: first_line = self.total_line time0 = time() while True: duration = time() - time0 if self.max_process_time < duration: count = self.total_line - first_line self.warning("Too slow: proceed %s lines in %.1f sec" % (count, duration)) return oldpos = self.file_obj.tell() self.file_obj.seek(self.file_seed) data = self.file_obj.read(self.read_size) self.file_seed = self.file_obj.tell() self.file_obj.seek(oldpos) if not data: return for line in self.splitlines(data): yield line except IOError as err: if err.errno == EBADF: self.error("Unable to read data: closed file (Bad file descriptor error)") self.file_obj = None return else: raise def on_session_stop(self): if self.min_nb_line \ and (self.total_line < self.min_nb_line[0]): self.warning("Fewer than %s lines (total=%s): add %+.1f to the score" % (self.min_nb_line[0], self.total_line, self.min_nb_line[1]*100)) self.score += self.min_nb_line[1] def live(self): # File closed: just exit if not self.file_obj: return for line in self.readlines(): if 1.0 <= abs(self.score): break self.processLine(line) def getScore(self): return self.score def close(self): if not self.file_obj: return self.file_obj.close() self.file_obj = None fusil-1.5/fusil/session.py0000664000175000017500000000762312110032732016143 0ustar haypohaypo00000000000000from fusil.project_agent import ProjectAgent from fusil.session_agent import SessionAgent from fusil.mas.agent_list import AgentList from fusil.session_directory import SessionDirectory from fusil.score import normalizeScore from logging import INFO from ptrace.os_tools import RUNNING_PYPY from logging import Formatter import re if RUNNING_PYPY: from gc import collect as gc_collect # Match "[0][session 0010] " PREFIX_REGEX = re.compile(r"\[[0-9]\]\[+session [0-9]+\] ") class SessionFormatter(Formatter): """ Log formatter for session.log: only write the message and remove fusil prefix: "[0][session 0010] text" => "text" """ def format(self, record): text = Formatter.format(self, record) return PREFIX_REGEX.sub('', text) class Session(SessionAgent): """ A session of the fuzzer: - create a directory as working directory - compute the score of the session """ def __init__(self, project): self.agents = AgentList() self.score = None self.log_handler = None name = "session %s" % project.session_index SessionAgent.__init__(self, self, name, project=project) def isSuccess(self): if self.score is None: return False return self.project().success_score <= self.score def computeScore(self, verbose=False): """ Compute the score of the session: - call getScore() method of all agents - normalize the score in [-1.0; 1.0] - apply score factor (weight) - compute the sum of all scores """ session_score = 0 for agent in self.project().agents: if not issubclass(agent.__class__, ProjectAgent): # Skip application agent which has no score continue if not agent.is_active: continue score = agent.getScore() if score is None: continue score = normalizeScore(score) score *= agent.score_weight score = normalizeScore(score) if verbose and score: self.info("- %s score: %.1f%%" % (agent, score*100)) session_score += score return session_score def registerAgent(self, agent): self.agents.append(agent) def unregisterAgent(self, agent, destroy=True): if agent not in self.agents: return self.agents.remove(agent, destroy) def init(self): self.directory = SessionDirectory(self) log_filename = self.createFilename("session.log") self.log_handler = self.logger.addFileHandler( log_filename, level=INFO, formatter_class=SessionFormatter) self.stopped = False def deinit(self): if self.log_handler: self.logger.removeFileHandler(self.log_handler) self.agents.clear() if RUNNING_PYPY: gc_collect() def live(self): """ Compute the score of the session and stop the session if the score is smaller than -50% or bigger than 50%. """ if self.stopped: return score = self.computeScore() if score is None: return project = self.project() if not(project.success_score <= score or score <= project.error_score): return self.send('session_stop') def on_session_stop(self): if self.stopped: return self.stopped = True score = self.computeScore(True) if self.project().success_score <= score: self.send('session_success') self.send('session_done', score) def createFilename(self, filename, count=None): """ Create a filename in the session working directory: add directory prefix and make sure that the generated filename is unique. """ return self.directory.uniqueFilename(filename, count=count) fusil-1.5/fusil/zzuf.py0000664000175000017500000000365112110032732015453 0ustar haypohaypo00000000000000from fusil.process.create import ProjectProcess from os.path import exists LIBRARY_PATHS = ( # Linux '/usr/lib/zzuf/libzzuf.so', # BSD '/usr/local/lib/zzuf/libzzuf.so', ) DEFAULT_RATIO = 0.004 class ZzufProcess(ProjectProcess): def __init__(self, project, arguments, library_path=None, **options): ProjectProcess.__init__(self, project, arguments, **options) # Options self.use_debug_file = True self.setRatio(DEFAULT_RATIO, DEFAULT_RATIO) # Locate libzzuf library if not library_path: for path in LIBRARY_PATHS: if not exists(path): continue library_path = path break if not library_path: raise ValueError("Unable to find zzuf library (try %s)" % ', '.join(LIBRARY_PATHS)) # Load zzuf using LD_PRELOAD self.env.set('LD_PRELOAD', library_path) def init(self): ProjectProcess.init(self) self.zzuf_file = None def closeStreams(self): ProjectProcess.closeStreams(self) if self.zzuf_file: self.zzuf_file.close() self.zzuf_file = None def createProcess(self): if self.use_debug_file: filename = self.session().createFilename('zzuf.dbg') self.zzuf_file = open(filename, 'w') self.env.set("ZZUF_DEBUG", str(self.zzuf_file.fileno())) ProjectProcess.createProcess(self) def on_aggressivity_value(self, value): ratio = value / 10.0 self.min_ratio = ratio self.max_ratio = ratio self.error("Set zzuf ratio to: %.3f" % ratio) self.setRatio(self.min_ratio, self.max_ratio) def setRatio(self, min_ratio, max_ratio): self.min_ratio = min_ratio self.env.set('ZZUF_MINRATIO', str(self.min_ratio)) self.max_ratio = max_ratio self.env.set('ZZUF_MAXRATIO', str(self.max_ratio)) fusil-1.5/fusil/error.py0000664000175000017500000000004712110032732015602 0ustar haypohaypo00000000000000class FusilError(Exception): pass fusil-1.5/fusil/network/0000775000175000017500000000000012305626161015602 5ustar haypohaypo00000000000000fusil-1.5/fusil/network/server_client.py0000664000175000017500000000552212253715322021024 0ustar haypohaypo00000000000000from fusil.network.tools import formatAddress from fusil.session_agent import SessionAgent from socket import error as socket_error, timeout as socket_timeout, SHUT_RDWR from ptrace.error import formatError from weakref import ref as weakref_ref class ServerClientDisconnect(Exception): pass class ServerClient(SessionAgent): def __init__(self, session, server, socket, address, family): self.server = weakref_ref(server) self.socket = socket self.address = address self.family = family name = "net_client:" + formatAddress(self.family, self.address, short=True) SessionAgent.__init__(self, session, name) self.tx_bytes = 0 self.rx_bytes = 0 def recvBytes(self, buffer_size=1024): log_data_exchange = self.server().log_data_exchange datas = [] while True: try: self.socket.settimeout(0.010) data = self.socket.recv(buffer_size) except socket_timeout: break except socket_error as err: errcode = err[0] if errcode == 11: # Resource temporarily unavailable break else: self.close() break if not data: break data_len = len(data) self.rx_bytes += data_len if log_data_exchange: self.warning("Read bytes: (%s) %r" % (data_len, data)) datas.append(data) if not datas: self.close() return None return ''.join(datas) def sendBytes(self, data, buffer_size=None): log_data_exchange = self.server().log_data_exchange index = 0 while index < len(data): if buffer_size: chunk = data[index:index+buffer_size] else: chunk = data[index:] if log_data_exchange: self.warning("Send bytes: (%s) %r" % (len(chunk), chunk)) try: count = self.socket.send(chunk) index += count self.tx_bytes += count except socket_error as err: self.warning("Send error: %s" % formatError(err)) self.close() break def close(self, emit_exception=True): if not self.socket: return self.info("Close socket") self.socket.shutdown(SHUT_RDWR) self.socket.close() self.socket = None if emit_exception: raise ServerClientDisconnect() def destroy(self): if self.socket: self.close(False) def __str__(self): return repr(self) def __repr__(self): return "<%s %s>" % ( self.__class__.__name__, formatAddress(self.family, self.address)) fusil-1.5/fusil/network/tcp_server.py0000664000175000017500000000076212110032732020322 0ustar haypohaypo00000000000000from socket import AF_INET from fusil.network.server import NetworkServer from fusil.network.tools import formatAddress class TcpServer(NetworkServer): def __init__(self, project, port, host=''): name = "tcp_server:" + formatAddress(AF_INET, (host, port), short=True) NetworkServer.__init__(self, project, name) self.host = host self.port = port def init(self): if self.socket: return self.bind(address=(self.host, self.port)) fusil-1.5/fusil/network/tools.py0000664000175000017500000000054712110032732017307 0ustar haypohaypo00000000000000from socket import AF_INET def formatAddress(family, address, short=False): if family == AF_INET: host, port = address if not host: host = '(localhost)' if not short: return "(host %s, port %s)" % (host, port) else: return "%s:%s" % (host, port) else: return repr(address) fusil-1.5/fusil/network/http_server.py0000664000175000017500000000361312110032732020511 0ustar haypohaypo00000000000000from fusil.network.tcp_server import TcpServer from fusil.network.server_client import ServerClientDisconnect from fusil.network.http_request import HttpRequest class HttpServer(TcpServer): def __init__(self, *args): TcpServer.__init__(self, *args) self.http_version = "1.0" def clientRead(self, client): # Read data try: data = client.recvBytes() except ServerClientDisconnect: self.clientDisconnection(client) return if not data: return # Process data request = HttpRequest(data) self.serveRequest(client, request) def serveRequest(self, client, request): url = request.uri[1:] if not url: url = "index.html" if url == "index.html": self.serveData(client, 200, "OK", "

Hello World!

") else: self.error404(client, url) def error404(self, client, url): self.warning("Error 404: %r" % url) self.serveData(client, 404, "Not Found") def serveData(self, client, code, code_text, data=None, content_type="text/html"): if data: data_len = len(data) else: data_len = 0 http_headers = [ ("Server", "Fusil"), ("Pragma", "no-cache"), ("Content-Type", content_type), ("Content-Length", str(data_len)), ] try: header = "HTTP/%s %s %s\r\n" % (self.http_version, code, code_text) for key, value in http_headers: header += "%s: %s\r\n" % (key, value) header += "\r\n" if data: data = header + data else: data = header client.sendBytes(data) client.close() except ServerClientDisconnect: self.clientDisconnection(client) fusil-1.5/fusil/network/server.py0000664000175000017500000000563012253715322017466 0ustar haypohaypo00000000000000from fusil.project_agent import ProjectAgent from fusil.network.server_client import ServerClient from fusil.network.tools import formatAddress from socket import (socket, error as socket_error, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR) from ptrace.error import writeError from select import select class NetworkServer(ProjectAgent): CLIENT_CLASS = ServerClient def __init__(self, project, name): ProjectAgent.__init__(self, project, name) self.log_data_exchange = False self.backlog = 5 self.client_class = self.CLIENT_CLASS self.socket = None self.family = None self.clients = [] def bind(self, address, family=AF_INET, type=SOCK_STREAM, reuse_address=True): try: self.socket = socket(family, type) if reuse_address: self.socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) self.family = family self.socket.bind(address) self.socket.listen(self.backlog) self.error("Server waiting on %s" % formatAddress(family, address)) except socket_error as err: writeError(self, err, "Unable to bind on %s" % formatAddress(family, address)) self.socket = None self.send('application_error', 'Network server bind error') def close(self): if self.clients: for client in self.clients: client.close(emit_exception=False) self.clients = [] if self.socket: self.info("Close socket") self.socket.close() self.socket = None def destroy(self): self.close() def acceptClient(self): client_socket, client_address = self.socket.accept() client = self.client_class( self.session(), self, client_socket, client_address, self.family) self.warning("New client: %s" % client) self.clients.append(client) def clientDisconnection(self, client): if client not in self.clients: return self.info("Client closed: %r" % client) self.clients.remove(client) def clientRead(self, client): # FIXME: use received bytes client.recvBytes() def live(self): if not self.socket: return server_fileno = self.socket.fileno() read_fds = [server_fileno] client_fds = dict( (client.socket.fileno(), client) for client in self.clients ) read_fds += list(client_fds.keys()) read_available = select(read_fds, [], [], 0)[0] if read_available is None: return for fd in read_available: if fd in client_fds: client = client_fds[fd] self.info("Read data from %s" % client) self.clientRead(client) else: self.info("Accept client") self.acceptClient() fusil-1.5/fusil/network/http_request.py0000664000175000017500000000271012110032732020670 0ustar haypohaypo00000000000000import re class HttpRequest: def __init__(self, data): self.method = None self.uri = None self.http_version = "1.0" self.host = None self.headers = [] self.parse(data) def parse(self, data): state = "init" for line in data.splitlines(): if state == "init": self.parseRequest(line) state = "host" continue if state == "host": match = re.match("host: (.*)$", line, re.IGNORECASE) if match: self.host = match.group(1) state = "keys" continue if not line: continue line = line.split(":", 1) if len(line) == 1: raise SyntaxError("Unable to parse client header: %r" % line[0]) key, value = line self.headers.append( (key, value) ) def parseRequest(self, line): # Extract method match = re.match("^(GET|POST) (.*)$", line) if not match: raise SyntaxError("Unable to parse request method: %r" % line) line = match.group(2) self.method = match.group(1) # Extract HTTP version if present match = re.match("^(.*) HTTP/(1.[01])$", line) if match: line = match.group(1) self.http_version = match.group(2) # Rest is the URI self.uri = line fusil-1.5/fusil/network/client.py0000664000175000017500000000751612253715322017443 0ustar haypohaypo00000000000000from fusil.project_agent import ProjectAgent from socket import (socket, SHUT_RDWR, error as socket_error, timeout as socket_timeout) from ptrace.error import formatError, writeError from fusil.network.tools import formatAddress from time import time from errno import EAGAIN class NetworkClient(ProjectAgent): def __init__(self, project, name): ProjectAgent.__init__(self, project, name) self.log_data_exchange = False def init(self): self.socket = None self.tx_bytes = 0 self.rx_bytes = 0 def connect(self, address, family, type, timeout=5.0): try: self.info("Create socket (family %s, type %s)" % ( family, type)) self.socket = socket(family, type) self.socket.settimeout(timeout) self.info("Connect to %s" % formatAddress(family, address)) self.socket.connect(address) except socket_error as err: writeError(self, err, "Unable to connect to %s" % formatAddress(family, address)) self.socket = None self.send('application_error', 'Network connection failure') def deinit(self): if self.socket: self.socket.close() info = [] if self.tx_bytes: info.append("TX bytes:%s" % self.tx_bytes) if self.rx_bytes: info.append("RX bytes:%s" % self.rx_bytes) if info: self.warning(", ".join(info)) def on_session_stop(self): self.closeSocket() def closeSocket(self): if self.socket: try: self.socket.shutdown(SHUT_RDWR) except socket_error: pass self.socket.close() self.socket = None def sendBytes(self, data, timeout=0): if self.socket is None: self.error("Error: socket is not initialized!") return False self.tx_bytes += len(data) if self.log_data_exchange: self.warning("Send bytes: (%s) %r (timeout=%s)" % ( len(data), data, timeout)) try: self.socket.settimeout(timeout) self.socket.send(data) except socket_error as err: self.warning("Send error: %s" % formatError(err)) self.closeSocket() self.send('session_stop') return False return True def recvBytes(self, max_size=None, timeout=0.250, buffer_size=1024): if self.socket is None: self.error("Error: socket is not initialized!") return None datas = [] data_len = 0 time_end = time() + timeout while True: if (not datas) and (not timeout): # First step in non blocking mode time_diff = 0 else: time_diff = time_end - time() if time_diff < 0: break if max_size is not None and max_size <= data_len: break try: self.socket.settimeout(time_diff) data = self.socket.recv(buffer_size) except socket_timeout: break except socket_error as err: code = err.args[0] if not time_diff and code == EAGAIN: break self.warning("Receive error: %s" % formatError(err)) self.closeSocket() self.send('session_stop') break if not data: break if self.log_data_exchange: self.warning("Receive bytes: (%s) %r" % (len(data), data)) datas.append(data) data_len += len(data) self.rx_bytes += data_len if datas: data = ''.join(datas) return data else: return None fusil-1.5/fusil/network/unix_client.py0000664000175000017500000000103512110032732020461 0ustar haypohaypo00000000000000from fusil.network.client import NetworkClient from socket import AF_UNIX, SOCK_STREAM class UnixSocketClient(NetworkClient): def __init__(self, project, socket_filename, connect_timeout=5.0): NetworkClient.__init__(self, project, "unix_socket:%s" % socket_filename) self.socket_filename = socket_filename self.connect_timeout = connect_timeout def on_session_start(self): self.connect( self.socket_filename, AF_UNIX, SOCK_STREAM, timeout=self.connect_timeout) fusil-1.5/fusil/network/__init__.py0000664000175000017500000000000012110032732017666 0ustar haypohaypo00000000000000fusil-1.5/fusil/network/tcp_client.py0000664000175000017500000000102112110032732020257 0ustar haypohaypo00000000000000from fusil.network.client import NetworkClient from socket import AF_INET, SOCK_STREAM class TcpClient(NetworkClient): def __init__(self, project, host, port, connect_timeout=5.0): NetworkClient.__init__(self, project, "network:%s:%s" % (host, port)) self.host = host self.port = port self.connect_timeout = connect_timeout def on_session_start(self): self.connect( (self.host, self.port), AF_INET, SOCK_STREAM, timeout=self.connect_timeout) fusil-1.5/fusil/incr_mangle_op.py0000664000175000017500000000573012110032732017431 0ustar haypohaypo00000000000000from random import randint, choice from fusil.mangle_op import SPECIAL_VALUES, MAX_INCR from array import array from fusil.tools import minmax def createBitOffset(agent, datalen): min_offset = 0 if agent.min_offset is not None: min_offset = max(agent.min_offset*8, min_offset) max_offset = datalen*8 - 1 if agent.max_offset is not None: max_offset = min(agent.max_offset*8 + 7, max_offset) return randint(min_offset, max_offset) def createByteOffset(agent, datalen): min_offset = 0 if agent.min_offset is not None: min_offset = max(agent.min_offset, min_offset) max_offset = datalen - 1 if agent.max_offset is not None: max_offset = min(agent.max_offset, max_offset) return randint(min_offset, max_offset) class Operation: def __init__(self, offset, size): self.offset = offset self.size = size def __call__(self, data): raise NotImplementedError() def __str__(self): raise NotImplementedError() class InverseBit(Operation): def __init__(self, agent, datalen): offset = createBitOffset(agent, datalen) Operation.__init__(self, offset, 1) def __call__(self, data): mask = 1 << (self.offset & 7) offset = self.offset >> 3 if data[offset] & mask: data[offset] &= (~mask & 0xFF) else: data[offset] |= mask def __str__(self): return "InverseBit(offset=%s.%s)" % (self.offset // 8, self.offset % 8) class ReplaceByte(Operation): def __init__(self, agent, datalen): offset = createByteOffset(agent, datalen-1) byte = randint(0, 255) Operation.__init__(self, offset*8, 8) self.byte = byte def __call__(self, data): data[self.offset // 8] = self.byte def __str__(self): return "ReplaceByte(byte=0x%02x, offset=%s)" % (self.byte, self.offset//8) class SpecialValue(Operation): def __init__(self, agent, datalen): self.bytes = array("B", choice(SPECIAL_VALUES)) offset = createByteOffset(agent, datalen-len(self.bytes)) Operation.__init__(self, offset*8, len(self.bytes)*8) def __call__(self, data): offset = self.offset // 8 data[offset:offset+len(self.bytes)] = self.bytes def __str__(self): bytes = ' '.join( '0x%02x' % byte for byte in self.bytes ) return "SpecialValue(bytes=%r, offset=%s)" % (bytes, self.offset//8) class Increment(Operation): def __init__(self, agent, datalen): self.incr = randint(1, MAX_INCR) if randint(0, 1) == 1: self.incr = -self.incr offset = createByteOffset(agent, datalen-1) Operation.__init__(self, offset*8, 8) def __call__(self, data): offset = self.offset // 8 data[offset] = minmax(0, data[offset] + self.incr, 255) def __str__(self): return "Increment(incr=%+u, offset=%s)" % (self.incr, self.offset//8) OPERATIONS = (InverseBit, ReplaceByte, SpecialValue, Increment) fusil-1.5/fusil/linux/0000775000175000017500000000000012305626161015250 5ustar haypohaypo00000000000000fusil-1.5/fusil/linux/cpu_load.py0000664000175000017500000001042012110032732017372 0ustar haypohaypo00000000000000from ptrace.linux_proc import openProc, readProcessStat, getSystemBoot, ProcError from os import sysconf from fusil.tools import listDiff, timedeltaSeconds, minmax from datetime import datetime, timedelta from time import sleep class CpuLoadError(ProcError): pass # sysconf() keys: values working on Ubuntu Feisty (libc 2.5) _SC_CLK_TCK = 2 _SC_NPROCESSORS_ONLN = 84 # max(..., 1) is for buggy SPARC glibc NB_CPU = max(sysconf(_SC_NPROCESSORS_ONLN), 1) HERTZ = sysconf(_SC_CLK_TCK) SYSLOAD_MIN_CYCLES = HERTZ // 2 # 500 ms CPULOAD_MIN_CYCLES = HERTZ // 10 # 100 ms SYSLOAD_SLEEP = 0.500 # 500 ms class CpuLoad: def __init__(self): self.datas = [] self.min_duration = timedelta(seconds=0.5) self.mesure_duration = timedelta(seconds=1.0) def isValid(self, item, current): raise NotImplementedError() def searchLast(self, current): ok = None for index, item in enumerate(self.datas): age = current.timestamp - item.timestamp if self.mesure_duration < age and self.isValid(item, current): ok = index if ok is None: item = self.datas[0] if self.isValid(item, current): return item else: return None if 0 < ok: del self.datas[0:ok] return self.datas[0] class CpuLoadValue: def __init__(self): self.timestamp = datetime.now() class SystemCpuLoadValue(CpuLoadValue): def __init__(self): CpuLoadValue.__init__(self) self.data = None stat_file = openProc('stat') for data in stat_file: # Look for "cpu ..." line if not data.startswith("cpu "): continue self.data = [ max(int(item), 0) for item in data.split()[1:] ] break stat_file.close() if not self.data: raise CpuLoadError("Unable to get system load!") class SystemCpuLoad(CpuLoad): def __init__(self): CpuLoad.__init__(self) self.min_cycles = SYSLOAD_MIN_CYCLES value = SystemCpuLoadValue() self.datas.append(value) def isValid(self, item, current): data = listDiff(item.data, current.data) return (self.min_cycles <= sum(data)) \ and (self.min_duration < current.timestamp - item.timestamp) def get(self, estimate=False): while True: current = SystemCpuLoadValue() last = self.searchLast(current) if last: break if estimate: return None else: sleep(SYSLOAD_SLEEP) # Store value self.datas.append(current) # Compute system load: 100% - idle percent data = listDiff(last.data, current.data) load = 1.0 - float(data[3]) / sum(data) return load class ProcessCpuLoadValue(CpuLoadValue): def __init__(self, pid): CpuLoadValue.__init__(self) stat = readProcessStat(pid) self.start_time = stat.starttime self.tics = stat.utime + stat.stime class ProcessCpuLoad(CpuLoad): """ Compute process cpu usage """ def __init__(self, pid): CpuLoad.__init__(self) self.min_cycles = CPULOAD_MIN_CYCLES self.pid = pid # Read first value value = ProcessCpuLoadValue(self.pid) self.datas.append(value) # Compute process start datetime boot = getSystemBoot() start = float(value.start_time) / HERTZ start = timedelta(seconds=start) self.start = boot + start def isValid(self, item, current): return (self.min_cycles <= current.tics - item.tics) \ and (self.min_duration < current.timestamp - item.timestamp) def get(self, estimate=True): current = ProcessCpuLoadValue(self.pid) previous = self.searchLast(current) self.datas.append(current) tics = current.tics if previous: time = current.timestamp - previous.timestamp tics -= previous.tics else: if not estimate: return None time = current.timestamp - self.start time = timedeltaSeconds(time) load = tics / (HERTZ * time) return minmax(0.0, load, 1.0) fusil-1.5/fusil/linux/syslog.py0000664000175000017500000000210512110032732017125 0ustar haypohaypo00000000000000from fusil.project_agent import ProjectAgent from fusil.file_watch import FileWatch from os.path import exists, basename FILENAMES = ( # Linux: system logs 'syslog', 'messages', # Linux: kernel logs 'dmesg', 'kern.log', # Linux: authentication (PAM) logs 'auth.log', # Linux: user logs 'user.log', # FreeBSD: kernel logs 'dmesg.today', # FreeBSD: user logs 'userlog', ) class Syslog(ProjectAgent): def __init__(self, project): ProjectAgent.__init__(self, project, "syslog") self.logs = [] for filename in FILENAMES: agent = self.create(project, '/var/log/' + filename) if not agent: continue self.logs.append(agent) def create(self, project, filename): if exists(filename): return FileWatch(project, open(filename), 'syslog:%s' % basename(filename), start='end') else: self.warning("Skip (non existent) log file: %s" % filename) return None def __iter__(self): return iter(self.logs) fusil-1.5/fusil/linux/__init__.py0000664000175000017500000000000012110032732017334 0ustar haypohaypo00000000000000fusil-1.5/fusil/session_agent.py0000664000175000017500000000142712110032732017315 0ustar haypohaypo00000000000000from fusil.project_agent import ProjectAgent from weakref import ref as weakref_ref class SessionAgent(ProjectAgent): def __init__(self, session, name, project=None): if project: mta = project.mta() else: mta = session.mta() project = session.project() self._session = weakref_ref(session) ProjectAgent.__init__(self, project, name, mta=mta) self.activate() def session(self): return self._session() def register(self): ProjectAgent.register(self) self.session().registerAgent(self) def unregister(self, destroy=True): ProjectAgent.unregister(self, destroy) session = self.session() if session: session.unregisterAgent(self, destroy) fusil-1.5/fusil/process/0000775000175000017500000000000012305626161015567 5ustar haypohaypo00000000000000fusil-1.5/fusil/process/replay_python.py0000664000175000017500000004117312261304506021041 0ustar haypohaypo00000000000000from fusil.python_tools import RUNNING_PYTHON3 from fusil.write_code import WriteCode from fusil.xhost import xhostCommand from os.path import normpath from ptrace.six import itervalues, iteritems, binary_type, text_type from sys import executable, getfilesystemencoding, path as sys_path import itertools def formatValue(value): r""" >>> print formatValue('aaaaa') 'a' * 5 >>> print formatValue(u'xxxxx') u'x' * 5 >>> print formatValue('ab\0') 'ab\x00' """ if 5 <= len(value): repeat = True first = None for item in value: if first: if first != item: repeat = False break else: first = item else: repeat = False if repeat: return repr(value[:1]) + ' * %s' % len(value) else: return repr(value) def formatPath(value, cwd, cwd_bytes): """ >>> print formatPath('ab', '', u'') 'a' + cwd_bytes + 'b' >>> print formatPath('a', '', u'') 'a' + cwd_bytes >>> print formatPath(u'b', '', u'') cwd + u'b' """ result = [] if isinstance(value, binary_type): pattern = cwd_bytes replace = u'cwd_bytes' else: pattern = cwd replace = u'cwd' if value.startswith(pattern): result.append(replace) value = value[len(pattern):] pos = value.find(pattern) if 0 <= pos: before = value[:pos] if before: result.append(formatValue(before)) result.append(replace) value = value[pos+len(pattern):] if value.endswith(pattern): value = value[:-len(pattern)] result.append(formatValue(value)) result.append(replace) value = '' elif value: result.append(formatValue(value)) return u' + '.join(result) class WriteReplayScript(WriteCode): def __init__(self): WriteCode.__init__(self) self.comment_column = 40 def debug(self, level, format, *args): line = u'debug("%s"' % format if args: line += u' % ' if len(args) == 1: line += text_type(args[0]) else: line += u'(%s)' % ', '.join(args) line += ')' self.write(level, line) def writeFunction(self, name, callback, *args): self.write(0, u"def %s:" % name) old = self.addLevel(1) callback(*args) self.restoreLevel(old) self.emptyLine() def pythonImports(self): self.write(0, u"#!%s" % normpath(executable)) self.write(0, u'from os import chdir, getcwd, getuid, execvpe, environ') self.write(0, u'from os.path import dirname, devnull') self.write(0, u'from sys import stderr, exit, getfilesystemencoding') self.write(0, u'from optparse import OptionParser') self.emptyLine() def setSysPath(self): self.write(0, u'import sys') self.write(0, u'sys.path = [') for path in sys_path: self.write(1, u'"%s",' % path) self.write(1, u']') self.emptyLine() def limitResources(self): self.write(0, 'if options.limit:') self.write(1, 'return') self.emptyLine() self.write(0, 'if options.gdb or options.valgrind or options.ptrace:') self.debug(1, "Don't limit resources when using --gdb, --valgrind or --ptrace") self.write(1, 'return') self.emptyLine() self.write(0, u'from fusil.process.tools import allowCoreDump, limitMemory, limitUserProcess, limitCpuTime') self.emptyLine() self.write(0, u'if allow_core_dump:') self.debug(1, "allow core dump") self.write(1, u'allowCoreDump(hard=True)') self.emptyLine() self.write(0, u'if max_user_process:') self.debug(1, "limit user process to %s", "max_user_process") self.write(1, u'limitUserProcess(max_user_process)') self.emptyLine() self.write(0, u'if 0 < max_memory:') self.debug(1, "limit memory to %s bytes", "max_memory") self.write(1, u'limitMemory(max_memory, hard=True)') self.emptyLine() self.write(0, u'if 0 < timeout:') self.debug(1, "limit CPU time to %s seconds", "timeout") self.write(1, u'limitCpuTime(timeout)') def writePrint(self, level, message, arguments=None, file=None): if RUNNING_PYTHON3: message = '"%s"' % message else: message = 'u"%s"' % message if arguments: message += " %% (%s)" % arguments if RUNNING_PYTHON3: if file: code = "print (%s, file=%s)" % (message, file) else: code = "print (%s)" % message else: if file: code = "print >>%s, %s" % (file, message) else: code = "print %s" % message self.write(level, code) def safetyConfirmation(self): self.writePrint(0, u'!!!WARNING!!! The fuzzer will run as user %s and group %s,', u'uid, gid') self.writePrint(0, u'!!!WARNING!!! and may remove arbitrary files and kill arbitrary processes.') self.writePrint(0, u'') self.emptyLine() if RUNNING_PYTHON3: raw_input = u'input' else: raw_input = u'raw_input' self.write(0, u'try:') self.write(1, u'answer = None') self.write(1, u'while answer not in ("yes", "no", ""):') self.write(2, u'if answer:') self.write(3, u'''answer = %s('Please answer "yes" or "no": ')''' % raw_input) self.write(2, u'else:') self.write(3, u"answer = %s('Do you want to continue? (yes/NO) ')" % raw_input) self.write(2, u'answer = answer.strip().lower()') self.write(1, u"confirm = (answer == 'yes')") self.write(0, u'except (KeyboardInterrupt, EOFError):') self.writePrint(1, u'') self.write(1, u'confirm = False') self.write(0, u'if not confirm:') self.write(1, u'exit(1)') def changeUserGroup(self, process, config): imports = ['setgid', 'setuid', 'getuid', 'getgid'] if process.use_x11: imports.append('system') self.write(0, u'from os import %s' % ', '.join(imports)) self.emptyLine() # safety confirmation self.write(0, u'if (uid is None) or (uid == 0):') self.write(1, u'if uid is None:') self.write(2, u'child_uid = getuid()') self.write(1, u'else:') self.write(2, u'child_uid = uid') self.write(1, u'if gid is None:') self.write(2, u'child_gid = getgid()') self.write(1, u'else:') self.write(2, u'child_gid = gid') self.write(1, u'safetyConfirmation(child_uid, child_gid)') # xhost command if process.use_x11: command = xhostCommand(config.fusil_xhost_program, '%s') self.debug(0, u"allow user %s to use the X11 server", "uid") self.write(0, u'system("%s" %% uid)' % ' '.join(command)) self.emptyLine() # setgid() self.write(0, 'if gid is not None:') self.debug(1, 'set group identifier to %s', 'gid') self.write(1, u'setgid(gid)') self.emptyLine() # setuid() self.write(0, 'if uid is not None:') self.debug(1, 'set user identifier to %s', 'uid') self.write(1, u'setuid(uid)') def writeDebugFunction(self): self.write(0, u'if quiet:') self.write(1, u'return') self.writePrint(0, u'[Fusil] %s', u'message', file=u'stderr') def parseOptions(self): self.write(0, u'parser = OptionParser()') self.write(0, u'parser.add_option("-q", "--quiet",') self.write(1, u'help="Be quiet (don\'t write debug messages)", action="store_true")') self.write(0, u'parser.add_option("-u", "--user",') self.write(1, u'help="Don\'t change user/group", action="store_true")') self.write(0, u'parser.add_option("-l", "--limit",') self.write(1, u'help="Don\'t set resource limits", action="store_true")') self.write(0, u'parser.add_option("-e", "--environ",') self.write(1, u' help="Copy environment variables (default: empty environment)", action="store_true")') self.write(0, u'parser.add_option("--gdb",') self.write(1, u' help="Run command in gdb", action="store_true")') self.write(0, u'parser.add_option("--valgrind",') self.write(1, u' help="Run command in valgrind", action="store_true")') self.write(0, u'parser.add_option("--ptrace",') self.write(1, u' help="Run command in the python-ptrace debugger", action="store_true")') self.write(0, u'options, arguments = parser.parse_args()') self.write(0, u'if arguments:') self.write(1, u'parser.print_help()') self.write(1, u'exit(1)') self.write(0, u'return options') def writeGdbCommands(self): self.write(0, u"filename = 'gdb.cmds'") self.debug(0, u'Write gdb commands into: %s', u'filename') self.write(0, u"cmd = open(filename, 'w')") self.write(0, u'for key in set(gdb_env) - set(env):') self.writePrint(1, u'unset environment %s', u'key', file=u'cmd') self.write(0, u'for key, value in env.items():') self.writePrint(1, u'set environment %s=%s', u'key, value', file=u'cmd') self.write(0, u'gdb_arguments = []') self.write(0, u'for arg in arguments[1:]:') self.write(1, r'''arg = arg.replace('\\', r'\\')''') self.write(1, r'''arg = arg.replace('"', r'\\"')''') self.write(1, r'''arg = '"%s"' % arg''') self.write(1, u'gdb_arguments.append(arg)') self.writePrint(0, u'run %s', u'" ".join(gdb_arguments)', file=u'cmd') self.write(0, u'cmd.close()') self.write(0, u'return filename') def runGdb(self): self.write(0, u'from pwd import getpwuid') self.emptyLine() self.write(0, u'uid = getuid()') self.write(0, u'home = getpwuid(uid).pw_dir') self.write(0, u"gdb_env = {'HOME': home}") self.write(0, u'filename = writeGdbCommands(arguments, gdb_env, env)') self.emptyLine() self.write(0, u'gdb_arguments = [') self.write(1, u'gdb_program,') self.write(1, u'arguments[0],') self.write(1, u"'-x',") self.write(1, u'filename,') self.write(0, u']') self.debug(0, u'Execute %r in environment %r', u"' '.join(gdb_arguments)", u"gdb_env") self.write(0, u'execvpe(gdb_arguments[0], gdb_arguments, gdb_env)') def runCommand(self): self.write(0, u"global null_stdin") self.emptyLine() self.write(0, u'if options.environ:') self.write(1, u'env = environ') self.write(1, u'env.update(program_env)') self.write(0, u'else:') self.write(1, u'env = program_env') self.emptyLine() self.write(0, u'for key, value in env.items():') self.debug(1, u'set env %s = (%s bytes) %s', 'key', 'len(value)', 'repr(value)') self.write(0, u'if options.gdb:') self.write(1, u'runGdb(gdb_program, arguments, env)') self.write(1, u'return') self.emptyLine() self.write(0, u'if options.valgrind:') self.write(1, u"arguments = [valgrind_program, '--'] + arguments") self.write(0, u'elif options.ptrace:') self.write(1, u'from fusil.process.tools import locateProgram') self.write(1, u'from sys import executable') self.emptyLine() self.write(1, u'program = locateProgram(ptrace_program, raise_error=True)') self.write(1, u"arguments = [executable, program, '--'] + arguments") self.write(1, u"null_stdin = False") self.emptyLine() self.write(0, u'if null_stdin:') self.debug(1, u'Redirect stdin to %s', 'devnull') self.write(1, u'from os import dup2') self.write(1, u"stdin = open(devnull, 'wb')") self.write(1, u'dup2(stdin.fileno(), 0)') self.emptyLine() self.debug(0, u'Execute %r', u"' '.join(arguments)") self.write(0, u'execvpe(arguments[0], arguments, env)') def writeMain(self): self.write(0, 'global uid, gid, quiet') self.emptyLine() self.write(0, 'options = parseOptions()') self.write(0, 'quiet = options.quiet') self.write(0, 'if options.user:') self.write(1, 'uid = None') self.write(1, 'gid = None') self.write(0, u'try:') self.write(1, u'changeUserGroup(uid, gid)') if RUNNING_PYTHON3: self.write(0, 'except OSError as err:') else: self.write(0, u'except OSError, err:') self.writePrint(1, u'Error on changing user/group: %s', 'err') self.write(1, u'if getuid() != 0:') self.writePrint(2, u'=> Retry as root user!') self.write(1, u'exit(1)') self.write(0, 'limitResources(options)') self.debug(0, "current working directory: %s", "cwd") self.write(0, 'runCommand(arguments, env, options)') def globalVariables(self, process, config, cwd, arguments, env): fs_charset = getfilesystemencoding() cwd_bytes = cwd.encode(fs_charset) need_cwd_bytes = False for value in itertools.chain(arguments, itervalues(env)): if not isinstance(value, binary_type): continue if cwd_bytes not in value: continue need_cwd_bytes = True break self.write(0, u'chdir(dirname(__file__))') self.write(0, u'cwd = getcwd()') if need_cwd_bytes: self.write(0, 'cwd_bytes = cwd.encode(getfilesystemencoding())') self.emptyLine() self.write(0, u'arguments = [') # Use relative PATH in arguments for arg in arguments: arg = formatPath(arg, cwd, cwd_bytes) self.write(1, u'%s,' % arg) self.write(0, u']') if env: self.write(0, u'env = {') for name, value in iteritems(env): value = formatPath(value, cwd, cwd_bytes) self.write(1, u'"%s": %s,' % (name, value)) self.write(0, u'}') else: self.write(0, u'env = {}') self.emptyLine() self.write(0, u'null_stdin = %r' % bool(not process.stdin)) self.write(0, u'uid = %r' % config.process_uid) self.write(0, u'gid = %r' % config.process_gid) self.write(0, u'allow_core_dump = %r' % process.core_dump) if config.process_user and (0 < process.max_user_process): max_user_process = process.max_user_process else: max_user_process = None self.write(0, u'max_user_process = %r' % max_user_process) self.write(0, u'max_memory = %r # bytes' % process.max_memory) if 0 < process.timeout: timeout = int(process.timeout) else: timeout = None self.write(0, u'timeout = %r # seconds' % timeout) self.write(0, u"gdb_program = 'gdb'") self.write(0, u"valgrind_program = 'valgrind'") self.write(0, u"ptrace_program = 'gdb.py'") self.write(0, u'quiet = False') self.emptyLine() def callMain(self): self.write(0, "if __name__ == '__main__':") self.write(1, "main()") self.emptyLine() def writeCode(self, process, arguments, popen_args): project = process.project() session = project.session config = project.config cwd = process.getWorkingDirectory() env = popen_args['env'] if not env: env = {} # Create the script file filename = session.createFilename("replay.py") self.createFile(filename, 0o755) self.pythonImports() self.globalVariables(process, config, cwd, arguments, env) self.setSysPath() self.writeFunction('debug(message)', self.writeDebugFunction) self.writeFunction('parseOptions()', self.parseOptions) self.writeFunction('safetyConfirmation(uid, gid)', self.safetyConfirmation) self.writeFunction('changeUserGroup(uid, gid)', self.changeUserGroup, process, config) self.writeFunction('limitResources(options)', self.limitResources) self.writeFunction('writeGdbCommands(arguments, gdb_env, env)', self.writeGdbCommands) self.writeFunction('runGdb(gdb_program, arguments, env)', self.runGdb) self.writeFunction('runCommand(arguments, program_env, options)', self.runCommand) self.writeFunction('main()', self.writeMain) self.callMain() self.close() def createReplayPythonScript(process, arguments, popen_args): writer = WriteReplayScript() writer.writeCode(process, arguments, popen_args) fusil-1.5/fusil/process/mangle.py0000664000175000017500000000210612110032732017370 0ustar haypohaypo00000000000000from fusil.process.create import CreateProcess from fusil.file_tools import relativePath class MangleProcess(CreateProcess): def __init__(self, project, arguments, mangle_pattern, use_relative_mangle=True, **kw): CreateProcess.__init__(self, project, arguments, **kw) self.orig_cmdline = self.cmdline.arguments self.mangle_pattern = mangle_pattern self.use_relative_mangle = use_relative_mangle def mangleCmdline(self, filenames): file_index = 0 args = list(self.orig_cmdline) directory = self.getWorkingDirectory() for index, arg in enumerate(args): if self.mangle_pattern not in arg: continue filename = filenames[file_index] if self.use_relative_mangle: filename = relativePath(filename, directory) args[index] = arg.replace(self.mangle_pattern, filename) file_index += 1 self.cmdline.arguments = args def on_mangle_filenames(self, filenames): self.mangleCmdline(filenames) self.createProcess() fusil-1.5/fusil/process/cpu_probe.py0000664000175000017500000000311012110032732020077 0ustar haypohaypo00000000000000from fusil.project_agent import ProjectAgent from fusil.linux.cpu_load import ProcessCpuLoad from ptrace.linux_proc import ProcError from time import time class CpuProbe(ProjectAgent): def __init__(self, project, name, max_load=0.75, max_duration=10.0, max_score=1.0): ProjectAgent.__init__(self, project, name) self.max_load = max_load self.max_duration = max_duration self.max_score = max_score def init(self): self.score = None self.timeout = None self.load = None def setPid(self, pid): self.load = ProcessCpuLoad(pid) def live(self): # Read CPU load if not self.load: return try: load = self.load.get() if not load: return except ProcError: self.load = None return # Check maximum load if load < self.max_load: self.timeout = None return if self.timeout is None: self.warning("CPU load: %.1f%%" % (load*100)) self.timeout = time() return # Check maximum duration duration = time() - self.timeout if duration < self.max_duration: return # Success self.score = self.max_score self.error("CPU load (%.1f%%) bigger than maximum (%.1f%%) during %.1f sec: score=%.1f%%" % (load*100, self.max_load*100, duration, self.score*100)) self.send('session_rename', 'cpu_load') self.load = None def getScore(self): return self.score fusil-1.5/fusil/process/cmdline.py0000664000175000017500000000045612110032732017546 0ustar haypohaypo00000000000000from fusil.project_agent import ProjectAgent class CommandLine(ProjectAgent): def __init__(self, process, arguments): ProjectAgent.__init__(self, process.project(), "%s:cmdline" % process.name) self.arguments = arguments def create(self): return list(self.arguments) fusil-1.5/fusil/process/env.py0000664000175000017500000001234712254431553016742 0ustar haypohaypo00000000000000from fusil.project_agent import ProjectAgent from random import choice, randint from os import getenv from fusil.bytes_generator import BytesGenerator, LengthGenerator, ASCII0 from fusil.unicode_generator import IntegerGenerator from ptrace.os_tools import RUNNING_WINDOWS class EnvironmentVariable: def __init__(self, name, max_count=1): self.name = name self.min_count = 1 self.max_count = max_count def hasName(self, name): if isinstance(self.name, (list, tuple)): return name in self.name else: return self.name == name def createName(self): """ Generate variable name """ if isinstance(self.name, (list, tuple)): return choice(self.name) else: return self.name def createValue(self): """ Generate variable content (bytes string) """ raise NotImplementedError() def create(self): """ Generate variable content """ if isinstance(self.name, (list, tuple)): max_count = len(self.name) else: max_count = 1 if self.max_count: max_count = min(max_count, self.max_count) count = randint(self.min_count, max_count) for index in range(count): name = self.createName() value = self.createValue() yield (name, value) class EnvVarValue(EnvironmentVariable): def __init__(self, name, value='', max_count=1): EnvironmentVariable.__init__(self, name, max_count) self.value = value def createValue(self): return self.value class EnvVarLength(LengthGenerator, EnvironmentVariable): def __init__(self, name, max_length, min_length=0, max_count=1): LengthGenerator.__init__(self, min_length=min_length, max_length=max_length) EnvironmentVariable.__init__(self, name, max_count) class EnvVarInteger(IntegerGenerator, EnvironmentVariable): def __init__(self, name, max_count=1): IntegerGenerator.__init__(self) EnvironmentVariable.__init__(self, name, max_count) def createValue(self): return str(IntegerGenerator.createValue(self)) class EnvVarIntegerRange(EnvironmentVariable): def __init__(self, name, min, max, max_count=1): EnvironmentVariable.__init__(self, name, max_count) self.min = min self.max = max def createValue(self): value = randint(self.min, self.max) return str(value) class EnvVarRandom(BytesGenerator, EnvironmentVariable): def __init__(self, name, min_length=0, max_length=10000, max_count=1, bytes_set=ASCII0): BytesGenerator.__init__(self, min_length, max_length, bytes_set) EnvironmentVariable.__init__(self, name, max_count) class Environment(ProjectAgent): def __init__(self, process): ProjectAgent.__init__(self, process.project(), "%s:env" % process.name) self.clear() if RUNNING_WINDOWS: self.copies.append('SYSTEMROOT') def clear(self): self.copies = [] self.variables = [] def set(self, name, value, max_count=1): """ Set a fixed environment variable value: create an EnvVarValue. Return the EnvVarValue objet. """ try: variable = self[name] if not isinstance(variable, EnvVarValue): raise TypeError("Variable %s is already set but the type is not EnvVarValue but %s" % (name, variable.__class__.__name__)) except KeyError: variable = EnvVarValue(name, value, max_count) self.add(variable) return variable def add(self, variable): """ Add a new EnvironmentVariable object. """ self.variables.append(variable) def copy(self, name): """ Add the name of the environment variable to copy. """ if name in self.copies: return self.copies.append(name) def __getitem__(self, name): if isinstance(name, (list, tuple)): raise TypeError("Environment[name] doesn't support name list") for var in self.variables: if var.hasName(name): return var raise KeyError('No environment variable: %r' % name) def create(self): """ Create process environment variable dictionnary: name (str) => value (str). """ env = {} # Copy some environment variables for name in self.copies: value = getenv(name) if value is not None: env[name] = value # Generate new variables for var in self.variables: for name, value in var.create(): message = "Create environment variable %s: (len=%s)" % (name, len(value)) if len(value) <= 50: message += ' ' + repr(value) self.info(message) if "\0" in value: raise ValueError("Nul byte in environment variable value is forbidden!") env[name] = value # Write result to logs self.info("Environment: %r" % env) return env def copyX11(self): self.copy('HOME') self.copy('DISPLAY') fusil-1.5/fusil/process/prepare.py0000664000175000017500000000563212253715322017605 0ustar haypohaypo00000000000000from fusil.unsafe import SUPPORT_UID from errno import EACCES from os import chdir, access, X_OK from fusil.process.tools import ( limitMemory, beNice, allowCoreDump, limitUserProcess) from fusil.unsafe import permissionHelp if SUPPORT_UID: from os import getuid, setuid, setgid from pwd import getpwuid class ChildError(Exception): # Exception raised after the fork(), in prepareProcess() pass def prepareProcess(process): project = process.project() config = project.config options = process.application().options # Trace the new process process.debugger.traceme() # Change the user and group if SUPPORT_UID: changeUserGroup(config, options) # Set current working directory directory = process.getWorkingDirectory() try: chdir(directory) except OSError as err: if err.errno != EACCES: raise user = getuid() user = getpwuid(user).pw_name message = 'The user %s is not allowed enter directory to %s' \ % (user, directory) help = permissionHelp(options) if help: message += ' (%s)' % help raise ChildError(message) # Make sure that the program is executable by the current user program = process.current_arguments[0] if not access(program, X_OK): user = getuid() user = getpwuid(user).pw_name message = 'The user %s is not allowed to execute the file %s' \ % (user, program) help = permissionHelp(options) if help: message += ' (%s)' % help raise ChildError(message) # Limit process resources limitResources(process, config, options) def limitResources(process, config, options): # Change process priority to be nice if not options.fast: beNice() # Set process priority to nice and limit memory if 0 < process.max_memory: limitMemory(process.max_memory, hard=True) elif 0 < config.fusil_max_memory: # Reset Fusil process memory limit limitMemory(-1) if process.core_dump: allowCoreDump(hard=True) if config.process_user and (0 < process.max_user_process): limitUserProcess(process.max_user_process, hard=True) def changeUserGroup(config, options): # Change group? gid = config.process_gid errors = [] if gid is not None: try: setgid(gid) except OSError: errors.append("group to %s" % gid) # Change user? uid = config.process_uid if uid is not None: try: setuid(uid) except OSError: errors.append("user to %s" % uid) if not errors: return # On error: propose some help help = permissionHelp(options) # Raise an error message errors = ' and '.join(reversed(errors)) message = 'Unable to set ' + errors if help: message += ' (%s)' % help raise ChildError(message) fusil-1.5/fusil/process/tools.py0000664000175000017500000001460212254530433017303 0ustar haypohaypo00000000000000from os import getenv, access, X_OK, pathsep, devnull, getcwd from os.path import dirname, normpath, join as path_join, isabs from ptrace.os_tools import RUNNING_WINDOWS from ptrace.signames import signalName from ptrace.six import string_types from subprocess import Popen, STDOUT import re if RUNNING_WINDOWS: from win32process import SetPriorityClass, BELOW_NORMAL_PRIORITY_CLASS, IDLE_PRIORITY_CLASS from win32api import GetCurrentProcessId, OpenProcess from win32con import PROCESS_ALL_ACCESS else: from resource import ( getrlimit, setrlimit, RLIMIT_AS, RLIMIT_CORE, RLIMIT_NPROC, RLIMIT_CPU) try: from os import nice except ImportError: # PyPy doesn't have os.nice() def nice(level): pass def _setrlimit(key, value, change_hard): try: soft, hard = getrlimit(key) # Change soft limit if hard != -1: soft = min(value, hard) else: soft = value if change_hard: # Change hard limit hard = soft except ValueError: hard = -1 setrlimit(key, (soft, hard)) return soft def limitMemory(nbytes, hard=False): if RUNNING_WINDOWS: return return _setrlimit(RLIMIT_AS, nbytes, hard) def limitUserProcess(nproc, hard=False): if RUNNING_WINDOWS: return return _setrlimit(RLIMIT_NPROC, nproc, hard) def allowCoreDump(hard=False): if RUNNING_WINDOWS: return return _setrlimit(RLIMIT_CORE, -1, hard) def limitCpuTime(seconds, hard=False): if RUNNING_WINDOWS: return # Round float to int if isinstance(seconds, float): seconds = int(seconds + 0.5) return _setrlimit(RLIMIT_CPU, seconds, hard) def beNice(very_nice=False): if RUNNING_WINDOWS: if very_nice: value = BELOW_NORMAL_PRIORITY_CLASS else: value = IDLE_PRIORITY_CLASS pid = GetCurrentProcessId() handle = OpenProcess(PROCESS_ALL_ACCESS, True, pid) SetPriorityClass(handle, value) else: if very_nice: value = 10 else: value = 5 nice(value) def displayProcessStatus(logger, status, prefix="Process"): if status == 0: logger.info("%s exited normally" % prefix) elif status < 0: signum = -status logger.error("%s killed by signal %s" % (prefix, signalName(signum))) else: logger.warning("%s exited with error code: %s" % (prefix, status)) def runCommand(logger, command, stdin=False, stdout=True, options=None, raise_error=True): """ Run specified command: - logger is an object with a info() method - command: a string or a list of strings (eg. 'uname' or ['gcc', 'printf.c']) - stdin: if value is False, use null device as stdin (default: no stdin) - stdout: if value is False, use null device as stdout and stderr (default: keep stdout) Raise RuntimeError on failure: if the exit code is not nul or if the process is killed by a signal. If raise_error=False, return the status and don't raise RuntimeError if status is not nul. """ if isinstance(command, string_types): command_str = repr(command) else: command_str = ' '.join(command) command_str = repr(command_str) logger.info("Run the command: %s" % command_str) if not options: options = {} if not stdin: stdin_file = open(devnull, 'r') options['stdin'] = stdin_file else: stdin_file = None stdout_file = None if not stdout: stdout_file = open(devnull, 'wb') options['stdout'] = stdout_file options['stderr'] = STDOUT elif stdout is not True: options['stdout'] = stdout options['stderr'] = STDOUT if ('close_fds' not in options) \ and (not RUNNING_WINDOWS): options['close_fds'] = True process = Popen(command, **options) status = process.wait() if stdin_file: stdin_file.close() if stdout_file: stdout_file.close() if not raise_error: return status if not status: return if status < 0: errmsg = 'process killed by signal %s' % signalName(-status) else: errmsg = 'exit code %s' % status raise RuntimeError("Unable to run the command %s: %s" % ( command_str, errmsg)) def locateProgram(program, use_none=False, raise_error=False): if isabs(program): # Absolute path: nothing to do return program if dirname(program): # ./test => $PWD/./test # ../python => $PWD/../python program = path_join(getcwd(), program) program = normpath(program) return program if use_none: default = None else: default = program paths = getenv('PATH') if not paths: if raise_error: raise ValueError("Unable to get PATH environment variable") return default for path in paths.split(pathsep): filename = path_join(path, program) if access(filename, X_OK): return filename if raise_error: raise ValueError("Unable to locate program %r in PATH" % program) return default def splitCommand(command): r""" Split a command (string): create a command as a list of strings. >>> splitCommand("ls -l") ['ls', '-l'] >>> splitCommand("python -c 'import sys; print sys.version'") ['python', '-c', 'import sys; print sys.version'] >>> splitCommand("python -c 'import sys; print \"sys.version\"'") ['python', '-c', 'import sys; print "sys.version"'] """ if "\\" in command: raise SyntaxError("splitCommand() doesn't support antislash") arguments = [] start = 0 in_quote = None for match in re.finditer(r'''[ \t"']''', command): index = match.start() sep = command[index] write = False if in_quote == sep: write = True in_quote = None elif sep == ' ': if not in_quote: write = True elif not in_quote: in_quote = sep start = index + 1 if write: arguments.append(command[start:index]) start = index + 1 if in_quote: raise SyntaxError("Quote %r is not closed" % in_quote) if start < len(command): arguments.append(command[start:]) return arguments fusil-1.5/fusil/process/debugger.py0000664000175000017500000001337612253715322017737 0ustar haypohaypo00000000000000from fusil.project_agent import ProjectAgent from ptrace.os_tools import HAS_PTRACE from fusil.process.watch import DEFAULT_SIGNAL_SCORE from ptrace.signames import signalName if HAS_PTRACE: from signal import SIGCHLD, SIGTRAP from ptrace import PtraceError from ptrace.binding import ptrace_traceme from ptrace.debugger import ( PtraceDebugger, DebuggerError as PtraceDebuggerError, ProcessExit, ProcessSignal, NewProcessEvent) from errno import EPERM from fusil.unsafe import permissionHelp class DebuggerError(Exception): pass # Status for "process terminated abnormally" ABNORMAL_STATUS = 1 from signal import SIGFPE, SIGSEGV, SIGABRT FATAL_SIGNALS = set((SIGFPE, SIGSEGV, SIGABRT)) try: from signal import SIGBUS FATAL_SIGNALS.add(SIGBUS) except ImportError: pass class Debugger(ProjectAgent): def __init__(self, project): ProjectAgent.__init__(self, project, "dbg") self.debugger = None self.enabled = (HAS_PTRACE and project.config.use_debugger) self.fusil_processes = {} if self.enabled: self.error("Use python-ptrace debugger") self.debugger = PtraceDebugger() if project.config.debugger_trace_forks: try: self.debugger.traceFork() self.warning("Debugger trace process forks") except PtraceDebuggerError as err: self.error("Unable to trace forks: %s" % err) else: self.error("Debugger disabled") def disable(self): if not self.enabled: return self.warning("Disable the debugger") self.enabled = False self.debugger = None def attachPID(self, agent, pid): if not self.enabled: raise DebuggerError("Debugger is not enabled") try: ptrace_process = self.debugger[pid] except KeyError: ptrace_process = self.debugger.addProcess(pid, True) ptrace_process.cont() self.registerProcess(agent, ptrace_process) return ptrace_process def registerProcess(self, agent, process): if process in self.fusil_processes: return self.fusil_processes[process] = agent def detach(self, ptrace_process): ptrace_process.detach() del self.fusil_processes[ptrace_process] def pollPID(self, pid): event = self.debugger.waitProcessEvent(pid, blocking=False) if event: return self.useProcessEvent(event) else: return None def useProcessEvent(self, event): if isinstance(event, ProcessSignal): self.processSignal(event) event.process.cont(event.signum) return None if isinstance(event, ProcessExit): return self.processExit(event) if isinstance(event, NewProcessEvent): new_process = event.process self.warning("New process: %s" % new_process) new_process.cont() return None raise event def processExit(self, event): # Get the Fusil process agent fusil_process = self.getFusilProcess(event.process) # Create exit status if event.signum is not None: status = -event.signum elif event.exitcode is not None: status = event.exitcode else: status = ABNORMAL_STATUS # Display the exit status? if fusil_process.show_exit: message = str(event) if event.signum or (event.exitcode is None): # killed by a signal or abnormal exit log = self.error elif event.exitcode: log = self.warning else: log = self.info log(message) if event.exitcode: fusil_process.send('session_rename', 'exitcode%s' % event.exitcode) elif event.signum: name = signalName(event.signum) name = name.lower() fusil_process.send('session_rename', name) fusil_process.send("process_exit", fusil_process, status) return status def getFusilProcess(self, ptrace_process): return self.fusil_processes[ptrace_process] def processSignal(self, event): if event.signum in (SIGCHLD, SIGTRAP): # Ignore these signals return fusil_process = self.getFusilProcess(event.process) if not fusil_process.show_exit: return if event.signum in FATAL_SIGNALS: fusil_process.score = DEFAULT_SIGNAL_SCORE event.display(self.error) reason = event.reason if reason: fusil_process.send('session_rename', reason.name) def tracePID(self, agent, pid): if not self.enabled: return None try: process = self.debugger.addProcess(pid, False) self.registerProcess(agent, process) process.cont() except PtraceError as err: if err.errno == EPERM: msg = "You are not allowed to trace the process %s: permission denied or process already traced" % pid else: msg = "Process can no be attached! %s" % err help = permissionHelp(self.application().options) if help: msg += " (%s)" % help raise DebuggerError("ERROR: %s" % msg) return process def traceme(self): if not self.enabled: return False ptrace_traceme() return True def quit(self): if not self.enabled: return self.enabled = False self.debugger.quit() def destroy(self): self.quit() def on_project_stop(self): self.quit() fusil-1.5/fusil/process/stdout.py0000664000175000017500000000123212110032732017446 0ustar haypohaypo00000000000000from fusil.file_watch import FileWatch from weakref import ref as weakref_ref class WatchStdout(FileWatch): def __init__(self, process): FileWatch.__init__(self, process.project(), None, "watch:stdout") self.process = weakref_ref(process) def on_process_stdout(self, agent, filename): if agent != self.process(): return input_file = open(filename, 'rb') self.setFileObject(input_file) def on_process_exit(self, agent, status): if agent != self.process(): return self.live() self.close() def deinit(self): FileWatch.deinit(self) self.close() fusil-1.5/fusil/process/attach.py0000664000175000017500000000755312253715322017417 0ustar haypohaypo00000000000000from fusil.project_agent import ProjectAgent from ptrace.os_tools import HAS_PROC, RUNNING_WINDOWS if HAS_PROC: from ptrace.linux_proc import ( ProcError, readProcessStatm, searchProcessByName) from ptrace.process_tools import dumpProcessInfo if HAS_PROC: from os import stat from fusil.process.cpu_probe import CpuProbe elif not RUNNING_WINDOWS: from os import kill from errno import ESRCH, ENOENT class AttachProcessPID(ProjectAgent): def __init__(self, project, pid, name=None): if not name: name = "pid:%s" % pid ProjectAgent.__init__(self, project, name) if RUNNING_WINDOWS: raise NotImplementedError( "AttachProcessPID is not supported on Windows") self.death_score = 1.0 self.show_exit = True # needed by the debugger self.max_memory = 100*1024*1024 self.memory_score = 1.0 self.debugger = project.debugger self.dbg_process = None if HAS_PROC and project.config.use_cpu_probe: self.cpu = CpuProbe(project, "%s:cpu" % self.name) else: self.warning("CpuProbe is not available on your OS") self.cpu = None if pid: self.setPid(pid) else: self.pid = None def init(self): self.score = 0.0 def on_project_stop(self): if self.dbg_process: self.dbg_process.detach() self.dbg_process = None def setPid(self, pid): self.pid = pid dumpProcessInfo(self.info, self.pid) if self.cpu: self.cpu.setPid(pid) if not self.dbg_process: self.dbg_process = self.debugger.tracePID(self, pid) def live(self): if self.pid is None: return if not self.checkAlive(): return if self.max_memory: if not self.checkMemory(): return def checkAlive(self): if RUNNING_WINDOWS: raise NotImplementedError() if self.dbg_process: status = self.debugger.pollPID(self.pid) if status is None: return True elif HAS_PROC: try: stat('/proc/%s' % self.pid) return True except OSError as err: if err.errno != ENOENT: raise else: try: kill(self.pid, 0) return True except OSError as err: if err.errno != ESRCH: raise self.error("Process %s disappeared" % self.pid) self.stop(self.death_score) return False def checkMemory(self): if not HAS_PROC: return True try: memory = readProcessStatm(self.pid)[0] except ProcError as error: self.error(error) self.stop() return False if memory < self.max_memory: return True self.error("Memory limit reached: %s > %s" % ( memory, self.max_memory)) self.stop(self.memory_score) return False def stop(self, score=None): if score: self.score = score self.pid = None def getScore(self): return self.score class AttachProcess(AttachProcessPID): def __init__(self, project, process_name): AttachProcessPID.__init__(self, project, None, "attach_process:%s" % process_name) self.process_name = process_name if not HAS_PROC: # Missing searchProcessByName() function raise NotImplementedError( "AttachProcess is not supported on your OS") def init(self): AttachProcessPID.init(self) self.pid = None def on_session_start(self): pid = searchProcessByName(self.process_name) self.send('process_pid', self, pid) self.setPid(pid) fusil-1.5/fusil/process/watch.py0000664000175000017500000000505512110032732017241 0ustar haypohaypo00000000000000from fusil.project_agent import ProjectAgent from weakref import ref as weakref_ref from ptrace.os_tools import RUNNING_LINUX if RUNNING_LINUX: from fusil.process.cpu_probe import CpuProbe DEFAULT_EXITCODE_SCORE = 0.50 DEFAULT_TIMEOUT_SCORE = 1.0 DEFAULT_SIGNAL_SCORE = 1.0 class WatchProcess(ProjectAgent): def __init__(self, process, exitcode_score=DEFAULT_EXITCODE_SCORE, signal_score=DEFAULT_SIGNAL_SCORE, default_score=0.0, timeout_score=DEFAULT_TIMEOUT_SCORE): self.process = weakref_ref(process) project = process.project() ProjectAgent.__init__(self, project, "watch:%s" % process.name) if RUNNING_LINUX and project.config.use_cpu_probe: self.cpu = CpuProbe(project, "%s:cpu" % self.name) else: self.cpu = None # Score if process exited normally self.default_score = default_score # Score if process exit code is not nul self.exitcode_score = exitcode_score # Score if process has been killed by signal self.signal_score = signal_score # Score if process timeout has been reached self.timeout_score = timeout_score def init(self): self.score = None self.pid = None def on_process_create(self, agent): if agent != self.process(): return self.pid = agent.process.pid self.prepareProcess() def on_session_start(self): if self.pid is not None: self.prepareProcess() def prepareProcess(self): if self.cpu: self.cpu.setPid(self.pid) def live(self): if not self.pid: return # Check if process is done or not status = self.process().poll() if status is not None: self.processDone(status) return def processDone(self, status): self.score = self.computeScore(status) self.send('session_stop') self.pid = None def getScore(self): return self.score def computeScore(self, status): # Timeout reached if self.process().timeout_reached: return self.timeout_score # No status: no way to compute score if status is None: return None # Process exit code is not nul? if 0 < status: return self.exitcode_score # Process killed by a signal if status < 0: return self.signal_score # Process exited normally: default score return self.default_score def deinit(self): self.pid = None fusil-1.5/fusil/process/time_watch.py0000664000175000017500000000074512110032732020260 0ustar haypohaypo00000000000000from fusil.time_watch import TimeWatch from time import time class ProcessTimeWatch(TimeWatch): def init(self): TimeWatch.init(self) self.time0 = None def on_process_create(self, agent): self.time0 = time() def on_session_done(self, score): pass def on_process_exit(self, agent, status): duration = time() - self.time0 self.warning("Process done: duration=%.1f ms" % (duration*1000)) self.setScore(duration) fusil-1.5/fusil/process/__init__.py0000664000175000017500000000000012110032732017653 0ustar haypohaypo00000000000000fusil-1.5/fusil/process/create.py0000664000175000017500000002510012254530446017405 0ustar haypohaypo00000000000000from fusil.unsafe import SUPPORT_UID from errno import ENOENT from os import devnull from os.path import basename from subprocess import Popen, STDOUT from time import time, sleep from fusil.process.cmdline import CommandLine from fusil.process.prepare import prepareProcess, ChildError from fusil.process.env import Environment from fusil.process.replay_python import createReplayPythonScript from fusil.process.tools import ( locateProgram, displayProcessStatus, splitCommand) from fusil.project_agent import ProjectAgent from ptrace.os_tools import RUNNING_WINDOWS from ptrace.signames import signalName from ptrace.six import string_types, text_type, binary_type if SUPPORT_UID: from pwd import getpwuid if RUNNING_WINDOWS: from win32api import TerminateProcess else: from os import kill from signal import SIGKILL DEFAULT_TIMEOUT = 10.0 def terminateProcess(process): if RUNNING_WINDOWS: TerminateProcess(process._handle, 1) else: kill(process.pid, SIGKILL) class ProcessError(Exception): # Exception raised by Fusil, by the CreateProcess class pass class CreateProcess(ProjectAgent): def __init__(self, project, arguments=None, stdout="file", stdin=False, timeout=DEFAULT_TIMEOUT, name=None): if isinstance(arguments, string_types): arguments = splitCommand(arguments) config = project.config if not name: name = "process:%s" % basename(arguments[0]) ProjectAgent.__init__(self, project, name) self.env = Environment(self) if arguments is None: arguments = [] self.cmdline = CommandLine(self, arguments) self.timeout = timeout self.max_memory = config.process_max_memory self.max_user_process = config.process_max_user_process self.core_dump = config.process_core_dump self.stdout = stdout self.debugger = project.debugger self.use_x11 = False self.popen_args = { 'stderr': STDOUT, } if not RUNNING_WINDOWS: self.popen_args['close_fds'] = True self.stdin = stdin def init(self): self.score = None self.process = None self.dbg_process = None self.timeout_reached = False self.status = None self.current_popen_args = None self.current_arguments = None self.show_exit = True self.wrote_replay = False self.stdout_file = None def prepareProcess(self): prepareProcess(self) def getWorkingDirectory(self): return self.session().directory.directory def createProcess(self): arguments = self.createArguments() for index, argument in enumerate(arguments): if isinstance(argument, binary_type): has_null = ("\0" in argument) elif isinstance(argument, text_type): has_null = (u"\0" in argument) else: raise ValueError("Process argument %s is not a byte or unicode string: (%s) %r" % ( index, type(argument).__name__, argument)) if has_null: raise ValueError("Process argument %s contains nul byte: %r" % ( index, argument)) arguments[0] = locateProgram(arguments[0], raise_error=True) popen_args = self.createPopenArguments() self.info("Create process: %s" % repr(arguments)) self.info("Working directory: %s" % self.getWorkingDirectory()) self.writeReplayScripts(arguments, popen_args) try: self.current_arguments = arguments self.current_popen_args = popen_args self.time0 = time() self.process = Popen(arguments, **popen_args) except ChildError as err: raise ProcessError(unicode(err)) except OSError as err: if err.errno == ENOENT: raise ProcessError("Program doesn't exist: %s" % arguments[0]) else: raise pid = self.process.pid self.info("Process identifier: %s" % pid) self.closeStreams() if self.debugger.enabled: self._attach() self.send('process_create', self) self.send('process_pid', self, pid) def writeReplayScripts(self, arguments, popen_args): if self.wrote_replay: return self.wrote_replay = True createReplayPythonScript(self, arguments, popen_args) def _attach(self): self.dbg_process = self.debugger.attachPID(self, self.process.pid) def createPopenArguments(self): popen_args = dict(self.popen_args) env = self.env.create() if SUPPORT_UID: uid = self.project().config.process_uid if 'HOME' in env \ and uid is not None: # Use the fuzzer user home directory env['HOME'] = getpwuid(uid).pw_dir popen_args['env'] = env self.stdin_file = self.createStdin() if self.stdin_file: popen_args['stdin'] = self.stdin_file.fileno() self.stdout_file = self.createStdout() popen_args['stdout'] = self.stdout_file.fileno() if not RUNNING_WINDOWS: popen_args['preexec_fn'] = self.prepareProcess return popen_args def createStdin(self): if self.stdin: return None self.info("Stdin: %s" % devnull) return open(devnull, 'rb') def createStdout(self): # Check stdout type if self.stdout not in ('null', 'file'): raise ValueError('Invalid stdout type: %r' % self.stdout) # Ignore stdout? if self.stdout != "null": # Otherwise, create a "stdout" file as output filename = self.session().createFilename('stdout') self.send('process_stdout', self, filename) else: filename = devnull self.info("Stdout filename: %s" % filename) return open(filename, "wb") def createArguments(self): return self.cmdline.create() def detach(self): if not self.dbg_process: return self.debugger.detach(self.dbg_process) self.dbg_process = None def renameSession(self, status): if status < 0: signum = -status name = signalName(signum) name = name.lower() elif 0 < status: name = "exitcode%s" % status else: # nul exitcode: don't rename the session return self.send('session_rename', name) def processExited(self, status): if self.show_exit: displayProcessStatus(self, status, "Process %s" % self.process.pid) if not self.debugger.enabled: self.renameSession(status) self.send("process_exit", self, status) self.status = status if self.debugger.enabled: # Inform Popen() object that the process exited self.process.returncode = status self.clearProcess() self.detach() def closeStreams(self): if self.stdout_file: self.stdout_file.close() self.stdout_file = None if self.stdin_file: self.stdin_file.close() self.stdin_file = None def clearProcess(self): self.closeStreams() self.process = None def poll(self): """ Get process exit status: - zero: process exited with code 0 - a positive value: process exited with code (status) - a negative value: process killed by the signal (-status) """ if not self.process: return self.status if self.debugger.enabled: if not self.dbg_process: self._attach() status = self.debugger.pollPID(self.process.pid) if status is None: return None # Message already displayed by the debugger # (and event "process_exit" sent) self.show_exit = False else: status = self.process.poll() if status is None: return None self.processExited(status) return status def live(self): if (not self.process) \ or not (0 < self.timeout): return if time() - self.time0 < self.timeout: return self.warning("Timeout! (%.1f second)" % self.timeout) self.send('session_rename', 'timeout') self.timeout_reached = True self.terminate() def terminate(self): # Manual terminate, so don't show exit status self.show_exit = False # Check if process is still running or not if not self.process: return if self.poll() is not None: return # Kill the process and wait for its exit status self.warning("Terminate process %s" % self.process.pid) self._terminate() self.waitExit() def _terminate(self): if self.debugger.enabled: self.dbg_process.terminate(wait_exit=False) else: terminateProcess(self.process) def waitExit(self): # Get the process exit status to avoid creation of a zombi process start = time() timeout = 60.0 next_msg = start + 1.5 while True: # Timeout? diff = time() - start if timeout < diff: raise ValueError("Unable to kill process %s after %.1f seconds" % ( self.process.pid, diff)) # Inform user about this loop if next_msg <= time(): next_msg = time() + 5.0 self.error("Wait until process %s death (since %.1f seconds)..." % (self.process.pid, diff)) # Is process terminated? status = self.poll() if status is not None: break if diff < 0.050: # During first 50 ms, try five times to get its status sleep(0.010) elif diff < 1.0: # 50 ms .. 1000 ms: retry four times sleep(0.250) else: # After one second, resend KILL signal each half second self._terminate() sleep(0.500) def deinit(self): if self.process: self.terminate() self.process = None self.detach() def setupX11(self): self.use_x11 = True self.application().initX11() self.env.copyX11() def getScore(self): return self.score class ProjectProcess(CreateProcess): def on_session_start(self): self.createProcess() fusil-1.5/fusil/cmd_help_parser.py0000664000175000017500000001261412253715322017616 0ustar haypohaypo00000000000000import re class Option: def __init__(self, format, nb_argument): self.format = format self.nb_argument = nb_argument def formatArguments(self, arguments): result = [] index = 0 for part in self.format.split(): count = part.count("%s") arg = part % tuple(arguments[index:index+count]) result.append(arg) index += count if index != len(arguments): raise TypeError("not all arguments converted during formatting") return result def __str__(self): text = self.format arguments = tuple( ("ARG%s" % index) for index in range(1, self.nb_argument+1) ) return text % arguments class CommandHelpParser: def __init__(self, program): self.program = program self.options = [] self.options_set = set() self.parse_line = self.parseLine # "-a", "-9" or "-C" SHORT_OPT_REGEX = r'-[a-zA-Z0-9]' # "-long", "-long-option" or "-Wunsued" LONG_OPT_REGEX = r'-[a-zA-Z][a-z-]+' # "--print" or "--very-long-option LONGLONG_OPT_REGEX = r'--[a-z][a-z-]+' # "value", "VALUE", "define:option" or "LONG_VALUE" VALUE_REGEX = '[a-zA-Z_:]+' # @ value@, @=value@, @,@, @ "value"@, @[=value]@, ... VALUE_REGEX = r'[ =]%s|[ ,=]<%s>|[ =]"%s"|\[=%s\]' % ( VALUE_REGEX, VALUE_REGEX, VALUE_REGEX, VALUE_REGEX) # -o # -o, --option # -o, --long-option=VALUE self.gnu_regex = re.compile(r'^\s+(%s), (%s)?(%s)?' % (SHORT_OPT_REGEX, LONGLONG_OPT_REGEX, VALUE_REGEX)) # -option # -Long-option VALUE self.long_opt_regex = re.compile(r'^\s+(%s)(%s)?(%s)?' % (LONG_OPT_REGEX, VALUE_REGEX, VALUE_REGEX)) # -C # -o VALUE # --option # --long-option=VALUE self.opt_regex = re.compile(r'^\s+(%s|%s)(%s)?' % (SHORT_OPT_REGEX, LONGLONG_OPT_REGEX, VALUE_REGEX)) self.opt2_regex = re.compile(r'^(%s|%s)(%s)?\s+: ' % (SHORT_OPT_REGEX, LONGLONG_OPT_REGEX, VALUE_REGEX)) # Match "Usage: ping [-LRUbdfnqrvVaA]" self.usage_prefix_regex = re.compile(r'^[Uu]sage: %s (.*)$' % self.program) # Match "[..]" self.usage_group_regex = re.compile(r'\[([^]]+)\]') def addOption(self, name, values, default_separator=None): nb_arg = 0 format = [] if not values: value = tuple() for value in values: if not value: continue if default_separator: separator = default_separator else: separator = value[0] if '"' in value: format.append( separator + '"%s"' ) else: if separator == '[': separator = value[1] format.append( separator + "%s" ) nb_arg = len(format) format = name + ''.join(format) option = Option(format, nb_arg) self._addOption(option) def _addOption(self, option): key = str(option) if key in self.options_set: return self.options.append(option) self.options_set.add(key) def parseFile(self, stdout): for line in stdout: line = line.rstrip() self.parse_line(line) def parseLine(self, line): match = self.gnu_regex.match(line) if match: name = match.group(1) value = match.group(3) # Short option: -f FILE self.addOption(name, [value], ' ') name = match.group(2) if name: # Long option: --file=FILE self.addOption(name, [value]) return match = self.long_opt_regex.match(line) if match: name = match.group(1) value = match.group(2) value2 = match.group(3) if value2: self.addOption(name, (value, value2)) else: self.addOption(name, [value]) return match = self.opt_regex.match(line) if not match: match = self.opt2_regex.match(line) if match: name = match.group(1) value = match.group(2) self.addOption(name, [value]) return match = self.usage_prefix_regex.match(line) if match: self.parseUsage(match.group(1)) return def parseUsageGroup(self, line): """ Parse "group" like [-cdaf] (line="-cdaf") """ line = line.strip() if not line.startswith('-'): return False if ' ' in line: # '-c count' parts = line.split() values = parts[1:] self.addOption(parts[0], values, ' ') else: # '-ntpu' for opt in line[1:]: self.addOption('-' + opt, tuple()) return True def parseUsage(self, line): line = line.strip() match_line = False for match in self.usage_group_regex.finditer(line): line = match.group(1) for group in line.split("|"): match_line |= self.parseUsageGroup(group) if match_line: self.parse_line = self.parseUsage else: self.parse_line = self.parseLine self.parse_line(line) fusil-1.5/fusil/unsafe.py0000664000175000017500000000102112110032732015723 0ustar haypohaypo00000000000000from ptrace.os_tools import RUNNING_WINDOWS SUPPORT_UID = not RUNNING_WINDOWS if SUPPORT_UID: from os import getuid def permissionHelp(options): """ On "Operation not permitted error", propose some help to fix this problem. Example: "retry as root". """ if not SUPPORT_UID: return None help = [] if getuid() != 0: help.append('retry as root') if not options.unsafe: help.append('use --unsafe option') if not help: return None return ' or '.join(help) fusil-1.5/fusil/session_directory.py0000664000175000017500000001056512253715322020241 0ustar haypohaypo00000000000000from fusil.session_agent import SessionAgent from fusil.directory import Directory from fusil.tools import makeUnicode from fusil.error import FusilError from os.path import basename from fusil.unsafe import SUPPORT_UID from os import rename if SUPPORT_UID: from os import getgid, chown from fusil.unsafe import permissionHelp import re from errno import EPERM # allow letters, digits, understand and dash NORMALIZE_REGEX = re.compile(u'[^a-zA-Z0-9_-]') # len('invalid_mem-access-0x8000000000000000') = 37 PART_MAXLEN = 40 class SessionDirectory(SessionAgent, Directory): def __init__(self, session): project = session.project() directory = project.createFilename(u'session', count=project.session_index) Directory.__init__(self, directory) SessionAgent.__init__(self, session, "directory:%s" % basename(self.directory)) self.rename_parts = [] self.rename_set = set() def init(self): self.info("Create the directory: %s" % self.directory) self.mkdir() # Allow fuzzer to write in the session directory uid = self.application().config.process_uid if uid is not None: self.changeOwner(uid) def changeOwner(self, uid): if not SUPPORT_UID: return gid = getgid() try: chown(self.directory, uid, gid) except OSError as err: if err.errno != EPERM: raise help = permissionHelp(self.application().options) message = "You are not allowed to change the owner of the directory %s to %s:%s" \ % (self.directory, uid, gid) if help: message += " (%s)" % help raise FusilError(message) def checkKeepDirectory(self): session = self.session() application = self.application() if not self.isEmpty(False): if session.isSuccess(): # Session sucess and non-empty directory: keep directory self.warning("Success: keep the directory %s" % self.directory) return True if application.exitcode: # Session sucess and non-empty directory: keep directory self.warning("Fusil error: keep the directory %s" % self.directory) return True if application.options.keep_sessions: # User asked to keep all datas self.warning("Keep the directory %s" % self.directory) return True if application.options.keep_generated_files \ and not self.isEmpty(True): # Project generated some extra files: keep the directory self.warning("Keep the non-empty directory %s" % self.directory) return True # Remove empty directory return False def keepDirectory(self): # Ask project directory to keep the session directory at exit filename = basename(self.directory) project_dir = self.project().directory project_dir.ignore(filename) # Rename the session directory? if not self.rename_parts: return # Create the new filenme old_directory = self.directory filename = '-'.join(self.rename_parts) self.directory = project_dir.uniqueFilename(filename, save=False) self.error("Rename the session directory: %s" % basename(self.directory)) rename(old_directory, self.directory) def deinit(self): if self.checkKeepDirectory(): self.keepDirectory() return self.info("Remove the directory %s" % self.directory) self.rmtree() def on_session_rename(self, part): # Convert to unicode if needed part = makeUnicode(part) if not part: return # Truncate length if PART_MAXLEN < len(part): orig_part = part part = part[:PART_MAXLEN] self.info("Truncate the session name part %r to %r" % (orig_part, part)) # Normalize orig_part = part part = NORMALIZE_REGEX.sub('_', part) if part != orig_part: self.info("Normalize the session name part %r: %r" % (orig_part, part)) # Store the name part if part in self.rename_set: # skip duplicates return self.rename_set.add(part) self.rename_parts.append(part) fusil-1.5/fusil/auto_mangle.py0000664000175000017500000000300512110032732016741 0ustar haypohaypo00000000000000from fusil.mangle import MangleFile from fusil.tools import minmax class AutoMangle(MangleFile): def __init__(self, project, *args, **kw): MangleFile.__init__(self, project, *args, **kw) self.hard_max_op = 10000 self.hard_min_op = 0 self.aggressivity = None self.fixed_size_factor = 1.0 def on_session_start(self): pass def on_aggressivity_value(self, value): self.aggressivity = value self.mangle() def setupConf(self, data): operations = ["bit"] size_factor = 0.30 if 0.25 <= self.aggressivity: operations.append("increment") if 0.30 <= self.aggressivity: operations.extend(("replace", "special_value")) if 0.50 <= self.aggressivity: operations.extend(("insert_bytes", "delete_bytes")) size_factor = 0.20 self.config.operations = operations # Display config count = len(data) * size_factor * self.fixed_size_factor count = minmax(self.hard_min_op, count, self.hard_max_op) count = int(count * self.aggressivity) self.config.max_op = max(count, self.hard_min_op) self.config.min_op = max(int(self.config.max_op * 0.80), self.hard_min_op) self.warning("operation#:%s..%s operations=%s" % (self.config.min_op, self.config.max_op, self.config.operations)) def mangleData(self, data, file_index): self.setupConf(data) return MangleFile.mangleData(self, data, file_index) fusil-1.5/fusil/time_watch.py0000664000175000017500000000255112110032732016577 0ustar haypohaypo00000000000000from fusil.project_agent import ProjectAgent from time import time class TimeWatch(ProjectAgent): def __init__(self, project, too_fast=None, too_slow=None, too_fast_score=-1.0, too_slow_score=1.0): if too_fast is not None and too_slow is not None: if too_fast > too_slow: raise ValueError("too_fast > too_slow") else: if too_fast is None and too_slow is None: raise ValueError("TimeWatch requires too_fast or too_slow parameters") ProjectAgent.__init__(self, project, "time watch") self.too_fast = too_fast self.too_slow = too_slow self.too_fast_score = too_fast_score self.too_slow_score = too_slow_score self.time0 = None self.duration = None def init(self): self.score = None self.duration = None self.time0 = time() def getScore(self): return self.score def setScore(self, duration): if self.too_fast is not None and duration < self.too_fast: self.score = self.too_fast_score if self.too_slow is not None and duration > self.too_slow: self.score = self.too_slow_score def on_session_done(self, score): duration = time() - self.time0 self.warning("Session done: duration=%.1f ms" % (duration*1000)) self.setScore(duration) fusil-1.5/fusil/xhost.py0000664000175000017500000000063312110032732015617 0ustar haypohaypo00000000000000from fusil.process.tools import locateProgram def xhostCommand(xhost_program, user, allow=True): if not xhostCommand.program: xhostCommand.program = locateProgram( xhost_program, raise_error=True) if allow: prefix = '+' else: prefix = '-' return [ xhostCommand.program, "%slocal:%s" % (prefix, user)] xhostCommand.program = None fusil-1.5/fusil/__init__.py0000664000175000017500000000000012110032732016175 0ustar haypohaypo00000000000000fusil-1.5/fusil/mangle_agent.py0000664000175000017500000000517512254530457017121 0ustar haypohaypo00000000000000from __future__ import with_statement from array import array from fusil.project_agent import ProjectAgent from os import fstat from ptrace.six import string_types from ptrace.six.moves import range as xrange from random import choice from stat import ST_SIZE class MangleAgent(ProjectAgent): def __init__(self, project, sources, nb_file=1): ProjectAgent.__init__(self, project, "mangle") if isinstance(sources, string_types): self.source_filenames = (sources,) else: # Remove duplicates self.source_filenames = tuple(set(sources)) if 1 < len(self.source_filenames): self.error("Sources filenames: %s" % len(self.source_filenames)) self.max_size = None # 10*1024*1024 self.nb_file = nb_file def readData(self, filename, file_index): # Open file and read file size self.info("Load input file: %s" % filename) data = open(filename, 'rb') orig_filesize = fstat(data.fileno())[ST_SIZE] if not orig_filesize: raise ValueError("Input file (%s) is empty!" % filename) # Read bytes if self.max_size: data = data.read(self.max_size) else: data = data.read() # Display message if input is truncated if len(data) < orig_filesize: percent = len(data) * 100.0 / orig_filesize self.warning("Truncate file to %s bytes (%.2f%% of %s bytes)" \ % (len(data), percent, orig_filesize)) # Convert to Python array object return array('B', data) def writeData(self, filename, data): self.info("Generate file: %s" % filename) with open(filename, 'wb') as output: data.tofile(output) return filename def createFilename(self, filename, file_index): if 1 < self.nb_file: count = 1 else: count = None return self.session().createFilename(filename, count=count) def mangle(self): filenames = [] for file_index in xrange(self.nb_file): filename = choice(self.source_filenames) data = self.readData(filename, file_index) data = self.mangleData(data, file_index) filename = self.createFilename(filename, file_index) self.writeData(filename, data) filenames.append(filename) self.send('mangle_filenames', filenames) # --- Abstract methods --- def mangleData(self, data, file_index): # data: array of unsigned bytes, array('B', ...) raise NotImplementedError() def on_session_start(self): self.mangle() fusil-1.5/fusil/project.py0000664000175000017500000002337612110032732016131 0ustar haypohaypo00000000000000from fusil.project_agent import ProjectAgent from fusil.session import Session from fusil.mas.agent_list import AgentList from fusil.project_directory import ProjectDirectory from time import time from fusil.aggressivity import AggressivityAgent from ptrace.os_tools import RUNNING_LINUX, RUNNING_PYPY from fusil.process.debugger import Debugger from shutil import copyfile if RUNNING_PYPY: from gc import collect as gc_collect if RUNNING_LINUX: from fusil.system_calm import SystemCalm class Project(ProjectAgent): """ A fuzzer project runs fuzzing sessions until we get enough successes or the user interrupts the project. Initialize all agents before a session starts, and cleanup agents at the session end. Before a session start, the project sleeps until the system load is under 50% (may change with command line options). """ def __init__(self, application): ProjectAgent.__init__(self, self, "project", mta=application.mta(), application=application) self.config = application.config options = application.options self.agents = AgentList() if RUNNING_LINUX: if options.fast: self.system_calm = None elif not options.slow: self.system_calm = SystemCalm( self.config.fusil_normal_calm_load, self.config.fusil_normal_calm_sleep) else: self.system_calm = SystemCalm( self.config.fusil_slow_calm_load, self.config.fusil_slow_calm_sleep) else: self.warning("SystemCalm class is not available") self.system_calm = None # Configuration self.max_session = options.sessions self.success_score = self.config.fusil_success_score self.error_score = self.config.fusil_error_score self.max_success = options.success # Session self.step = None self.nb_success = 0 self.session = None self.session_index = 0 self.session_timeout = None # in second # Statistics self.session_executed = 0 self.session_total_duration = 0 self.total_duration = None self._destroyed = False # Add Application agents, order is important: # MTA have to be the first agent for agent in application.agents: self.registerAgent(agent) self.registerAgent(self) # Create aggressivity agent self.aggressivity = AggressivityAgent(self) # Initial aggresssivity value if options.aggressivity is not None: self.aggressivity.setValue(options.aggressivity / 100) self.error("Initial aggressivity: %s" % self.aggressivity) # Create the debugger self.debugger = Debugger(self) # Create the working directory self.directory = ProjectDirectory(self) self.directory.activate() self.error("Use directory: %s" % self.directory.directory) # Initilize project logging self.initLog() def registerAgent(self, agent): self.agents.append(agent) def unregisterAgent(self, agent, destroy=True): if agent not in self.agents: return self.agents.remove(agent, destroy) def init(self): """ Function called once on project creation: create the project working directory, prepare the logging and create the first session. """ self.project_start = time() self.createSession() def initLog(self): # Move fusil.log into run-xxx/project.log: copy fusil.log content # and then remove fusil.log file and log handler) logger = self.application().logger filename = self.createFilename("project.log") if logger.filename: copyfile(logger.filename, filename) logger.unlinkFile() mode = 'a' else: mode = 'w' logger.file_handler = logger.addFileHandler(filename, mode=mode) logger.filename = filename def deinit(self): if self.session_executed: self.summarize() def destroy(self): if self._destroyed: return self._destroyed = True # Destroy all project agents self.aggressivity = None self.debugger = None for agent in self.application().agents: self.agents.remove(agent, False) self.agents.clear() # Keep project directory? keep = self.directory.keepDirectory() if not keep: # Don't keep the directory: destroy log file logger = self.application().logger logger.unlinkFile() # And then remove the whole directory self.directory.rmtree() self.directory = None if RUNNING_PYPY: gc_collect() def createSession(self): """ Create a new session: - make sure that system load is under 50% - activate all project agents - send project_start (only for the first session) and session_start messages """ # Wait until system is calm if self.system_calm: self.system_calm.wait(self) self.info("Create session") self.step = 0 self.session_index += 1 self.use_timeout = bool(self.session_timeout) self.session_start = time() # Enable project agents for agent in self.agents: if not agent.is_active: agent.activate() # Create session self.session = Session(self) # Send 'project_start' and 'session_start' message if self.session_index == 1: self.send('project_start') self.send('session_start') text = "Start session" if self.max_session: percent = self.session_index * 100.0 / self.max_session text += " (%.1f%%)" % percent self.error(text) def destroySession(self): """ Destroy the current session: - deactive all project agents - clear agents mailbox """ # Update statistics if not self.application().exitcode: self.session_executed += 1 self.session_total_duration += (time() - self.session_start) # First deactivate session agents self.session.deactivate() # Deactivate project agents application_agents = self.application().agents for agent in self.agents: if agent not in application_agents: agent.deactivate() # Clear session variables self.step = None self.session = None # Remove waiting messages for agent in application_agents: agent.mailbox.clear() self.mta().clear() def on_session_done(self, session_score): self.send('project_session_destroy', session_score) def on_project_stop(self): self.send('univers_stop') def on_univers_stop(self): if self.session: self.destroySession() def on_project_session_destroy(self, session_score): # Use session score self.session.score = session_score duration = time() - self.session_start if self.success_score <= session_score: log = self.error else: log = self.warning log("End of session: score=%.1f%%, duration=%.3f second" % ( session_score*100, duration)) # Destroy session self.destroySession() # Session success? project is done if self.success_score <= session_score: self.nb_success += 1 text = "#%s" % self.nb_success if 0 < self.max_success: percent = self.nb_success * 100.0 / self.max_success text += "/%s (%.1f%%)" % (self.max_success, percent) self.error("Success %s!" % text) if 0 < self.max_success \ and self.max_success <= self.nb_success: self.error("Stop! Limited to %s successes, use --success option for more" % self.max_success) self.send('univers_stop') return # Hit maximum number of session? if 0 < self.max_session \ and self.max_session <= self.session_index: self.error("Stop! Limited to %s sessions, use --sessions option for more" % self.max_session) self.send('univers_stop') return # Otherwise: start new session self.createSession() def live(self): if self.step is not None: self.step += 1 if not self.session: return if not self.use_timeout: return duration = time() - self.session_start if self.session_timeout <= duration: self.error("Project session timeout!") self.send('session_stop') self.use_timeout = False def summarize(self): """ Display a summary of all executed sessions """ count = self.session_executed info = [] if count: duration = self.session_total_duration info.append("%s sessions in %.1f seconds (%.1f ms per session)" % (count, duration, duration * 1000 / count)) duration = time() - self.project_start info.append("total %.1f seconds" % duration) info.append("aggresssivity: %s" % self.aggressivity) self.error("Project done: %s" % ", ".join(info)) self.error("Total: %s success" % self.nb_success) def createFilename(self, filename, count=None): """ Create a filename in the project working directory: add directory prefix and make sure that the generated filename is unique. """ return self.directory.uniqueFilename(filename, count=count) fusil-1.5/fusil/terminal_echo.py0000664000175000017500000000051012110032732017255 0ustar haypohaypo00000000000000from fusil.project_agent import ProjectAgent from ptrace.terminal import enableEchoMode class TerminalEcho(ProjectAgent): def __init__(self, project): ProjectAgent.__init__(self, project, "terminal") def deinit(self): if enableEchoMode(): self.info("Terminal: restore echo mode to stdin") fusil-1.5/fusil/score.py0000664000175000017500000000046512110032732015570 0ustar haypohaypo00000000000000from fusil.tools import minmax def scoreLogFunc(object, score): if score in (None, 0): return object.info elif 0.50 <= abs(score): return object.error else: return object.warning def normalizeScore(score): score = minmax(-1.0, score, 1.0) return round(score, 2) fusil-1.5/fusil/aggressivity.py0000664000175000017500000001166012253715322017207 0ustar haypohaypo00000000000000from __future__ import print_function from fusil.project_agent import ProjectAgent from fusil.tools import minmax from datetime import datetime CREATE_GRAPH_DAT = True class AggressivityAgent(ProjectAgent): def __init__(self, project): ProjectAgent.__init__(self, project, "aggressivity") # Options for state machine self.aggressivity_min = 0.01 self.aggressivity_max = 1.00 self.add_after = 5 # steps self.faster_after = 10 # steps self.slower_after = 5 # steps self.state_increment = { "+": 0.01, "++": 0.04, "-": -0.01, "--": -0.04, } # Variables of state machine self.state = "init" self.state_age = 1 self.min_score = None self.max_score = None # Attributes for graph.dat file self.graph_data = None self.last_session_index = None # Set default value of aggressivity self.setValue(self.aggressivity_min) def setValue(self, value): value = round(value, 2) value = minmax(self.aggressivity_min, value, self.aggressivity_max) self.aggressivity = value def on_session_start(self): self.send('aggressivity_value', self.aggressivity) def destroy(self): if not self.graph_data: return self.writeGraphData(self.last_session_index+1, 0.0) self.writeGraphData(self.last_session_index+1, 0.0) def writeGraphData(self, session_index, score): self.last_session_index = session_index if CREATE_GRAPH_DAT and not self.graph_data: filename = self.project().createFilename('aggressivity.dat') self.graph_data = open(filename, 'w') print("# Aggressivity data", file=self.graph_data) print("# Started at %s" % datetime.now(), file=self.graph_data) print("#", file=self.graph_data) print("# session_index score aggressivity", file=self.graph_data) if self.project().success_score <= score: score = 1.0 else: score = minmax(-1.0, score, 1.0) if self.graph_data: print("%u\t%.3f\t%.3f" % ( self.last_session_index, score, self.aggressivity), file=self.graph_data) self.graph_data.flush() def on_session_done(self, score): self.writeGraphData(self.project().session_index, score) self.update(score) def update(self, score): # Update previous/min/max score variables if self.min_score is None: self.min_score = score if self.max_score is None: self.max_score = score # Choose state using score self.state_age += 1 state = self.updateState(score) if state: self.state = state self.state_age = 1 # Update aggressivity depending on the state try: self.setValue(self.aggressivity + self.state_increment[self.state]) except KeyError: # State has no increment pass def updateState(self, score): if self.project().success_score <= score: return "success" if self.max_score < score: # Best new score: continue to augment aggressivity to reach success self.max_score = score return "0" if score < self.min_score: self.min_score = score return "-" if self.state == "init": # First aggressivity evolution: start to grow return "+" if self.state == "success": # If we reach a success, try to keep aggressivity constant # to get more success return "0" if self.aggressivity < self.aggressivity_max: if self.state == "0" and self.add_after < self.state_age: # Nothing interesting since many cycles, # augment to make the system react return "+" if self.state == "+" and self.faster_after < self.state_age: # Grow acceleration return "++" if self.aggressivity_min < self.aggressivity: if score < 0: # Target error: reduce aggressivity if self.state not in ("-", "--"): return "-" if self.state == "-" and self.slower_after < self.state_age: # Reduction acceleration return "--" if self.state in ("-", "--") and 0 <= score: # During a reduction stage, target is now calm: stop reduction return "0" # Hit min/max aggressivity limits if self.aggressivity == self.aggressivity_min and self.state in ("-", "--"): return "0" if self.aggressivity == self.aggressivity_max and self.state in ("+", "++"): return "0" return None def __str__(self): return "%.1f%%" % (self.aggressivity*100) fusil-1.5/fusil/config.py0000664000175000017500000000705112263525103015730 0ustar haypohaypo00000000000000try: from configparser import RawConfigParser, NoSectionError, NoOptionError except ImportError: # Python 2 from ConfigParser import RawConfigParser, NoSectionError, NoOptionError from os.path import exists as path_exists, join as path_join from os import getenv class ConfigError(Exception): pass class FusilConfig: def __init__(self): self._parser = RawConfigParser() self.filename = self.createFilename() if path_exists(self.filename): self._parser.read([self.filename]) # Fusil application options self.fusil_max_memory = self.getint('fusil', 'max_memory', 500 * 1024 * 1024) self.fusil_success_score = self.getfloat('fusil', 'success_score', 0.50) self.fusil_error_score = self.getfloat('fusil', 'error_score', -0.50) self.fusil_success = self.getint('fusil', 'success', 1) self.fusil_session = self.getint('fusil', 'session', 0) self.fusil_normal_calm_load = self.getfloat('fusil', 'normal_calm_load', 0.50) self.fusil_normal_calm_sleep = self.getfloat('fusil', 'normal_calm_sleep', 0.5) self.fusil_slow_calm_load = self.getfloat('fusil', 'slow_calm_load', 0.30) self.fusil_slow_calm_sleep = self.getfloat('fusil', 'slow_calm_sleep', 3.0) self.fusil_xhost_program = self.getstr('fusil', 'xhost_program', 'xhost') # Process options self.use_cpu_probe = self.getbool('process', 'use_cpu_probe', True) self.process_max_memory = self.getint('process', 'max_memory', 0) self.process_core_dump = self.getbool('process', 'core_dump', True) self.process_max_user_process = self.getint('process', 'max_user_process', 10) # User used for subprocess self.process_user = self.getstr('process', 'user', 'fusil') self.process_uid = None # Group used for subprocess self.process_group = self.getstr('process', 'group', 'fusil') self.process_gid = None # Debugger options self.use_debugger = self.getbool('debugger', 'use_debugger', True) self.debugger_trace_forks = self.getbool('debugger', 'trace_forks', False) self._parser = None def createFilename(self): configdir = getenv("XDG_CONFIG_HOME") if not configdir: homedir = getenv("HOME") if not homedir: raise ConfigError("Unable to retreive user home directory: empty HOME environment variable") configdir = path_join(homedir, ".config") return path_join(configdir, "fusil.conf") def _gettype(self, func, type_name, section, key, default_value): try: value = func(section, key) if func == self._parser.get: value = value.strip() return value except (NoSectionError, NoOptionError): return default_value except ValueError as err: raise ConfigError("Value %s of section %s is not %s! %s" % ( key, section, type_name, err)) def getstr(self, section, key, default_value=None): return self._gettype(self._parser.get, "a string", section, key, default_value) def getbool(self, section, key, default_value): return self._gettype(self._parser.getboolean, "a boolean", section, key, default_value) def getint(self, section, key, default_value): return self._gettype(self._parser.getint, "an integer", section, key, default_value) def getfloat(self, section, key, default_value): return self._gettype(self._parser.getfloat, "a float", section, key, default_value) fusil-1.5/fusil/fixpng.py0000664000175000017500000000265012253715322015761 0ustar haypohaypo00000000000000""" Functions to recompute (fix) CRC32 checksums of an PNG picture. """ from array import array try: from io import StringIO except ImportError: # Python 2 from StringIO import StringIO from fusil.bits import bytes2uint, BIG_ENDIAN, uint2bytes from zlib import crc32 from logging import info def pngCRC32(data): """ Compute the CRC32 of specified data (str) as an unsigned integer. """ return crc32(data) & 0xFFFFFFFF def fixPNG(data): """ Fix a mangled PNG picture: - Rewrite PNG header (first 8 bytes) - Recompute CRC32 of each PNG chunk Stop if a chunk length is invalid. """ # array -> str data = data.tostring() origdata = data datalen = len(data) data = StringIO(data) data.seek(0) data.write("\x89PNG\r\n\x1a\n") index = 8 while index < (datalen-4): data.seek(index) size = bytes2uint(data.read(4), BIG_ENDIAN) chunksize = size+12 if datalen < (index+chunksize): info("fixPNG: Skip invalid chunk at %s" % index) break data.seek(index+4) crcdata = data.read(chunksize-8) newcrc = uint2bytes(pngCRC32(crcdata), BIG_ENDIAN, 4) data.seek(index+chunksize-4) data.write(newcrc) index += chunksize data.seek(0,0) data = data.read() assert len(data) == len(origdata) # str -> array data = array('B', data) return data fusil-1.5/fusil/incr_mangle.py0000664000175000017500000001654512253715322016754 0ustar haypohaypo00000000000000from fusil.mangle_agent import MangleAgent from fusil.incr_mangle_op import OPERATIONS from random import randint, choice from array import array MAX_TRY = 25000 class IncrMangleError(ValueError): pass class DataVersion: """ Immutable data version """ def __init__(self, versions, data, operations, dirty_bits): self.versions = versions self.data = data self.operations = operations self.version = versions.getVersionNumber() self.dirty_bits = frozenset(dirty_bits) def createVersion(self, agent, datalen): operations = list(self.operations) dirty_bits = set(self.dirty_bits) wanted_len = len(operations) + randint(1, agent.operation_per_version) nb_try = 0 while len(operations) < wanted_len: if MAX_TRY <= nb_try: raise IncrMangleError("Unable to create new operation (try: %s)" % nb_try) nb_try += 1 operation = self.createOperation(agent, datalen, dirty_bits) if not operation: continue for bit in xrange(operation.offset, operation.offset+operation.size): dirty_bits.add(bit) operations.append(operation) nb_try = 0 return ModifiedVersion(self.versions, tuple(operations), dirty_bits) def createOperation(self, agent, datalen, dirty_bits): operation_cls = choice(agent.operations) operation = operation_cls(agent, datalen) for bit in xrange(operation.offset, operation.offset+operation.size): if bit in dirty_bits: return None return operation def getPrevious(self): return self.versions.getPrevious(self) def createData(self): raise NotImplementedError() def revert(self): """ Revert this version and return new last version """ self.versions.removeVersion(self) return self.versions.getLast() def __str__(self): return "" % ( self.version, len(self.operations), len(self.dirty_bits)) def clearCache(self): pass class OriginalVersion(DataVersion): def __init__(self, versions, data): DataVersion.__init__(self, versions, data, tuple(), set()) def createData(self): return array('B', self.data) def revert(self): return self def __str__(self): return "" class ModifiedVersion(DataVersion): def __init__(self, versions, operations, dirty_bits): DataVersion.__init__(self, versions, None, operations, dirty_bits) def createData(self): # Cached result? if self.data: return array('B', self.data) # Get previous complete data previous = self while True: previous = previous.getPrevious() if previous.data: break # Apply new operations (since previous version) data = array('B', previous.data) for operation in self.operations[len(previous.operations):]: operation(data) # Cache result self.data = data.tostring() return data def clearCache(self): self.data = None class DataVersions: def __init__(self): self.versions = [] def getVersionNumber(self): return len(self.versions)+1 def addVersion(self, version): self.versions.append(version) # Clear cache of old versions for version in self.versions[:-2]: version.clearCache() def removeVersion(self, version): self.versions.remove(version) def getLast(self): try: return self.versions[-1] except IndexError: return None def getPrevious(self, version): index = self.versions.index(version) return self.versions[index-1] def rollback(self, version_number): del self.versions[version_number:] return self.versions[-1] class IncrMangle(MangleAgent): def __init__(self, project, source): MangleAgent.__init__(self, project, source, 1) self.versions = DataVersions() self.previous_score = None self.previous_session = "init" self.score_diff = None # User config self.operation_per_version = 1 self.max_version = 25 self.min_offset = None self.max_offset = None self.operations = OPERATIONS def on_session_done(self, score): self.score_diff = None if score < 0: self.previous_session = "error" elif (self.project().success_score <= score): self.previous_session = "success" else: self.previous_session = "normal" if self.previous_score is not None: self.score_diff = score - self.previous_score self.previous_score = score def stateText(self): text = self.previous_session if self.previous_score is not None: text += ", score:%.1f" % (self.previous_score*100) if self.score_diff is not None: text += ", score diff:%+.1f" % (self.score_diff*100) return text def checkPreviousVersion(self, version): # Success: Rollback to original version if self.previous_session == "success": original = self.versions.rollback(1) self.error("Rollback to %s" % original) return original # Failure: revert previous change if self.previous_session == "error": self.warning("Revert previous version (error): %s (score %s)" % (version, self.stateText())) return version.revert() # Smaller score if self.score_diff is not None and self.score_diff < 0: self.error("Revert previous version (smaller score): %s (score %s)" % (version, self.stateText())) return version.revert() # Too many version: rollback to random version if self.max_version < version.version: return self.rollbackLast() # Valid version: keep it previous = version.getPrevious() for operation in version.operations[len(previous.operations):]: self.warning("New operation: %s" % operation) self.error("Accepted version: %s (%s)" % ( version, self.stateText())) return version def rollbackLast(self): number = randint(1, self.versions.getLast().version) version = self.versions.rollback(number) self.error("Rollback to %s" % version) return version def mangleData(self, data, file_index): # Get last version version = self.versions.getLast() if version: version = self.checkPreviousVersion(version) else: version = OriginalVersion(self.versions, data.tostring()) self.warning("Create first version: %s" % version) self.versions.addVersion(version) # New version previous = version try: version = previous.createVersion(self, len(data)) self.warning("New version %s based on %s" % (version, previous)) self.versions.addVersion(version) except IncrMangleError as err: for index in range(10): self.error(str(err)) version = self.rollbackLast() # Create data return version.createData() fusil-1.5/fusil/project_agent.py0000664000175000017500000000230412110032732017273 0ustar haypohaypo00000000000000from fusil.mas.agent import Agent from weakref import ref as weakref_ref from fusil.score import scoreLogFunc class ProjectAgent(Agent): def __init__(self, project, name, mta=None, application=None): if not mta: mta = project.mta() Agent.__init__(self, name, mta) self.project = weakref_ref(project) if not application: application = project.application() self.application = weakref_ref(application) if project is not self: self.score_weight = 1.0 self.register() def session(self): project = self.project() if not project: return None return project.session def register(self): self.project().registerAgent(self) def unregister(self, destroy=True): project = self.project() if not project: return project.unregisterAgent(self, destroy) def scoreLogFunc(self): score = self.getScore() return scoreLogFunc(self, score) def getScore(self): # Score: floating number, -1.0 <= score <= 1.0 # 1: bug found # 0: nothing special # -1: inputs rejected return None fusil-1.5/IDEAS0000664000175000017500000000276012110032732013531 0ustar haypohaypo00000000000000Reuse existing libraries and projects ===================================== * http://www.nongnu.org/failmalloc/ Ideas to crash programs ======================= * Don't create stdin, stdout or stderr to check if first open file gets file descriptor #0 * Continue to analyze gettext :-) * write C library to inject errors in libc calls (eg. malloc() failure) * Generate [http://michael-prokop.at/blog/2007/06/12/error-handling-enospc/ ENOSPC] errors? * file: open(), close(), read(), write() * directory: opendir(), chdir() * memory: malloc(), realloc(), calloc() * network: socket(), setsockopt() * time: time(), gettimeofday() http://software.inl.fr/trac/trac.cgi/wiki/Macfly * network socket proxy fuzzer Signals ------- Send signals like SIGINT, SIGTERM, SIGSTOP, SIGUSR1, SIGUSR2. Old bugs: * broken pipe (SIGPIPE) https://bugzilla.mindrot.org/show_bug.cgi?id=85 * libc deadlock http://sourceware.org/bugzilla/show_bug.cgi?id=838 * openssh pre-authentification denial of service https://bugzilla.mindrot.org/show_bug.cgi?id=1129 Score ===== * Code coverage: * gcov: http://gcc.gnu.org/onlinedocs/gcc/Gcov.html * Valgrind: http://www.valgrind.org/ Valgrind * DynamoRio: http://www.cag.lcs.mit.edu/dynamorio/ * Check invalid use of memory using Valgrind (or any memory checker tool) * increment score if one of these function is called: - fgets(), memcpy(), strcpy() - input comes from user (?) - bytes read by the program - memory usage fusil-1.5/graph.sh0000775000175000017500000000135012110032732014413 0ustar haypohaypo00000000000000#!/bin/sh DATA=$1 OUTPUT=/tmp/graph.png if [ "x$DATA" = "x" ]; then echo "usage: $0 aggressivity.dat" exit 1 fi if [ ! -f "$DATA" ]; then echo "File $DATA doesn't exit" exit 1 fi # Exit on error set -e cat <', output] else: cmdline = ['identify', '-verbose', ''] if not self.options.use_stdout: options['stdout'] = 'null' process = MangleProcess(project, cmdline, '', **options) options = {'exitcode_score': -0.25} if orig_filename.endswith(".jpg"): # Don't care about libjpeg stdout flooding options['timeout_score'] = -0.25 WatchProcess(process, **options) if self.options.use_stdout: stdout = WatchStdout(process) stdout.max_nb_line = (3000, 0.20) stdout.addRegex('Memory allocation failed', 1.0) stdout.addRegex('no decode delegate for this image format', -1.0) stdout.addRegex('Corrupt', 0.05) stdout.addRegex('Unsupported', 0.05) stdout.addRegex('Not a JPEG file', -0.50) stdout.addRegex('JPEG datastream contains no image', -0.50) stdout.show_not_matching = False class ImageMangle(BaseMangle): def writeData(self, filename, data): if filename.endswith(".png"): self.info("Fix CRC32 of PNG chunks") data = fixPNG(data) BaseMangle.writeData(self, filename, data) if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/fusil-poppler0000775000175000017500000000577012254533702017240 0ustar haypohaypo00000000000000#!/usr/bin/env python """ libpoppler fuzzer using "pdftotext" command line program. """ MANGLE = "auto" USE_TIME = False USE_STDOUT = True from fusil.application import Application from fusil.process.time_watch import ProcessTimeWatch from fusil.process.mangle import MangleProcess from fusil.process.watch import WatchProcess from fusil.process.stdout import WatchStdout if MANGLE == "auto": from fusil.auto_mangle import AutoMangle as MangleFile elif MANGLE == "incr": from fusil.incr_mangle import IncrMangle as MangleFile else: from fusil.mangle import MangleFile import re class Fuzzer(Application): NAME = "poppler" USAGE = "%prog [options] document.pdf" NB_ARGUMENTS = 1 def setupProject(self): project = self.project if USE_TIME: ProcessTimeWatch(project, too_slow=3.0, too_slow_score=0.10, too_fast=0.100, too_fast_score=-0.80, ) orig_filename = self.arguments[0] mangle = MangleFile(project, orig_filename) if MANGLE == "auto": mangle.hard_max_op = 1000 elif MANGLE == "incr": mangle.operation_per_version = 100 mangle.max_version = 50 else: mangle.config.max_op = 1000 options = {'timeout': 5.0} if not USE_STDOUT: options['stdout'] = 'null' process = MangleProcess(project, ['pdftotext', '', 'output.txt'], '', **options) WatchProcess(process, exitcode_score=-0.10) if USE_STDOUT: stdout = WatchStdout(process) def cleanupLine(line): match = re.match(r"Error(?: \([0-9]+\))?: (.*)", line) if match: line = match.group(1) return line stdout.cleanup_func = cleanupLine del stdout.words['error'] del stdout.words['unknown'] # stdout.show_not_matching = True # stdout.ignoreRegex(r"Unknown operator 'allocate'$") # stdout.ignoreRegex(r" operator is wrong type \(error\)$") # stdout.ignoreRegex(r'^No current point in lineto$') # stdout.ignoreRegex(r'^No current point in lineto') # stdout.ignoreRegex(r'^Unknown operator ') # stdout.ignoreRegex(r"^Couldn't open 'nameToUnicode' file ") # stdout.ignoreRegex(r"^Illegal character ") # stdout.ignoreRegex(r"^No font in show$") # stdout.ignoreRegex(r"^Element of show/space array must be number or string$") # stdout.ignoreRegex(r"^No current point in curveto$") # stdout.ignoreRegex(r"^Badly formatted number$") # stdout.ignoreRegex(r"^Dictionary key must be a name object$") # stdout.ignoreRegex(r"^End of file inside array$") # stdout.ignoreRegex(r"^Too few \([0-9]+\) args to .* operator$") # stdout.ignoreRegex(r"Too many args in content stream") stdout.max_nb_line = (100, 0.20) if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/fusil-firefox0000775000175000017500000002431312254417116017213 0ustar haypohaypo00000000000000#!/usr/bin/env python """ HTTP server serving fuzzy JPEG image or Flash animation. Project is currently specific to Firefox on Linux. """ from __future__ import with_statement HOST = '127.0.0.1' PORT = 8080 NB_FILES = 9 ROWS = 3 MAX_MEMORY = 300*1024*1024 HTML_REFRESH_TIMEOUT = 2.0 from fusil.application import Application from optparse import OptionGroup from fusil.file_tools import filenameExtension from fusil.network.http_server import HttpServer from fusil.process.attach import AttachProcess from fusil.process.create import CreateProcess from fusil.process.watch import WatchProcess from fusil.process.tools import runCommand from fusil.project_agent import ProjectAgent from fusil.auto_mangle import AutoMangle from fusil.dummy_mangle import DummyMangle from datetime import datetime from time import time from fusil.x11 import sendKey, getDisplay, findWindowByNameRegex from Xlib.keysymdef.miscellany import XK_F5 import re from os import getenv IMAGE_TEMPLATE = '%(text)s' EMBEDED_TEMPLATE = '' FILE_EXTENSIONS = { # Imaged '.bmp': ('image/x-ms-bmp', IMAGE_TEMPLATE), '.gif': ('image/gif', IMAGE_TEMPLATE), '.ico': ('image/x-ico', IMAGE_TEMPLATE), '.jpg': ('image/jpeg', IMAGE_TEMPLATE), '.jpeg': ('image/jpeg', IMAGE_TEMPLATE), '.png': ('image/png', IMAGE_TEMPLATE), # Embeded '.svg': ('image/svg+xml', EMBEDED_TEMPLATE), '.swf': ('application/x-shockwave-flash', EMBEDED_TEMPLATE), } class Fuzzer(Application): NAME = "firefox" USAGE = "%prog [options] filename" NB_ARGUMENTS = 1 def createFuzzerOptions(self, parser): options = OptionGroup(parser, "Firefox") options.add_option("--iceweasel", help="Firefox name is Iceweasel", action="store_true") options.add_option("--test", help="Test mode (no fuzzing, just make sure that the fuzzer works)", action="store_true") options.add_option("--port", help="TCP port using to listen on (default: %s)" % PORT, type="int", default=PORT) options.add_option("--nb-files", help="Number of generated files (default: %s)" % NB_FILES, type="int", default=NB_FILES) options.add_option("--rows", help="Number of HTML table rows (default: %s)" % ROWS, type="int", default=ROWS) options.add_option("--max-memory", help="Maximum memory in bytes (default: %s)" % MAX_MEMORY, type="int", default=MAX_MEMORY) options.add_option("--attach", help='Attach to an existing process using its name (eg. "firefox-bin")', type="str") return options def setupProject(self): project = self.project homepage_url = "http://%s:%s/" % (HOST, self.options.port) self.error("HTTP fuzzer homepage: %s" % homepage_url) if not self.options.attach: arguments = ["firefox", '-safe-mode', homepage_url] firefox = FirefoxProcess(project, arguments, timeout=None) # Firefox forks many times firefox.max_user_process = 50 # On core dump, Firefox proposes to start a debugger # whereas the user is unable to answer to this question... # Possible workaround: close stdin? firefox.core_dump = False firefox.setupX11() firefox.max_memory = self.options.max_memory WatchProcess(firefox, default_score=1.0) else: fireboxbin = AttachProcess(project, self.options.attach) fireboxbin.max_memory = self.options.max_memory FirefoxOpenURL(project, homepage_url, self.options.iceweasel) orig_filename = self.arguments[0] filename_ext = filenameExtension(orig_filename) if self.options.test: DummyMangle(project, orig_filename, nb_file=self.options.nb_files) else: AutoMangle(project, orig_filename, nb_file=self.options.nb_files) FuzzyHttpServer(project, self.options, filename_ext, rows=self.options.rows) if self.options.iceweasel: regex = r"Iceweasel$" else: regex = r"Mozilla Firefox$" FirefoxWindow(project, regex) class FirefoxProcess(CreateProcess): def on_project_start(self): CreateProcess.init(self) self.error("Start Firefox...") self.createProcess() self.error("Start Firefox... done") def on_project_done(self): CreateProcess.deinit(self) def init(self): if 1 < self.project().session_index: self.send('process_create', self) def deinit(self): pass class HtmlPage: def __init__(self): self.title = None self.headers = [] self.body = '' def __str__(self): html = ['\n'] if self.title or self.headers: html.append(' \n') if self.title: html.append(' %s\n' % self.title) html.append(' \n') html.append('\n') html.append(self.body+"\n") html.append('\n') html.append('\n') return ''.join(html) class FuzzyHttpServer(HttpServer): def __init__(self, project, options, filename_ext, rows=5): HttpServer.__init__(self, project, options.port) self.options = options if filename_ext: ext = filename_ext.lower() else: ext = None try: self.content_type, self.file_html_format = FILE_EXTENSIONS[ext] except KeyError: raise ValueError("Unknown file extension: %r" % filename_ext) self.file_url_format = "file-%u-%u" + ext self.file_url_match = re.compile("file-[0-9]+-([0-9]+)" + re.escape(ext)).match self.timeout = HTML_REFRESH_TIMEOUT self.rows = rows def init(self): HttpServer.init(self) self.pages = set() # self.filenames = tuple() self.filenames = None self.is_done = False self.done_at = None def on_mangle_filenames(self, filenames): self.filenames = filenames self.send('http_server_ready') def serveRequest(self, client, request): url = request.uri[1:] if not url: url = "index.html" page = url self.error("Serve URL=%r" % url) match = self.file_url_match(url) if match: file_index = int(match.group(1)) filename = self.filenames[file_index] data = open(filename, 'rb').read() self.serveData(client, 200, "OK", data=data, content_type=self.content_type) elif url == "index.html": self.createHomepage(client) else: page = None self.error("Error 404! %r" % url) self.error404(client, url) if page: self.pages.add(page) if (1 + self.options.nb_files) <= len(self.pages) and not self.is_done: self.is_done = True self.done_at = time() + self.timeout def createHomepage(self, client): self.error("Serve homepage") page = HtmlPage() page.title = 'Fusil HTTP server' page.body = '

Fuzzing

\n' page.body += '\n' tr_open = False count = len(self.filenames) session_index = self.project().session_index for index in xrange(count): url = self.file_url_format % (session_index, index) if (index % self.rows) == 0: if tr_open: page.body += ' \n' page.body += ' \n' tr_open = True span = '' if index == (count-1): colspan = (index+1) % self.rows if colspan: span += ' colspan="%s"' % (self.rows - colspan + 1) content = self.file_html_format % { 'url': url, 'text': "[%s]" % url, 'mime': self.content_type} page.body += ' %s\n' % (span, content) page.body += ' \n' page.body += '
\n' page.body += '

Created: %s

\n' % datetime.now() page.body += '

Session: %s

\n' % self.project().session_index # Write the HTML in a file page = str(page) filename = self.session().createFilename("index.html") with open(filename, 'wb') as fp: fp.write(page) # Send bytes to the client self.serveData(client, 200, "OK", data=page, content_type="text/html") def live(self): HttpServer.live(self) if not self.is_done: return if time() < self.done_at: return self.error("DONE") self.is_done = False self.send('session_stop') class FirefoxOpenURL(ProjectAgent): def __init__(self, project, homepage_url, iceweasel): ProjectAgent.__init__(self, project, "open_url") self.first = True self.homepage_url = homepage_url if iceweasel: self.program = "iceweasel" else: self.program = "firefox" def on_http_server_ready(self): if not self.first: return self.first = False env = { 'HOME': getenv('HOME'), 'DISPLAY': getenv('DISPLAY'), } runCommand(self, (self.program, self.homepage_url), options={'env': env}) class FirefoxWindow(ProjectAgent): def __init__(self, project, regex): ProjectAgent.__init__(self, project, "firefox_window") self.display = getDisplay() self.root_window = self.display.screen().root self.F5_keycode = self.display.keysym_to_keycode(XK_F5) self.window = None self.regex = regex def findWindow(self): if self.window: return self.window = findWindowByNameRegex(self.root_window, self.regex) def on_http_server_ready(self): if self.project().session_index == 1: return self.error("HTTP SERVER READY") self.findWindow() self.error("Send key F5 (%s) to Firefox window!" % self.F5_keycode) sendKey(self.window, self.F5_keycode, released=False) # 71=keycode of "F5" key (reload page) if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/fusil-gettext0000775000175000017500000001136012254417141017231 0ustar haypohaypo00000000000000#!/usr/bin/env python """ Demonstration of poor gettext parser quality: inject errors in valid .mo file and use it using dummy program (bash). """ from __future__ import print_function from fusil.application import Application from optparse import OptionGroup from fusil.process.create import CreateProcess from fusil.process.watch import WatchProcess from fusil.process.stdout import WatchStdout from fusil.auto_mangle import AutoMangle from os.path import basename, dirname, join as path_join from sys import stderr, exit from os import unlink, mkdir from os.path import isabs from fusil.process.tools import runCommand, locateProgram from fusil.project_agent import ProjectAgent import re # strace program STRACE = 'strace' # Any command using gettext and displaying a translated message COMMAND = '/bin/cat /nonexistantpath/nonexistantfile' # Default .mo filename MO_FILENAME = 'libc.mo' class Fuzzer(Application): NAME = "gettext" def createFuzzerOptions(self, parser): options = OptionGroup(parser, "Gettext fuzzer") options.add_option("--command", help="command using libc translation (default: %s)" % COMMAND, type="str", default=COMMAND) options.add_option("--mo-filename", help="MO file used by the command (default: %s)" % MO_FILENAME, type="str", default=MO_FILENAME) options.add_option("--strace", help="strace program path, used to locate full path of the mo file (default: %s)" % STRACE, type="str", default=STRACE) return options def setupProject(self): project = self.project command = self.options.command # Locate MO full path orig_filename = self.locateMO(project, self.options.mo_filename) # Create (...)/LC_MESSAGES/ directory LocaleDirectory(project, "locale_dir") # Create mangled MO file mangle = MangleGettext(project, orig_filename) mangle.max_size = None mangle.config.max_op = 2000 # Run program with fuzzy MO file and special LANGUAGE env var process = GettextProcess(project, command) process.timeout = 10.0 # value will be replaced later, on the mangle_filenames() event process.env.set('LANGUAGE', '') process.env.copy('LANG') # Watch process failure with its PID # Ignore bash exit code (127: command not found) WatchProcess(process, exitcode_score=0) # Watch process failure with its text output stdout = WatchStdout(process) stdout.words['failed'] = 0 def locateMO(self, project, mo_filename): """ Locate full path of a MO file used by a command using strace program. """ if isabs(mo_filename): return mo_filename command = self.options.command # Run strace program log = project.createFilename('strace') strace_program = locateProgram(self.options.strace, raise_error=True) arguments = [ strace_program, "-e", "open", "-o", log, "--"] arguments += command.split() runCommand(self, arguments, stdout=None, raise_error=False) # Find full mo filename in strace output regex = re.compile('open\("([^"]+%s)", [^)]+\) = [0-9]+' % mo_filename) mo_path = None for line in open(log): match = regex.match(line.rstrip()) if not match: continue mo_path = match.group(1) break unlink(log) if not mo_path: print("Unable to find the full path of the MO file (%s) used by command %r" \ % (mo_filename, command), file=stderr) exit(1) return mo_path class LocaleDirectory(ProjectAgent): def on_session_start(self): messages_dir = self.session().createFilename('LC_MESSAGES') mkdir(messages_dir) self.send('gettext_messages_dir', messages_dir) class MangleGettext(AutoMangle): def on_aggressivity_value(self, value): self.aggressivity = value self.checkMangle() def checkMangle(self): if self.messages_dir and self.aggressivity is not None: self.mangle() def createFilename(self, filename, index): return path_join(self.messages_dir, basename(filename)) def on_gettext_messages_dir(self, messages_dir): self.messages_dir = messages_dir self.checkMangle() def init(self): self.messages_dir = None self.aggressivity = None class GettextProcess(CreateProcess): def on_mangle_filenames(self, filenames): locale_dir = dirname(dirname(filenames[0])) self.env['LANGUAGE'].value = '../'*10 + locale_dir self.createProcess() if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/fusil-python0000775000175000017500000010323612263525240017072 0ustar haypohaypo00000000000000#!/usr/bin/env python """ Generate Python source code: random function calls with random arguments. Use "python" command line program. Interresting modules: all modules written in C or having some code written in C, see Modules/*.c in Python source code. """ # Fuzzer options IGNORE_TIMEOUT = True IGNORE_CPU = True SHOW_STDOUT = False DEBUG = False from fusil.application import Application from optparse import OptionGroup from os.path import exists as path_exists, isabs, sep as path_sep from fusil.process.watch import WatchProcess from types import FunctionType, BuiltinFunctionType from random import choice, randint from fusil.bytes_generator import BytesGenerator from fusil.unicode_generator import ( UnicodeGenerator, IntegerGenerator, IntegerRangeGenerator, UnsignedGenerator, UnixPathGenerator, LETTERS, DECIMAL_DIGITS, UNICODE_65535, PRINTABLE_ASCII) from fusil.process.stdout import WatchStdout from fusil.project_agent import ProjectAgent from fusil.process.create import CreateProcess from fusil.write_code import WriteCode from inspect import ismethoddescriptor, isclass from ptrace.os_tools import RUNNING_PYTHON3 from ptrace.six import text_type, string_types from ptrace.six.moves import range as xrange from sys import executable, version as PYTHON_VERSION import imp import pkgutil import re import sys import warnings if not RUNNING_PYTHON3: from sys import getfilesystemencoding # Constants TIMEOUT = 30.0 PYTHON = executable FILENAMES = "/etc/motd,/bin/sh" PARSE_PROTOTYPE = True PROTOTYPE_REGEX = re.compile(r"[A-Za-z]+[A-Za-z_0-9]*\(([^)]*)\)", re.MULTILINE) MODULE_BLACKLIST = set(( # wait keystroke from the keyboard "getpass", # execute child processes "commands", "subprocess", # run a webbrowser "antigravity", # too slow, search for .py in the whole hard drive "compileall", # nothing to fuzz "user", "this", # module dedicated to tests, not used in real world project, but it's build # by default "_testcapi", # ignore the whole CPython test suite "test", # read/write from/to arbitrary memory (eg. ctypes.string_at()) "ctypes", "_ctypes", # don't fuzz fusil nor python-ptrace "fusil", "ptrace", )) CTYPES = set(( # Read/write arbitrary memory 'PyObj_FromPtr', 'string_at', 'wstring_at', 'call_function', 'call_cdeclfunction', 'Py_INCREF', 'Py_DECREF', 'dlsym', 'dlclose', '_string_at_addr', '_wstring_at_addr', # _ctypes.dlopen("/bin/sh", -5): # Inconsistency detected by ld.so: dl-open.c: 652: # _dl_open: Assertion `_dl_debug_initialize (0, args.nsid)->r_state == RT_CONSISTENT' failed! # The bug is specific to Python (not reproductible outside Python) "dlopen", )) SOCKET = set(( # Avoid DNS request (timeout) "gethostbyname", "gethostbyname_ex", "gethostbyaddr", "getnameinfo", "getaddrinfo", # network objects having blocking operations like accept() or recv() "socket", "SocketType", )) POSIX = set(( # exit python "_exit", "abort", # read -> timeout "read", # truncate file, remove directory, remove file "ftruncate", "rmdir", "unlink", # send a signal to the current process or a process group "kill", "killpg", # create child process "fork", "forkpty", "system", "popen", "popen2", "popen3", "popen4", "spawnl", "spawnle", "spawnlp", "spawnlpe", "spawnv", "spawnve", "spawnvp", "spawnvpe", "execl", "execle", "execlp", "execlpe", "execv", "execve", "execvp", "execvpe", # wait process exit (-> timeout) "wait", "wait3", "waitpid", # break the terminal? "tcsetpgrp", # long loop "closerange", )) BUILTINS = set(( # Create huge integer, very long string or list "pow", "round", )) # Functions and methods blacklist. Format: # module name => function and class names # and # module name:class name => method names BLACKLIST = { # Dangerous module: ctypes 'ctypes': CTYPES, '_ctypes': CTYPES, # Eat lot of CPU with large arguments 'itertools': set(("tee",)), 'math': set(("factorial",)), 'operator': set(( # Create huge integer, very long string or list "pow", "__pow__", "ipow", "__ipow__", "mul", "__mul__", "repeat", "__repeat__", )), '__builtin__': BUILTINS, 'builtins': BUILTINS, # Don't raise SystemError '_builtin__:set': set(("test_c_api",)), 'builtins:set': set(("test_c_api",)), # Sleep 'time': set(("sleep",)), 'select': set(("epoll", "poll", "select")), 'signal': set(("pause", "alarm", "setitimer", "pthread_kill")), '_socket': SOCKET, 'socket': SOCKET, 'posix': POSIX, 'os': POSIX, '_fileio:_FileIO': set(( # timeout "read", "readall", )), # timeout 'multiprocessing': set(("Pool",)), '_multiprocessing:SemLock': set(( # deadlock "acquire", )), '_multiprocessing:Connection': set(( # timeout "recv", "recv_bytes", "poll", )), '_tkinter': set(( # timeout 'dooneevent', # no window -> no event -> loop on select() 'mainloop', )), "termios": set(( # tcflow(0, False) suspend output to stdout "tcflow", )), "dl": set(( # dl.open("/bin/sh", -5): # Inconsistency detected by ld.so: dl-open.c: 652: # _dl_open: Assertion `_dl_debug_initialize (0, args.nsid)->r_state == RT_CONSISTENT' failed! # The bug is specific to Python (not reproductible outside Python) "open", )), "pydoc": set(( # listen to a socket and wait for requests "serve", # avoid false positive with pattern (eg. "memory") on stdout "doc", "apropos", )), # listen to a socket and wait for requests "BaseHTTPServer": set(("test",)), "CGIHTTPServer": set(("test",)), "SimpleHTTPServer": set(("test",)), "pprint": set(("_perfcheck",)), # timeout (unlimited loop?) "tabnanny": set(("check",)), # python 2.5.2 implementation is just slow # create child process "popen2": set(("popen2", "popen3", "popen4", "Popen3", "Popen4")), "pty": set(("fork", "spawn")), "platform": set(("_syscmd_uname",)), # avoid false positive with pattern on stdout "logging": set(("warning", "error", "fatal", "critical")), "formatter": set(("test",)), # Create huge integer, very long string or list "fpformat": set(("fix",)), # remove directory "shutil": set(("copytree", "rmtree")), # open a network connection (timeout) # FIXME: only blacklist the blocking methods, not the whole class? "imaplib": set(("IMAP4", "IMAP4_stream",)), "telnetlib": set(("Telnet",)), "nntplib": set(("NNTP",)), "smtplib": set(("SMTP", "SMTP_SSL")), # open a network connection (timeout), # the constructor opens directly a connection "poplib": set(("POP3", "POP3_SSL",)), "ftplib": set(("FTP", "FTP_TLS")), # set resource limit, may stop the process: # setrlimit(RLIMIT_CPU, (0, 0)) kills the process with a SIGKILL signal "resource": set(("setrlimit",)), "xmllib": set(("test",)), # timeout "urllib2": set(("randombytes",)), # unlimited loop "py_compile": set(("compile",)), "runpy": set(("run_path",)), "faulthandler": set(( '_fatal_error', '_read_null', '_sigabrt', '_sigbus', '_sigfpe', '_sigill', '_sigsegv', '_stack_overflow', )), # TODO: blacklist distutils/spawn.py (35): spawn # TODO: blacklist distutils/spawn.py (121): _spawn_posix } if DEBUG: NB_CALL = 5 NB_METHOD = 1 NB_CLASS = 5 else: NB_CALL = 250 NB_METHOD = 15 NB_CLASS = 50 MAX_ARG = 6 MAX_VAR_ARG = 5 if RUNNING_PYTHON3: SURROGATES = ( u'"\\uDC80"', u'"\\uDC80"', u'"\\U0010FFFF"', u'"\\udbff\\udfff"', ) BUFFER_OBJECTS = ( u'bytearray(b"abc\\xe9\\xff")', u'memoryview(b"abc\\xe9\\xff")', u'memoryview(bytearray(b"abc\\xe9\\xff"))', ) else: SURROGATES = ( u'u"\\uDC80"', u'u"\\uDC80"', u'u"\\U0010FFFF"', u'u"\\udbff\\udfff"', ) BUFFER_OBJECTS = ( u'buffer("abc\\xe9\\xff")', ) class ListAllModules: def __init__(self, logger, only_c, site_package, blacklist): self.logger = logger self.only_c = only_c self.site_package = site_package self.blacklist = blacklist self.names = set(sys.builtin_module_names) - set(('__main__',)) def matchModule(self, is_package, name, filename): if filename is not None and not self.filter_filename(filename): return False if not self.only_c: return True if is_package: return False if any(filename.endswith(ext) for ext in ('.py', '.pyc', '.pyo')): return False return True def filter_filename(self, filename): if not self.site_package: if 'site-packages' in filename.split(path_sep): return False return True def search_modules(self, path, prefix): if path is not None and not self.site_package: path = [name for name in path if self.filter_filename(name)] if not path: return for loader, name, is_package in pkgutil.iter_modules(path, ''): if name.endswith('_d'): # Ignore Debian debug modules # (eg. ignore "_bisect_d", the real module is "_bisect) continue if name in self.blacklist: # Ignore the module and all of its submodules continue fullname = prefix + name # Get module filename / directory name try: module = imp.find_module(name, path) except ImportError: self.logger.warning("Failed to import module %s" % name) continue if module[0]: module[0].close() filename = module[1] if self.matchModule(is_package, name, filename): self.names.add(fullname) if is_package: new_paths = [filename] new_prefix = prefix + name + '.' self.search_modules(new_paths, new_prefix) def search(self): self.search_modules(None, '') return self.names class Fuzzer(Application): NAME = "python" def createFuzzerOptions(self, parser): options = OptionGroup(parser, "Python fuzzer") options.add_option("--modules", help="Tested Python module names separated by commas (default: test all modules)", type="str", default="*") options.add_option("--blacklist", help='Module blacklist separated by commas (eg. "_lsprof,_json")', type="str") options.add_option("--test-private", help="Test private methods (default: skip privates methods)", action="store_true") options.add_option("--timeout", help="Timeout in seconds (default: %.1f)" % TIMEOUT, type="float", default=TIMEOUT) options.add_option("--filenames", help="Names separated by commas of readable files (default: %s)" % FILENAMES, type="str", default=FILENAMES) options.add_option("--python", help="Python executable program path (default: %s)" % PYTHON, type="str", default=PYTHON) options.add_option("--only-c", help="Only search for modules written in C (default: search all module)", action="store_true") options.add_option("--no-site-packages", help="Don't search modules in site-packages directory", action="store_true") return options def setupProject(self): project = self.project project.error("Use python interpreter: %s" % self.options.python) version = ' -- '.join( line.strip() for line in PYTHON_VERSION.splitlines()) project.error("Python version: %s" % version) PythonSource(project, self.options) process = PythonProcess(project, [self.options.python, '-u', ''], timeout=self.options.timeout) options = {'exitcode_score' : 0} if IGNORE_TIMEOUT: options['timeout_score'] = 0 watch = WatchProcess(process, **options) if watch.cpu and IGNORE_CPU: watch.cpu.max_score = 0 stdout = WatchStdout(process) stdout.max_nb_line = None # Disable dummy error messages stdout.words = { 'oops': 0.30, 'bug': 0.30, 'fatal': 1.0, 'assert': 1.0, 'assertion': 1.0, 'critical': 1.0, 'panic': 1.0, 'glibc detected': 1.0, 'segfault': 1.0, 'segmentation fault': 1.0, } # CPython critical messages stdout.addRegex("^XXX undetected error", 1.0) stdout.addRegex("Fatal Python error", 1.0) # Match "Cannot allocate memory"? # PyPy messages stdout.addRegex("Fatal RPython error", 1.0) if SHOW_STDOUT or DEBUG: stdout.show_matching = True stdout.show_not_matching = True # avoid matching on "assert" keyword stdout.ignoreRegex(r"ast\.Assert()") # PyPy interact prompt # avoid false positive on "# assert did not crash" stdout.ignoreRegex(r"^And now for something completely different:") # Hide Python warnings on import warnings.simplefilter('ignore') ERRBACK_NAME = u'errback' METHODS_NB_ARG = { '__str__': 0, '__repr__': 0, '__hash__': 0, '__reduce__': 0, '__delattr__': 1, '__getattribute__': 1, '__getitem__': 1, '__getslice__': 2, '__reduce_ex__': (0, 1), '__getstate__': 0, '__setattr__': 2, '__setstate__': 1, } class PythonFuzzerError(Exception): pass class PythonSource(ProjectAgent): def __init__(self, project, options): ProjectAgent.__init__(self, project, "python_source") self.options = options if self.options.modules != "*": self.modules = set() for module in self.options.modules.split(","): module = module.strip() if not len(module): continue self.modules.add(module) else: self.error("Search all Python modules...") self.modules = ListAllModules(self, self.options.only_c, not self.options.no_site_packages, MODULE_BLACKLIST).search() blacklist = self.options.blacklist if blacklist: blacklist = set(blacklist.split(",")) removed = self.modules & blacklist self.error("Blacklist modules: %s" % removed) self.modules = list(self.modules - blacklist) self.modules = list(self.modules) self.modules.sort() self.error("Found %s Python modules" % len(self.modules)) for name in self.modules: self.info("Python module: %s" % name) self.filenames = self.options.filenames if not RUNNING_PYTHON3: encoding = getfilesystemencoding() self.filenames = text_type(self.filenames, encoding) self.filenames = self.filenames.split(u",") for filename in self.filenames: if not isabs(filename): raise ValueError("Filename %r is not an absolute path! Fix the --filenames option" % filename) if not path_exists(filename): raise ValueError("File doesn't exist: %s! Use different --filenames option" % filename) project.error(u"Use filenames: %s" % u', '.join(self.filenames)) def loadModule(self, module_name): self.module_name = module_name self.debug("Import %s" % self.module_name) self.module = __import__(self.module_name) for name in self.module_name.split(".")[1:]: self.module = getattr(self.module, name) try: self.warning("Module filename: %s" % self.module.__file__) except AttributeError: pass self.write = WritePythonCode(self, self.filename, self.module, self.module_name) def on_session_start(self): self.filename = self.session().createFilename(u'source.py') # copy sys.modules old_sys_modules = sys.modules.copy() while self.modules: name = choice(self.modules) try: self.loadModule(name) break except BaseException as err: self.error("Unable to load module %s: [%s] %s" % (name, err.__class__.__name__, err)) self.modules.remove(name) if not self.modules: self.error("There is no more modules!") self.send('project_stop') return self.error("Test module %s" % name) self.send('session_rename', name) self.write.writeSource() self.send('python_source', self.filename) # unload new modules sys.modules.clear() sys.modules.update(old_sys_modules) class PythonProcess(CreateProcess): def on_python_source(self, filename): self.cmdline.arguments[-1] = filename self.createProcess() # >'<, >"<, >\< ESCAPE_CHARARACTERS = u"'" + u'"' + u'\\' def formatCharacter(char): if char in ESCAPE_CHARARACTERS: # >\"< return u'\\' + char code = ord(char) if 32 <= code <= 126: # >a< return char elif code <= 255: # >\xEF< return u'\\x%02X' % code elif code <= 65535: # >\u0101< return u'\\u%04X' % code else: # >\U00010FA3< return u'\\U%08X' % code def escapeUnicode(text): return ''.join( formatCharacter(char) for char in text) class WritePythonCode(WriteCode): def __init__(self, parent, filename, module, module_name): WriteCode.__init__(self) self.filename = filename self.filenames = parent.filenames self.options = parent.options self.hashable_argument_generators = ( self.genNone, self.genBool, self.genSmallUint, self.genInt, self.genLetterDigit, self.genBytes, self.genString, self.genSurrogates, self.genAsciiString, self.genUnixPath, self.genFloat, self.genExistingFilename, self.genErrback, # self.genOpenFile, # self.genException, ) self.simple_argument_generators = self.hashable_argument_generators + ( self.genBufferObject, ) self.complex_argument_generators = ( self.genList, self.genTuple, self.genDict, ) self.smallint_generator = IntegerRangeGenerator(-19, 19) self.int_generator = IntegerGenerator(20) self.bytes_generator = BytesGenerator(0, 20) self.unicode_generator = UnicodeGenerator(1, 20, UNICODE_65535) self.ascii_generator = UnicodeGenerator(0, 20, PRINTABLE_ASCII) self.unix_path_generator = UnixPathGenerator(100) self.letters_generator = UnicodeGenerator(1, 8, LETTERS | DECIMAL_DIGITS) self.float_int_generator = IntegerGenerator(3) self.float_float_generator = UnsignedGenerator(3) self.module = module self.module_name = module_name self.functions, self.classes = self.getFunctions() if not self.functions and not self.classes: raise PythonFuzzerError("Module %s has no function and no class!" % self.module_name) def writePrint(self, level, arguments): if RUNNING_PYTHON3: code = u"print (%s, file=stderr)" % arguments else: code = u"print >>stderr, %s" % arguments self.write(level, code) def writeSource(self): self.createFile(self.filename) self.write(0, u"# -*- coding: ASCII -*-") self.write(0, u"from gc import collect") self.write(0, u"from sys import stderr") self.writePrint(0, u'"import %s"' % self.module_name) self.write(0, u"import %s" % self.module_name) self.emptyLine() self.write(0, u"def %s(*args, **kw):" % ERRBACK_NAME) self.write(1, u"raise ValueError('error')") self.emptyLine() self.write(0, "def callMethod(prefix, object, name, *arguments):") level = self.addLevel(1) self.writeCallMethod() self.restoreLevel(level) self.emptyLine() self.write(0, "def callFunc(prefix, name, *arguments):") self.write(1, "return callMethod(prefix, %s, name, *arguments)" % self.module_name) self.emptyLine() self.writeCode(u'', self.module, self.functions, self.classes, 1, NB_CALL) self.close() def writeCallMethod(self): self.write(0, u'funcname = "%s.%%s()" %% name' % self.module_name) self.write(0, u'message = "[%s] %s" % (prefix, funcname)') self.writePrint(0, u'message') self.write(0, u'func = getattr(object, name)') self.write(0, u'try:') self.write(1, u'result = func(*arguments)') exceptions = u'(Exception, SystemExit, KeyboardInterrupt)' if RUNNING_PYTHON3: self.write(0, u'except %s as err:' % exceptions) else: self.write(0, u'except %s, err:' % exceptions) self.write(1, u'errmsg = repr(err)') if RUNNING_PYTHON3: self.write(1, u"errmsg = errmsg.encode('ASCII', 'replace')") self.writePrint(1, u'"[%s] %s => %s: %s" % (prefix, funcname, err.__class__.__name__, errmsg)') self.write(1, u'result = None') self.writePrint(0, u'"[%s] -garbage collector-" % prefix') self.write(0, u'collect() # explicit call to the garbage collector') self.write(0, u'return result') def getFunctions(self): classes = [] functions = [] try: blacklist = BLACKLIST[self.module_name] except KeyError: blacklist = set() if not hasattr(self.module, "__all__"): names = set(dir(self.module)) else: names = set(self.module.__all__) names -= set(("__builtins__", "__doc__", "__file__", "__name__")) names -= blacklist for name in names: try: attr = getattr(self.module, name) except AttributeError: # attribute declared in __all__, but no declared? continue if isinstance(attr, (FunctionType, BuiltinFunctionType)): functions.append(name) elif isinstance(attr, type) or isclass(attr): classes.append(name) return functions, classes def getMethods(self, object, class_name): try: key = "%s:%s" % (self.module_name, class_name) blacklist = BLACKLIST[key] except KeyError: blacklist = set() methods = [] for name in dir(object): if name in blacklist: continue if (not self.options.test_private) and name.startswith("__"): continue attr = getattr(object, name) if not ismethoddescriptor(attr): continue methods.append(name) return methods def _createArgument(self, generators): callback = choice(generators) value = callback() for item in value: if not isinstance(item, text_type): raise ValueError("%s returned type %s" % (callback, type(item))) return value def createArgument(self): return self._createArgument(self.simple_argument_generators) def createHashableArgument(self): return self._createArgument(self.hashable_argument_generators) def createComplexArgument(self): if randint(0, 9) == 0: # 10% generators = self.complex_argument_generators else: # 90% generators = self.simple_argument_generators return self._createArgument(generators) def getNbArg(self, func, func_name, min_arg): try: # Known method of arguments? value = METHODS_NB_ARG[func_name] if isinstance(value, tuple): min_arg, max_arg = value else: min_arg = max_arg = value return min_arg, max_arg except KeyError: pass if PARSE_PROTOTYPE: # Try using the documentation args = parseDocumentation(func.__doc__, MAX_VAR_ARG) if args: return args return min_arg, MAX_ARG def callFunction(self, prefix, func_index, func_name, func, min_arg): min_arg, max_arg = self.getNbArg(func, func_name, min_arg) nb_arg = randint(min_arg, max_arg) if prefix: prefix += str(1+func_index) first_line = u'callMethod("%s", obj, "%s"' % (prefix, func_name) else: prefix = "f%s" % (1+func_index) first_line = u'callFunc("%s", "%s"' % (prefix, func_name) if nb_arg: self.write(0, first_line + u',') level = self.addLevel(1) last_char = u',' for index in xrange(nb_arg): if index == nb_arg-1: last_char = u')' self.writeArgument(1, last_char) self.restoreLevel(level) else: self.write(0, first_line + ')') self.emptyLine() def writeArgument(self, level, last_char=u','): lines = self.createComplexArgument() lines[-1] += last_char for line in lines: self.write(level, line) def useClass(self, cls_index, cls, class_name): nb_arg = randint(1, MAX_ARG) prefix = 'o%s' % (1 + cls_index) self.writePrint(0, u'"[%s] Create object %s"' % (prefix, 1 + cls_index)) obj_name = u'obj' self.write(0, u'%s = callFunc("%s", "%s",' % (obj_name, prefix, class_name)) for index in xrange(nb_arg): self.write(2, u'# argument %s/%s' % (1+index, nb_arg)) self.writeArgument(2) self.write(1, u')') methods = self.getMethods(cls, class_name) if methods: self.write(0, u'if %s:' % obj_name) level = self.addLevel(1) self.writeCode(prefix+'m', cls, methods, tuple(), 0, NB_METHOD) self.write(0, u'del %s' % obj_name) self.writePrint(0, u'"[%s] -garbage collector -"' % prefix) self.write(0, u'collect() # explicit call to the garbage collector') self.restoreLevel(level) self.emptyLine() def writeCode(self, prefix, object, functions, classes, func_min_arg, nb_call): if functions: for index in xrange(nb_call): func_name = choice(functions) func = getattr(object, func_name) self.callFunction(prefix, index, func_name, func, func_min_arg) if classes: self.nb_class = NB_CLASS for index in xrange(self.nb_class): class_name = choice(classes) cls = getattr(object, class_name) self.useClass(index, cls, class_name) def genNone(self): return [u'None'] def genBool(self): if randint(0, 1) == 1: return [u'True'] else: return [u'False'] def genSmallUint(self): return [self.smallint_generator.createValue()] def genInt(self): return [self.int_generator.createValue()] def genBytes(self): # Bytes string bytes = self.bytes_generator.createValue() if RUNNING_PYTHON3: text = ''.join( u"\\x%02X" % byte for byte in bytes) text = 'b"%s"' % text else: text = u''.join( u"\\x%02X" % ord(byte) for byte in bytes) text = u'"%s"' % text return [text] def genUnixPath(self): path = self.unix_path_generator.createValue() return [u'"%s"' % path] def _genUnicode(self, generator): # (Unicode) character string text = generator.createValue() text = escapeUnicode(text) if RUNNING_PYTHON3: text = u'"%s"' % text else: text = u'u"%s"' % text return [text] def genLetterDigit(self): return self._genUnicode(self.letters_generator) def genString(self): return self._genUnicode(self.unicode_generator) def genSurrogates(self): text = choice(SURROGATES) return [text] def genBufferObject(self): text = choice(BUFFER_OBJECTS) return [text] def genAsciiString(self): return self._genUnicode(self.ascii_generator) def genFloat(self): int_part = self.float_int_generator.createValue() float_part = self.float_float_generator.createValue() return [u"%s.%s" % (int_part, float_part)] def genExistingFilename(self): filename = choice(self.filenames) return [u"'%s'" % filename] def genErrback(self): return ["%s" % ERRBACK_NAME] def genOpenFile(self): filename = choice(self.filenames) if RUNNING_PYTHON3: instr = "open('%s')" % filename else: instr = "open(u'%s')" % filename return [instr] def genException(self): return ["Exception('pouet')"] def _genList(self, open_text, close_text, empty, is_dict=False): # 90% of the time generate values of the same type same_type = (randint(0, 9) != 0) nb_item = randint(0, 9) if not nb_item: return [empty] items = [] if same_type: if is_dict: key_callback = choice(self.hashable_argument_generators) value_callback = choice(self.simple_argument_generators) for index in xrange(nb_item): if is_dict: item = self.createDictItem(key_callback, value_callback) else: item = value_callback() items.append(item) else: for index in xrange(nb_item): if is_dict: item = self.createDictItem() else: item = self.createArgument() items.append(item) lines = [] for item_index, item_lines in enumerate(items): if item_index: lines[-1] += u"," for index, line in enumerate(item_lines): # Add ' ' suffix to all lines item_lines[index] = u' ' + line lines.extend(item_lines) if nb_item == 1 and empty == u'tuple()': lines[-1] += u',' lines[0] = open_text + lines[0] lines[-1] += close_text return lines def createDictItem(self, key_callback=None, value_callback=None): if key_callback: key = key_callback() else: key = self.createHashableArgument() if value_callback: value = value_callback() else: value = self.createArgument() key[-1] += u": " + value[0] key.extend(value[1:]) return key def genList(self): return self._genList(u'[', u']', u'[]') def genTuple(self): return self._genList(u'(', u')', u'tuple()') def genDict(self): return self._genList(u'{', u'}', u'{}', True) def parseArguments(arguments, defaults): for arg in arguments.split(","): arg = arg.strip(" \n[]") if not arg: continue if "=" in arg: arg, value = arg.split("=", 1) defaults[arg] = value yield arg def parsePrototype(doc): r""" >>> parsePrototype("test([x])") ((), None, ('x',), {}) >>> parsePrototype('dump(obj, file, protocol=0)') (('obj', 'file'), None, ('protocol',), {'protocol': '0'}) >>> parsePrototype('setitimer(which, seconds[, interval])') (('which', 'seconds'), None, ('interval',), {}) >>> parsePrototype("decompress(string[, wbits[, bufsize]])") (('string',), None, ('wbits', 'bufsize'), {}) >>> parsePrototype("decompress(string,\nwbits)") (('string', 'wbits'), None, (), {}) >>> parsePrototype("get_referents(*objs)") ((), '*objs', (), {}) >>> parsePrototype("nothing") """ if not doc: return None if not isinstance(doc, string_types): return None doc = doc.strip() match = PROTOTYPE_REGEX.match(doc) if not match: return None arguments = match.group(1) if arguments == '...': return None defaults = {} vararg = None varkw = tuple() if "[" in arguments: arguments, varkw = arguments.split("[", 1) arguments = tuple(parseArguments(arguments, defaults)) varkw = tuple(parseArguments(varkw, defaults)) else: arguments = tuple(parseArguments(arguments, defaults)) # Argument with default value? => varkw move = None for index in xrange(len(arguments)-1, -1, -1): arg = arguments[index] if arg not in defaults: break move = index if move is not None: varkw = arguments[move:] + varkw arguments = arguments[:move] if arguments and arguments[-1].startswith("*"): vararg = arguments[-1] arguments = arguments[:-1] return (arguments, vararg, varkw, defaults) def parseDocumentation(doc, max_var_arg): """ Arguments: - doc: documentation string - max_var_arg: maximum number of arguments for variable argument, eg. test(*args). """ prototype = parsePrototype(doc) if not prototype: return None args, varargs, varkw, defaults = prototype min_arg = len(args) max_arg = min_arg + len(varkw) if varargs: max_arg += max_var_arg return min_arg, max_arg if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/fusil-wizzard0000775000175000017500000003021412263525103017234 0ustar haypohaypo00000000000000#!/usr/bin/env python """ Fusil wizzard """ from __future__ import print_function # File format version FILE_MAGIC = u"fusil-wizzard 0.1.3" # Use stdout probe? USE_STDOUT = False # Stages STAGE_HELP, STAGE_GETENV, STAGE_FUZZ, STAGE_DONE = tuple(range(4)) # Command line option to get the usage HELP_OPTIONS = ("--help", "-h") # ltrace program name LTRACE = 'ltrace' from fusil.application import Application from optparse import OptionGroup from fusil.project_agent import ProjectAgent from fusil.process.create import CreateProcess from fusil.process.env import EnvVarRandom from fusil.process.watch import WatchProcess from fusil.process.tools import runCommand, locateProgram from fusil.cmd_help_parser import CommandHelpParser, Option from fusil.bytes_generator import ( BytesGenerator, # LengthGenerator, ASCII0, PRINTABLE_ASCII, LETTERS, HEXADECIMAL_DIGITS, PUNCTUATION) from os.path import basename from random import randint, choice from errno import ENOENT from sys import argv from os import rename, unlink import codecs import re if USE_STDOUT: from fusil.process.stdout import WatchStdout # Match >getenv("COLUMNS")< GETENV_REGEX = re.compile(r'^[0-9]+ getenv\("([^"]+)"\)') CONFIG = ( "arg_min_count", "arg_max_count", "opt_min_count", "opt_max_count", "env_min_count", "env_max_count", "env_min_length", "env_max_length", ) class Fuzzer(Application): NAME = "wizzard" USAGE = "%prog [options] --project=PROJECT program [arg1 arg2 ...]" NB_ARGUMENTS = (0, None) def createFuzzerOptions(self, parser): options = OptionGroup(parser, "Mplayer") options.add_option("--project", help="Project filename", type="str") options.add_option("--no-ltrace", dest="ltrace", help="Don't use ltrace to find environment variables", action="store_false", default=True) return options def processOptions(self, parser, options, arguments): Application.processOptions(self, parser, options, arguments) if not options.project: parser.print_help() exit(1) def setupProject(self): if self.options.ltrace and self.config.use_debugger: self.error("Disable the debugger to be able to use ltrace") self.project.debugger.disable() process = CreateProcess(self.project, ["program"]) WatchProcess(process, exitcode_score=0.25) if USE_STDOUT: stdout = WatchStdout(process) stdout.words = dict( (pattern, score) for pattern, score in stdout.words.items() if score >= 0.7) stdout.max_nb_line = None Wizzard(self.project, self.options, self.arguments, process) class FuzzOption: def __init__(self, option): self.option = option class Wizzard(ProjectAgent): def __init__(self, project, options, arguments, process): ProjectAgent.__init__(self, project, "wizzard") self.options = options self.filename = options.project self.arg_min_count = 0 self.arg_max_count = 0 self.opt_min_count = 1 self.opt_max_count = 5 self.env_min_count = 1 self.env_max_count = 5 self.env_min_length = 0 self.env_max_length = 10 self.generators = [ BytesGenerator(1, 20, ASCII0), BytesGenerator(1, 20, PRINTABLE_ASCII), BytesGenerator(1, 20, LETTERS | HEXADECIMAL_DIGITS | PUNCTUATION), # LengthGenerator(1024, 8192), ] self.stage = STAGE_HELP self.help_index = 0 self.cmdline_options = [] self.environment = set() self.arguments = arguments self.process = process self.saved = False self.ltrace_program = None # Try to load the project loaded = self.load() if loaded: if not self.arguments: raise ValueError("Missing program name on command line") self.arguments[0] = locateProgram(self.arguments[0], raise_error=True) self.createEnv() def destroy(self): if self.saved: self.error("==> continue fuzzing using command: %s --project %s" % (argv[0], self.filename)) def load(self): try: out = codecs.open(self.filename, 'r', 'utf-8') except IOError as err: if err.errno == ENOENT: return False else: raise self.error("Reload project: %s" % self.filename) self.stage = STAGE_FUZZ self.arguments = [] section = None line_number = 0 for line in out: line_number += 1 line = line.rstrip() if line_number == 1: if line != FILE_MAGIC: raise SyntaxError("Unknown file format or version: %r" % line) continue if not line: section = None continue if section: if section == "[arguments]": arg = str(line) self.arguments.append(arg) elif section == "[config]": key, value = line.split("=", 1) if key not in CONFIG: raise SyntaxError("Line %s: unknown option %s" % (line_number, key)) value = int(value) setattr(self, key, value) elif section == "[options]": format = str(line) nb_arg = format.count("%s") opt = Option(format, nb_arg) self.cmdline_options.append(opt) elif section == "[environment]": name = str(line) self.environment.add(name) else: raise SyntaxError("Line %s: unknown section %r" % (line_number, line)) else: section = line out.close() return True def write(self): # Create a new file (using a temporary name) tmpname = self.filename + ".tmp" out = codecs.open(tmpname, 'w', 'utf-8') try: self._write(out) except: out.close() unlink(tmpname) raise out.close() # Rename the file on success rename(tmpname, self.filename) self.saved = True def _write(self, out): # write arguments print(FILE_MAGIC, file=out) print(file=out) # [config] print("[config]", file=out) for key in CONFIG: value = getattr(self, key) print("%s=%s" % (key, value), file=out) print(file=out) print("[arguments]", file=out) for arg in self.arguments: arg = str(arg) print(arg, file=out) print(file=out) # write options (if any) if self.cmdline_options: print("[options]", file=out) options = list(self.cmdline_options) options.sort(key=lambda opt: str(opt)) for opt in options: print("%s" % opt.format, file=out) print(file=out) # write environment (if any) if self.environment: print("[environment]", file=out) environ = list(self.environment) environ.sort() for name in environ: print(name, file=out) print(file=out) def createArgument(self): generator = choice(self.generators) return generator.createValue() def createOption(self): option = choice(self.cmdline_options) generator = choice(self.generators) arguments = [] for index in range(option.nb_argument): value = generator.createValue() arguments.append(value) return option.formatArguments(arguments) def init(self): self.trace_filename = None self.live_done = False def live(self): # Only execute live() once by session if self.live_done: return self.live_done = True if self.stage == STAGE_HELP: self.stageHelp() elif self.stage == STAGE_GETENV: self.stageGetenv() elif self.stage == STAGE_FUZZ: self.stageFuzz() else: self.nextStage() def deinit(self): self.write() def nextStage(self): if STAGE_DONE <= self.stage: self.send('project_stop') else: self.stage += 1 self.send('session_stop') if self.stage == STAGE_GETENV and (not self.options.ltrace): self.stage += 1 def parseLtrace(self, filename): trace = open(filename) for line in trace: match = GETENV_REGEX.search(line) if not match: continue name = match.group(1) if name in self.environment: continue self.environment.add(name) self.error("Found new environment variable: %s" % name) trace.close() def createEnv(self): if not self.environment: return names = list(self.environment) var = EnvVarRandom(names, self.env_min_length, self.env_max_length, max_count=self.env_max_count) var.min_count = self.env_min_count self.process.env.add(var) def ltraceArguments(self): if not self.ltrace_program: self.ltrace_program = locateProgram(LTRACE, raise_error=True) tracefile = self.session().createFilename("ltrace") return [self.ltrace_program, "-f", "-e", "getenv", "-o", tracefile, "--"], tracefile def stageGetenv(self): arguments, tracefile = self.ltraceArguments() arguments += self.arguments runCommand(self, arguments, stdout=False) self.parseLtrace(tracefile) self.nextStage() def stageFuzz(self): # ltrace arguments if self.options.ltrace: arguments, self.trace_filename = self.ltraceArguments() # Workaround ltrace bug: # https://bugzilla.redhat.com/show_bug.cgi?id=1044766 self.process.env.clear() self.process.env.copy('PATH') else: arguments = [] self.process.env.clear() self.createEnv() # random options arguments.append(self.arguments[0]) if self.cmdline_options: nbopt = randint(self.opt_min_count, self.opt_max_count) for index in range(nbopt): opts = self.createOption() arguments.extend(opts) arguments.extend(self.arguments[1:]) # random arguments nbarg = randint(self.arg_min_count, self.arg_max_count) for index in range(nbarg): arg = self.createArgument() arguments.append(arg) # create the process self.warning("Arguments: %s" % repr(arguments)) self.process.cmdline.arguments = arguments self.process.createProcess() def on_process_exit(self, agent, status): if self.trace_filename: try: self.parseLtrace(self.trace_filename) except IOError: pass def stageHelp(self): filename = self.session().createFilename("help.stdout") stdout = open(filename, 'w') program = self.arguments[0] help_opt = HELP_OPTIONS[self.help_index] arguments = [program, help_opt] try: runCommand(self, arguments, stdout=stdout) except RuntimeError as err: self.error(str(err)) stdout.close() # Get the help help = CommandHelpParser(basename(program)) stdout = open(filename) help.parseFile(stdout) stdout.close() if not help.options: self.help_index += 1 if self.help_index == len(HELP_OPTIONS): raise ValueError("Unable to parse the help :-/") self.send('session_stop') return self.cmdline_options = help.options for opt in self.cmdline_options: self.error("Found option: %s" % opt) self.nextStage() if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/fusil-zzuf0000775000175000017500000001325412263525103016545 0ustar haypohaypo00000000000000#!/usr/bin/env python """ Fuzz a process using the zzuf library. zzuf homepage: http://libcaca.zoy.org/wiki/zzuf """ MIN_SEED = 0 MAX_SEED = 2**32 - 1 from fusil.application import Application from fusil.process.create import DEFAULT_TIMEOUT from fusil.process.env import EnvVarIntegerRange from fusil.process.watch import WatchProcess, DEFAULT_TIMEOUT_SCORE from fusil.zzuf import ZzufProcess, DEFAULT_RATIO from optparse import OptionGroup from ptrace.six.moves import map as imap import re class Fuzzer(Application): NAME = "zzuf" USAGE = "%prog [options] program [arg1 arg2 ...]" NB_ARGUMENTS = (1, None) def createFuzzerOptions(self, parser): options = OptionGroup(parser, "zzuf fuzzer options") options.add_option("-r", "--ratio", help="bit fuzzing ratio (default %g) or ratio range ()" % DEFAULT_RATIO, type="str") options.add_option("-b", "--bytes", help="only fuzz bytes at offsets within ", type="str") options.add_option("-l", "--list", help="only fuzz Nth descriptor with N in ", type="str") options.add_option("-f", "--fuzzing", help="use fuzzing mode : xor, set, unset (default: xor)", type="str") options.add_option("-E", "--exclude", help="do not fuzz files matching ", type="str") options.add_option("-I", "--include", help="only fuzz files matching ", type="str") options.add_option("-p", "--ports", help="only fuzz network destination ports in ", type="str") options.add_option("-P", "--protect", help="protect bytes and characters in ", type="str") options.add_option("-R", "--refuse", help="refuse bytes and characters in ", type="str") options.add_option("-n", "--network", help="fuzz network input", action="store_true") options.add_option("--cmdline", help="Only fuzz files specified in the command line", action="store_true") options.add_option("--timeout", help="Process maximum execution time in seconds (default: %.1f sec)" % DEFAULT_TIMEOUT, type="float", default=DEFAULT_TIMEOUT) options.add_option("--check-exit", help="report processes that exit with a non-zero status", action="store_true") options.add_option("--timeout-score", help="Process timeout score in percent (default: %.1f%%)" % (DEFAULT_TIMEOUT_SCORE*100), type="float", default=DEFAULT_TIMEOUT_SCORE*100) options.add_option("--zzuf-library", help="zzuf library full path (default: guess common paths)", type="str", default=None) return options def getFilenames(self): dashdash = False filenames = [] for arg in self.arguments[1:]: if dashdash: filenames.append(arg) elif arg == '--': dashdash = True elif not arg.startswith("-"): filenames.append(arg) return filenames def setupProject(self): # Check options if self.options.ports \ and not self.options.network: raise ValueError("port option (-p) requires network fuzzing (-n)") # Create include/exclude filters include = None if self.options.include: include = self.options.include elif self.options.cmdline: filenames = self.getFilenames() filenames = imap(re.escape, filenames) include = '(%s)' % '|'.join(filenames) exclude = self.options.exclude # Get the ratio if self.options.ratio: ratio = self.options.ratio if ":" in ratio: ratio = ratio.split(":", 1) minratio = float(ratio[0]) maxratio = float(ratio[1]) else: minratio = float(ratio) maxratio = minratio else: minratio = DEFAULT_RATIO maxratio = DEFAULT_RATIO # Create the process agent process = ZzufProcess(self.project, self.arguments, library_path=self.options.zzuf_library, timeout=self.options.timeout) # Generate a random zzuf seed process.env.add(EnvVarIntegerRange('ZZUF_SEED', MIN_SEED, MAX_SEED)) # Set zzuf options process.setRatio(minratio, maxratio) if include: process.env.set('ZZUF_INCLUDE', include) if exclude: process.env.set('ZZUF_EXCLUDE', exclude) if self.options.network: process.env.set('ZZUF_NETWORK', '1') if self.options.fuzzing: process.env.set('ZZUF_FUZZING', self.options.fuzzing) if self.options.bytes: process.env.set('ZZUF_BYTES', self.options.bytes) if self.options.list: process.env.set('ZZUF_LIST', self.options.list) if self.options.ports: process.env.set('ZZUF_PORTS', self.options.ports) if self.options.protect: process.env.set('ZZUF_PROTECT', self.options.protect) if self.options.refuse: process.env.set('ZZUF_REFUSE', self.options.refuse) # Watch process exit status and stdout if self.options.check_exit: exitcode_score = 1.00 else: exitcode_score = 0.10 WatchProcess(process, exitcode_score=exitcode_score, timeout_score=self.options.timeout_score / 100) if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/fusil-vlc0000775000175000017500000001020512110032732016314 0ustar haypohaypo00000000000000#!/usr/bin/env python """ VLC fuzzer. """ VLC_PROGRAM = 'vlc' INTERFACE = 'dummy' SECONDS = 5 MAX_FILESIZE = 1024*1024 from fusil.application import Application from optparse import OptionGroup from fusil.process.mangle import MangleProcess from fusil.process.watch import WatchProcess from fusil.process.stdout import WatchStdout from fusil.auto_mangle import AutoMangle class Fuzzer(Application): NAME = "vlc" USAGE = "%prog [options] filename" NB_ARGUMENTS = 1 def createFuzzerOptions(self, parser): options = OptionGroup(parser, "VLC") options.add_option("--program", help="VLC program path (default: %s)" % VLC_PROGRAM, type="str", default=VLC_PROGRAM) options.add_option("--seconds", help="Play/convert duration in seconds (default: %s)" % SECONDS, type="int", default=SECONDS) options.add_option("--filesize", help="Maximum file size in bytes (default: %s)" % MAX_FILESIZE, type="int", default=MAX_FILESIZE) options.add_option("--video", help="Enable the video output (default: use dummy video output)", action="store_true", default=False) options.add_option("--audio", help="Enable the audio output (default: use dummy audio output)", action="store_true", default=False) options.add_option("--interface", help="Interface name (default: %s)" % INTERFACE, type="str", default=INTERFACE) return options def setupProject(self): project = self.project # Command line minutes, seconds = divmod(self.options.seconds, 60) stop_time = "%02u:%02u" % (minutes, seconds) arguments = [ self.options.program, # No GUI '--intf', self.options.interface, ] arguments.append('-vvv') has_run_time_opt = False if has_run_time_opt: arguments.append('--run-time=%s' % stop_time) #option for 0.8.* else: arguments.append('--stop-time=%s' % stop_time) #option for 0.9.* and 1.* if not self.options.audio: # Null audio output arguments.extend(('--aout', 'dummy')) if not self.options.video: # Null video output arguments.extend(('--vout', 'dummy')) # Input filename arguments.append('') if not has_run_time_opt: # Quit when done arguments.append('vlc://quit') # Create buggy input file orig_filename = self.arguments[0] mangle = AutoMangle(project, orig_filename) mangle.max_size = self.options.filesize mangle.first_offset = 100 # Create the process timeout = self.options.seconds + 2.0 process = MangleProcess(project, arguments, "",use_relative_mangle=False, timeout=timeout) if self.options.interface != 'dummy': process.setupX11() # process.env.copy('HOME') process.max_memory = None WatchProcess(process, timeout_score=0) stdout = WatchStdout(process) stdout.score_weight = 0.4 # stdout.ignoreRegex(r"libdvdread: Can't stat ") stdout.ignoreRegex(r'no access_demux module matching "file" could be loaded') stdout.addRegex(r'main input error: no suitable demux module', -0.50) #stdout.addRegex(r'main playlist: nothing to play', -0.50) stdout.addRegex(r'removing module "direct3d"', -0.50) stdout.addRegex(r'garbage at input', -0.50) stdout.addRegex(r'theora decoder error: this bitstream does not contain Theora video data', -0.10) stdout.addRegex(r'Trying to seek to far : EOF?', 0.20) stdout.addRegex(r'marker does not match f_code', 0.20) stdout.addRegex(r'vorbis decoder error: this bitstream does not contain Vorbis audio data', -0.10) stdout.addRegex(r'Error: No ogg data found in file', -0.50) # stdout.addRegex(r'access_file access error: seeking too far', 0.10) # stdout.score_weight = 0.40 del stdout.words['error'] del stdout.words['failed'] del stdout.words["can't"] if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/fusil-libc-printf0000775000175000017500000001765412254417344017777 0ustar haypohaypo00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ Generate valid printf format to test GNU libc implementation Written using manual page to get all options. """ FORMAT_TO_SIZE = { 's': 'str', 'S': 'wide str', 'c': 'char', 'C': 'wide char', 'p': 'pointer', 'm': None, '%': None, 'n': 'write', 'a': 'double', 'A': 'double', 'e': 'double', 'E': 'double', 'f': 'double', 'F': 'double', 'g': 'double', 'G': 'double', 'i': 'int', 'u': 'int', 'd': 'int', 'x': 'int', 'X': 'int', 'o': 'int', 'O': 'int', } from fusil.application import Application from optparse import OptionGroup from random import choice, randint from fusil.c_tools import encodeUTF32, quoteString, CodeC, CompilerError from fusil.process.create import CreateProcess from fusil.process.watch import WatchProcess from fusil.project_agent import ProjectAgent from ptrace.compatibility import any HELLO_UTF32 = encodeUTF32(u"Héllô")+"\0"*4 class Fuzzer(Application): NAME = "printf" def createFuzzerOptions(self, parser): options = OptionGroup(parser, "printf fuzzer") options.add_option("--asprintf", help="Don't use asprintf()", action="store_true") return options def setupProject(self): project = self.project printf = GeneratePrintfProgram(project, self.options) printf.max_nb_arg = 10 # AVOID printf("%*d", 10000000, 42) crash #printf.max_width = 10*1000 # AVOID "%.10000000s" crash #printf.max_precision = 10*1000 # AVOID "%10000000hc" crash del printf.modifiers['char']['h'] # AVOID "%qp" and "%llC" crashes for size in ('char', 'wide char', 'pointer'): for key in ('ll', 'q', 'j'): del printf.modifiers[size][key] process = PrintfProcess(project, name="printf", stdout='null') WatchProcess(process) class PrintfProcess(CreateProcess): def on_printf_program(self, program): self.cmdline.arguments = [program] self.createProcess() class GeneratePrintfArguments: def __init__(self, printf): self.printf = printf def genFormat(self, argument_index): # choose type prefix = None type = choice(self.printf.types) size = FORMAT_TO_SIZE[type] format = ['%'] # add attribute format.append(choice(self.printf.format_attr)) # add width rnd = randint(0, 2) if rnd == 1: format.append(str(randint(0, self.printf.max_width))) elif rnd == 2: width = randint(self.printf.min_width, self.printf.max_width) prefix = '/* width of x%s */ %s' % (argument_index, width) format.append('*') #if randint(0, 1) == 1: # format.append('*') #else: # format.append('%s$*%s$' % ( # argument_index+2, argument_index+1)) # add precision if randint(0, 1) == 1: format.append('.%s' % randint(0, self.printf.max_precision)) # add modifier if size in self.printf.modifiers: modifiers = self.printf.modifiers[size] keys = list(modifiers.keys())+[None] modifier = choice(keys) if modifier: format.append(modifier) size = modifiers[modifier] # add type format.append(type) return format, prefix, size def generate(self, nb_arg): text = [] arguments = [] for index in range(nb_arg): if text: text.append(' -- ') # Generate format and value format, prefix, size = self.genFormat(index) if size: value = self.printf.values[size] else: value = None # Append format and value format = ''.join(format) text.append('x%s' % index + '=' + format) if prefix: arguments.append(prefix) if value is not None: if value == "&written": arguments.append('/* written bytes at x%s */ ' % index + value) else: arguments.append('/* x%s value */ ' % index + value) text.append('\n') return [quoteString(''.join(text))]+arguments class GeneratePrintfProgram(ProjectAgent): def __init__(self, project, options): ProjectAgent.__init__(self, project, "gen printf") self.has_asprintf = (not options.asprintf) # --- printf options --- self.min_nb_arg = 1 self.max_nb_arg = 6 self.types = 'aAcCdeEfFgGimnopuxXsS%' self.format_attr = ('#', '0', '-', ' ', '+', "'", 'I', '') self.min_width = 0 self.max_width = 10*1000*1000 self.max_precision = 10*1000*1000 self.values = { 'str': quoteString('Hello'), 'wide str': quoteString(HELLO_UTF32), 'char': "'A'", 'wide char': "(wchar_t)322", # 'ł' 'double': "(double)3.14", 'short': "(short)7", 'pointer': "(void *)0xDEADBEEF", 'int': "(int)42", 'intmax': "(intmax_t)232", 'size_t': "(size_t)-1", 'long': "(long)1234567890", 'long long': "(long long)10101010", 'ptrdiff_t': "(ptrdiff_t)100", 'write': "&written", 'long double': '(long double)5.92', } int_modifiers = { 'hh': 'char', 'h': 'short', 'l': 'long', 'll': 'long long', 'q': 'long long', 'j': 'intmax', 'z': 'size_t', 't': 'ptrdiff_t', } self.modifiers = { 'int': dict(int_modifiers), 'char': dict(int_modifiers), 'wide char': dict(int_modifiers), 'pointer': dict(int_modifiers), 'str': {'l': 'wide str'}, 'wide str': {'l': 'wide str'}, 'double': {'L': 'long double'}, } def on_session_start(self): self.use_locale = (randint(0, 1) == 0) if self.has_asprintf: self.use_asprintf = (randint(0, 1) == 0) else: self.use_asprintf = False # Generate printf() arguments nb_arg = randint(self.min_nb_arg, self.max_nb_arg) arguments = GeneratePrintfArguments(self).generate(nb_arg) self.info("Arguments: %s" % repr(arguments[1:])) self.info("Format: %s" % repr(arguments[0])) # Write C code to reproduce the bug code = CodeC() self.writeC(code, arguments) session = self.session() self.c_filename = session.createFilename("printf.c") self.program_filename = session.createFilename("printf") try: code.compile(self, self.c_filename, self.program_filename, options="-Wno-format") except CompilerError as err: self.error("Compiler error: %s" % err) self.send('project_stop') return self.send('printf_program', self.program_filename) def writeC(self, code, arguments): if self.use_asprintf: code.gnu_source = True code.includes = [ '', # for ptrdiff_t '', # for intmax_t '', # for printf() ] if self.use_locale: code.includes.append('') # for setlocale() main = code.addMain() if any( "&written" in text for text in arguments): main.variables.append('int written') if self.use_locale: main.callFunction('setlocale', ['LC_ALL', '""']) if self.use_asprintf: main.variables.append("char *text = NULL") arguments.insert(0, "&text") name = "asprintf" else: name = "printf" main.callFunction(name, arguments) if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/fusil-mplayer0000775000175000017500000000773612110032732017220 0ustar haypohaypo00000000000000#!/usr/bin/env python """ Mplayer audio/video mplayer. Supported file formats: - AVI video - WAV audio - Ogg/Vorbis audio - Ogg/Theora video - Mastroska (.mkv) video - DVD """ MPLAYER_PROGRAM = 'mplayer' PLAY_DURATION = 5 MAX_FILESIZE = 1024*1024 from fusil.application import Application from optparse import OptionGroup from fusil.process.mangle import MangleProcess from fusil.process.watch import WatchProcess from fusil.process.stdout import WatchStdout from fusil.auto_mangle import AutoMangle from fusil.terminal_echo import TerminalEcho class Fuzzer(Application): NAME = "mplayer" USAGE = "%prog [options] filename" NB_ARGUMENTS = 1 def createFuzzerOptions(self, parser): options = OptionGroup(parser, "Mplayer") options.add_option("--mplayer", help="Mplayer program path (default: %s)" % MPLAYER_PROGRAM, type="str", default=MPLAYER_PROGRAM) options.add_option("--duration", help="Playing maximum duration in seconds (default: %s)" % PLAY_DURATION, type="int", default=PLAY_DURATION) options.add_option("--filesize", help="Maximum file size in bytes (default: %s)" % MAX_FILESIZE, type="int", default=MAX_FILESIZE) options.add_option("--video", help="Enable video (default: use null output)", action="store_true", default=False) options.add_option("--audio", help="Enable audio (default: use null output)", action="store_true", default=False) return options def setupProject(self): project = self.project # Command line arguments = [self.options.mplayer, '-quiet'] if not self.options.audio: arguments.extend(['-ao', 'null']) if not self.options.video: arguments.extend(['-vo', 'null']) timeout = self.options.duration + 1.0 arguments.extend(('-endpos', str(self.options.duration))) arguments.append("") # Create buggy input file orig_filename = self.arguments[0] mangle = AutoMangle(project, orig_filename) mangle.max_size = self.options.filesize process = MangleProcess(project, arguments, "", timeout=timeout) if self.options.video: process.setupX11() process.env.copy('HOME') watch = WatchProcess(process, timeout_score=0) if watch.cpu: watch.cpu.weight = 0.20 watch.cpu.max_load = 0.50 watch.cpu.max_duration = min(3, timeout-0.5) watch.cpu.max_score = 0.50 stdout = WatchStdout(process) # Ignore input errors stdout.ignoreRegex('^Failed to open LIRC support') stdout.ignoreRegex("^Can't init input joystick$") stdout.ignoreRegex("^Can't open joystick device ") # Ignore codec loading errors stdout.ignoreRegex('^Failed to create DirectShow filter$') stdout.ignoreRegex('^Win32 LoadLibrary failed') stdout.ignoreRegex('^Error loading dll$') stdout.ignoreRegex('^ERROR: Could not open required DirectShow codec ') stdout.ignoreRegex("could not open DirectShow") # Ignore other errors stdout.ignoreRegex("^Terminal type `unknown' is not defined.$") stdout.ignoreRegex('^VDecoder init failed') stdout.ignoreRegex("Read error at pos\. [0-9]+") stdout.ignoreRegex("could not connect to socket") stdout.ignoreRegex('^ADecoder init failed') stdout.ignoreRegex('^error while decoding block:') stdout.ignoreRegex('^Error while decoding frame!$') stdout.ignoreRegex('^\[(mpeg4|msmpeg4|wmv1|h264|NULL) @ ') stdout.addRegex('[oO]verflow', 0.10) stdout.addRegex('MPlayer interrupted by signal', 1.0) stdout.addRegex('AVI: Missing video stream', -0.50) stdout.addRegex('^No stream found.$', -0.50) stdout.max_nb_line = None # Restore terminal state TerminalEcho(project) if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/fusil-clamav0000775000175000017500000000630012110032732016774 0ustar haypohaypo00000000000000#!/usr/bin/env python """ ClamAV anti-virus. Supported file formats: - ZIP, CAB archive - JPEG - Windows PE program (.exe) - HTML """ NB_FILES = 10 MAX_MUTATIONS = 100 MAX_MEMORY = 100*1024*1024 from fusil.application import Application from optparse import OptionGroup from fusil.process.create import CreateProcess from fusil.process.watch import WatchProcess from fusil.process.attach import AttachProcess from fusil.process.stdout import WatchStdout from fusil.auto_mangle import AutoMangle from fusil.file_watch import FileWatch class Fuzzer(Application): NAME = "clamav" USAGE = "%prog [options] filename" NB_ARGUMENTS = 1 def createFuzzerOptions(self, parser): options = OptionGroup(parser, "ClamAV") options.add_option("--use-clamd", help="Use the ClamAV daemon (clamd)", action="store_true") options.add_option("--change-filesize", help="Allow mutation to change file size", action="store_true", default=False) options.add_option("--nb-files", help="Number of generated files (default: %s)" % NB_FILES, type="int", default=NB_FILES) options.add_option("--max-mutations", help="Maximum number of mutations (default: %s)" % MAX_MUTATIONS, type="int", default=MAX_MUTATIONS) options.add_option("--max-memory", help="Maximum clamd server memory in bytes (default: %s)" % MAX_MEMORY, type="int", default=MAX_MEMORY) return options def setupProject(self): project = self.project if self.options.use_clamd: PROGRAM = 'clamdscan' else: PROGRAM = 'clamscan' orig_filename = self.arguments[0] mangle = AutoMangle(project, orig_filename, self.options.nb_files) mangle.config.max_op = self.options.max_mutations mangle.config.change_size = self.options.change_filesize # Watch clamd server if self.options.use_clamd: clamd = AttachProcess(project, 'clamd') clamd.max_memory = self.options.max_memory process = ClamavProcess(project, [PROGRAM], timeout=100.0) process.max_memory = self.options.max_memory WatchProcess(process, exitcode_score=0.10) stdout = WatchStdout(process) stdout.max_nb_line = (50+self.options.nb_files, 1.0) stdout.addRegex(r"Can't connect to clamd", 1.0) logs = [stdout] if self.options.use_clamd: log = FileWatch.fromFilename(project, '/var/log/clamav/clamav.log', start="end") log.max_nb_line = None logs.append(log) for log in logs: log.ignoreRegex(r"\*\*\* DON'T PANIC!") log.ignoreRegex('SCAN SUMMARY') log.ignoreRegex(': OK$') log.ignoreRegex('^Infected files: 0$') log.ignoreRegex('^Time: ') log.addRegex(' FOUND$', 0.05) del log.words['error'] log.show_matching = True log.show_not_matching = True class ClamavProcess(CreateProcess): def on_mangle_filenames(self, new_files): self.cmdline.arguments = self.cmdline.arguments[:1] + new_files self.createProcess() if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/fusil-gimp0000775000175000017500000000674312254532554016520 0ustar haypohaypo00000000000000#!/usr/bin/env python """ Gimp fuzzer. """ from __future__ import print_function, with_statement from fusil.application import Application from optparse import OptionGroup from fusil.process.create import CreateProcess from fusil.process.watch import WatchProcess from fusil.process.stdout import WatchStdout from fusil.auto_mangle import AutoMangle from fusil.dummy_mangle import DummyMangle from os.path import basename PROGRAM = 'gimp' NB_FILES = 25 MAX_FILESIZE = 1024*1024 class Fuzzer(Application): NAME = "gimp" USAGE = "%prog [options] image1 [image2 ...]" NB_ARGUMENTS = (1, None) def createFuzzerOptions(self, parser): options = OptionGroup(parser, "Gimp") options.add_option("--nb-files", help="Number of generated files (default: %s)" % NB_FILES, type="int", default=NB_FILES) options.add_option("--program", help="Gimp program path (default: %s)" % PROGRAM, type="str", default=PROGRAM) options.add_option("--filesize", help="Maximum file size in bytes (default: %s)" % MAX_FILESIZE, type="int", default=MAX_FILESIZE) options.add_option("--test", help="Test mode (no fuzzing, just make sure that the fuzzer works)", action="store_true") return options def setupProject(self): if self.options.test: DummyMangle(self.project, self.arguments) else: mangle = AutoMangle(self.project, self.arguments, self.options.nb_files) mangle.max_size = self.options.filesize # Create the process arguments = [self.options.program, '--no-interface', # '--verbose', '--batch-interpreter', 'plug-in-script-fu-eval', '--batch', '-'] process = GimpProcess(self.project, arguments) WatchProcess(process) stdout = WatchStdout(process) stdout.ignoreRegex('fatal parse error') # > Error: Procedure execution of gimp-file-load failed: This XCF file is corrupt! # I could not even salvage any partial image data from it. stdout.ignoreRegex('file is corrupt') stdout.max_nb_line = None del stdout.words['warning'] del stdout.words['error'] class GimpProcess(CreateProcess): def init(self): CreateProcess.init(self) self.script_filename = None def on_mangle_filenames(self, filenames): self.script_filename = self.session().createFilename("script") filenames_str = ' '.join('"%s"' % basename(filename) for filename in filenames) with open(self.script_filename, "w") as fp: print('(gimp-message "Start Gimp fuzzer")', file=fp) print('(define (fuzzfiles n f)', file=fp) print(' (let* (', file=fp) print(' (fname (car f))', file=fp) print(' (img 0)', file=fp) print(' )', file=fp) print(' (gimp-message fname)', file=fp) print(' (set! img (car (gimp-file-load RUN-NONINTERACTIVE fname fname)))', file=fp) print(' (gimp-image-delete img)', file=fp) print(' (if (= n 1) 1 (fuzzfiles (- n 1) (cdr f)))', file=fp) print(' )', file=fp) print(')', file=fp) print("(fuzzfiles %s '(%s))" % (len(filenames), filenames_str), file=fp) print('(gimp-quit 0)', file=fp) self.createProcess() def createStdin(self): return open(self.script_filename, 'rb') if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/fusil-ogg1230000775000175000017500000000505712110032732016543 0ustar haypohaypo00000000000000#!/usr/bin/env python """ ogg123 fuzzer """ MAX_FILESIZE = 32*1024 PROGRAM = 'ogg123' MANGLE = "fixed" from fusil.application import Application from optparse import OptionGroup from fusil.process.mangle import MangleProcess from fusil.process.watch import WatchProcess from fusil.process.stdout import WatchStdout if MANGLE == "incr": from fusil.incr_mangle import IncrMangle as OggMangle elif MANGLE == "auto": from fusil.auto_mangle import AutoMangle as OggMangle else: from fusil.mangle import MangleFile as OggMangle class Fuzzer(Application): NAME = "ogg123" USAGE = "%prog [options] audio.ogg" NB_ARGUMENTS = 1 def createFuzzerOptions(self, parser): options = OptionGroup(parser, "ogg123 fuzzer") options.add_option("--max-filesize", help="Maximum file size in bytes (default: %s)" % MAX_FILESIZE, type="int", default=MAX_FILESIZE) options.add_option("--program", help="Ogg program: ogg123 or ogginfo (default: %s)" % PROGRAM, choices=("ogg123", "ogginfo"), default=PROGRAM) return options def setupProject(self): project = self.project orig_filename = self.arguments[0] mangle = OggMangle(project, orig_filename) mangle.max_size = self.options.max_filesize if MANGLE == "auto": mangle.hard_min_op = 1 mangle.hard_max_op = 100 elif MANGLE == "incr": from fusil.incr_mangle_op import InverseBit, Increment mangle.operations = (InverseBit, Increment) else: mangle.config.min_op = 1 mangle.config.max_op = 10 if self.options.program == "ogginfo": COMMAND = ['ogginfo', ''] else: COMMAND = ['ogg123', '-d', 'null', ''] process = MangleProcess(project, COMMAND, "", timeout=60.0) process.env.copy('HOME') if COMMAND[0] == 'ogg123': WatchProcess(process, exitcode_score=-0.25) else: WatchProcess(process, exitcode_score=0) stdout = WatchStdout(process) stdout.max_nb_line = None stdout.show_matching = True stdout.addRegex(r"The file may be corrupted", -0.50) stdout.addRegex(r"Corrupted ogg", -0.50) stdout.addRegex(r"Could not decode vorbis header packet", -0.50) # stdout.ignoreRegex('^Warning: Could not decode vorbis header packet') stdout.ignoreRegex('^Warning: sequence number gap') stdout.ignoreRegex('^New logical stream.*: type invalid$') if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/notworking/0000775000175000017500000000000012305626161016700 5ustar haypohaypo00000000000000fusil-1.5/fuzzers/notworking/fusil-libc-env0000775000175000017500000001175412110032732021442 0ustar haypohaypo00000000000000#!/usr/bin/env python """ Fuzzer for GNU libc environment variables. Errors (?) with libc 2.5 and 2.6.1: MALLOC_TOP_PAD_: (len=38) '-8819591051163692829984324870324709886' LD_HWCAP_MASK: (len=31) '4604331229650750074196056802320' GNU libc variable list: http://www.scratchbox.org/documentation/general/tutorials/glibcenv.html Old advisories about unsecure variables: * 2000-09-01: NLSPATH Unix locale format string vulnerability http://www.coresecurity.com/index.php5?module=ContentMod&action=item&id=1067 * 2003-12-30: LANG Xsok "LANG" Environment Variable Privilege Escalation Vulnerability http://secunia.com/advisories/10513/ * 2007-07-05: LD_HWCAP_MASK integer overflow http://securitytracker.com/alerts/2007/Jul/1018334.html * MALLOC_TOP_PAD_: exploit http://www.synnergy.net/downloads/exploits/traceroute-exp.txt * 2007-11-07: LC_TIME: setlocale() exploit for aix 5.2 (CVE-2006-4254) http://www.milw0rm.com/exploits/4612 Disabled variables for SUID programs: * __libc_init_secure(): elf/enbl-secure.c (__libc_enable_secure=1) * UNSECURE_ENVVARS: sysdeps/generic/unsecvars.h - GCONV_PATH - GETCONF_DIR - HOSTALIASES - LD_AUDIT - LD_DEBUG - LD_DEBUG_OUTPUT - LD_DYNAMIC_WEAK - LD_LIBRARY_PATH - LD_ORIGIN_PATH - LD_PRELOAD - LD_PROFILE - LD_SHOW_AUXV - LD_USE_LOAD_BIAS - LOCALDOMAIN - LOCPATH - MALLOC_TRACE - NIS_PATH - NLSPATH - RESOLV_HOST_CONF - RES_OPTIONS - TMPDIR - TZDIR * EXTRA_UNSECURE_ENVVARS: sysdeps/unix/sysv/linux/i386/dl-librecon.h: - LD_AOUT_LIBRARY_PATH - LD_AOUT_PRELOAD """ # Use non trival program to make sure that libc uses many environment variables COMMAND = """python -c 'print "Hello World!"'""" MAX_COUNT = 5 from fusil.application import Application from fusil.bytes_generator import LETTERS, PUNCTUATION from fusil.process.env import EnvVarRandom, EnvVarInteger, EnvVarLength from fusil.process.create import ProjectProcess from fusil.process.watch import WatchProcess from fusil.process.stdout import WatchStdout from optparse import OptionGroup class Fuzzer(Application): NAME = "libc-env" def createFuzzerOptions(self, parser): options = OptionGroup(parser, "libc env fuzzer") options.add_option("--max-count", help="Maximum number of environment variables (default: %s)" % MAX_COUNT, type="int", default=MAX_COUNT) options.add_option("--command", help="Command used to test the variables (default: %r)" % COMMAND, type="str", default=COMMAND) return options def setupProject(self): project = self.project # Run program with fuzzed environment variables vars = list(LIBC_VARIABLES) if False: # AVOID libc bugs vars.remove('LD_HWCAP_MASK') vars.remove('MALLOC_TOP_PAD_') if True: var = EnvVarInteger(vars, max_count=self.options.max_count) elif False: var = EnvVarLength(vars, max_length=4096, max_count=self.options.max_count) elif False: var = EnvVarRandom(vars, max_count=self.options.max_count, max_length=200, bytes_set = LETTERS | PUNCTUATION) else: var = EnvVarRandom(vars, max_length=2000, max_count=self.options.max_count) command = self.options.command process = ProjectProcess(project, command) process.env.add(var) # Watch process failure with its PID WatchProcess(process) # Watch process failure with its text output stdout = WatchStdout(process) stdout.words['failed'] = 0 # List generated from GNU libc 2.4 using shell command: # find -name "*.c"|xargs grep -H 'getenv *("'\ # | sed 's/^.*getenv *("\([A-Z_0-9]*\)".*$/\1/'\ # | sort -u LIBC_VARIABLES = ( 'ARGP_HELP_FMT', 'CHARSET', 'COREFILE', 'CRASHSERVER', 'DATEMSK', 'GCONV_PATH', 'GETCONF_DIR', 'GMON_OUT_PREFIX', 'HES_DOMAIN', 'HESIOD_CONFIG', 'HOME', 'HOSTALIASES', 'HZ', 'I18NPATH', 'IFS', 'LANG', 'LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LD_BIND_NOT', 'LD_BIND_NOW', 'LD_DYNAMIC_WEAK', 'LD_HWCAP_MASK', 'LD_LIBRARY_PATH', 'LD_PROFILE_OUTPUT', 'LD_WARN', 'LIBC_FATAL_STDERR_', 'LOCALDOMAIN', 'LOCPATH', 'MALLOC_CHECK_', 'MALLOC_MMAP_MAX_', 'MALLOC_MMAP_THRESHOLD_', 'MALLOC_PERTURB_', 'MALLOC_TOP_PAD_', 'MALLOC_TRIM_THRESHOLD_', 'MEMUSAGE_BUFFER_SIZE', 'MEMUSAGE_NO_TIMER', 'MEMUSAGE_OUTPUT', 'MEMUSAGE_PROG_NAME', 'MEMUSAGE_TRACE_MMAP', 'MSGVERB', 'NIS_DEFAULTS', 'NIS_GROUP', 'NIS_PATH', 'NLSPATH', 'OUTPUT_CHARSET', 'PATH', 'PCPROFILE_OUTPUT', 'POSIXLY_CORRECT', 'PWD', 'RES_OPTIONS', 'SEGFAULT_OUTPUT_NAME', 'SEGFAULT_SIGNALS', 'SEGFAULT_USE_ALTSTACK', 'SEV_LEVEL', 'SOMETHING_NOBODY_USES', 'TIMEOUTFACTOR', 'TMPDIR', 'TZ', 'TZDIR', 'X', 'XYZZY', ) if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/notworking/fusil-linux-proc0000775000175000017500000000511012254531067022046 0ustar haypohaypo00000000000000#!/usr/bin/env python """ Write random data in /proc/PID/* files """ from fusil.application import Application from fusil.process.create import ProjectProcess from fusil.process.watch import WatchProcess from fusil.process.stdout import WatchStdout from fusil.project_agent import ProjectAgent from fusil.bytes_generator import BytesGenerator from fusil.linux.syslog import Syslog from os.path import join as path_join from random import choice from errno import ENOENT, EACCES, EINVAL, EPERM class Fuzzer(Application): NAME = "proc" def setupProject(self): project = self.project # project.session_timeout = 1.0 process = ProjectProcess(project, ['/bin/bash'], timeout=5.0) AttackProc(project) WatchProcess(process, timeout_score=0) WatchStdout(process) syslog = Syslog(project) for watch in syslog: watch.ignoreRegex('info="invalid command"') watch.show_not_matching = True class AttackProc(ProjectAgent): def __init__(self, project): ProjectAgent.__init__(self, project, "proc") self.generator = BytesGenerator(1, 256) def init(self): self.proc_keys = [ 'attr/current', 'attr/exec', 'attr/fscreate', 'attr/keycreate', 'attr/sockcreate', 'clear_refs', 'seccomp', # Strange keys #'mem', #'oom_adj', ] self.proc_path = None def on_process_create(self, agent): self.proc_path = "/proc/%s/" % agent.process.pid def live(self): if not self.proc_path: return self.info("Proc path: %s" % self.proc_path) key = choice(self.proc_keys) filename = path_join(self.proc_path, key) data = self.generator.createValue() self.info("Write data in %s: (len=%s) %r" % (filename, len(data), data)) try: output = open(filename, 'wb') output.write(data) output.close() except IOError as err: if err.errno in (EINVAL, EPERM): pass elif err.errno in (ENOENT, EACCES): self.error("Unable to write %s: %s" % (filename, err)) self.removeKey(key) else: raise def removeKey(self, key): self.proc_keys.remove(key) if not self.proc_keys: self.error("All /proc entries are invalid!") self.send('project_done') self.proc_path = None if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/notworking/fusil-libexif0000775000175000017500000000345512110032732021364 0ustar haypohaypo00000000000000#!/usr/bin/env python """ libexif fuzzer: use "exif picture.jpeg" command. Supported file formats: JPEG """ INCR_MANGLE = False from fusil.application import Application from fusil.process.mangle import MangleProcess from fusil.process.watch import WatchProcess from fusil.process.stdout import WatchStdout if INCR_MANGLE: from fusil.incr_mangle import IncrMangle else: from fusil.auto_mangle import AutoMangle class Fuzzer(Application): NAME = "libexif" USAGE = "%prog [options] image.jpg" NB_ARGUMENTS = 1 def setupProject(self): project = self.project orig_filename = self.arguments[0] if INCR_MANGLE: mangle = IncrMangle(project, orig_filename) mangle.operation_per_version = 25 mangle.max_version = 50 # FIXME: Only fuzz JPEG EXIF header #mangle.min_offset = 2 #mangle.max_offset = 555 else: AutoMangle(project, orig_filename) process = MangleProcess(project, ['exif', ""], "") WatchProcess(process, # exitcode_score=-0.50, exitcode_score=0, ) stdout = WatchStdout(process) stdout.min_nb_line = (3, -0.5) stdout.words['error'] = 0.10 # "Color Space |Internal error (unknown value 4097)." is not a fatal error stdout.ignoreRegex(r'unknown (value|data)') stdout.ignoreRegex(r'Unknown Exif version') stdout.addRegex(r'^Corrupt data', -1.0) stdout.addRegex(r'does not contain EXIF data!$', -1.0) stdout.addRegex(r'The data supplied does not seem to contain EXIF data.$', -1.0) stdout.addRegex(r'does not contain EXIF data!$', -1.0) stdout.addRegex(r'^Unknown encoding\.$', -1.0) if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/notworking/fusil-rpm0000775000175000017500000000775512110032732020547 0ustar haypohaypo00000000000000#!/usr/bin/env python """ librpm fuzzer using "rpm" command program. Inject errors in valid RPM file and then recompute MD5 and SHA1 checksums. """ from fusil.application import Application from fusil.process.mangle import MangleProcess from fusil.process.watch import WatchProcess from fusil.process.stdout import WatchStdout from fusil.mangle import MangleFile from array import array from md5 import md5 from sha import sha from hachoir_core import config as hachoir_config from hachoir_core.stream import StringInputStream from hachoir_parser import guessParser class Fuzzer(Application): NAME = "rpm" USAGE = "%prog [options] package.rpm" NB_ARGUMENTS = 1 def setupProject(self): project = self.project # Hachoir: be quiet! hachoir_config.quiet = True # Create mangle agent orig_filename = self.arguments[0] mangle = MangleRPM(project, orig_filename) mangle.config.max_op = 200 # Create rpm process process = MangleProcess(project, ['rpm', '-qpi', ''], '', timeout=10.0) # Create some probes WatchProcess(process, exitcode_score=0.10) stdout = WatchStdout(process) stdout.addRegex('memory allocation failed', 1.0) del stdout.words['error'] class MangleRPM(MangleFile): def useHachoirParser(self, parser): last = None sha1_offset = None md5_offset = None if "checksum" in parser: for item in parser.array('checksum/item'): last = item if ('tag' not in item) or ('offset' not in item): continue tag = item['tag'].value if tag == 269: sha1_offset = item['offset'].value elif tag == 1004: md5_offset = item['offset'].value if last: offset0 = (last.absolute_address + last.size) // 8 if sha1_offset is not None: self.sha1_offset = offset0 + sha1_offset if md5_offset is not None: self.md5_offset = offset0 + md5_offset if "header" in parser: header = parser['header'] self.header_offset = header.absolute_address // 8 self.filedata_offset = self.header_offset + (header.size // 8) def mangleData(self, data, index): data = MangleFile.mangleData(self, data, index) # Create Hachoir parser data_str = data.tostring() parser = guessParser(StringInputStream(data_str)) # Find checksum offets self.sha1_offset = None self.md5_offset = None self.header_offset = None self.filedata_offset = None if parser: self.useHachoirParser(parser) else: self.error("Unable to create Hachoir parser") # Recompute the checksums self.fixChecksums(data) return data def fixChecksums(self, data): if not self.header_offset: self.error("Unable to get header offset! (don't recompute checksums)") return if (self.md5_offset is not None) \ and (self.header_offset is not None): summary_data = data[self.header_offset:].tostring() checksum = md5(summary_data).digest() data[self.md5_offset:self.md5_offset+16] = array('B', checksum) else: self.error("Warning: Unable to get MD5 ckecksum or header offset! (don't recompute MD5 checksum)") if (self.sha1_offset is not None) \ and (self.header_offset is not None) \ and (self.filedata_offset is not None): summary_data = data[self.header_offset:self.filedata_offset].tostring() checksum = sha(summary_data).hexdigest() data[self.sha1_offset:self.sha1_offset+40] = array('B', checksum) else: self.error("Warning: Unable to get SHA-1, file data or header offset! (don't recompute SHA-1 checksum)") if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/notworking/fusil-apache0000775000175000017500000002434712254530540021177 0ustar haypohaypo00000000000000#!/usr/bin/env python """ Apache fuzzer: HTTP open connection. """ from fusil.application import Application from optparse import OptionGroup from ptrace.linux_proc import searchProcessesByName from fusil.process.attach import AttachProcessPID from fusil.file_watch import FileWatch # from hashlib import md5 from fusil.network.tcp_client import TcpClient from fusil.bytes_generator import ( BytesGenerator, LOWER_LETTERS, LETTERS, ASCII8, PRINTABLE_ASCII, DECIMAL_DIGITS, PUNCTUATION) from fusil.unicode_generator import UnixPathGenerator from ptrace.six.moves import range as xrange from random import choice, randint import re DEFAULT_HOST = '127.0.0.1' DEFAULT_PORT = 80 CLEANUP_REGEX = ( re.compile(r"((?:Date|Last-Modified|Content-Length|Keep-Alive): ).*", re.IGNORECASE | re.MULTILINE), re.compile(r"(
).*(
)", re.MULTILINE), re.compile(r"(The requested URL ).* (was not found)", re.MULTILINE), ) ANSWER_REGEX = re.compile(r"^HTTP/1.1 ([0-9]{3}) (.*)") ERROR_LOG_REGEX = re.compile(r"^\[[^]]+\] \[error\] \[client [^]]+\] ") class Fuzzer(Application): NAME = "apache" def createFuzzerOptions(self, parser): options = OptionGroup(parser, "Apacher fuzzer") options.add_option("--host", help="Apache Host name or IP (default: %s)" % DEFAULT_HOST, type="str", default=DEFAULT_HOST) options.add_option("--port", help="Apache port number (default: %s)" % DEFAULT_PORT, type="int", default=DEFAULT_PORT) options.add_option("--http-width", help="HTTP line width (default: 80)", type="int", default=80) options.add_option("--printable", help="Only use ASCII7 printable characters (32..127)", action="store_true") options.add_option("--webdav", help="Use also WEBDAV verbs (PUT, DELETE, PROPFIND, ...)", action="store_true") return options def setupProject(self): project = self.project project.session_timeout = 10.0 for pid in searchProcessesByName("apache2"): project.error("Found Apache process: %s" % pid) AttachProcessPID(project, pid) HttpClient(project, self.options, self.options.host, self.options.port, connect_timeout=1.0) error = FileWatch.fromFilename(project, '/var/log/apache2/error.log', 'end') error.cleanup_func = lambda text: ERROR_LOG_REGEX.sub("", text) error.ignoreRegex(r"^File does not exist: ") error.ignoreRegex(r"^Invalid URI in request ") error.ignoreRegex(r"^Client sent malformed Host header") access = FileWatch.fromFilename(project, '/var/log/apache2/access.log', 'end') access.ignoreRegex(r'^[^-]+- - \[[^]]+\] "(GET|POST|HEAD)') for log in (error, access): log.show_matching = True log.show_not_matching = True class CommonHeader: def __init__(self): self.headers = ( "Host", "User-Agent", "Accept", "Accept-Language", "Accept-Encoding", "Accept-Charset", "Keep-Alive", "Connection", "Cookie", "Referer", ) def createValue(self): return choice(self.headers) class Answer: def __init__(self, raw): self.raw = raw self.code = None self.message = None self.parse() def parse(self): match = ANSWER_REGEX.search(self.raw) if not match: return None self.code = int(match.group(1)) self.message = match.group(2).strip() class HttpClient(TcpClient): def __init__(self, project, options, host, port, **kw): TcpClient.__init__(self, project, host, port, **kw) self.options = options self.max_request_size = 49000 key_size = 400 self.key_generators = ( BytesGenerator(1, key_size, LETTERS | set("-")), CommonHeader(), ) self.uri_generator = UnixPathGenerator(100, absolute=True) self.verbs = ( "GET", "HEAD", "POST") if self.options.webdav: self.verbs += ( "PUT", "COPY", "MERGE", "DELETE", "CHECKOUT" "PROPFIND", "PROPPATCH", "CONNECT", "MKACTIVITY", "MKCOL", "REPORT", "OPTIONS") # See limit_req_fieldsize and DEFAULT_LIMIT_REQUEST_FIELDSIZE # in Apache source code # # why max-2? -1 for the nul byte and -1 to avoid the limit! self.max_header_size = 8190 - 2 if self.options.printable: charset = PRINTABLE_ASCII else: charset = ASCII8 self.value_generators = ( BytesGenerator(1, 20, LETTERS | DECIMAL_DIGITS | PUNCTUATION), UriGenerator(100), BytesGenerator(0, self.max_header_size, charset - set("\r\n")), ) self.min_nb_header = 1 # Apache hard limit: 100 self.max_nb_header = 6 self.checksums = set(( # Error 400 (GET) "18f151df7e0029b70d8c8cb18915524b", # Error 400 (HEAD) "8c74110d0c22403d1c9a2b87812d727c", # Error 404 (GET) "a230e5cdc2a1cb909f0e720ecfa0425c", # Error 404 (GET keepalive) "a387c51bbecd904318a6b5ccf1db7f07", # Error 404 (HEAD) "82c22eb36543805e036826ef1d22e1b0", # Error 404 (HEAD keepalive) "701edea58d4248a6f0860306eb9f1937", )) def init(self): TcpClient.init(self) self.score = None self.step = "send" def stopSession(self): self.closeSocket() self.send('session_stop') def createHeaders(self): # VERB uri = str(self.uri_generator.createValue()) verb = choice(self.verbs) yield ("%s %s HTTP/1.0" % (verb, uri),) # Headers nb_header = randint(self.min_nb_header, self.max_nb_header) for index in xrange(nb_header): generator = choice(self.key_generators) raw = generator.createValue() raw += ": " generator = choice(self.value_generators) raw += generator.createValue() size = self.max_header_size lines = [] while raw: if lines: raw = ' ' + raw width = min(self.options.http_width, size) if width < 1: break lines.append(raw[:width]) size -= width raw = raw[width:] yield lines def live(self): if not self.socket: return if self.step == "send": self.sendRequest() self.step = "recv" else: answer = self.recvBytes(timeout=4.0) self.processAnswer(answer) self.stopSession() def createRequest(self): size = self.max_request_size - 2 request = [] for lines in self.createHeaders(): header_len = sum( len(line) for line in lines ) + len(lines) if not request: header_len -= 1 if size < header_len: break size -= header_len request.extend(lines) request = "\n".join(request) + "\n\n" return request def createFile(self, filename): filename = self.session().createFilename(filename) return open(filename, "w") def sendRequest(self): request = self.createRequest() # Store the request output = self.createFile("request.txt") output.write(request) output.close() self.socket.setblocking(False) if not self.sendBytes(request): self.stopSession() return def sessionSuccess(self): self.score = 1.0 #self.send('project_stop') def processAnswer(self, answer): if not answer: self.error("Server doesn't answer!") self.sessionSuccess() return # Store the answer output = self.createFile("answer.txt") output.write(answer) output.close() answer = Answer(answer) if answer.code == 200: # Nothing interesting here return if answer.code: self.warning("Answer: code=%s, message=%r" % (answer.code, answer.message)) if answer.code in (400, 404): return self.sessionSuccess() # def replace(regs): # return ''.join(regs.groups()) # # clean_answer = answer.raw # for regex in CLEANUP_REGEX: # clean_answer = regex.sub(replace, clean_answer) # # checksum = md5(clean_answer).hexdigest() # display = clean_answer # if checksum not in self.checksums: # self.error("Unknown answer checksum! MD5=%s" % checksum) # log = self.error # else: # log = self.info # for line in display.splitlines(): # log("Answer: %r" % line) def getScore(self): return self.score class UriGenerator(BytesGenerator): def __init__(self, max_length): BytesGenerator.__init__(self, 1, max_length) self.domain_generator = BytesGenerator(2, 3, LOWER_LETTERS) self.host_part_generator = BytesGenerator(1, 10, LOWER_LETTERS | set("-")) self.path_generator = UnixPathGenerator(100) self.host_min_part = 1 self.host_max_part = 5 self.min_port = 1 self.max_port = 65535 self.protocols = ("http", "https", "ftp") def createValue(self, length=None): if length is None: length = self.createLength() uri = choice(self.protocols) + "://" # TODO: Username and password # Host parts = randint(self.host_min_part, self.host_max_part) uri += '.'.join( self.host_part_generator.createValue() for index in xrange(parts) ) uri += '.' + self.domain_generator.createValue() # Port if randint(0, 3) == 0: uri += ":%s" % randint(self.min_port, self.max_port) # Path uri += "/" size = length-len(uri) if 0 < size: uri += str(self.path_generator.createValue(size)) return uri if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/notworking/fusil-linux-syscall0000775000175000017500000001104612254530561022560 0ustar haypohaypo00000000000000#!/usr/bin/env python """ Linux syscall fuzzer: generate random syscalls Project based on "sysfuzz.c" fuzzer by Digital Dwarf Society http://www.digitaldwarf.be/ """ from fusil.application import Application from fusil.c_tools import FuzzyFunctionC, CodeC from fusil.process.create import CreateProcess from fusil.process.watch import WatchProcess from fusil.process.stdout import WatchStdout from ptrace.six.moves import range as xrange from random import choice, randint from fusil.project_agent import ProjectAgent from fusil.linux.syslog import Syslog from ptrace.syscall import SYSCALL_NAMES USERLAND_ADDRESS = "0x0804fd00" UNMAPPED_ADDRESS = "0x0000a000" # kernel addr, this is a guess ... should actually get a real one ... KERNEL_ADDRESS = "0xc01fa0b6" SYS_EXIT = 1 SYS_OLD_SELECT = 82 SYS_EPOLL_WAIT = 252 IGNORE_SYSCALLS = set(( 2, 120, 190, # fork, clone, vfork 29, 72, # pause, sigsuspend (suspend until signal send) 88, # reboot # 91, # munmap 113, # vm86old # 166, # vm86old, vm86: enter VM86 mode (virtual-8086 in Intel literature) 119, 173, # sigreturn, rt_sigreturn 162, # nanosleep SYS_EPOLL_WAIT, # epoll_wait 111, # vhangup )) class Fuzzer(Application): NAME = "syscall" def setupProject(self): project = self.project syscall = GenerateSyscall(project) syscall.fixed_arguments[SYS_EXIT] = {1: "0"} syscall.fixed_arguments[SYS_OLD_SELECT] = {5: "0"} syscall.syscalls = list(set(SYSCALL_NAMES.keys()) - IGNORE_SYSCALLS) process = SyscallProcess(project, name="syscall") WatchProcess(process, exitcode_score=0.10) stdout = WatchStdout(process) stdout.score_weight = 0.10 stdout.show_matching = True stdout.show_not_matching = True syslog = Syslog(project) for log in syslog: log.addRegex('syscall', 1.0) class SyscallProcess(CreateProcess): def on_syscall_program(self, program): self.cmdline.arguments = [program] self.createProcess() class Main(FuzzyFunctionC): def __init__(self, syscall): FuzzyFunctionC.__init__(self, "main", type="int", random_bytes=400) self.footer.append('return 0;') self.syscall = syscall def getarg(self, syscall, arg_index): try: return self.syscall.fixed_arguments[syscall][arg_index] except KeyError: pass state = randint(0, 5) if state == 0: return USERLAND_ADDRESS elif state == 1: return UNMAPPED_ADDRESS elif state == 2: return KERNEL_ADDRESS elif state == 3: return "%sU" % self.createInt32() elif state == 4: return "%s" % randint(-3, 5) else: return "&%s" % self.createRandomBytes()[0] class GenerateSyscall(ProjectAgent): def __init__(self, project): ProjectAgent.__init__(self, project, "syscall") # Syscall parameters self.syscalls = xrange(0, 255+1) self.fixed_arguments = {} def on_session_start(self): # Intialize some parameters self.buffer_count = 0 # Create program using C compiler code = CodeC() code.includes = [ "", "", "", "", "", ] main = Main(self) code.addFunction(main) main.variables.append('int ret') main.footer = ['exit(0);'] syscallnr = choice(self.syscalls) if syscallnr in SYSCALL_NAMES: syscall_name = SYSCALL_NAMES[syscallnr] syscall = "/* %s */ %s" % (syscall_name, syscallnr) else: syscall_name = "syscall<%s>" % syscallnr syscall = str(syscallnr) self.send('session_rename', syscall_name) arguments = [syscall] for index in xrange(1, 8+1): value = main.getarg(syscallnr, index) arguments.append("/* argument %s */ %s" % (index, value)) main.callFunction("syscall", arguments, "ret") main.add('if (errno) { perror("%s() error"); exit(1); }' % syscall_name) main.add(r'printf("%s() -> %%i (0x%%08x)\n", ret, (unsigned int)ret);' % syscall_name) session = self.session() self.c_filename = session.createFilename("syscall.c") self.program_filename = session.createFilename("syscall") code.compile(self, self.c_filename, self.program_filename) self.send('syscall_program', self.program_filename) if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/notworking/fusil-mysql0000775000175000017500000002332012254530106021107 0ustar haypohaypo00000000000000#!/usr/bin/env python """ Generate random SQL requets to MySQL daemon using "mysql" command line program. """ from __future__ import with_statement from fusil.application import Application from optparse import OptionGroup from fusil.process.create import CreateProcess from fusil.file_watch import FileWatch from fusil.process.watch import WatchProcess from fusil.process.attach import AttachProcess from fusil.process.stdout import WatchStdout from fusil.linux.syslog import Syslog from fusil.project_agent import ProjectAgent from fusil.bytes_generator import (BytesGenerator, LengthGenerator, LETTERS, DECIMAL_DIGITS, PUNCTUATION) from fusil.unicode_generator import IntegerGenerator from fusil.c_tools import quoteString from ptrace.six.moves import range as xrange from random import choice, randint DEBUG = False USE_STDOUT = True PROGRAM = 'mysql' HOST = 'localhost' LOGIN = 'root' class Fuzzer(Application): NAME = "mysql" def createFuzzerOptions(self, parser): options = OptionGroup(parser, "MySQL fuzzer") options.add_option("--host", help="MySQL host name or address (default: %s)" % HOST, type="str", default=HOST) options.add_option("--user", help="MySQL user name (default: %s)" % LOGIN, type="str", default=LOGIN) options.add_option("--password", "-p", help="MySQL password (default: no password)", type="str", default=None) options.add_option("--database", help="MySQL password (default: no database)", type="str", default=None) options.add_option("--mysql-program", help="MySQL program path (default: %s)" % PROGRAM, type="str", default=PROGRAM) options.add_option("--port", help="MySQL server port (default: use default port)", type="int", default=None) return options def setupProject(self): project = self.project # Some options sql = GenerateSQL(project, "sql") if DEBUG: sql.max_nb_instr = 1 # Watch mysqld process mysqld = AttachProcess(project, 'mysqld') mysqld.max_memory = 300*1024*1024 if USE_STDOUT: stdout = 'file' else: stdout = 'null' # MySQL client used to send fuzzy SQL arguments = [ self.options.mysql_program, '--no-beep', '--unbuffered', '--user', self.options.user, '--host', self.options.host, ] if self.options.password: arguments.append('--password=%s' % self.options.password) if self.options.database: arguments.extend(('--database', self.options.database)) if self.options.port: arguments.extend(('--port', str(self.options.port))) process = MysqlProcess(project, arguments, stdout) WatchProcess(process, exitcode_score=0.15, timeout_score=0.15) if USE_STDOUT: stdout = WatchStdout(process) stdout.addRegex(r'Access denied for user', -1.0) stdout.ignoreRegex(r'You have an error in your SQL syntax; check the manual') if not DEBUG: stdout.words['error'] = 0.10 else: stdout.words['error'] = 1.0 # Watch logs syslog = Syslog(project) mysql_log = FileWatch(project, open('/var/log/mysql/mysql.log'), 'mysql.log', start="end") # FileWatch(project, open('/var/log/mysql/mysql.err'), 'mysql.err', start="end"), logs = tuple(syslog) + (mysql_log,) for log in logs: log.words['mysqld'] = 1.0 class GenerateSQL(ProjectAgent): def __init__(self, project, name): ProjectAgent.__init__(self, project, name) self.smart_string_generator = BytesGenerator(0, 10, LETTERS | DECIMAL_DIGITS | set(' ')) self.string_generator = BytesGenerator(0, 40, LETTERS | DECIMAL_DIGITS | PUNCTUATION) self.random_string_generator = BytesGenerator(0, 200) self.character_generator = BytesGenerator(1, 1) self.digit_generator = BytesGenerator(1, 30, DECIMAL_DIGITS) self.integer_generator = IntegerGenerator(11) self.printf_set = list(LETTERS | set('%')) self.long_string = LengthGenerator(5000, 10000) self.functions = list(set(( # Tests 'COALESCE', 'GREATEST', 'ISNULL', 'INTERVAL', 'LEAST', 'IF', 'IFNULL', 'NULLIF', 'STRCMP', # Math 'ABS', 'ACOS', 'ASIN', 'ATAN', 'ATAN2', 'CEILING', 'CEIL', 'COS', 'COT', 'CRC32', 'DEGREES', 'EXP', 'FLOOR', 'LN', 'LOG', 'LOG2', 'LOG10', 'MOD', 'PI', 'POW', 'POWER', 'RADIANS', 'RAND', 'ROUND', 'SIGN', 'SQRT', 'TAN', 'TRUNCATE', # String 'ASCII', 'BIN', 'BIT_LENGTH', 'CHAR', 'CHAR_LENGTH', 'COMPRESS', 'CONCAT', 'CONCAT_WS', 'CONV', 'ELT', 'EXPORT_SET', 'FIELD', 'FIND_IN_SET', 'HEX', 'INSERT', 'INSTR', 'LCASE', 'LEFT', 'LENGTH', 'LOAD_FILE', 'LOCATE', 'LOWER', 'LPAD', 'LTRIM', 'MAKE_SET', 'MID', 'OCTET_LENGTH', 'ORD', 'QUOTE', 'REPEAT', 'REPLACE', 'REVERSE', 'RIGHT', 'RPAD', 'RTRIM', 'SOUNDEX', 'SPACE', 'SUBSTRING', 'SUBSTRING_INDEX', 'TRIM', 'UCASE', 'UNCOMPRESS', 'UNCOMPRESSED_LENGTH', 'UNHEX', 'UPPER', # Date 'ADDDATE', 'ADDTIME', 'CURDATE', 'CURRENT_DATE', 'CURTIME', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'DATE', 'DATEDIFF', 'DATE_FORMAT', 'DAY', 'DAYNAME', 'DAYOFMONTH', 'DAYOFWEEK', 'DAYOFYEAR', 'EXTRACT', 'FROM_DAYS', 'FROM_UNIXTIME', 'GET_FORMAT', 'HOUR', 'LAST_DAY', 'LOCALTIME', 'LOCALTIMESTAMP', 'MAKEDATE', 'MAKETIME', 'MICROSECOND', 'MINUTE', 'MONTH', 'MONTHNAME', 'NOW', 'PERIOD_ADD', 'PERIOD_DIFF', 'QUARTER', 'SECOND', 'SEC_TO_TIME', 'STR_TO_DATE', 'SUBDATE', 'SUBTIME', 'SYSDATE', 'TIME', 'TIMEDIFF', 'TIMESTAMP', 'TIMESTAMPADD', 'TIMESTAMPDIFF', 'TIME_FORMAT', 'TIME_TO_SEC', 'TO_DAYS', 'UNIX_TIMESTAMP', 'UTC_DATE', 'UTC_TIME', 'UTC_TIMESTAMP', 'WEEK', 'WEEKDAY', 'WEEKOFYEAR', 'YEAR', 'YEARWEEK', # Encryption 'AES_DECRYPT', 'AES_ENCRYPT', 'DECODE', 'ENCODE', 'DES_DECRYPT', 'DES_ENCRYPT', 'ENCRYPT', 'MD5', 'OLD_PASSWORD', 'PASSWORD', 'SHA', 'SHA1', # Information 'BENCHMARK', 'CHARSET', 'COERCIBILITY', 'COLLATION', 'CONNECTION_ID', 'CURRENT_USER', 'DATABASE', 'FOUND_ROWS', 'LAST_INSERT_ID', 'SESSION_USER', 'SYSTEM_USER', 'USER', 'VERSION', # Autres 'BIT_COUNT', 'FORMAT', 'GET_LOCK', 'INET_ATON', 'INET_NTOA', 'IS_FREE_LOCK', 'IS_USED_LOCK', 'MASTER_POS_WAIT', 'RELEASE_LOCK', 'UUID', ))) self.min_nb_arg = 0 self.max_nb_arg = 4 self.min_nb_instr = 1 self.max_nb_instr = 3 self.booleans = ('true', 'false') self.create_value = ( self.createCharacter, self.createString, self.createSmartString, self.createRandomString, self.createInteger, self.createFloat, self.createNull, self.createBoolean, self.createPrintf, # self.createLength, ) def createPrintf(self): count = randint(1, 20) format = ('%' + choice(self.printf_set) for index in xrange(count)) value = ''.join(format) return quoteString(value) def createString(self): value = self.string_generator.createValue() return quoteString(value) def createSmartString(self): value = self.smart_string_generator.createValue() return quoteString(value) def createRandomString(self): value = self.random_string_generator.createValue() return quoteString(value) def createCharacter(self): value = self.character_generator.createValue() return quoteString(value) def createInteger(self): return str(self.integer_generator.createValue()) def createFloat(self): return self.createInteger() + '.' + self.digit_generator.createValue() def createBoolean(self): return choice(self.booleans) def createNull(self): return 'NULL' def createValue(self): func = choice(self.create_value) return func() def createLength(self): return quoteString(self.long_string.createValue()) def createFunction(self): function = choice(self.functions) sql = [function, '('] nb_arg = randint(self.min_nb_arg, self.max_nb_arg) for index in xrange(1, nb_arg+1): if 1 < index: sql.append(', ') value = self.createValue() sql.append(value) sql.append(')') return ''.join(sql) def createInstr(self): return 'SELECT %s;' % self.createFunction() def createSQL(self): sql = [] nb_instr = randint(self.min_nb_instr, self.max_nb_instr) for index in xrange(nb_instr): sql.append(self.createInstr()) sql.append('') return sql def on_session_start(self): sql = '\n'.join(self.createSQL()) self.send('mysql_sql', sql) class MysqlProcess(CreateProcess): def init(self): CreateProcess.init(self) self.sql = None def createStdin(self): # Create stdin file filename = self.session().createFilename('mysql.sql') with open(filename, 'w') as fp: fp.write(self.sql) return open(filename, 'rb') def on_mysql_sql(self, sql): self.sql = sql self.createProcess() if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/notworking/fusil-linux-ioctl0000775000175000017500000002255312254530744022230 0ustar haypohaypo00000000000000#!/usr/bin/env python """ Linux ioctl() fuzzer. Project based on "ioctlfuzz.c" by Digital Dwarf Society http://www.digitaldwarf.be/ Vulnerabilities (not found using Fusil): Linux Kernel "isdn_net_setcfg()" Buffer Overflow Vulnerability http://secunia.com/advisories/27842/ """ from fusil.application import Application from fusil.c_tools import FuzzyFunctionC, CodeC, MIN_INT32, MAX_INT32 from fusil.process.create import CreateProcess from fusil.process.watch import WatchProcess from fusil.project_agent import ProjectAgent from fusil.linux.syslog import Syslog from random import choice, randint class Fuzzer(Application): NAME = "ioctl" def setupProject(self): project = self.project GenerateIOCTL(project) process = IoctlProcess(project, name="ioctl") WatchProcess(process, exitcode_score=0.0) syslog = Syslog(project) for log in syslog: log.show_matching = True log.show_not_matching = True class IoctlProcess(CreateProcess): def on_ioctl_program(self, program_filename): self.cmdline.arguments = [program_filename] self.createProcess() class Main(FuzzyFunctionC): def __init__(self): FuzzyFunctionC.__init__(self, "main", type="int", random_bytes=400) self.footer.append('return 0;') def getnumber(self): state = randint(0,4) if state == 0: return randint(MIN_INT32, MAX_INT32) elif state == 1: return (0xffffff00 | randint(0, 255)) elif state == 2: return 0x8000 elif state == 3: return 0xffff else: return 0x80000000 def getarg(self, arg_index): return "%sU" % self.getnumber() class GenerateIOCTL(ProjectAgent): def __init__(self, project): ProjectAgent.__init__(self, project, "ioctl") self.dev_filename = "/dev/snd/seq" self.requests = IOCTL_REQUESTS def on_session_start(self): self.ioctl_request = choice(self.requests) # Create C source code code = CodeC() code.includes = [ "", "", "", "", "", ] main = Main() code.addFunction(main) main.variables = ["int fd, ret"] main.add('fd = open("%s", O_RDWR)' % self.dev_filename) arguments = ['fd', "/* request# */ 0x%08X" % self.ioctl_request] for index in range(1, 8+1): value = main.getarg(index) arguments.append("/* argument %s */ %s" % (index, value)) main.callFunction('ioctl', arguments, 'ret') main.add('if (ret != 0) { perror("ioctl"); return 1; }') main.add("close(fd);") session = self.session() c_filename = session.createFilename("ioctl.c") program_filename = session.createFilename("ioctl") code.compile(self, c_filename, program_filename) self.send('ioctl_program', program_filename) IOCTL_REQUESTS = ( 0x00000000, 0x00000001, 0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000006, 0x00000007, 0x00000008, 0x0000000A, 0x0000000B, 0x0000000C, 0x0000000E, 0x00000010, 0x00000014, 0x00000015, 0x00000016, 0x00000017, 0x00000018, 0x00000019, 0x0000001B, 0x0000001C, 0x0000001E, 0x00000028, 0x00000301, 0x00000302, 0x00000304, 0x00000307, 0x00000308, 0x00000309, 0x0000030A, 0x0000030B, 0x0000031F, 0x00000321, 0x00000322, 0x00000323, 0x00000324, 0x00000325, 0x00000326, 0x000004D2, 0x000004D3, 0x000004D4, 0x000004D5, 0x000004D6, 0x000004D7, 0x000004D8, 0x000004D9, 0x000004DA, 0x000004DB, 0x000004DC, 0x00000601, 0x00000602, 0x00000604, 0x00000605, 0x00000606, 0x00000608, 0x00000609, 0x0000060A, 0x0000060B, 0x0000060C, 0x0000060D, 0x0000125D, 0x0000125E, 0x0000125F, 0x00001260, 0x00001261, 0x00001262, 0x00001263, 0x00002000, 0x00002001, 0x00004300, 0x00004B2F, 0x00004B30, 0x00004B31, 0x00004B32, 0x00004B33, 0x00004B34, 0x00004B35, 0x00004B36, 0x00004B37, 0x00004B3A, 0x00004B3B, 0x00004B3C, 0x00004B3D, 0x00004B40, 0x00004B41, 0x00004B44, 0x00004B45, 0x00004B46, 0x00004B47, 0x00004B48, 0x00004B49, 0x00004B4A, 0x00004B4B, 0x00004B4C, 0x00004B4D, 0x00004B4E, 0x00004B60, 0x00004B61, 0x00004B62, 0x00004B63, 0x00004B64, 0x00004B65, 0x00004B66, 0x00004B67, 0x00004B68, 0x00004B69, 0x00004B6A, 0x00004B6B, 0x00004B6C, 0x00004B70, 0x00004B71, 0x00005000, 0x00005001, 0x00005008, 0x0000500E, 0x00005100, 0x00005101, 0x00005111, 0x00005301, 0x00005302, 0x00005303, 0x00005304, 0x00005305, 0x00005306, 0x00005307, 0x00005308, 0x00005309, 0x0000530A, 0x0000530B, 0x0000530C, 0x0000530D, 0x0000530E, 0x0000530F, 0x00005310, 0x00005311, 0x00005312, 0x00005313, 0x00005314, 0x00005315, 0x00005316, 0x00005382, 0x00005383, 0x00005384, 0x00005385, 0x00005401, 0x00005402, 0x00005403, 0x00005404, 0x00005405, 0x00005406, 0x00005407, 0x00005408, 0x00005409, 0x0000540A, 0x0000540B, 0x0000540C, 0x0000540D, 0x0000540E, 0x0000540F, 0x00005410, 0x00005411, 0x00005412, 0x00005413, 0x00005414, 0x00005415, 0x00005416, 0x00005417, 0x00005418, 0x00005419, 0x0000541A, 0x0000541B, 0x0000541C, 0x0000541D, 0x0000541E, 0x0000541F, 0x00005420, 0x00005421, 0x00005422, 0x00005423, 0x00005424, 0x00005425, 0x00005426, 0x00005450, 0x00005451, 0x00005452, 0x00005453, 0x00005454, 0x00005455, 0x00005456, 0x00005457, 0x00005458, 0x00005459, 0x0000545A, 0x0000545B, 0x00005470, 0x00005471, 0x00005472, 0x00005473, 0x00005474, 0x00005490, 0x00005491, 0x00005492, 0x00005493, 0x00005494, 0x00005495, 0x00005497, 0x00005498, 0x00005499, 0x0000549A, 0x0000549B, 0x0000549C, 0x0000549D, 0x0000549E, 0x0000549F, 0x00005600, 0x00005601, 0x00005602, 0x00005603, 0x00005604, 0x00005605, 0x00005606, 0x00005607, 0x00005608, 0x00005609, 0x0000560A, 0x00007314, 0x00007315, 0x00007316, 0x00007317, 0x00008901, 0x00008902, 0x00008903, 0x00008904, 0x00008905, 0x00008906, 0x0000890B, 0x0000890C, 0x00008910, 0x00008911, 0x00008912, 0x00008913, 0x00008914, 0x00008915, 0x00008916, 0x00008917, 0x00008918, 0x00008919, 0x0000891A, 0x0000891B, 0x0000891C, 0x0000891D, 0x0000891E, 0x0000891F, 0x00008920, 0x00008921, 0x00008922, 0x00008923, 0x00008924, 0x00008925, 0x00008926, 0x00008927, 0x00008929, 0x00008930, 0x00008931, 0x00008932, 0x00008940, 0x00008941, 0x00008950, 0x00008951, 0x00008952, 0x00008960, 0x00008961, 0x00008962, 0x00008970, 0x00008971, 0x000089E0, 0x000089E1, 0x000089E2, 0x000089E3, 0x000089E4, 0x000089E5, 0x000089E6, 0x000089F0, 0x000089F1, 0x000089F2, 0x000089F3, 0x000089F4, 0x000089F5, 0x00009000, 0x00435901, 0x00435902, 0x00435903, 0x00435904, 0x00435905, 0x00435906, 0x00435907, 0x00435908, 0x00435909, 0x40045106, 0x40045108, 0x40045109, 0x4004510D, 0x4004510F, 0x40045407, 0x40045408, 0x40046602, 0x40047602, 0x40085112, 0x40086D01, 0x40144304, 0x40144305, 0x40206D05, 0x40285107, 0x4FA44308, 0x80027501, 0x80044D00, 0x80044D01, 0x80044D02, 0x80044D03, 0x80044D04, 0x80044D05, 0x80044D06, 0x80044D07, 0x80044D08, 0x80044D09, 0x80044D0A, 0x80044D0B, 0x80044D0C, 0x80044D0D, 0x80044D0E, 0x80044D0F, 0x80044D10, 0x80044D1C, 0x80044D1D, 0x80044D1E, 0x80044DFB, 0x80044DFC, 0x80044DFD, 0x80044DFE, 0x80044DFF, 0x80045002, 0x80045005, 0x80045006, 0x80045007, 0x8004500B, 0x80045104, 0x80045105, 0x8004510A, 0x8004510B, 0x80046601, 0x80046D03, 0x80047601, 0x800C500C, 0x800C500D, 0x801C6D02, 0x80206D04, 0x8FA44309, 0xC0044D00, 0xC0044D01, 0xC0044D02, 0xC0044D03, 0xC0044D04, 0xC0044D05, 0xC0044D06, 0xC0044D07, 0xC0044D08, 0xC0044D09, 0xC0044D0A, 0xC0044D0B, 0xC0044D0C, 0xC0044D0D, 0xC0044D0E, 0xC0044D0F, 0xC0044D10, 0xC0044D1C, 0xC0044D1D, 0xC0044D1E, 0xC0044DFF, 0xC0045002, 0xC0045003, 0xC0045004, 0xC0045005, 0xC0045006, 0xC0045007, 0xC0045009, 0xC004500A, 0xC0045103, 0xC004510E, 0xC0045401, 0xC0045405, 0xC0045406, 0xC0046D00, 0xC0046D01, 0xC0144302, 0xC0144303, 0xC0144306, 0xC0144307, 0xC0216D02, 0xC074510C, 0xC08C5102, 0xCFB04301, 0xCFB85001, 0xCFB85110, ) if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/fusil-gstreamer0000775000175000017500000001140512110032732017524 0ustar haypohaypo00000000000000#!/usr/bin/env python """ Gstreamer fuzzing project. """ INCR_MANGLE = False TIMEOUT = 5 GST_LAUNCH = 'gst-launch-0.10' NO_AUDIO = True NO_VIDEO = True VIDEO_EXTENSIONS = "avi,mkv,mov,mpg,mpeg,mp4" from fusil.application import Application from optparse import OptionGroup from fusil.process.mangle import MangleProcess from fusil.process.watch import WatchProcess from fusil.process.stdout import WatchStdout if INCR_MANGLE: from fusil.incr_mangle import IncrMangle else: from fusil.auto_mangle import AutoMangle from fusil.file_tools import filenameExtension class Fuzzer(Application): NAME = "gstreamer" USAGE = "%prog [options] filename" NB_ARGUMENTS = 1 def createFuzzerOptions(self, parser): options = OptionGroup(parser, "ImageMagick fuzzer") options.add_option("--playbin", help="Use playbin instead of decodebin", action="store_true") options.add_option("--gst-launch", help="gst-launch program path (default: %s)" % GST_LAUNCH, type="str", default=GST_LAUNCH) options.add_option("--timeout", help="Timeout in seconds (default: %s)" % TIMEOUT, type="float", default=TIMEOUT) options.add_option("--video", help="Enable video (default: use fakesink), always on with --playbin", action="store_true", default=False) options.add_option("--audio", help="Enable audio (default: use fakesink), always on with --playbin", action="store_true", default=False) options.add_option("--is-video", help="The file is a video (only used by decodebin) (default: guess using the filename extension)", action="store_true", default=False) options.add_option("--video-ext", help="Video filename extensions separated by commas (only used by decodebin) (default: %s)" % VIDEO_EXTENSIONS, type="str", default=VIDEO_EXTENSIONS) return options def setupProject(self): project = self.project # Profile parameters if self.options.audio: audio_sink = "alsasink" else: audio_sink = "fakesink" if self.options.video: video_sink = "xvimagesink" else: video_sink = "fakesink" # Create buggy input file orig_filename = self.arguments[0] if INCR_MANGLE: mangle = IncrMangle(project, orig_filename) mangle.max_size = 50*1024 # OGG #mangle.operation_per_version = 10 #mangle.max_version = 100 # WAVE #mangle.operation_per_version = 100 #mangle.max_version = 30 # AVI mangle.operation_per_version = 500 mangle.max_version = 50 else: mangle = AutoMangle(project, orig_filename) mangle.hard_max_op = 500 mangle.max_size = 10*1024*1024 # -f option: Do not install a fault handler arguments = [self.options.gst_launch, '-f'] if not self.options.playbin: arguments.extend(( "filesrc", "location=", "!", "decodebin", "name=decoder", "decoder.", "!", "queue", "!", "audioconvert", "!", "audioresample", "!", audio_sink, )) if self.isVideo(orig_filename): arguments.extend(( "decoder.", "!", "ffmpegcolorspace", "!", video_sink, )) else: arguments.extend(('playbin', 'uri=file://')) process = MangleProcess(project, arguments, "", use_relative_mangle=False, timeout=self.options.timeout) if self.options.video: process.setupX11() WatchProcess(process, exitcode_score=0.20, timeout_score=0.20) stdout = WatchStdout(process) stdout.words['error'] = 0.10 stdout.words['critical'] = 0.30 del stdout.words['assertion'] stdout.addRegex(r'Could not decode stream\.$', -1.0) stdout.addRegex(r'Could not (?:decode stream|determine type of stream|demultiplex stream)\.$', -1.0) stdout.addRegex(r'The stream is of a different type than handled by this element\.$', -1.0) stdout.addRegex(r'You might need to install the necessary plugins', 1.0) stdout.score_weight = 0.40 def isVideo(self, filename): if self.options.is_video: return True file_ext = filenameExtension(filename) if file_ext: file_ext = file_ext.lower() video_extensions = self.options.video_ext.lower() video_extensions = set(('.%s' % ext) for ext in video_extensions.split(',')) return (file_ext in video_extensions) if __name__ == "__main__": Fuzzer().main() fusil-1.5/fuzzers/fusil-php0000775000175000017500000004646312254530501016344 0ustar haypohaypo00000000000000#!/usr/bin/env python """ Generate PHP source code using random functions with random arguments. Use "php" command line program. """ from fusil.application import Application from fusil.process.create import CreateProcess from fusil.process.stdout import WatchStdout from fusil.process.tools import locateProgram from fusil.process.watch import WatchProcess from fusil.project_agent import ProjectAgent from fusil.bytes_generator import ( BytesGenerator, LengthGenerator, ASCII8, PRINTABLE_ASCII) from fusil.write_code import WriteCode from optparse import OptionGroup from ptrace.six import unichr, PY2 from random import choice, randint from weakref import ref import re DEBUG_PROJECT = True VARIABLES = 'abcde' class Fuzzer(Application): NAME = "php" def createFuzzerOptions(self, parser): options = OptionGroup(parser, "Python fuzzer") options.add_option("--php", help="PHP program path (default: php)", type="str", default="php") return options def setupProject(self): php_program = locateProgram(self.options.php) self.error("Use PHP interpreter: %s" % php_program) project = self.project php = PhpSource(project) # Don't kill any process! php.ignore_functions.add('posix_kill') # Avoid timeout php.ignore_functions.add('ftp_connect') php.ignore_functions.add('mysql_connect') php.ignore_functions.add('mysql_pconnect') php.ignore_functions.add('dns_get_mx') php.ignore_functions.add('getmxrr') php.ignore_functions.add('dns_check_record') php.ignore_functions.add('pfsockopen') php.ignore_functions.add('dns_get_record') php.ignore_functions.add('gethostbyname') php.ignore_functions.add('gethostbynamel') php.ignore_functions.add('ftp_ssl_connect') process = PhpProcess(project, [php_program, ''], timeout=10.0) watch = WatchProcess(process, exitcode_score=0.10) if watch.cpu: watch.cpu.score_weight = 0.3 stdout = WatchPhpStdout(process, php) stdout.max_nb_line = (10000, 1.0) del stdout.words['memory'] del stdout.words['exception'] stdout.addRegex('Call to undefined function', -1.0) if DEBUG_PROJECT: stdout.words['warning'] = 0.01 stdout.words['error'] = 0.01 stdout.words['fatal'] = 0.10 stdout.words['assert'] = 0.01 stdout.words['assertion'] = 0.01 stdout.ignoreRegex("Only variables can be passed by reference") stdout.ignoreRegex("^Fatal error: Can't use function return value in ") stdout.ignoreRegex("^Fatal error: Class .* not found") stdout.ignoreRegex("^Parse error:") stdout.ignoreRegex("sem_get.*Permission denied") else: stdout.score_weight = 0.3 def escapeCharacter(character): if PY2: code = ord(character) else: code = character if code == ord(b'\\'): # \ => \\ return u'\\\\' elif code == ord(b"'"): # ' => \' return u'\\"' elif 32 <= code <= 126: return unichr(code) else: return u'\\x%02X' % code def quoteString(text): escaped = u'' for code in text: escaped += escapeCharacter(code) return u"'%s'" % escaped class StringGenerator(BytesGenerator): def __init__(self, min_length, max_length, bytes_set): BytesGenerator.__init__(self, min_length, max_length, bytes_set) def _createValue(self, length): text = BytesGenerator._createValue(self, length) return quoteString(text) class WatchPhpStdout(WatchStdout): def __init__(self, process, php_source): WatchStdout.__init__(self, process) self.php_source = ref(php_source) self.regex = re.compile('Call to undefined function ([a-z0-9_]+)') def processLine(self, line): match = self.regex.search(line) if match: name = match.group(1) functions = self.php_source().functions if name in functions: functions.remove(name) self.error("Remove undefined function: %s (new function list length: %s)" % ( name, len(functions))) else: WatchStdout.processLine(self, line) class NullGenerator: def createValue(self): return u'null' class ReferenceGenerator: def createValue(self): return u"&$%s" % choice(VARIABLES) class BufferOverflow(LengthGenerator): def __init__(self, min, max): LengthGenerator.__init__(self, min, max) def _createValue(self, length): return u'str_repeat("a", %s)' % length class VariableGenerator: def createValue(self): return u"$%s" % choice(VARIABLES) class PhpSource(ProjectAgent, WriteCode): def __init__(self, project): ProjectAgent.__init__(self, project, "php_source") WriteCode.__init__(self) self.generators = [ StringGenerator(0, 10, ASCII8), StringGenerator(1, 40, PRINTABLE_ASCII), BufferOverflow(4*1024, 64*1024), NullGenerator(), ReferenceGenerator(), VariableGenerator(), ] self.min_arguments = 0 self.max_arguments = 10 self.min_instr = 1 self.max_instr = 10 self.ignore_functions = set() self.functions = set(PHP_FUNCTIONS) def createArgument(self, func_name, arg_index): while True: if func_name == 'sleep' and arg_index == 1: return '0' generator = choice(self.generators) value = generator.createValue() if func_name == 'isset' and (value == 'null' or value.startswith('&')): continue return value def writeFunction(self, name): if name not in ('print', 'die', 'eval', 'exit', 'isset', 'empty'): nb_argument = randint(self.min_arguments, self.max_arguments) else: nb_argument = 1 self.write(0, u'echo "%s();\\n";' % name) self.write(0, u'echo "Result: ";') self.write(0, u'var_dump(') self.write(1, name + u'(') for index in range(1, nb_argument+1): value = self.createArgument(name, index) text = u'/* %s */ ' % index + value if index < nb_argument: text += u',' self.write(2, text) self.write(1, u')') self.write(0, u');') def writeCode(self, filename, functions): self.createFile(filename) self.write(0, u'') self.close() def on_session_start(self): functions = list(self.functions - self.ignore_functions) filename = self.session().createFilename('source.php') self.writeCode(filename, functions) self.send('php_source', filename) class PhpProcess(CreateProcess): def on_php_source(self, filename): self.cmdline.arguments[1] = filename self.createProcess() # From "/usr/share/vim/vim71/syntax/php.vim" (vim 7.1) # From "Function and Methods ripped from php_manual_de.tar.gz Jan 2003" # # Skip many PHP extensions # Skip get_defined_functions, get_defined_vars, get_defined_constants PHP_FUNCTIONS = """array_change_key_case array_chunk array_combine array_count_values array_diff_assoc array_diff_uassoc array_diff array_fill array_filter array_flip array_intersect_assoc array_intersect array_key_exists array_keys array_map array_merge_recursive array_merge array_multisort array_pad array_pop array_push array_rand array_reduce array_reverse array_search array_shift array_slice array_splice array_sum array_udiff_assoc array_udiff_uassoc array_udiff array_unique array_unshift array_values array_walk array arsort asort compact count current each end extract in_array key krsort ksort list natcasesort natsort next pos prev range reset rsort shuffle sizeof sort uasort uksort usort bcadd bccomp bcdiv bcmod bcmul bcpow bcpowmod bcscale bcsqrt bcsub bzclose bzcompress bzdecompress bzerrno bzerror bzerrstr bzflush bzopen bzread bzwrite cal_days_in_month cal_from_jd cal_info cal_to_jd easter_date easter_days exit frenchtojd gregoriantojd jddayofweek jdmonthname jdtofrench jdtogregorian jdtojewish jdtojulian jdtounix jewishtojd juliantojd unixtojd call_user_method_array call_user_method class_exists get_class_methods get_class_vars get_class get_declared_classes get_object_vars get_parent_class is_a is_subclass_of method_exists ctype_alnum ctype_alpha ctype_cntrl ctype_digit ctype_graph ctype_lower ctype_print ctype_punct ctype_space ctype_upper ctype_xdigit checkdate date getdate gettimeofday gmdate gmmktime gmstrftime localtime microtime mktime strftime strtotime time dba_close dba_delete dba_exists dba_fetch dba_firstkey dba_handlers dba_insert dba_key_split dba_list dba_nextkey dba_open dba_optimize dba_popen dba_replace dba_sync chdir chroot dir closedir getcwd opendir readdir rewinddir scandir debug_backtrace debug_print_backtrace error_log error_reporting restore_error_handler set_error_handler trigger_error user_error escapeshellarg escapeshellcmd exec passthru proc_close proc_get_status proc_nice proc_open proc_terminate shell_exec system basename chgrp chmod chown clearstatcache copy dirname disk_free_space disk_total_space diskfreespace fclose feof fflush fgetc fgetcsv fgets fgetss file_exists file_get_contents file_put_contents file fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype flock fnmatch fopen fpassthru fputs fread fscanf fseek fstat ftell ftruncate fwrite glob is_dir is_executable is_file is_link is_readable is_uploaded_file is_writable is_writeable link linkinfo lstat mkdir move_uploaded_file parse_ini_file pathinfo pclose popen readfile readlink realpath rename rewind rmdir set_file_buffer stat symlink tempnam tmpfile touch umask unlink ftp_alloc ftp_cdup ftp_chdir ftp_chmod ftp_close ftp_connect ftp_delete ftp_exec ftp_fget ftp_fput ftp_get_option ftp_get ftp_login ftp_mdtm ftp_mkdir ftp_nb_continue ftp_nb_fget ftp_nb_fput ftp_nb_get ftp_nb_put ftp_nlist ftp_pasv ftp_put ftp_pwd ftp_quit ftp_raw ftp_rawlist ftp_rename ftp_rmdir ftp_set_option ftp_site ftp_size ftp_ssl_connect ftp_systype call_user_func_array call_user_func create_function func_get_arg func_get_args func_num_args function_exists register_shutdown_function register_tick_function unregister_tick_function bind_textdomain_codeset bindtextdomain dcgettext dcngettext dgettext dngettext gettext ngettext textdomain header headers_list headers_sent setcookie iconv_get_encoding iconv_mime_decode_headers iconv_mime_decode iconv_mime_encode iconv_set_encoding iconv_strlen iconv_strpos iconv_strrpos iconv_substr iconv ob_iconv_handler assert_options assert dl extension_loaded get_cfg_var get_current_user get_extension_funcs get_include_path get_included_files get_loaded_extensions get_magic_quotes_gpc get_magic_quotes_runtime get_required_files getenv getlastmod getmygid getmyinode getmypid getmyuid getopt getrusage ini_alter ini_get_all ini_get ini_restore ini_set memory_get_usage php_ini_scanned_files php_logo_guid php_sapi_name php_uname phpinfo phpversion putenv restore_include_path set_include_path set_magic_quotes_runtime set_time_limit version_compare zend_logo_guid zend_version ezmlm_hash mail abs acos acosh asin asinh atan2 atan atanh base_convert bindec ceil cos cosh decbin dechex decoct deg2rad exp expm1 floor fmod getrandmax hexdec hypot is_finite is_infinite is_nan lcg_value log10 log1p log max min mt_getrandmax mt_rand mt_srand octdec pi pow rad2deg rand round sin sinh sqrt srand tan tanh mb_convert_case mb_convert_encoding mb_convert_kana mb_convert_variables mb_decode_mimeheader mb_decode_numericentity mb_detect_encoding mb_detect_order mb_encode_mimeheader mb_encode_numericentity mb_ereg_match mb_ereg_replace mb_ereg_search_getpos mb_ereg_search_getregs mb_ereg_search_init mb_ereg_search_pos mb_ereg_search_regs mb_ereg_search_setpos mb_ereg_search mb_ereg mb_eregi_replace mb_eregi mb_get_info mb_http_input mb_http_output mb_internal_encoding mb_language mb_output_handler mb_parse_str mb_preferred_mime_name mb_regex_encoding mb_regex_set_options mb_send_mail mb_split mb_strcut mb_strimwidth mb_strlen mb_strpos mb_strrpos mb_strtolower mb_strtoupper mb_strwidth mb_substitute_character mb_substr_count mb_substr mime_content_type mysql_affected_rows mysql_client_encoding mysql_close mysql_connect mysql_data_seek mysql_db_name mysql_db_query mysql_errno mysql_error mysql_escape_string mysql_fetch_array mysql_fetch_assoc mysql_fetch_field mysql_fetch_lengths mysql_fetch_object mysql_fetch_row mysql_field_flags mysql_field_len mysql_field_name mysql_field_seek mysql_field_table mysql_field_type mysql_free_result mysql_get_client_info mysql_get_host_info mysql_get_proto_info mysql_get_server_info mysql_info mysql_insert_id mysql_list_dbs mysql_list_fields mysql_list_processes mysql_list_tables mysql_num_fields mysql_num_rows mysql_pconnect mysql_ping mysql_query mysql_real_escape_string mysql_result mysql_select_db mysql_stat mysql_tablename mysql_thread_id mysql_unbuffered_query connection_aborted connection_status constant define defined die eval get_browser highlight_file highlight_string ignore_user_abort pack show_source sleep uniqid unpack usleep checkdnsrr closelog define_syslog_variables dns_check_record dns_get_mx dns_get_record fsockopen gethostbyaddr gethostbyname gethostbynamel getmxrr getprotobyname getprotobynumber getservbyname getservbyport ip2long long2ip openlog pfsockopen socket_get_status socket_set_blocking socket_set_timeout syslog flush ob_clean ob_end_clean ob_end_flush ob_flush ob_get_clean ob_get_contents ob_get_flush ob_get_length ob_get_level ob_get_status ob_gzhandler ob_implicit_flush ob_list_handlers ob_start output_add_rewrite_var output_reset_rewrite_vars pcntl_exec pcntl_fork pcntl_signal pcntl_waitpid pcntl_wexitstatus pcntl_wifexited pcntl_wifsignaled pcntl_wifstopped pcntl_wstopsig pcntl_wtermsig preg_grep preg_match_all preg_match preg_quote preg_replace_callback preg_replace preg_split posix_ctermid posix_get_last_error posix_getcwd posix_getegid posix_geteuid posix_getgid posix_getgrgid posix_getgrnam posix_getgroups posix_getlogin posix_getpgid posix_getpgrp posix_getpid posix_getppid posix_getpwnam posix_getpwuid posix_getrlimit posix_getsid posix_getuid posix_isatty posix_kill posix_mkfifo posix_setegid posix_seteuid posix_setgid posix_setpgid posix_setsid posix_setuid posix_strerror posix_times posix_ttyname posix_uname ereg_replace ereg eregi_replace eregi split spliti sql_regcase ftok msg_get_queue msg_receive msg_remove_queue msg_send msg_set_queue msg_stat_queue sem_acquire sem_get sem_release sem_remove shm_attach shm_detach shm_get_var shm_put_var shm_remove_var shm_remove session_cache_expire session_cache_limiter session_decode session_destroy session_encode session_get_cookie_params session_id session_is_registered session_module_name session_name session_regenerate_id session_register session_save_path session_set_cookie_params session_set_save_handler session_start session_unregister session_unset session_write_close shmop_close shmop_delete shmop_open shmop_read shmop_size shmop_write socket_accept socket_bind socket_clear_error socket_close socket_connect socket_create_listen socket_create_pair socket_create socket_get_option socket_getpeername socket_getsockname socket_last_error socket_listen socket_read socket_recv socket_recvfrom socket_select socket_send socket_sendto socket_set_block socket_set_nonblock socket_set_option socket_shutdown socket_strerror socket_write stream_context_create stream_context_get_options stream_context_set_option stream_context_set_params stream_copy_to_stream stream_filter_append stream_filter_prepend stream_filter_register stream_get_contents stream_get_filters stream_get_line stream_get_meta_data stream_get_transports stream_get_wrappers stream_register_wrapper stream_select stream_set_blocking stream_set_timeout stream_set_write_buffer stream_socket_accept stream_socket_client stream_socket_get_name stream_socket_recvfrom stream_socket_sendto stream_socket_server stream_wrapper_register addcslashes addslashes bin2hex chop chr chunk_split convert_cyr_string count_chars crc32 crypt explode fprintf get_html_translation_table hebrev hebrevc html_entity_decode htmlentities htmlspecialchars implode join levenshtein localeconv ltrim md5_file md5 metaphone money_format nl_langinfo nl2br number_format ord parse_str print printf quoted_printable_decode quotemeta rtrim setlocale sha1_file sha1 similar_text soundex sprintf sscanf str_ireplace str_pad str_repeat str_replace str_rot13 str_shuffle str_split str_word_count strcasecmp strchr strcmp strcoll strcspn strip_tags stripcslashes stripos stripslashes stristr strlen strnatcasecmp strnatcmp strncasecmp strncmp strpos strrchr strrev strripos strrpos strspn strstr strtok strtolower strtoupper strtr substr_compare substr_count substr_replace substr trim ucfirst ucwords vprintf vsprintf wordwrap token_get_all token_name base64_decode base64_encode get_meta_tags http_build_query parse_url rawurldecode rawurlencode urldecode urlencode doubleval empty floatval get_resource_type gettype import_request_variables intval is_array is_bool is_callable is_double is_float is_int is_integer is_long is_null is_numeric is_object is_real is_resource is_scalar is_string isset print_r serialize settype strval unserialize unset var_dump var_export wddx_add_vars wddx_deserialize wddx_packet_end wddx_packet_start wddx_serialize_value wddx_serialize_vars utf8_decode utf8_encode xml_error_string xml_get_current_byte_index xml_get_current_column_number xml_get_current_line_number xml_get_error_code xml_parse_into_struct xml_parse xml_parser_create_ns xml_parser_create xml_parser_free xml_parser_get_option xml_parser_set_option xml_set_character_data_handler xml_set_default_handler xml_set_element_handler xml_set_end_namespace_decl_handler xml_set_external_entity_ref_handler xml_set_notation_decl_handler xml_set_object xml_set_processing_instruction_handler xml_set_start_namespace_decl_handler xml_set_unparsed_entity_decl_handler xmlrpc_decode_request xmlrpc_decode xmlrpc_encode_request xmlrpc_encode xmlrpc_get_type xmlrpc_parse_method_descriptions xmlrpc_server_add_introspection_data xmlrpc_server_call_method xmlrpc_server_create xmlrpc_server_destroy xmlrpc_server_register_introspection_callback xmlrpc_server_register_method xmlrpc_set_type zip_close zip_entry_close zip_entry_compressedsize zip_entry_compressionmethod zip_entry_filesize zip_entry_name zip_entry_open zip_entry_read zip_open zip_read gzclose gzcompress gzdeflate gzencode gzeof gzfile gzgetc gzgets gzgetss gzinflate gzopen gzpassthru gzputs gzread gzrewind gzseek gztell gzuncompress gzwrite readgzfile zlib_get_coding_type""".split() if __name__ == "__main__": Fuzzer().main() fusil-1.5/pyflakes.sh0000775000175000017500000000014612110032732015132 0ustar haypohaypo00000000000000#!/bin/bash pyflakes $(find fusil -name "*.py") examples/* fuzzers/fusil-* fuzzers/notworking/fusil-* fusil-1.5/lsall.sh0000775000175000017500000000015412110032732014422 0ustar haypohaypo00000000000000#!/bin/bash find fusil -name "*.py" ls -1 examples/* ls -1 fuzzers/fusil-* ls -1 fuzzers/notworking/fusil-* fusil-1.5/MANIFEST.in0000664000175000017500000000067012110032732014515 0ustar haypohaypo00000000000000include AUTHORS include ChangeLog include COPYING include doc/*.rst include doc/Makefile include examples/good-bye-world* include examples/hello-world* include examples/xterm* include fuzzers/notworking/fusil-* include graph.sh include IDEAS include INSTALL include lsall.sh include pyflakes.sh include MANIFEST.in include README include README.windows.txt include test_doc.py include tests/*.rst include tests/cmd_help/*.help include TODO fusil-1.5/examples/0000775000175000017500000000000012305626161014605 5ustar haypohaypo00000000000000fusil-1.5/examples/hello-world0000775000175000017500000000172612110032732016756 0ustar haypohaypo00000000000000#!/usr/bin/env python # The most simple fuzzer for Fusil: just starts the command: # echo "Hello World" # # Since the process is not watched, Fusil will kills the process after the # timeout (10 seconds by default). # Reuse objets from Fusil library from fusil.application import Application from fusil.process.create import ProjectProcess # Any fuzzer have to create a class based on Application class Fuzzer(Application): # Fuzzer name: short alphanumeric string NAME = "hello" # setupProject() is the main fuzzer function: # it creates the fuzzer agents def setupProject(self): # Create an agent: don't store the object, it's already done # in the agent constructor ProjectProcess(self.project, ['echo', 'Hello World!']) if __name__ == "__main__": # Create the fuzzer application and call its method main() # Fusil will parse the command line, create all agents, and start the # fuzzing project Fuzzer().main() fusil-1.5/examples/xterm0000775000175000017500000000217012110032732015657 0ustar haypohaypo00000000000000#!/usr/bin/env python """ Demontration of xterm bug: off-by-one error in memory allocation used to parse PATH environement variable. Bug fixed in xterm version 236: http://bugs.freedesktop.org/show_bug.cgi?id=16790 """ from fusil.application import Application from fusil.process.env import EnvVarLength from fusil.process.create import ProjectProcess from fusil.process.watch import WatchProcess from fusil.process.stdout import WatchStdout class Fuzzer(Application): NAME = "xterm" def setupProject(self): # Run "xterm ls" command with a timeout of one second process = ProjectProcess(self.project, ['xterm', 'ls'], timeout=1.0) # Project is using X11 (eg. setup environment variables) process.setupX11() # Ask Fusil to generate the environment variable "PATH" with a size in 0..1000 process.env.add(EnvVarLength('PATH', max_length=1000)) # Watch the created process # Since timeouts are meaningless, just ignore them (use nul score) WatchProcess(process, timeout_score=0) WatchStdout(process) if __name__ == "__main__": Fuzzer().main() fusil-1.5/examples/good-bye-world0000775000175000017500000000261212110032732017353 0ustar haypohaypo00000000000000#!/usr/bin/env python # Improved version of the Fusil "Hello World!": run the echo command with # random arguments and watch the created process (status and standard output). from fusil.application import Application from fusil.process.create import CreateProcess from fusil.bytes_generator import BytesGenerator, ASCII0 from fusil.process.watch import WatchProcess from fusil.process.stdout import WatchStdout from random import randint, choice class EchoProcess(CreateProcess): OPTIONS = ("-e", "-E", "-n") def __init__(self, project): CreateProcess.__init__(self, project, ["echo"]) self.datagen = BytesGenerator(1, 10, ASCII0) def createCmdline(self): arguments = ['echo'] for index in xrange(randint(3, 6)): if randint(1, 5) == 1: option = choice(self.OPTIONS) arguments.append(option) else: data = self.datagen.createValue() arguments.append(data) self.error("Command line=%s" % repr(arguments)) return arguments def on_session_start(self): self.cmdline.arguments = self.createCmdline() self.createProcess() class Fuzzer(Application): NAME = "goodbye" def setupProject(self): process = EchoProcess(self.project) WatchProcess(process) WatchStdout(process) if __name__ == "__main__": Fuzzer().main() fusil-1.5/TODO0000664000175000017500000000106512110032732013446 0ustar haypohaypo00000000000000Fusil TODO list =============== * setup.py: run 2to3 on docstrings and rst files ("2to3 -w -d . doc/*.rst tests/*.rst"). See also python3.0.rst * replay.py is unable to open a file as stdin, required by fusil-gimp * Factorize code responsible to rename the session on process exit (share code between Debugger and CreateProcess) * Protect the terminal using setsid(), setpgrp(), or setpgid() * Use initgroups() in CreateProcess? * Remove the class WatchProcess: move code to CreateProcess to avoid duplicate events (agent score) and duplicate code fusil-1.5/test_doc.py0000775000175000017500000000252612253715141015154 0ustar haypohaypo00000000000000#!/usr/bin/python from doctest import testfile, ELLIPSIS, testmod from sys import exit, path as sys_path from os.path import dirname def testDoc(filename, name=None): print("--- %s: Run tests" % filename) failure, nb_test = testfile( filename, optionflags=ELLIPSIS, name=name) if failure: exit(1) print("--- %s: End of tests" % filename) def importModule(name): mod = __import__(name) components = name.split('.') for comp in components[1:]: mod = getattr(mod, comp) return mod def testModule(name): print("--- Test module %s" % name) module = importModule(name) failure, nb_test = testmod(module) if failure: exit(1) print("--- End of test") def main(): fusil_dir = dirname(__file__) sys_path.append(fusil_dir) # Test documentation in doc/*.rst files testDoc('doc/c_tools.rst') testDoc('doc/file_watch.rst') testDoc('doc/mangle.rst') testDoc('doc/process.rst') # Unit tests as reST testDoc('tests/file_watch_read.rst') testDoc('tests/file_watch_ignore.rst') testDoc('tests/cmd_help_parser.rst') # Test documentation of some functions/classes testModule("fusil.bits") testModule("fusil.tools") testModule("fusil.process.replay_python") testModule("fusil.process.tools") if __name__ == "__main__": main() fusil-1.5/README0000664000175000017500000000543612110032732013644 0ustar haypohaypo00000000000000Fusil is a Python library used to write fuzzing programs. It helps to start process with a prepared environment (limit memory, environment variables, redirect stdout, etc.), start network client or server, and create mangled files. Fusil has many probes to detect program crash: watch process exit code, watch process stdout and syslog for text patterns (eg. "segmentation fault"), watch session duration, watch cpu usage (process and system load), etc. Fusil is based on a multi-agent system architecture. It computes a session score used to guess fuzzing parameters like number of injected errors to input files. Available fuzzing projects: ClamAV, Firefox (contains an HTTP server), gettext, gstreamer, identify, libc_env, libc_printf, libexif, linux_syscall, mplayer, php, poppler, vim, xterm. Website: http://bitbucket.org/haypo/fusil/wiki/Home Usage ===== Fusil is a library and a set of fuzzers called "fusil-...". To run a fuzzer, call it by its name. Example: :: $ fusil-gettext Fusil version 0.9.1 -- GNU GPL v2 http://bitbucket.org/haypo/fusil/wiki/Home (...) [0][session 13] Start session [0][session 13] ------------------------------------------------------------ [0][session 13] PID: 16989 [0][session 13] Signal: SIGSEGV [0][session 13] Invalid read from 0x0c1086e0 [0][session 13] - instruction: CMP EDX, [EAX] [0][session 13] - mapping: 0x0c1086e0 is not mapped in memory [0][session 13] - register eax=0x0c1086e0 [0][session 13] - register edx=0x00000019 [0][session 13] ------------------------------------------------------------ [0][session 13] End of session: score=100.0%, duration=3.806 second (...) Success 1/1! Project done: 13 sessions in 5.4 seconds (414.5 ms per session), total 5.9 seconds, aggresssivity: 19.0% Total: 1 success Keep non-empty directory: /home/haypo/prog/SVN/fusil/trunk/run-3 Features ======== Why using Fusil instead your own hand made C script? * Fusil limits child process environment: limit memory, use timeout, make sure that process is killed on session end * Fusil waits until system load is load before starting a fuzzing session * Fusil creates a session directory used as the process current working directory and Fusil only creates files in this directory (and not in /tmp) * Fusil stores all actions in fusil.log but also session.log for all actions related of a session * Fusil has multiple available probes to compute session score: guess if a sessions is a succes or not * Fusil redirects process output to a file and searchs bug text patterns in the stdout/stderr (Fusil contains many text patterns to detect crashes and problems) Installation ============ Read INSTALL documentation file. Documentation ============= Read doc/index.rst: documentation index. fusil-1.5/INSTALL0000664000175000017500000000223712254530231014017 0ustar haypohaypo00000000000000Fusil dependencies ================== * Python 2.6+ http://python.org/ * python-ptrace 0.7+ http://python-ptrace.hachoir.org/ * GCC needed by compileC() function from fusil.c_tools: http://gcc.gnu.org/ Optional dependencies: * rst2html program, part of docutils Python project Debian package: python-docutils http://docutils.sourceforge.net/ * Xlib Python module, required by fusil.xlib module and used by fusil-firefox: http://python-xlib.sourceforge.net/ Running on Windows: * win32api for Python: http://starship.python.net/crew/mhammond/win32/ Projects dependencies ===================== Each project may require external program or special environment: * Linux operating system: *linux_ioctl* and *linux_syscall* projects are specific to Linux * Mplayer program: needed by *mplayer* project * MySQL server and MySQL command line client: needed by *mysql* project * etc. Installation ============ Fusil uses the user "fusil" and the group "fusil" to run child processes to avoid remove an arbitrary file or kill an arbitrary process. Type as root: ./setup.py install Or using sudo program: sudo python setup.py install fusil-1.5/PKG-INFO0000664000175000017500000002677412305626161014104 0ustar haypohaypo00000000000000Metadata-Version: 1.1 Name: fusil Version: 1.5 Summary: Fuzzing framework Home-page: http://bitbucket.org/haypo/fusil/wiki/Home Author: Victor Stinner Author-email: UNKNOWN License: GNU GPL v2 Download-URL: http://bitbucket.org/haypo/fusil/wiki/Home Description: Fusil is a Python library used to write fuzzing programs. It helps to start process with a prepared environment (limit memory, environment variables, redirect stdout, etc.), start network client or server, and create mangled files. Fusil has many probes to detect program crash: watch process exit code, watch process stdout and syslog for text patterns (eg. "segmentation fault"), watch session duration, watch cpu usage (process and system load), etc. Fusil is based on a multi-agent system architecture. It computes a session score used to guess fuzzing parameters like number of injected errors to input files. Available fuzzing projects: ClamAV, Firefox (contains an HTTP server), gettext, gstreamer, identify, libc_env, libc_printf, libexif, linux_syscall, mplayer, php, poppler, vim, xterm. Website: http://bitbucket.org/haypo/fusil/wiki/Home Usage ===== Fusil is a library and a set of fuzzers called "fusil-...". To run a fuzzer, call it by its name. Example: :: $ fusil-gettext Fusil version 0.9.1 -- GNU GPL v2 http://bitbucket.org/haypo/fusil/wiki/Home (...) [0][session 13] Start session [0][session 13] ------------------------------------------------------------ [0][session 13] PID: 16989 [0][session 13] Signal: SIGSEGV [0][session 13] Invalid read from 0x0c1086e0 [0][session 13] - instruction: CMP EDX, [EAX] [0][session 13] - mapping: 0x0c1086e0 is not mapped in memory [0][session 13] - register eax=0x0c1086e0 [0][session 13] - register edx=0x00000019 [0][session 13] ------------------------------------------------------------ [0][session 13] End of session: score=100.0%, duration=3.806 second (...) Success 1/1! Project done: 13 sessions in 5.4 seconds (414.5 ms per session), total 5.9 seconds, aggresssivity: 19.0% Total: 1 success Keep non-empty directory: /home/haypo/prog/SVN/fusil/trunk/run-3 Features ======== Why using Fusil instead your own hand made C script? * Fusil limits child process environment: limit memory, use timeout, make sure that process is killed on session end * Fusil waits until system load is load before starting a fuzzing session * Fusil creates a session directory used as the process current working directory and Fusil only creates files in this directory (and not in /tmp) * Fusil stores all actions in fusil.log but also session.log for all actions related of a session * Fusil has multiple available probes to compute session score: guess if a sessions is a succes or not * Fusil redirects process output to a file and searchs bug text patterns in the stdout/stderr (Fusil contains many text patterns to detect crashes and problems) Installation ============ Read INSTALL documentation file. Documentation ============= Read doc/index.rst: documentation index. Changelog ========= Fusil 1.5 (2013-03-05) ---------------------- * experimental Python 3.3 support with the same code base; python 2.5 is no more supported * fusil-python: generate buffer objects and Unicode strings with surrogate characters * Change the default process memory limit from 100 MB to 500 MB Fusil 1.4 (2011-02-16) ---------------------- * Python 3 support * fusil-python: - improve function listing all Python modules: use sys.builtin_module_names and pkgutil.iter_modules() - blacklist more modules, classes and functions Fusil 1.3.2 (2010-01-09) ------------------------ * replay.py: set sys.path to ease the usage of Fusil without installing it * Fix fusil-gettext: ignore strace errors in locateMO() * fusil-python: - hide Python warnings - listAllModules() includes builtin modules - new option --only-c to test only modules written in C - fix memory leak: unload tested modules - fix getFunctions(): use also isclass() to detect classes * Disable Fusil process maximum memory limit Fusil 1.3.1 (2009-11-09) ------------------------ * fusil-python: autodiscover all modules instead of using a static list of modules, catch any exception when loading a module, only fuzz public functions (use module.__all__) * FileWatch: ignore duplicate parts on session rename * Remove session name parts duplicate (eg. "pickle-error-error" => "picke-error") * replay.py: don't redirect stdin to /dev/null if --ptrace is used * CPU probe: set max duration from 3 to 10 seconds (and rename the session on success) Fusil 1.3 (2009-09-18) ---------------------- * Create fusil-gimp * Remove charset from WriteCode: use builtin open() instead codecs.open() because files created by open() are much faster * Optimize FileWatch: don't recompile patterns at each session * fusil now depends on python-ptrace 0.6 * Don't use close_fds argument of subprocess.Popen() on Windows * Fix configuration reader: normal_calm_load, normal_calm_sleep, slow_calm_load, slow_calm_sleep keys global options are float, not integer * Project website moved to http://bitbucket.org/haypo/fusil/wiki/Home * FileWatch uses the pattern to rename the session Fusil 1.2.1 (2009-02-06) ------------------------ * Fix mangle agent of the Image Magick fuzzer * Fix AttachProcessPID() probe: stop the probe at process exit Fusil 1.2 (2009-02-04) ---------------------- User visible changes: * Fusil now requires Python 2.5 * Documentation: write an index (index.rst) and an user guide (usage.rst) * Replay script: copy HOME environment for GDB and catch setuid() error * fusil-firefox: support more file formats (bmp, gif, ico, png, svg), create --test command line option, write the HTML page into index.html file * fusil-python: write errors to stderr (instead of stdout) to avoid unicode error (especially with Python3) * FileWatch: rename the session with "long_output" if the program wrote more than max_nbline lines * fusil-python: blacklist posix.fork() to avoid false positive * If the process is killed by a signal, rename the session using the signal name (already worked if the debugger was disabled) Developer changes: * MangleAgent supports multiple input files * Create DummyMangle: agent with MangleFile API but don't touch file content to test the fuzzer * Network: close() method of NetworkClient and ServerClient use shutdown(SHUT_RDWR) * NetworkServer uses a backlog of 5 clients for socket.listen() (instead of 1) Bugfixes: * Fix Directory.rmtree() and replay script for Python 3.0 * Fix ServerClient.sendBytes(): use socket.send() result to get the next data offset Fusil 1.1 (2008-10-22) ---------------------- User visible changes: * replay.py: ask confirmation if the fuzzer will not be running under a different user or as root * Even with --force-unsafe, show safety warning if the fuzzer is running as the root user * Close files for child processes (close_fds=True) * Fix directory.rmtree() for Python 3.0 final Developer changes: * Create IntegerRangeGenerator in fusil.unicode_generator * Create EnvVarIntegerRange in fusil.process.env * Create fusil-wizzard fuzzer * Write timestamp in session.log * Add session() method to ProjectAgent * Add NAME attribute to a fuzzer, reused to choose the project directory name Bugfixes: * Fix Debugger.processSignal(): use the process agent to send the message (session_rename) since the debugger agent may be disabled * Fix replay.py: quote gdb arguments escape quote and antislash characters (eg. "text=\"Hello\\n\".") * replay.py uses /dev/null for stdin as Fusil does * FileWatch: open file in binary mode to use bytes in Python3 Fusil 1.0 final (2008-09-13) ---------------------------- Visible changes: * Create fusil-zzuf fuzzer (use the zzuf library) * Create fusil-vlc fuzzer (VLC media player) * For each session, generate a Python script (replay.py) to replay the session. The script can run the target in gdb, valgrind or gdb.py (python-ptrace debugger), with many options (--user, --limit, etc.) * Create --force-unsafe option, like --unsafe without the confirmation * CreateProcess is now a probe (with a score): if the debugger catchs a fatal signal, the session stops * Always use a null device as stdin for child processes to avoid blocking the fuzzer if the process reads stdin (eg. call getchar()) * Write the created process identifier in the logs Developer: * Create EnvVarIntegerRange: environment variable with an integer value in a fixed range * Changes to get a minimal Windows support: disable "change user/group" feature on Windows; remove log file before removing the project directory; use ":NUL" instead of /dev/null for null input/output * On setupProject() error, make sure that the project is cleaned * Close stdout files (input and output) at process exit (fix needed by Windows) * Rename long2raw() to uint2bytes(), and bytes2long() to bytes2uint() * Normalize score that make sure that a probe score is in range [-1; +1] and so that score*weight is in range[-weight; +weight] * CodeC: remove method lines(), writeCode() is renamed writeIntoFile(), use unicode strings (instead of byte strings) * Remove StdoutFile class, code merged in CreateProcess Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Operating System :: OS Independent Classifier: Natural Language :: English Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 fusil-1.5/AUTHORS0000664000175000017500000000025112110032732014022 0ustar haypohaypo00000000000000Main developers =============== Victor Stinner aka haypo Contributor =========== Geoffroy Couprie aka geal fusil-1.5/setup.cfg0000664000175000017500000000007312305626161014610 0ustar haypohaypo00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 fusil-1.5/setup.py0000775000175000017500000000454412305626104014510 0ustar haypohaypo00000000000000#!/usr/bin/env python # Todo list to prepare a release: # - hg in # check that there is no incoming changes # - run: ./pyflakes.sh # - run: ./test_doc.py # - run: sudo bash -c "PYTHONPATH=$PWD ./fuzzers/fusil-gettext" # - edit fusil/version.py: check/set version # - edit ChangeLog: set release date # - hg ci # - hg tag fusil-x.y # - hg push # - ./setup.py sdist register upload # - upload the tarball to Python Package Index # - update the website home page (url, md5 and news) # # After the release: # - edit fusil/version.py: set version to n+1 # - edit ChangeLog: add a new empty section for version n+1 # - hg ci # - hg push from imp import load_source from os import path from sys import argv from glob import glob CLASSIFIERS = [ 'Intended Audience :: Developers', 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'License :: OSI Approved :: GNU General Public License (GPL)', 'Operating System :: OS Independent', 'Natural Language :: English', 'Programming Language :: Python', 'Programming Language :: Python :: 3', ] MODULES = ( "fusil", "fusil.linux", "fusil.mas", "fusil.network", "fusil.process", ) SCRIPTS = glob("fuzzers/fusil-*") def main(): if "--setuptools" in argv: argv.remove("--setuptools") from setuptools import setup use_setuptools = True else: from distutils.core import setup use_setuptools = False fusil = load_source("version", path.join("fusil", "version.py")) PACKAGES = {} for name in MODULES: PACKAGES[name] = name.replace(".", "/") with open('README') as fp: long_description = fp.read() with open('ChangeLog') as fp: long_description += fp.read() install_options = { "name": fusil.PACKAGE, "version": fusil.VERSION, "url": fusil.WEBSITE, "download_url": fusil.WEBSITE, "author": "Victor Stinner", "description": "Fuzzing framework", "long_description": long_description, "classifiers": CLASSIFIERS, "license": fusil.LICENSE, "packages": list(PACKAGES.keys()), "package_dir": PACKAGES, "scripts": SCRIPTS, } if use_setuptools: install_options["install_requires"] = ["python-ptrace>=0.7"] setup(**install_options) if __name__ == "__main__": main() fusil-1.5/tests/0000775000175000017500000000000012305626161014131 5ustar haypohaypo00000000000000fusil-1.5/tests/file_watch_read.rst0000664000175000017500000000163512110032732017755 0ustar haypohaypo00000000000000 >>> filename = 'test.txt' >>> from fusil.mockup import Project >>> from fusil.file_watch import FileWatch >>> from os import unlink >>> output = open(filename, 'wb') >>> def writeText(text): ... output.write(text) ... output.flush() ... >>> input = open(filename, 'rb') >>> project = Project() >>> watch = FileWatch(project, input, 'test') >>> watch.read_size = 1 >>> watch.init() >>> list(watch.readlines()) [] >>> writeText('da') >>> list(watch.readlines()) [] >>> writeText('t') >>> list(watch.readlines()) [] >>> writeText('a\n') >>> list(watch.readlines()) ['data'] >>> writeText('linea\nlineb\n') >>> list(watch.readlines()) ['linea', 'lineb'] >>> writeText('line1\nline2\nline') >>> list(watch.readlines()) ['line1', 'line2'] >>> writeText('3\n') >>> list(watch.readlines()) ['line3'] >>> unlink(filename) fusil-1.5/tests/cmd_help_parser.rst0000664000175000017500000000455612110032732020011 0ustar haypohaypo00000000000000Setup tests =========== >>> from os.path import join as path_join >>> def openTest(name): ... filename = path_join('tests', 'cmd_help', name) ... return open(filename) ... >>> from fusil.cmd_help_parser import CommandHelpParser >>> from StringIO import StringIO >>> def testcase(program): ... stdout = openTest(program + '.help') ... parser = CommandHelpParser(program) ... parser.parseFile(stdout) ... for option in parser.options: ... print option ... Test identify ============= >>> testcase('identify') -authenticate ARG1 -channel ARG1 -crop ARG1 -debug ARG1 -define ARG1 -density ARG1 -depth ARG1 -extract ARG1 -format "ARG1" -fuzz ARG1 -help -interlace ARG1 -limit ARG1 ARG2 -list ARG1 -log ARG1 -matte -monitor -ping -quiet -sampling-factor ARG1 -set ARG1 ARG2 -size ARG1 -strip -units ARG1 -verbose -version -virtual-pixel ARG1 Test gcc ======== >>> testcase('gcc') -pass-exit-codes --help --target-help -dumpspecs -dumpversion -dumpmachine -print-search-dirs -print-libgcc-file-name -print-file-name=ARG1 -print-prog-name=ARG1 -print-multi-directory -print-multi-lib -print-multi-os-directory ARG1 ARG2 -Wa,ARG1 -Wp,ARG1 -Wl,ARG1 -Xassembler ARG1 -Xpreprocessor ARG1 -Xlinker ARG1 -combine -save-temps -pipe -time -specs=ARG1 -std=ARG1 --sysroot=ARG1 -B ARG1 -b ARG1 -V ARG1 -v -E -S -c -o ARG1 -x ARG1 Test ls ======= >>> testcase('ls') -a --all -A --almost-all --author -b --escape --block-size=ARG1 -B --ignore-backups -c -C --color=ARG1 -d --directory -D --dired -f -F --classify --file-type --format=ARG1 --full-time -g -G --no-group -h --human-readable --si -H --dereference-command-line --dereference-command-line-symlink-to-dir --hide=ARG1 --indicator-style=ARG1 -i --inode -I ARG1 --ignore=ARG1 -k -l -L --dereference -m -n --numeric-uid-gid -N --literal -o -p ARG1 -q --hide-control-chars --show-control-chars -Q --quote-name --quoting-style=ARG1 -r --reverse -R --recursive -s --size -S --sort=ARG1 --time=ARG1 --time-style=ARG1 -t -T ARG1 --tabsize=ARG1 -u -U -v -w ARG1 --width=ARG1 -x -X -1 --lcontext -Z --context --scontext --help --version Test ping ========= >>> testcase('ping') -L -R -U -b -d -f -n -q -r -v -V -a -A -c ARG1 -i ARG1 -w ARG1 -p ARG1 -s ARG1 -t ARG1 -I ARG1 ARG2 ARG3 -M ARG1 ARG2 ARG3 -S ARG1 -T ARG1 ARG2 -Q ARG1 Test python ========= >>> testcase('python') -c ARG1 -m ARG1 -d -E -h -i -O -Q ARG1 -S -t -u -v -V -W ARG1 -x fusil-1.5/tests/file_watch_ignore.rst0000664000175000017500000000165212110032732020324 0ustar haypohaypo00000000000000 >>> from fusil.mockup import Project, Logger >>> from fusil.file_watch import FileWatch >>> from os import unlink >>> from StringIO import StringIO >>> logger = Logger(show=True) >>> project = Project(logger) >>> buffer = StringIO() >>> def writeText(text): ... buffer.write(text) ... >>> watch = FileWatch(project, buffer, 'test') >>> watch.show_matching = True >>> watch.show_not_matching = True >>> watch.ignoreRegex("[Hh]ello") >>> watch.ignoreRegex("hello") >>> watch.addRegex('XDSDFOS', 1.0) >>> watch.activate() >>> watch.init() >>> writeText("HELLO\nhello\nHello\n") >>> watch.live() Not matching line: 'HELLO' >>> writeText("this is an error\n") >>> watch.live() Match pattern 'error' (score 30.0%) in 'this is an error' >>> writeText("test pattern XDSDFOS\n") >>> watch.live() Match pattern 'XDSDFOS' (score 100.0%) in 'test pattern XDSDFOS' fusil-1.5/tests/cmd_help/0000775000175000017500000000000012305626161015704 5ustar haypohaypo00000000000000fusil-1.5/tests/cmd_help/python.help0000664000175000017500000000360512110032732020070 0ustar haypohaypo00000000000000usage: python [option] ... [-c cmd | -m mod | file | -] [arg] ... Options and arguments (and corresponding environment variables): -c cmd : program passed in as string (terminates option list) -d : debug output from parser (also PYTHONDEBUG=x) -E : ignore environment variables (such as PYTHONPATH) -h : print this help message and exit (also --help) -i : inspect interactively after running script, (also PYTHONINSPECT=x) and force prompts, even if stdin does not appear to be a terminal -m mod : run library module as a script (terminates option list) -O : optimize generated bytecode (a tad; also PYTHONOPTIMIZE=x) -OO : remove doc-strings in addition to the -O optimizations -Q arg : division options: -Qold (default), -Qwarn, -Qwarnall, -Qnew -S : don't imply 'import site' on initialization -t : issue warnings about inconsistent tab usage (-tt: issue errors) -u : unbuffered binary stdout and stderr (also PYTHONUNBUFFERED=x) see man page for details on internal buffering relating to '-u' -v : verbose (trace import statements) (also PYTHONVERBOSE=x) -V : print the Python version number and exit (also --version) -W arg : warning control (arg is action:message:category:module:lineno) -x : skip first line of source, allowing use of non-Unix forms of #!cmd file : program read from script file - : program read from stdin (default; interactive mode if a tty) arg ...: arguments passed to program in sys.argv[1:] Other environment variables: PYTHONSTARTUP: file executed on interactive startup (no default) PYTHONPATH : ':'-separated list of directories prefixed to the default module search path. The result is sys.path. PYTHONHOME : alternate directory (or :). The default module search path uses /pythonX.X. PYTHONCASEOK : ignore case in 'import' statements (Windows). fusil-1.5/tests/cmd_help/gcc.help0000664000175000017500000000652712110032732017311 0ustar haypohaypo00000000000000Usage: gcc [options] file... Options: -pass-exit-codes Exit with highest error code from a phase --help Display this information --target-help Display target specific command line options (Use '-v --help' to display command line options of sub-processes) -dumpspecs Display all of the built in spec strings -dumpversion Display the version of the compiler -dumpmachine Display the compiler's target processor -print-search-dirs Display the directories in the compiler's search path -print-libgcc-file-name Display the name of the compiler's companion library -print-file-name= Display the full path to library -print-prog-name= Display the full path to compiler component -print-multi-directory Display the root directory for versions of libgcc -print-multi-lib Display the mapping between command line options and multiple library search directories -print-multi-os-directory Display the relative path to OS libraries -Wa, Pass comma-separated on to the assembler -Wp, Pass comma-separated on to the preprocessor -Wl, Pass comma-separated on to the linker -Xassembler Pass on to the assembler -Xpreprocessor Pass on to the preprocessor -Xlinker Pass on to the linker -combine Pass multiple source files to compiler at once -save-temps Do not delete intermediate files -pipe Use pipes rather than intermediate files -time Time the execution of each subprocess -specs= Override built-in specs with the contents of -std= Assume that the input sources are for --sysroot= Use as the root directory for headers for headers and libraries -B Add to the compiler's search paths -b Run gcc for target , if installed -V Run gcc version number , if installed -v Display the programs invoked by the compiler -### Like -v but options quoted and commands not executed -E Preprocess only; do not compile, assemble or link -S Compile only; do not assemble or link -c Compile and assemble, but do not link -o Place the output into -x Specify the language of the following input files Permissible languages include: c c++ assembler none 'none' means revert to the default behavior of guessing the language based on the file's extension Options starting with -g, -f, -m, -O, -W, or --param are automatically passed on to the various sub-processes invoked by gcc. In order to pass other options on to these processes the -W options must be used. For bug reporting instructions, please see: . For Debian GNU/Linux specific bug reporting instructions, please see: . fusil-1.5/tests/cmd_help/ping.help0000664000175000017500000000044012110032732017476 0ustar haypohaypo00000000000000ping: invalid option -- - Usage: ping [-LRUbdfnqrvVaA] [-c count] [-i interval] [-w deadline] [-p pattern] [-s packetsize] [-t ttl] [-I interface or address] [-M mtu discovery hint] [-S sndbuf] [ -T timestamp option ] [ -Q tos ] [hop1 ...] destination fusil-1.5/tests/cmd_help/ls.help0000664000175000017500000001576612110032732017200 0ustar haypohaypo00000000000000Usage: ls [OPTION]... [FILE]... List information about the FILEs (the current directory by default). Sort entries alphabetically if none of -cftuvSUX nor --sort. Mandatory arguments to long options are mandatory for short options too. -a, --all do not ignore entries starting with . -A, --almost-all do not list implied . and .. --author with -l, print the author of each file -b, --escape print octal escapes for nongraphic characters --block-size=SIZE use SIZE-byte blocks -B, --ignore-backups do not list implied entries ending with ~ -c with -lt: sort by, and show, ctime (time of last modification of file status information) with -l: show ctime and sort by name otherwise: sort by ctime -C list entries by columns --color[=WHEN] control whether color is used to distinguish file types. WHEN may be `never', `always', or `auto' -d, --directory list directory entries instead of contents, and do not dereference symbolic links -D, --dired generate output designed for Emacs' dired mode -f do not sort, enable -aU, disable -lst -F, --classify append indicator (one of */=>@|) to entries --file-type likewise, except do not append `*' --format=WORD across -x, commas -m, horizontal -x, long -l, single-column -1, verbose -l, vertical -C --full-time like -l --time-style=full-iso -g like -l, but do not list owner -G, --no-group like -l, but do not list group -h, --human-readable with -l, print sizes in human readable format (e.g., 1K 234M 2G) --si likewise, but use powers of 1000 not 1024 -H, --dereference-command-line follow symbolic links listed on the command line --dereference-command-line-symlink-to-dir follow each command line symbolic link that points to a directory --hide=PATTERN do not list implied entries matching shell PATTERN (overridden by -a or -A) --indicator-style=WORD append indicator with style WORD to entry names: none (default), slash (-p), file-type (--file-type), classify (-F) -i, --inode with -l, print the index number of each file -I, --ignore=PATTERN do not list implied entries matching shell PATTERN -k like --block-size=1K -l use a long listing format -L, --dereference when showing file information for a symbolic link, show information for the file the link references rather than for the link itself -m fill width with a comma separated list of entries -n, --numeric-uid-gid like -l, but list numeric user and group IDs -N, --literal print raw entry names (don't treat e.g. control characters specially) -o like -l, but do not list group information -p, --indicator-style=slash append / indicator to directories -q, --hide-control-chars print ? instead of non graphic characters --show-control-chars show non graphic characters as-is (default unless program is `ls' and output is a terminal) -Q, --quote-name enclose entry names in double quotes --quoting-style=WORD use quoting style WORD for entry names: literal, locale, shell, shell-always, c, escape -r, --reverse reverse order while sorting -R, --recursive list subdirectories recursively -s, --size with -l, print size of each file, in blocks -S sort by file size --sort=WORD extension -X, none -U, size -S, time -t, version -v, status -c, time -t, atime -u, access -u, use -u --time=WORD with -l, show time as WORD instead of modification time: atime, access, use, ctime or status; use specified time as sort key if --sort=time --time-style=STYLE with -l, show times using style STYLE: full-iso, long-iso, iso, locale, +FORMAT. FORMAT is interpreted like `date'; if FORMAT is FORMAT1FORMAT2, FORMAT1 applies to non-recent files and FORMAT2 to recent files; if STYLE is prefixed with `posix-', STYLE takes effect only outside the POSIX locale -t sort by modification time -T, --tabsize=COLS assume tab stops at each COLS instead of 8 -u with -lt: sort by, and show, access time with -l: show access time and sort by name otherwise: sort by access time -U do not sort; list entries in directory order -v sort by version -w, --width=COLS assume screen width instead of current value -x list entries by lines instead of by columns -X sort alphabetically by entry extension -1 list one file per line SELINUX options: --lcontext Display security context. Enable -l. Lines will probably be too wide for most displays. -Z, --context Display security context so it fits on most displays. Displays only mode, user, group, security context and file name. --scontext Display only security context and file name. --help display this help and exit --version output version information and exit SIZE may be (or may be an integer optionally followed by) one of following: kB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y. By default, color is not used to distinguish types of files. That is equivalent to using --color=none. Using the --color option without the optional WHEN argument is equivalent to using --color=always. With --color=auto, color codes are output only if standard output is connected to a terminal (tty). The environment variable LS_COLORS can influence the colors, and can be set easily by the dircolors command. Exit status is 0 if OK, 1 if minor problems, 2 if serious trouble. Report bugs to . fusil-1.5/tests/cmd_help/identify.help0000664000175000017500000000351412110032732020361 0ustar haypohaypo00000000000000Version: ImageMagick 6.2.4 10/02/07 Q16 http://www.imagemagick.org Copyright: Copyright (C) 1999-2005 ImageMagick Studio LLC Usage: identify [options ...] file [ [options ...] file ... ] Where options include: -authenticate value decrypt image with this password -channel type apply option to select image channels -crop geometry cut out a rectangular region of the image -debug events display copious debugging information -define format:option define one or more image format options -density geometry horizontal and vertical density of the image -depth value image depth -extract geometry extract area from image -format "string" output formatted image characteristics -fuzz distance colors within this distance are considered equal -help print program options -interlace type type of image interlacing scheme -limit type value pixel cache resource limit -list type Color, Configure, Delegate, Format, Magic, Module, Resource, or Type -log format format of debugging information -matte store matte channel if the image has one -monitor monitor progress -ping efficiently determine image attributes -quiet suppress all error or warning messages -sampling-factor geometry horizontal and vertical sampling factor -set attribute value set an image attribute -size geometry width and height of image -strip strip image of all profiles and comments -units type the units of image resolution -verbose print detailed information about the image -version print version information -virtual-pixel method virtual pixel access method fusil-1.5/doc/0000775000175000017500000000000012305626161013534 5ustar haypohaypo00000000000000fusil-1.5/doc/mas.rst0000664000175000017500000000070312110032732015033 0ustar haypohaypo00000000000000Multi agent system (MAS) ======================== Univers agent is responsible to execute all agents. Univers is stopped using univers_stop() event. A session can be stopped using session_stop() event. Main MAS events: * project_start(): event received at first step but only for the first session of a project * session_start(): event received at first step on a session * session_done(score): event received at the last step of a session fusil-1.5/doc/agent.rst0000664000175000017500000000436512110032732015361 0ustar haypohaypo00000000000000Agent ===== An agent is an object able to send/receive messages to/from other agents. Agents have a limited vision of the whole application: agents do not see them each other. For pratical reasons, they have access to Application, Project and Session objets. Active or inactive ------------------ By default, an agent is inactive. It does not receive any event and is not allowed to send messages. Calling activate() method does active then agent and then call init() method. To disable an agent, use deactive() which calls deinit() method. Sum up of attributes and methods: - is_active: boolean - activate(): enable the agent, call init() - deactivate(): disable the agent, call deinit() - init(): create objects (eg. open file) - deinit(): destroy objects (eg. close file) live() ------ When an agent is active, it's live() method is called at each session step. The method have to be fastest as possible, so don't use any blocking function (eg. select() before read()). Events ------ Method related to message handling: - readMailbox(): read messages and call related agent event handler - send(event, \*arguments): send an event to other agents You don't have to call readMailbox(), this job is done by Session.executeObject(). To register to a message, just add a method to your class with the prototype:: def on_EVENT(self, *arguments): ... Example of method called on session start:: def on_session_start(self): ... Other attributes ----------------- - name (str): Agent name (should be unique in the whole application) - agent_id (int): Unique identifier in the whole application (integer counter starting at 1) - logger: Logger object used by methods debug(), info(), ... - mailbox: Mailbox used to store message until readMailbox() is called Logging ------- To write string to logger, use methods: - debug(message): DEBUG level - info(message): INFO level - warning(message): WARNING level - error(message): ERROR level Score ----- ProjectAgent and SessionAgent have a method getScore() which return the agent score. Default value is None (agent has no score). An agent has also 'score_weight' attribute (default value: 1.0) which is used to compute final agent score: minmax(-1.0, agent.getScore() * agent.score_weight, 1.0) fusil-1.5/doc/events.rst0000664000175000017500000000252212110032732015560 0ustar haypohaypo00000000000000++++++++++++ Fusil events ++++++++++++ An event can only be sent once in a session step (eg. you can not send session_stop event twice). Application =========== - application_done(): Fusil is done (exit) - application_interrupt(): Ask Fusil application to stop - application_error(message): Fatal Fusil error Project ======= - project_start(): Creation of the project - project_stop(): Ask to stop active project - project_session_destroy(): Destroy session and create a new session if we are not done Session ======= - session_start(): Creation of a new session - session_stop(): Ask session to stop - session_done(score): End of the active session, score is the final session score - session_success(): The session is a success, sent at the end of the session - session_rename('name'): Rename the session: all names are joined using '-' separator to rename the session directory Aggressivity ============ - aggressivity_value(value): New aggressivity value with -1.0 <= value <= 1.0 Process ======= - process_create(agent): New process created - process_stdout(agent, filename): Filename of the process stdout - process_exit(agent, status): Process finished (exited or killed by a signal) - process_pid(agent, pid): Attached process identifier MangleFile ========== - mangle_filenames(filenames): Generated filenames fusil-1.5/doc/process.rst0000664000175000017500000001354312110032732015737 0ustar haypohaypo00000000000000++++++++++++++++++++ Fusil process agents ++++++++++++++++++++ .. section-numbering:: .. contents:: Create your process =================== Today, Fusil only supports UNIX process. runCommand() ------------ runCommand() function from fusil.process.tools is a wrapper to os.system(). It raises a RuntimeError() if the command failed (exit code is different than zero). :: >>> from fusil.process.tools import runCommand >>> from fusil.mockup import Logger >>> logger = Logger() >>> runCommand(logger, ['echo', 'Hello World!']) CreateProcess and ProjectProcess -------------------------------- This action class does prepare the environment for a process and then use subprocess.Popen() to create the process object. ProjectProcess creates the process with 'session_start()' event whereas CreateProcess have to be inherited to add your own session handler calling self.createProcess() method. Attributes: * env: Environment() object used to create environment variables * stdin: if value is False, use a null device as stdin. * stdout: Stdout type: - 'file' (default): stdout and stderr are written in a file - 'null': use a a null device as stdout and stderr * cmdline: CommandLine() object used to create the command line * timeout: maximum execution duration of the process in second (default: 10 second). If the timeout is reached, the process is directly killed with SIGKILL signal. Use None value to disable timeout. * max_memory: Limit of memory grow in bytes (default: 100 MB). Use None value to disable memory limitation. * popen_args: Dictionary of supplementary options to Popen() constructor (default: see source code). Example: :: >>> from fusil.mockup import Project >>> project = Project() >>> from fusil.process.create import CreateProcess >>> null_stdout = CreateProcess(project, ['/bin/ls', '-lR'], ... stdout='null', timeout=None) ... Environment ----------- This class is responsible to create new process environment variables. It does copy some variables from Fusil environment and allow to set/generate some others. On Linux, no variable is copied. On Windows, only SYSTSEMROOT is copied. You may copy variables like LANGUAGE, LANG, PATH, HOME or DISPLAY using: :: env.copy('DISPLAY') Methods: * set(name, value): set a fixed value variable value * add(var): add a new fuzzy variable * copy(name): copy an environment variable value (only if it's set) * copyX11(): copy X11 environment variables (don't use directly! use process.setupX11()) To set/generate a variable, use on of these classes: * EnvVarValue: fixed value * EnvVarLength: generate long value to find buffer overflow. * EnvVarInteger: generate signed integer value * EnvVarIntegerRange(min, max): generate an integer value in the range [min; max] * EnvVarRandom: generate random bytes, use all byte values except nul byte (forbidden in environment variable value) Attributes (EnvVarValue has only name attribute): * name: Variable name, it can be a list * min_length: Minimum number of bytes (default: 0) * max_length: Maximum number of bytes (default: 2000) * bytes: bytes set (default: set('A')) Variable name is a string but it can be a tuple or list of strings. Examples: :: env.set('LANGUAGE', 'fr') env.set(('LANGUAGE', 'LANG', 'LC_ALL'), 'C', max_count=2) env.add(EnvVarLength('PATH')) env.add(EnvVarRandom('HOME')) env.add(EnvVarInteger(['COLUMNS', 'SIZE'])) Command line ------------ CreateProcess object has cmdline attribute of type CommandLine. This object has only one attribute: arguments which is a list of string. Linux graphical application (X11) --------------------------------- To be able to use a graphical application on Linux, use: :: >>> process = CreateProcess(project, ['ls', '-l']) >>> process.setupX11() It allows fuzzer process to access to X11 server and copy needed environment variables (HOME, DISPLAY and XAUTHORITY). See Environment.copyX11(). Watch process activity ====================== WatchProcess ------------ Watch the process created by CreateProcess: wait until the process exit or the process death (killed by a signal). It uses the exit status to compute the probe score: - if process time has been reached the timeout, probe score is: 'timeout_score' (default: 100%) - if exit code is nul, score is 'default_score' (default: 0%) - if exit code is not nul, score is 'exitcode_score' (default: 50%) - if process has been killed by a signal, score is 'signal_score' (default: 100%) WatchProcessStdout ------------------ WatchProcessStdout inherits on FileWatch_: it looks for error message patterns in process stdout (and stderr if process is configured to write stderr to stdout). .. _FileWatch: file_watch.html AttachProcessPID ---------------- Watch an existing process: find it using its identifier. Example: :: >>> from fusil.mockup import Project >>> from fusil.process.attach import AttachProcessPID >>> project = Project() >>> pid = 42 >>> process = AttachProcessPID(project, pid) AttachProcess ------------- Similar to AttachProcessPID but find the process using its name instead of its identifier. Example: :: >>> from fusil.mockup import Project >>> from fusil.process.attach import AttachProcess >>> project = Project() >>> AttachProcess(project, "clamav") CPU load probe: CpuProbe ======================== AttachProcess, AttachProcessPID and WatchProcess have 'cpu' attribute of type CpuLoad. If CPU load is bigger than maximum load during maximum duration, set score to 'max_score' (default: 100%). Default values: :: >>> from fusil.mockup import Project >>> project = Project() >>> from fusil.process.cpu_probe import CpuProbe >>> probe = CpuProbe(project, 'cpu') >>> probe.max_load 0.75 >>> probe.max_duration 10.0 >>> probe.max_score 1.0 fusil-1.5/doc/safety.rst0000664000175000017500000000161012110032732015544 0ustar haypohaypo00000000000000++++++++++++ Fusil safety ++++++++++++ Safe environment ================ To avoid computer crash, Fusil limits its own process but also child processes resources. Limitation to Fusil process are set in Application.init(): * Process priority: 19 * Limit CPU usage: project.step_sleep (in second, default: 10 millisecond) * Limit memory: appplication.max_memory (in bytes, default: 100 MB) * Limit time: project.session_timeout (in second, default: disabled). See also 'time.txt' documentation. Limitation of child process: * Process priority: 19 * Limit memory: default limit of 100 MB * Limit time: default timeout of 10 seconds Limitation of attached process: * Check used memory: default limit of 100 MB Be nice with CPU ================ Project waits until system CPU load is below 30% before creating a new session. It stops Fusil if system is not calm enough after 60 seconds. fusil-1.5/doc/configuration.rst0000664000175000017500000000363112110032732017125 0ustar haypohaypo00000000000000+++++++++++++++++++ Fusil configuration +++++++++++++++++++ You can configure Fusil using a fusil.conf file in your configuration directory ($XDG_CONFIG_HOME environment variable or ~/.config/). Template file: :: ############################################################### # General Fusil options ############################################################### [fusil] # Maximum number of session (0=unlimited) session = 0 # Maximum number of success before exit (0=unlimited) success = 1 # Minimum score for a successful session success_score = 0.50 # Maximum score for a session error error_score = -0.50 # Maximum memory in bytes (0=unlimited) max_memory = 0 # (Normal) Maximum system load normal_calm_load = 0.50 # (Normal) Seconds to sleep until system load is low normal_calm_sleep = 0.5 # (Slow) Maximum system load slow_calm_load = 0.30 # (Slow) Seconds to sleep until system load is low slow_calm_sleep = 3.0 # xhost program path (change X11 permissions) xhost_program = xhost ############################################################### # Debugger used to trace child processes ############################################################### [debugger] # Use the debugger? use_debugger = True # Enable trace forks option trace_forks = True ############################################################### # Child processes options ############################################################### [process] # Dump core on crash core_dump = True # Maximum user process (RLIMIT_NPROC) max_user_process = 10 # Default maximum memory in bytes (O=unlimited) max_memory = 104857600 # Change the user (setuid) user = fusil # Change the group (setgid) group = fusil # Use a probe to watch CPU activity use_cpu_probe = True fusil-1.5/doc/index.rst0000664000175000017500000000336012110032732015364 0ustar haypohaypo00000000000000+++++++++++++++++++ Documentation index +++++++++++++++++++ User documentation ================== Start with `Fusil usage guide`_: quick guide to learn how to execute a fuzzer. * configuration_: Fusil configuration file * safety_: Protection used in Fusil to avoid denial of service of your computer .. _`Fusil usage guide`: usage.html .. _configuration: configuration.html .. _safety: safety.html Fuzzer developer documentation ============================== Start with the `HOWTO: Write a fuzzer using Fusil`_ document: quick introduction to write your own fuzzer. Technical documents: * architecture_: List of the most common action and probe agents * c_tools_: Tools for C source code manipulation * file_watch_: Probe reading a text to search text patterns (eg. stdout) * mangle_: Inject errors in a valid file * process_: Create your process (create the command line, set environment variables, setup X11) and watch its activity * score_: Probe agent score and probe weight * time_: Session timeout and process execution time * network_: Network client and server agents .. _`HOWTO: Write a fuzzer using Fusil`: howto_write_fuzzer.html .. _architecture: architecture.html .. _c_tools: c_tools.html .. _file_watch: file_watch.html .. _mangle: mangle.html .. _process: process.html .. _score: score.html .. _time: time.html .. _network: network.html Multi agent system ================== * agent_: Agent API, Fusil is a multi-agent system * events_: List of the Fusil agent events * mas_: Description of the multi agent system .. _agent: agent.html .. _events: events.html .. _mas: mas.html Misc documents ============== * linux_process_limits_: Process limits supported by Linux kernel .. _linux_process_limits: linux_process_limits.html fusil-1.5/doc/time.rst0000664000175000017500000000147512110032732015220 0ustar haypohaypo00000000000000Time managment ============== Session timeout --------------- You can set maximum session duration using Project.session_timeout option (value in second). If session reachs the timeout, it's stopped. Logging ------- Messages are written with a timestamp. TimeWatch --------- To compute session score, you can use TimeWatch probe. It has two parameters: * too_fast: mininum duration of a valid session, faster session would have 'too_fast_score' score (default: -100%) * too_long: maximum duration of a valid session, slower session would have 'too_long_score' score (default: 100%) Process ------- A process have a default timeout set to 10 seconds. If the timeout is reached, the process is directly killed using SIGKILL signal and WatchProcess will use its 'timeout_score' attribute as score (default: 100%). fusil-1.5/doc/architecture.rst0000664000175000017500000000301512110032732016734 0ustar haypohaypo00000000000000++++++++++++++++++ Fusil architecture ++++++++++++++++++ Architecture ============ Fusil is a multi-agent system (MAS): it uses simple objets called "agents" exchanging messages though asynchronus "message (mail) transfer agent" (MTA). This architecture allows the whole project to be very modular and very customisable. Each agent have a live() method called at each session "step", but also event handler. An event has a name and may contains arguments. The name is used in agent method name: eg. "on_session_start()" method is called when the session starts. Some agents do change the environment and some other watchs for errors and strange behaviour of programs. Action agents ============= * CreateProcess: create a process * StdoutFile: created by CreateProcess to store process output * MangleFile: generate an invalid file using valid file * AutoMangle: MangleFile with autoconfiguration based on aggressivity factor Network: * NetworkClient / NetworkServer: network client / server * TcpClient: TCP network client * UnixSocketClient: UNIX socket client * HttpServer: HTTP server Probes ====== * FileWatch: watch a text file, search specific text patterns like "segmentation fault" * CpuProbe: watch CPU used by the process created by CreateProcess * ProcessTimeWatch: watch process execution duration * WatchStdout: watch process output (stdout) * WatchProcess: watch process created by CreateProcess * AttachProcess: watch running process * Syslog: watch /var/log/messages and /var/log/syslog files fusil-1.5/doc/mangle.rst0000664000175000017500000000505012110032732015516 0ustar haypohaypo00000000000000***************** Mangle valid file ***************** MangleFile ========== To fuzz file parser, you can use MangleFile agent. It takes one or multiple valid files on input and then injects errors to create invalid files. It can generate multiple files for each session. Operations ---------- * replace: replace a byte by a random byte * bit: invert one bit value * special_value: replace one or more bytes to write a special value, eg. four bytes: "0xFF 0xFF 0xFF 0xFF" * insert_bytes: insert one or more random bytes * delete_bytes: delete one or more bytes MangleConfig ------------ You can configure some options to help fuzzing using 'config' attribute of MangleFile. The value is an instance of MangleConfig class. Options: * min_op: Minimum number of mangle operations (default: 1) * max_op: Maximum number of mangle operations (default: 10) * operations: List of operation name (default: ["replace", "bit", "special_value"]) * max_insert_bytes: Maximum number of insered bytes (default: 8) * max_delete_bytes: Maximum number of deleted bytes (default: 8) * change_size: Allow operations which change data size (default: False) Truncate -------- You can limit maximum file size using 'max_size' attribute of MangleFile. The value is the maximum number of bytes read from input file. AutoMangle ========== AutoMangle is an helper to MangleFile: it tries to find the best parameters to fuzz the target using session aggressivity. Option attributes: * hard_min_op (default: 0): Minimum number of operations * hard_max_op (default: 10000): Maximum number of operations * fixed_size_factor (default: 1.0): ratio used to compute the number of operations depending on the file IncrMangle ========== IncrMangle is the incremental mangle agent. Whereas AutoMangle regenerates all errors for each session, IncrMangle keeps errors between the sessions and add some new errors. Option attributes: * operation_per_version: Maximum number of operations applied to new session * max_version: Maximum version number for a file, if a file is older max_version, the operations are truncated to a random number of versions * min_offset and max_offset (default None): Minimum and maximum file offset, both are optional (use None value) Default values: >>> from fusil.mockup import Project >>> project = Project() >>> from fusil.incr_mangle import IncrMangle >>> mangle = IncrMangle(project, 'filename') >>> mangle.operation_per_version 1 >>> mangle.max_version 25 >>> mangle.min_offset, mangle.max_offset (None, None) fusil-1.5/doc/network.rst0000664000175000017500000000165712110032732015755 0ustar haypohaypo00000000000000Network server ============== - from fusil.network.server import NetworkServer - from fusil.network.tcp_server import TcpServer - from fusil.network.http_server import HttpServer On new client connection, a ServerClient object is created. Network client ============== - from fusil.network.client import NetworkClient - from fusil.network.tcp_client import TcpClient - from fusil.network.unix_client import UnixClient TpcClient methods: * __init__(project, host, port, timeout=10.0): constructor * recvBytes(max_size=None, timeout=0.250, buffer_size=1024): Read max_size bytes by chunks of buffer_size bytes, stop after timeout seconds * sendBytes(bytes): send bytes on socket. Return False on error, True on success TpcClient attributes: * host: host name/IP address * port: port number * timeout: socket timeout (in second) * tx_bytes: number of bytes sent to host * socket: socket object (set to None on error) fusil-1.5/doc/linux_process_limits.rst0000664000175000017500000000204712110032732020534 0ustar haypohaypo00000000000000+++++++++ setrlimit +++++++++ Linux limits ============ * RLIMIT_AS: maximum size of the process’s virtual memory in bytes. * RLIMIT_CORE: Maximum size of core file. * RLIMIT_CPU: CPU time limit in seconds. * RLIMIT_FSIZE: Maximum size of files that the process may create. * RLIMIT_LOCKS: Combined number of flock() locks and fcntl() leases * RLIMIT_MEMLOCK: Maximum number of bytes of memory that may be locked into RAM. * RLIMIT_MSGQUEUE: Limit on the number of bytes that can be allocated for POSIX message queues * RLIMIT_NICE: Ceiling to which the process’s nice value can be raised * RLIMIT_NOFILE: Value one greater than the maximum file descriptor number that can be opened by this process. * RLIMIT_NPROC: The maximum number of processes that can be created * RLIMIT_RTPRIO: Ceiling on the real-time priority * RLIMIT_SIGPENDING: Limit on the number of signals that may be queued * RLIMIT_STACK: Maximum size of the process stack in bytes Not implemented in Linux ======================== * RLIMIT_DATA * RLIMIT_RSS fusil-1.5/doc/c_tools.rst0000664000175000017500000000574112110032732015724 0ustar haypohaypo00000000000000Tools for C code manipulation ============================= The fusil.c_tools module contains many tools to manipulation C code. String manipulation ------------------- >>> from fusil.c_tools import quoteString, encodeUTF32 >>> quoteString('Hello World\n\0') '"Hello World\\n\\0"' >>> encodeUTF32("Hello") 'H\x00\x00\x00e\x00\x00\x00l\x00\x00\x00l\x00\x00\x00o\x00\x00\x00' encodeUTF32() use host endian. Generate C script ----------------- Hello World! ++++++++++++ >>> from fusil.c_tools import CodeC >>> from sys import stdout >>> hello = CodeC() >>> hello.includes.append('') >>> main = hello.addMain() >>> main.callFunction('printf', [quoteString("Hello World\n")]) >>> hello.useStream(stdout) >>> hello.writeCode() #include int main() { printf( "Hello World\n" ); return 0; } FunctionC +++++++++ addMain() is an helper to create main() function, but you can write your own functions with addFunction() method: >>> from fusil.c_tools import FunctionC >>> testcode = CodeC() >>> test = testcode.addFunction( FunctionC('test', type='int') ) >>> test.variables.append('int x') >>> test.add('x = 1+1') >>> test.add('return x') >>> testcode.useStream(stdout) >>> testcode.writeCode() int test() { int x; x = 1+1; return x; } You can get the function with: >>> hello['main'] >>> testcode['test'] Write to a file =============== To write a code to a file, use writeIntoFile() method: >>> from pprint import pprint >>> hello.writeIntoFile('hello.c') >>> pprint(open('hello.c').readlines()) ['#include \n', '\n', 'int main() {\n', ' printf(\n', ' "Hello World\\n"\n', ' );\n', '\n', ' return 0;\n', '}\n', '\n'] Compile the code ================== To compile the code, use compile() method: >>> from os import system, WEXITSTATUS, unlink >>> from fusil.mockup import Logger >>> logger = Logger() >>> hello = CodeC() >>> main = hello.addMain(footer='return 2*3*7;') >>> hello.compile(logger, 'hello.c', 'hello') >>> WEXITSTATUS(system('./hello')) 42 >>> unlink('hello.c') >>> unlink('hello') Misc attributes =============== You can customize write() output: * 'indent' is the indententation string (default: 4 spaces) * 'eol' is the end of line string (default: "\n") Set gnu_source to True to get:: #define _GNU_SOURCE FuzzyFunctionC ============== Ok, let's play with fuzzing! FuzzFunctionC has methods to generate values. >>> from fusil.c_tools import FuzzyFunctionC >>> fuzzy = CodeC() >>> main = fuzzy.addFunction(FuzzyFunctionC('main', type='int')) Methods to generate data: * createInt32() * createInt() * createString() * createRandomBytes() Example: >>> main.add('return %s' % main.createInt()) fusil-1.5/doc/score.rst0000664000175000017500000000301412110032732015364 0ustar haypohaypo00000000000000Scoring system ============== Problematic ----------- Guess a fuzzing session success or failure is a complex task. We can use different parameters like process exit code, stdout, session duration, etc. But for each project, the meaning of the values may change. For some projects, session timeout is a success whereas you may ignore timeout for other projects. Fusil probes ------------ That's why Fusil use a scoring system. You can use multiple "probes" and each probe compute its own score. Session score is the sum of all scores. A probe score is a value between -1.0 and 1.0 where: * 1.0 is a success (eg. program crash) * 0.0 means "nothing special" * -1.0 means that the application just rejects your input, you may try next session with less noise Each probe score is normalized in -1.0..1.0 interval. Session score is not normalized, 130% value is allowed. You can also set a probe "weight" ('score_weight' attribute, default value: 1.0) to change its importance in session score (see example above). Example ------- Let's take a project with 4 probes: * WatchProcess(A) * WatchProcess(B) * TimeWatch: weight=0.5 (less important) * FileWatch: weight=2 (more important) At the end of the session, the scores are: * WatchProcess(A): score=0.25 * WatchProcess(B): score=None (no score) * TimeWatch: score=-0.10 * FileWatch: score=0.15 Session score is:: 0.25 + -0.10 * 0.5 + 0.15 * 2 = 0.50 Since minimum score for a success is 'project.success_score' (default: 50%), we can say that the session is a success! fusil-1.5/doc/Makefile0000664000175000017500000000024512110032732015162 0ustar haypohaypo00000000000000DOCS=$(wildcard *.rst) HTML=$(patsubst %.rst,%.html,$(DOCS)) RST2HTML=rst2html all: $(HTML) @echo $(HTML) %.html: %.rst $(RST2HTML) $< $@ clean: rm -f $(HTML) fusil-1.5/doc/file_watch.rst0000664000175000017500000001130612110032732016361 0ustar haypohaypo00000000000000+++++++++ FileWatch +++++++++ .. section-numbering:: .. contents:: FileWatch is a probe used by WatchProcessStdout agent to watch a plain text file. It looks for patterns in new inserted lines. Constructor ----------- Fake objects used for the documentation: >>> from fusil.mockup import Project, Logger >>> logger = Logger() >>> project = Project(logger) Constructor syntax: >>> from fusil.file_watch import FileWatch >>> log = FileWatch(project, open('README', 'rb'), 'README') Or just fromFilename() static method to only write the filename once: >>> log = FileWatch.fromFilename(project, 'README') If you watch server log, use start="end" to skip existing logs: >>> log = FileWatch.fromFilename(project, 'README', start='end') Ignore lines ------------ Use ignoreRegex() method to ignore lines: >>> from re import IGNORECASE >>> log.ignoreRegex('^error: meaningless error') >>> log.ignoreRegex('^ErrOR: another error', IGNORECASE) You can add your own ignore handler: >>> def ignoreNumber42(text): ... try: ... return int(text) == 42 ... except ValueError: ... return False ... >>> log.ignore.append(ignoreNumber42) >>> ignoreEmptyLine = lambda line: len(line.strip()) == 0 >>> log.ignore.append(ignoreEmptyLine) Words patterns -------------- 'words' patterns are case insensitive and only match 'word'. Example: "abc" pattern which match line "text: abc" but not "abcd". FileWatch includes many text patterns in 'words' attribute: >>> words = log.words.keys() >>> from pprint import pprint >>> words.sort(); pprint(words) [u'allocate', u'assert', u'assertion', u'bug', u"can't", u'could not', u'critical', u'error', u'exception', u'failed', u'failure', u'fatal', u'glibc detected', u'invalid', u'memory', u'not allowed', u'not valid', u'oops', u'overflow', u'panic', u'permission', u'pointer', u'segfault', u'segmentation fault', u'too large', u'unknown', u'warning'] Get/set pattern score: >>> print log.words['overflow'] 0.4 >>> log.words['overflow'] = 0.5 Regex patterns -------------- 'regexs' attribute is a list of regex, use addRegex() to add a regex: >>> log.addRegex('^Crash: ', 1.0) >>> log.addRegex('null pointer$', 1.0, flags=IGNORECASE) Patterns compilation -------------------- All patterns are compiled by createRegex() method on agent initialisation. It uses 'patterns' and 'words' attributes. Example: >>> log = FileWatch.fromFilename(project, 'README') >>> log.words = {'error': 0.5} >>> log.addRegex('mplayer', 1.0) >>> for pattern, score, match in log.compilePatterns(): ... print "%r, score %.1f%%, regex=%s" % (pattern, score, match) ... 'mplayer', score 1.0%, regex=... 'error', score 0.5%, regex=... Cleanup line ------------ You can register a function to cleanup lines: >>> log.cleanup_func = lambda text: text[7:] Test of the function: >>> # Prepare test >>> log.activate() >>> log.init() >>> log.show_not_matching = True; logger.show = True >>> # Example of line >>> log.processLine('PREFIX:Real line content') Not matching line: 'Real line content' >>> # Empty line >>> log.processLine('PREFIX:') >>> # Cleanup test >>> log.show_not_matching = False; logger.show = False Line number ----------- 'nb_line' contains the number of lines (without ignored lines) and 'total_line' the total number of lines. 'max_nb_line' attribute is the maximum number of total lines: (max, score). If 'nb_line' becomes bigger than max, score is incremented by score. Ignored lines are not included in 'nb_line'. Default value: >>> log.max_nb_line (100, 1.0) To disable the maximum of line number, set 'max_nb_line' to None. There is a similar option for the minimum number of line, but it's disabled by default (no minimum). Example to add -50% to the score if there is fewer than 10 lines of output: >>> log.min_nb_line = (10, -0.5) Pattern matching ---------------- For each text line, FileWatch calls processLine(). First it checks if the line matchs one ignore pattern. If not, it tries all patterns and uses the one with the biggest absolute score. >>> log.init() >>> log.processLine('This is an error') >>> print log.score 0.5 Attributes: - show_matching (default: False): use True to show matching lines (use ERROR log level instead of WARNING) - show_not_matching (default: False): use True to show not matching lines (--debug option enable this option) - log_not_matching (default: False): use True to log not matching lines. By default, lines are not logged because the output is already written to session "stdout" file. fusil-1.5/doc/usage.rst0000664000175000017500000001377312110032732015372 0ustar haypohaypo00000000000000++++++++++++++++ Fusil user guide ++++++++++++++++ .. section-numbering:: .. contents:: Introduction ============ Fusil projects contains a set of fuzzers called "fusil-..." (eg. fusil-firefox). Each fuzzer has its own command line options, use different input data (text, picture, video, etc.), and test a different program or library. For each crash, Fusil stores all files used to run the session in a dedicated directory and generate a script to replay the session (eg. reproduce a crash in gdb). The document presents how to run a fuzzer and analyze the crashs. Fuzzer list =========== Applications ------------ * fusil-clamav: ClamAV antivirus * fusil-firefox: Any content embedded in an HTML page in Firefox (JPEG/PNG pictures, video, Flash, etc.) * fusil-imagemagick: Image Magick (image manipulation), use identify or convert program * fusil-mplayer: Mplayer (audio/video player) * fusil-ogg123: Ogg/Vorbis music player, use the programs ogg123 (default) or ogginfo * fusil-vlc: VLC (audio/video player) Libraries --------- * fusil-gettext: Gettext library (text internationalization aka i18n), part of the GNU C library * fusil-gstreamer: Gstreamer (audio/video codec and player), use the program gst-launch-0.10 with playbin or build a pipeline * fusil-libc-printf: printf() function of the system C library * fusil-poppler: popper (PDF) library used by Evince and Kpdf programs Programming language -------------------- * fusil-php: PHP language * fusil-python: Python language Other ----- * fusil-wizzard: Generic Linux command line fuzzer * fusil-zzuf: wrapper to the zzuf fuzzer (mutation of input files and network sockets) Run a fuzzer ============ Example with the execution of gettext fuzzer: :: $ fusil-gettext Fusil version 0.9.1 -- GNU GPL v2 http://fusil.hachoir.org/ (...) [0][session 13] Start session [0][session 13] ------------------------------------------------------------ [0][session 13] PID: 16989 [0][session 13] Signal: SIGSEGV [0][session 13] Invalid read from 0x0c1086e0 [0][session 13] - instruction: CMP EDX, [EAX] [0][session 13] - mapping: 0x0c1086e0 is not mapped in memory [0][session 13] - register eax=0x0c1086e0 [0][session 13] - register edx=0x00000019 [0][session 13] ------------------------------------------------------------ [0][session 13] End of session: score=100.0%, duration=3.806 second (...) Success 1/1! Project done: 13 sessions in 5.4 seconds (414.5 ms per session), total 5.9 seconds, aggresssivity: 19.0% Total: 1 success Keep non-empty directory: /home/haypo/prog/SVN/fusil/trunk/run-3 A fuzzer does not require any human interaction: it generates data, start the target software, store files if the software crashed, and restart with new data. By default, the fuzzer stops after the first "success" (crash). You can interrupt the fuzzer with the key combinaison CTRL+c. Fuzzer options ============== Each fuzzer has its own options plus common Fusil options. Use --help option (eg. "fusil-firefox --help") to get the list of all available options. Common options: * --success=50: Stop after 50 success (default: 1) * --sessions=1000: Stop after 1000 sessions (default: unlimited) * --fast/--slow: Run faster/slower (more/less CPU intensive). WARNING: --fast may introduce false positive with some fuzzers. * -v or --verbose: Verbose mode, display more details about the fuzzer execution. Use --debug to get all messages, and --quiet for less messages. Analyze a crash =============== After a crash, Fusil stores all files used to execute the program in a dedicated directory. Go into this directory to analyze the crash. Directory names --------------- The directory tree looks like: :: gettext-4/ gettext-4/exitcode-1/ gettext-4/invalid_read-null/ gettext-4/invalid_read-null-2/ gettext-4/session-609/ The main directory name (gettext-4) is based on the fuzzer name (fusil-gettext in our example), and duplicate names ends with a number (gettext-2, gettext-3, ...). The name of a crash directory contains informations about the crash: * exitcode-1: gettext exited with the code 1 * invalid_read-null: gettext was killed because of an invalid memory read from the NULL addresss * session-609: Fusil doesn't know any useful information about the crash, the name is just the number of the session Duplicate directories have also a number as suffix (invalid_read-null-2, invalid_read-null-3, ...). List crash directories gives a global view of the crashs. You can also see duplicate crashs: invalid_read-null and invalid_read-null-2 should be the same bug. If the fuzzer is still running, you may see a temporary directory which will be destroyed at the end of the session (eg. session-130, session-131, session-132, ...). Read the session.log -------------------- A session directory always contains a file called "session.log" which contains events from the fuzzer. The file always contains useful informations about input data and usually also informations about the crash. Read the stdout --------------- Most fuzzers create a process. The process output, standard output and error streams (stdout/stderr), is written into a file called "stdout". It's the second most useful file to analyze a crash. The output may be truncated before the program crash because the output buffer was not flushed before the crash. fusil-python fuzzer uses the Python command line option "-u" to get unbuffered output. Replay the crash ---------------- To replay a crash, Fusil creates a script called replay.py to replay (reproduce) the crash. It starts the process with the same command line options, environment variables, but also the same process limitations (limit memory, start under a different user, ...) to avoid a denial of service of your computer. Just type "./replay.py" to replay the session. To get more information, you can run the process in the gdb debugger (--gdb option) or Valgrind (--valgrind). There are other options: use "./replay.py --help" to get the list of all options. fusil-1.5/doc/howto_write_fuzzer.rst0000664000175000017500000001342612110032732020240 0ustar haypohaypo00000000000000+++++++++++++++++++++++++++++++++ HOWTO: Write a fuzzer using Fusil +++++++++++++++++++++++++++++++++ .. section-numbering:: .. contents:: Identify your target ==================== Before writing a fuzzer, you have to analyze your target: identify input vectors and list methods to watch target activity. Identify input vectors ---------------------- Example of input vectors: - the command line - environment variables - files - sockets (TCP, UDP, UNIX, etc.) - etc. Some tools help in this job: - strace to watch syscalls (eg. "strace -e open program ...") - ltrace to watch library function calls (eg. "ltrace -e getenv program ...") - netstat to watch network connections (sockets) Watch target activity --------------------- Example target probes: - standard output: stdout and stderr - process exit status (exit code or signal used to kill the process) - logging file (eg. /var/log/clamav/clamav.log) - network ping - run the process in a debugger to trace faults (signals) - ... Hello world fuzzer ================== First draft ----------- Let's start with the most simple fuzzer. Create a file "hello.py" with this code: :: #!/usr/bin/env python from fusil.application import Application from fusil.process.create import ProjectProcess class Fuzzer(Application): def setupProject(self): ProjectProcess(self.project, ['echo', 'Hello World!']) if __name__ == "__main__": Fuzzer().main() Source code details: - it's a Python (executable) program - you have to inherit Application class and define the setupProject() method - setupProject() creates the project "agents" - the process agent is not stored in a variable: it's transparently register in Fusil agent list - main() method starts the fuzzer, activate agents, execute the different sessions, etc. Let's try this amazing fuzzer! Stop it using CTRL+c: :: $ chmod +x hello.py $ ./hello.py Fusil version 0.9.1 -- GNU GPL v2 http://fusil.hachoir.org/ Use directory: /home/haypo/prog/SVN/fusil/trunk/run-1 [0][session 1] Start session ^C [0][session 1] Project execution interrupted! [0][session 1] Project done: total 5.6 seconds, aggresssivity: 1.0% [0][session 1] Total: 0 success [0][session 1] Process 8210 exited normally Working example --------------- This fuzzer is useless because it doesn't know if the process is still active or not. We have to add some probes: WatchProcess to watch the exit status and WatchStdout to watch the standard output: :: from fusil.process.watch import WatchProcess from fusil.process.stdout import WatchStdout class Fuzzer(Application): def setupProject(self): process = ProjectProcess(self.project, ['echo', '"Hello World!"']) WatchProcess(process) WatchStdout(process) Let's retry with a maximum of 1 session: :: $ ./hello.py --sessions=1 Fusil version 0.9.1 -- GNU GPL v2 http://fusil.hachoir.org/ Use directory: /home/haypo/prog/SVN/fusil/trunk/run-1 [0][session 1] Start session [0][session 1] Process 8222 exited normally Stop! Limited to 1 sessions, use --session option for more Project done: 1 sessions in 0.1 seconds (63.8 ms per session), total 1.1 seconds, aggresssivity: 2.0% Total: 0 See "Process 8222 exited normally" line: the session stopped because the process is done. Real fuzzer ----------- Execute the echo program with a constant argument is not so funny, we have to add some random arguments. ProjectProcess can't be used because it creates automatically the process on session start. So we will use CreateProcess which allows to do some operations before the process start. The patch: :: from fusil.process.create import CreateProcess from fusil.bytes_generator import BytesGenerator, ASCII0 from random import randint, choice class EchoProcess(CreateProcess): OPTIONS = ("-e", "-E", "-n") def __init__(self, project): CreateProcess.__init__(self, project, ["echo"]) self.datagen = BytesGenerator(1, 10, ASCII0) def createCmdline(self): arguments = ['echo'] for index in xrange(randint(3, 6)): if randint(1, 5) == 1: option = choice(self.OPTIONS) arguments.append(option) else: data = self.datagen.createValue() arguments.append(data) return arguments def on_session_start(self): self.cmdline.arguments = self.createCmdline() self.createProcess() Details: - The most important method is on_session_start(): it's called when the event "session_start" is emitted. This method recreates the command line arguments and create the process. - createCmdline() creates random arguments for the echo program: 25% of options (-e, -E or -n) and 75% of random strings (ASCII characters in range 1..255) - __init__() is overwrite to create the datagen attribute - fusil.bytes_generator module contains tools (classes) to generate random byte strings Test examples ------------- See examples/ directory. It contains hello-world (first version of Hello World) and good-bye-world (working echo fuzzer). Inject data to your target ========================== Command line and environment variable ------------------------------------- See process.rst documentation. Files ----- See mangle.rst documentation. To write a C program, see c_tools.rst documentation Network ------- See network.rst documentation. Watch target activity ===================== Standard output and logging files --------------------------------- Use WatchStdout and FileWatch. See also file_watch.rst documentation. Process exit status ------------------- Use WatchProcess and read process.rst documentation. fusil-1.5/COPYING0000664000175000017500000004313312110032732014013 0ustar haypohaypo00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License.