diff --git a/src/bin/binwalk b/src/bin/binwalk index 3506ca9..206cc15 100755 --- a/src/bin/binwalk +++ b/src/bin/binwalk @@ -78,8 +78,9 @@ def main(): matryoshka = 1 block_size = 0 failed_open_count = 0 - weight = None + max_points = None max_extract_size = None + do_2d = False quiet = False do_comp = False do_files = False @@ -157,6 +158,10 @@ def main(): show_grids = True elif opt in ("-3", "--3D", "--3d"): requested_scans.append(binwalk.Binwalk.BINVIS) + do_2d = False + elif opt in ("-2", "--2D", "--2d"): + requested_scans.append(binwalk.Binwalk.BINVIS) + do_2d = True elif opt in ("-E", "--entropy"): requested_scans.append(binwalk.Binwalk.ENTROPY) elif opt in ("-W", "--diff"): @@ -179,8 +184,8 @@ def main(): requested_scans.append(binwalk.Binwalk.STRINGS) elif opt in ("-O", "--skip-unopened"): ignore_failed_open = True - elif opt in ("-Z", "--weight"): - weight = binwalk.common.str2int(arg) + elif opt in ("-Z", "--max-points"): + max_points = binwalk.common.str2int(arg) elif opt in ("-o", "--offset"): offset = binwalk.common.str2int(arg) elif opt in ("-l", "--length"): @@ -468,8 +473,11 @@ def main(): elif scan_type == binwalk.Binwalk.BINVIS: # Always enable verbose mode; generating the plot can take some time for large files, - # and without verbose mode enabled it looks like binwalk is just sitting there doing nothing. - bwalk.plot3d(target_files, offset=offset, length=length, weight=weight, show_grids=show_grids, verbose=True) + # and without verbose mode enabled it looks like binwalk is just sitting there doing nothing. + if do_2d: + bwalk.plot2d(target_files, offset=offset, length=length, max_points=max_points, show_grids=show_grids, verbose=True) + else: + bwalk.plot3d(target_files, offset=offset, length=length, max_points=max_points, show_grids=show_grids, verbose=True) elif scan_type == binwalk.Binwalk.ENTROPY: diff --git a/src/binwalk/__init__.py b/src/binwalk/__init__.py index f15065e..7acb617 100644 --- a/src/binwalk/__init__.py +++ b/src/binwalk/__init__.py @@ -290,14 +290,14 @@ class Binwalk(object): return data - def plot3d(self, target_files, offset=0, length=0, weight=None, show_grids=False, verbose=False): + def plot3d(self, target_files, offset=0, length=0, max_points=None, show_grids=False, verbose=False): ''' Generates a 3D data plot of the specified target files. @target_files - File or list of files to scan. @offset - Starting offset at which to start the scan. @length - Number of bytes to scan. Specify 0 to scan the entire file(s). - @weight - A data point must occur at least this many times before being plotted (default: auto-detect). + @max_points - Set the maximum number of data points to plot. @show_grids - Set to True to show axis grids in the 3D plot. @verbose - Set to True to enable verbose output. @@ -306,7 +306,25 @@ class Binwalk(object): if not isinstance(target_files, type([])): target_files = [target_files] - Plotter3D(target_files, offset=offset, length=length, weight=weight, show_grids=show_grids, verbose=verbose).plot() + Plotter3D(target_files, offset=offset, length=length, max_points=max_points, show_grids=show_grids, verbose=verbose).plot() + + def plot2d(self, target_files, offset=0, length=0, max_points=None, show_grids=False, verbose=False): + ''' + Generates a 2D data plot of the specified target files. + + @target_files - File or list of files to scan. + @offset - Starting offset at which to start the scan. + @length - Number of bytes to scan. Specify 0 to scan the entire file(s). + @max_points - Set the maximum number of data points to plot. + @show_grids - Set to True to show axis grids in the 3D plot. + @verbose - Set to True to enable verbose output. + + Returns None. + ''' + if not isinstance(target_files, type([])): + target_files = [target_files] + + Plotter2D(target_files, offset=offset, length=length, max_points=max_points, show_grids=show_grids, verbose=verbose).plot() def scan(self, target_files, offset=0, length=0, show_invalid_results=False, callback=None, start_callback=None, end_callback=None, base_dir=None, matryoshka=1, plugins_whitelist=[], plugins_blacklist=[]): ''' diff --git a/src/binwalk/cmdopts.py b/src/binwalk/cmdopts.py index 628f1a2..a4644da 100644 --- a/src/binwalk/cmdopts.py +++ b/src/binwalk/cmdopts.py @@ -5,8 +5,9 @@ import os import sys import binwalk.config -short_options = "3AaBbCcdEeGHhIiJkLMNnOPpQqrSTtUuVvWwz?D:F:f:g:j:K:o:l:m:R:s:X:x:Y:y:Z:" +short_options = "23AaBbCcdEeGHhIiJkLMNnOPpQqrSTtUuVvWwz?D:F:f:g:j:K:o:l:m:R:s:X:x:Y:y:Z:" long_options = [ + "2D", "3D", "3d", "rm", @@ -45,7 +46,7 @@ long_options = [ "no-legend", "strings", "carve", - "weight=", + "max-points=", "matryoshka=", "list-plugins", "disable-plugins", @@ -110,7 +111,8 @@ def usage(fd): fd.write("Binary Visualization:\n") fd.write("\t-3, --3D Generate a 3D binary visualization\n") - fd.write("\t-Z, --weight Manually set the cutoff weight (lower weight, more data points)\n") + fd.write("\t-2, --2D Project data points onto 3D cube walls only\n") + fd.write("\t-Z, --max-points Set the maximum number of plotted data points (defulat: %d)\n" % binwalk.plotter.Plotter.MAX_PLOT_POINTS) fd.write("\t-V, --show-grids Display the x-y-z grids in the resulting plot\n") fd.write("\n") diff --git a/src/binwalk/hashmatch.py b/src/binwalk/hashmatch.py index 43e056f..99d9a50 100644 --- a/src/binwalk/hashmatch.py +++ b/src/binwalk/hashmatch.py @@ -183,7 +183,7 @@ class HashMatch(object): else: return self.lib.fuzzy_compare(hash1, hash2) except Exception as e: - print "WARNING: Exception while doing fuzzy hash:", e + print ("WARNING: Exception while doing fuzzy hash: %s" % e) return None @@ -348,7 +348,7 @@ if __name__ == '__main__': import sys hmatch = HashMatch(strings=True, name=False, types={True:"^elf"}) - print hmatch.file(sys.argv[1], sys.argv[2:]) + print (hmatch.file(sys.argv[1], sys.argv[2:])) #for (match, fname) in hmatch.directories(sys.argv[1], sys.argv[2]): #for (match, fname) in hmatch.find_file(sys.argv[1], sys.argv[2:]): # print match, fname diff --git a/src/binwalk/plotter.py b/src/binwalk/plotter.py index d57f8bc..8bdbbe7 100644 --- a/src/binwalk/plotter.py +++ b/src/binwalk/plotter.py @@ -3,20 +3,41 @@ from binwalk.compat import * from binwalk.common import BlockFile class Plotter(object): + ''' + Base class for plotting binaries in Qt. + Other plotter classes are derived from this. + ''' DIMENSIONS = 3 VIEW_DISTANCE = 1024 + MAX_PLOT_POINTS = 25000 + + def __init__(self, files, offset=0, length=0, max_points=MAX_PLOT_POINTS, show_grids=False, verbose=False): + ''' + Class constructor. - def __init__(self, files, offset=0, length=0, weight=None, show_grids=False, verbose=False): + @files - A list of files to plot in the graph. + @offset - The starting offset for each file. + @length - The number of bytes to analyze from each file. + @max_points - The maximum number of data points to display. + @show_grids - Set to True to display x-y-z grids. + @verbse - Set to False to disable verbose print statements. + + Returns None. + ''' import pyqtgraph.opengl as gl from pyqtgraph.Qt import QtGui self.verbose = verbose self.show_grids = show_grids self.files = files - self.weight = weight self.offset = offset self.length = length + + if not max_points: + self.max_points = self.MAX_PLOT_POINTS + else: + self.max_points = max_points self.app = QtGui.QApplication([]) self.window = gl.GLViewWidget() @@ -26,55 +47,92 @@ class Plotter(object): self.window.setWindowTitle(self.files[0]) def _print(self, message): + ''' + Print console messages. For internal use only. + ''' if self.verbose: print (message) - def _generate_plot_points(self, data_points, data_weights): - plot_points = set() - max_plot_points = (24 * 1024) + def _generate_plot_points(self, data_points): + ''' + Generates plot points from a list of data points. + + @data_points - A dictionary containing each unique point and its frequency of occurance. + + Returns a set of plot points. + ''' + total = 0 + min_weight = 0 + weightings = {} + plot_points = {} + + # If the number of data points exceeds the maximum number of allowed data points, use a + # weighting system to eliminate data points that occur less freqently. + if sum(data_points.itervalues()) > self.max_points: + + # First, generate a set of weight values 1 - 10 + for i in range(1, 11): + weightings[i] = 0 + + # Go through every data point and how many times that point occurs + for (point, count) in iterator(data_points): + # For each data point, compare it to each remaining weight value + for w in get_keys(weightings): + + # If the number of times this data point occurred is >= the weight value, + # then increment the weight value. Since weight values are ordered lowest + # to highest, this means that more frequent data points also increment lower + # weight values. Thus, the more high-frequency data points there are, the + # more lower-frequency data points are eliminated. + if count >= w: + weightings[w] += 1 + else: + break - if self.weight: - weight = self.weight - else: - self._print("Calculating weight...") + # Throw out weight values that exceed the maximum number of data points + if weightings[w] > self.max_points: + del weightings[w] - weight = 1 + # If there's only one weight value left, no sense in continuing the loop... + if len(weightings) == 1: + break - if len(data_points) > max_plot_points: - weightings = {} + # The least weighted value is our minimum weight + min_weight = min(weightings) - for i in range(1, 11): - weightings[i] = 0 + # Get rid of all data points that occur less frequently than our minimum weight + for point in get_keys(data_points): + if data_points[point] < min_weight: + del data_points[point] - for point in data_points: - for w in get_keys(weightings): - if data_weights[point] >= w: - weightings[w] += 1 + for point in sorted(data_points, key=data_points.get, reverse=True): + plot_points[point] = data_points[point] + total += 1 + if total >= self.max_points: + break - if weightings[w] > max_plot_points: - del weightings[w] - - if len(weightings) <= 1: - break - - if weightings: - weight = min(weightings) - - self._print("Weight: %d" % weight) - - for point in data_points: - if data_weights[point] >= weight: - plot_points.add(point) - return plot_points def _generate_data_point(self, data): + ''' + Subclasses must override this to return the appropriate data point. + + @data - A string of data self.DIMENSIONS in length. + + Returns a data point tuple. + ''' return (0,0,0) def _generate_data_points(self, file_name): + ''' + Generates a dictionary of data points and their frequency of occurrance. + + @file_name - The file to generate data points from. + + Returns a dictionary. + ''' i = 0 - data_weights = {} - data_points = set() + data_points = {} self._print("Generating data points for %s" % file_name) @@ -89,40 +147,44 @@ class Plotter(object): i = 0 while (i+(self.DIMENSIONS-1)) < dlen: point = self._generate_data_point(data[i:i+self.DIMENSIONS]) - if point in data_points: - data_weights[point] += 1 + if has_key(data_points, point): + data_points[point] += 1 else: - data_points.add(point) - data_weights[point] = 1 + data_points[point] = 1 i += 3 - return (data_points, data_weights) + return data_points - def _generate_plot(self, plot_points, point_weights): + def _generate_plot(self, plot_points): import numpy as np import pyqtgraph.opengl as gl - nitems = len(plot_points) + nitems = float(len(plot_points)) pos = np.empty((nitems, 3)) size = np.empty((nitems)) color = np.empty((nitems, 4)) i = 0 - for point in plot_points: + for (point, weight) in iterator(plot_points): r = 0.0 g = 0.0 b = 0.0 pos[i] = point - size[i] = .05 + frequency_percentage = (weight / nitems) - if point_weights[point] > 15: + # Give points that occur more frequently a brighter color and larger point size. + # Frequency is determined as a percentage of total unique data points. + if frequency_percentage > .005: + size[i] = .20 r = 1.0 - elif point_weights[point] > 5: + elif frequency_percentage > .002: + size[i] = .10 g = 1.0 r = 1.0 else: + size[i] = .05 g = 1.0 color[i] = (r, g, b, 1.0) @@ -158,15 +220,16 @@ class Plotter(object): zgrid.scale(12.8, 12.8, 12.8) for file_name in self.files: - (data_points, data_weights) = self._generate_data_points(file_name) + data_points = self._generate_data_points(file_name) self._print("Generating plot points from %d data points" % len(data_points)) - plot_points = self._generate_plot_points(data_points, data_weights) + plot_points = self._generate_plot_points(data_points) + del data_points self._print("Generating graph from %d plot points" % len(plot_points)) - self.window.addItem(self._generate_plot(plot_points, data_weights)) + self.window.addItem(self._generate_plot(plot_points)) if wait: self.wait() @@ -180,7 +243,9 @@ class Plotter(object): class Plotter3D(Plotter): - + ''' + Plot data points within a 3D cube. + ''' DIMENSIONS = 3 def _generate_data_point(self, data): @@ -188,15 +253,16 @@ class Plotter3D(Plotter): class Plotter2D(Plotter): ''' - This is of questionable use. + Plot data points projected on each cube face. ''' DIMENSIONS = 2 + MAX_PLOT_POINTS = 12500 plane_count = -1 def _generate_data_point(self, data): self.plane_count += 1 - if self.plane_count > 2: + if self.plane_count > 5: self.plane_count = 0 if self.plane_count == 0: @@ -205,6 +271,12 @@ class Plotter2D(Plotter): return (ord(data[0]), 0, ord(data[1])) elif self.plane_count == 2: return (ord(data[0]), ord(data[1]), 0) + elif self.plane_count == 3: + return (255, ord(data[0]), ord(data[1])) + elif self.plane_count == 4: + return (ord(data[0]), 255, ord(data[1])) + elif self.plane_count == 5: + return (ord(data[0]), ord(data[1]), 255) if __name__ == '__main__': import sys @@ -214,5 +286,5 @@ if __name__ == '__main__': except: weight = None - Plotter3D(sys.argv[1:], weight=weight, verbose=True).plot() + Plotter2D(sys.argv[1:], weight=weight, verbose=True).plot() diff --git a/src/easy_install.sh b/src/easy_install.sh index adc63eb..c0dea84 100755 --- a/src/easy_install.sh +++ b/src/easy_install.sh @@ -102,6 +102,10 @@ function debian # Install binwalk/fmk pre-requisites and extraction tools $SUDO apt-get -y install git build-essential mtd-utils zlib1g-dev liblzma-dev ncompress gzip bzip2 tar arj p7zip p7zip-full openjdk-6-jdk $SUDO apt-get -y install python-opengl python-qt4 python-qt4-gl python-numpy python-scipy + if [ "$(which python3)" != "" ] + then + $SUDO apt-get -y install python3-pyqt4 python3-numpy python3-scipy + fi } function redhat @@ -109,12 +113,16 @@ function redhat $SUDO yum groupinstall -y "Development Tools" $SUDO yum install -y git mtd-utils unrar zlib1g-dev liblzma-dev xz-devel compress gzip bzip2 tar arj p7zip p7zip-full openjdk-6-jdk $SUDO yum install -y python-opengl python-qt4 python-qt4-gl python-numpy python-scipy + if [ "$(which python3)" != "" ] + then + $SUDO yum -y install python3-pyqt4 python3-numpy python3-scipy + fi } if [ "$1" == "" ] || [ "$1" == "--sumount" ] then - PLATFORM=$(python -c 'import platform; print platform.system().lower()') - DISTRO=$(python -c 'import platform; print platform.linux_distribution()[0].lower()') + PLATFORM=$(python -c 'import platform; print (platform.system().lower())') + DISTRO=$(python -c 'import platform; print (platform.linux_distribution()[0].lower())') else DISTRO="$1" fi @@ -185,7 +193,7 @@ then fi # Get and build the firmware mod kit -#fmk +fmk # Install binwalk $SUDO python setup.py install