#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
Nightmare Fuzzing Project generic test case minimizer
@author: joxean

@description: This generic test case minimizer works with text based 
files as well as with some Nightmare's specific mutators'.

With Nightmare's specific mutators, the generic minimized doesn't perform
any diffing by itself but, rather, reads the .diff files generated by
the mutator and replaces mutated data byte-per-byte. As of end of 2015,
it also supports generic text based documents and it works by simply 
removing lines. As simple as it sounds.

The generic algorithm using .diff files is as simple as this:

 1) Read the positions from the .diff file where changes were made.
 2) Read the modified bytes at those positions.
 3) Read the whole template file.
 4) Apply only one (byte) change at a time and check for a crash.
 5) If applying only one byte change doesn't work, consider that the 1st
   change applied to the file is mandatory. Apply it to the internally
   read template buffer and remove from the list of changes to check.
 6) Go back to 4.

In the future, the algorithm may be changed adding the following steps:

 * If a crashing change was found after applying a number of other small
   changes before try minimizing it even more by undoing the previously
   applied small patches.
   
Or the following "new" algorithm:

 * Start with all the changes applied. Remove one-per-one all of them
   until the process doesn't crash any more.

This generic test case minimizer have been extensively tested in Linux
and Windows. The most recommended debugging interface for Windows is the
pykd one.

The generic line based minimizer's algorithm (added at the end of 2015)
works in the following way:

 1) Starting from the 1st line, remove a random number of lines between
    1 to a maximum of "lines-percent" from the total file.
 2) If an exception (any) is still happening, remove these lines and 
    continue.
 3) If the change causes the target not to crash any more, restore the
    dropped lines, move to the next one and go back to 1).
 4) After the last line is reached, it then iterates again from line 0,
    but this time it removes a single line each time.

This behaviour can be changed or influenced at any time (i.e., while it
is running without the need to stop the tool) by setting the following
configuration settings in the config file:

 * "line-per-line": Minimize line per line?
 * "lines-to-rip": Fixed number of lines to rip. Applicable also when
   the configuration setting "line-per-line" is set.
 * "lines-percent": Percent of lines to remove from the file for each
   try.

Usually, the file is mostly minimized at the first round, however, it
can still be minimized even more with a 2nd pass. For some file types,
like JavaScript inside HTML files, removing line-per-line in the 2nd
pass doesn't make sense as it will remove, for example, the lines with
the code "function xxx()" and likely the "{" character. One can specify
the number of lines to remove in the 2nd step by setting the directive
"lines-to-rip" in the configuration file. It's also possible to change
the maximum percent of lines to remove during the first pass by setting
the value of "lines-percent". It's also possible to tell that we want to
force it to perform exclusively a line-per-line minimization, instead of
using the heuristic of removing a percent of lines in the 1st pass (as
explained right before this paragraph). However, it is not recommended
because, in general, it's better to use this heuristic but, who knows,
it could be useful for somebody tomorrow.

"""

import os
import sys

script_path = os.path.dirname(os.path.realpath(__file__))
sys.path.append(script_path)
tmp_path = os.path.join(script_path, "..")
sys.path.append(tmp_path)
tmp_path = os.path.join(tmp_path, "runtime")
sys.path.append(tmp_path)
tmp_path = os.path.join(tmp_path, "../lib/")
sys.path.append(tmp_path)
tmp_path = os.path.join(tmp_path, "../lib/interfaces")
sys.path.append(tmp_path)

from minimize_line import CLineMinimizer
from minimize_bindiff import CGenericBinaryDiffMinimizer

try:
  from minimize_html import CHtmlMinimizer
  
  html_support = True
except ImportError:
  html_support = False

#-----------------------------------------------------------------------
def main(mode, cfg, section, template, crash, diff, output=None):
  if mode == "generic":
    minimizer = CGenericBinaryDiffMinimizer(cfg, section)
    minimizer.minimize(template, crash, diff, output)
  elif mode == "line":
    output = diff
    minimizer = CLineMinimizer(cfg, section)
    minimizer.minimize(template, output)
  elif html_support and mode == "html":
    output = diff
    minimizer = CHtmlMinimizer(cfg, section)
    minimizer.minimize(template, output)
  else:
    print "Unknown mode '%s'" % mode

#-----------------------------------------------------------------------
def usage():
  print "Usage:", sys.argv[0], "<mode> <config file> <section> <template file> [<crashing file> <diff file>] <output directory>"
  print
  if html_support:
    print "The value for 'mode' is either 'line', 'generic' or 'html'."
  else:
    print "The value for 'mode' is either 'line' or 'generic'."
  print "The value for 'diff file' is only used for the 'generic' mode."
  print
  print "The 'generic' minimizer is meant to be used with Nightmare's mutators that output a .diff file."

if __name__ == "__main__":
  if len(sys.argv) < 6:
    usage()
  elif len(sys.argv) == 6:
    main(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5], sys.argv[5])
  elif len(sys.argv) == 7:
    main(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5], sys.argv[6])
