Commit a5217d62 by devttys0

s/\t/ /g

parent 84e83d0f
...@@ -6,126 +6,126 @@ import ctypes.util ...@@ -6,126 +6,126 @@ import ctypes.util
from binwalk.core.compat import * from binwalk.core.compat import *
class Function(object): class Function(object):
''' '''
Container class for defining library functions. Container class for defining library functions.
''' '''
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.name = None self.name = None
self.type = int self.type = int
for (k, v) in iterator(kwargs): for (k, v) in iterator(kwargs):
setattr(self, k, v) setattr(self, k, v)
class FunctionHandler(object): class FunctionHandler(object):
''' '''
Class for abstracting function calls via ctypes and handling Python 2/3 compatibility issues. Class for abstracting function calls via ctypes and handling Python 2/3 compatibility issues.
''' '''
PY2CTYPES = { PY2CTYPES = {
bytes : ctypes.c_char_p, bytes : ctypes.c_char_p,
str : ctypes.c_char_p, str : ctypes.c_char_p,
int : ctypes.c_int, int : ctypes.c_int,
float : ctypes.c_float, float : ctypes.c_float,
bool : ctypes.c_int, bool : ctypes.c_int,
None : ctypes.c_int, None : ctypes.c_int,
} }
RETVAL_CONVERTERS = { RETVAL_CONVERTERS = {
None : int, None : int,
int : int, int : int,
float : float, float : float,
bool : bool, bool : bool,
str : bytes2str, str : bytes2str,
bytes : str2bytes, bytes : str2bytes,
} }
def __init__(self, library, function): def __init__(self, library, function):
''' '''
Class constructor. Class constructor.
@library - Library handle as returned by ctypes.cdll.LoadLibrary. @library - Library handle as returned by ctypes.cdll.LoadLibrary.
@function - An instance of the binwalk.core.C.Function class. @function - An instance of the binwalk.core.C.Function class.
Returns None. Returns None.
''' '''
self.retype = function.type self.retype = function.type
self.function = getattr(library, function.name) self.function = getattr(library, function.name)
if has_key(self.PY2CTYPES, self.retype): if has_key(self.PY2CTYPES, self.retype):
self.function.restype = self.PY2CTYPES[self.retype] self.function.restype = self.PY2CTYPES[self.retype]
self.retval_converter = self.RETVAL_CONVERTERS[self.retype] self.retval_converter = self.RETVAL_CONVERTERS[self.retype]
else: else:
raise Exception("Unknown return type: '%s'" % self.retype) raise Exception("Unknown return type: '%s'" % self.retype)
def run(self, *args): def run(self, *args):
''' '''
Executes the library function, handling Python 2/3 compatibility and properly converting the return type. Executes the library function, handling Python 2/3 compatibility and properly converting the return type.
@*args - Library function arguments. @*args - Library function arguments.
Returns the return value from the libraray function. Returns the return value from the libraray function.
''' '''
args = list(args) args = list(args)
# Python3 expects a bytes object for char *'s, not a str. # Python3 expects a bytes object for char *'s, not a str.
# This allows us to pass either, regardless of the Python version. # This allows us to pass either, regardless of the Python version.
for i in range(0, len(args)): for i in range(0, len(args)):
if isinstance(args[i], str): if isinstance(args[i], str):
args[i] = str2bytes(args[i]) args[i] = str2bytes(args[i])
return self.retval_converter(self.function(*args)) return self.retval_converter(self.function(*args))
class Library(object): class Library(object):
''' '''
Class for loading the specified library via ctypes. Class for loading the specified library via ctypes.
''' '''
def __init__(self, library, functions): def __init__(self, library, functions):
''' '''
Class constructor. Class constructor.
@library - Library name (e.g., 'magic' for libmagic). @library - Library name (e.g., 'magic' for libmagic).
@functions - A dictionary of function names and their return types (e.g., {'magic_buffer' : str}) @functions - A dictionary of function names and their return types (e.g., {'magic_buffer' : str})
Returns None. Returns None.
''' '''
self.library = ctypes.cdll.LoadLibrary(self.find_library(library)) self.library = ctypes.cdll.LoadLibrary(self.find_library(library))
if not self.library: if not self.library:
raise Exception("Failed to load library '%s'" % library) raise Exception("Failed to load library '%s'" % library)
for function in functions: for function in functions:
f = FunctionHandler(self.library, function) f = FunctionHandler(self.library, function)
setattr(self, function.name, f.run) setattr(self, function.name, f.run)
def find_library(self, library): def find_library(self, library):
''' '''
Locates the specified library. Locates the specified library.
@library - Library name (e.g., 'magic' for libmagic). @library - Library name (e.g., 'magic' for libmagic).
Returns a string to be passed to ctypes.cdll.LoadLibrary. Returns a string to be passed to ctypes.cdll.LoadLibrary.
''' '''
lib_path = None lib_path = None
system_paths = { system_paths = {
'linux' : ['/usr/local/lib/lib%s.so' % library], 'linux' : ['/usr/local/lib/lib%s.so' % library],
'linux2' : ['/usr/local/lib/lib%s.so' % library], 'linux2' : ['/usr/local/lib/lib%s.so' % library],
'linux3' : ['/usr/local/lib/lib%s.so' % library], 'linux3' : ['/usr/local/lib/lib%s.so' % library],
'darwin' : ['/opt/local/lib/lib%s.dylib' % library, 'darwin' : ['/opt/local/lib/lib%s.dylib' % library,
'/usr/local/lib/lib%s.dylib' % library, '/usr/local/lib/lib%s.dylib' % library,
] + glob.glob('/usr/local/Cellar/lib%s/*/lib/lib%s.dylib' % (library, library)), ] + glob.glob('/usr/local/Cellar/lib%s/*/lib/lib%s.dylib' % (library, library)),
'win32' : ['%s.dll' % library] 'win32' : ['%s.dll' % library]
} }
lib_path = ctypes.util.find_library(library) lib_path = ctypes.util.find_library(library)
if not lib_path: if not lib_path:
for path in system_paths[sys.platform]: for path in system_paths[sys.platform]:
if os.path.exists(path): if os.path.exists(path):
lib_path = path lib_path = path
break break
if not lib_path: if not lib_path:
raise Exception("Failed to locate library '%s'" % library) raise Exception("Failed to locate library '%s'" % library)
return lib_path return lib_path
...@@ -7,68 +7,68 @@ import string ...@@ -7,68 +7,68 @@ import string
PY_MAJOR_VERSION = sys.version_info[0] PY_MAJOR_VERSION = sys.version_info[0]
if PY_MAJOR_VERSION > 2: if PY_MAJOR_VERSION > 2:
string.letters = string.ascii_letters string.letters = string.ascii_letters
def iterator(dictionary): def iterator(dictionary):
''' '''
For cross compatibility between Python 2 and Python 3 dictionaries. For cross compatibility between Python 2 and Python 3 dictionaries.
''' '''
if PY_MAJOR_VERSION > 2: if PY_MAJOR_VERSION > 2:
return dictionary.items() return dictionary.items()
else: else:
return dictionary.iteritems() return dictionary.iteritems()
def has_key(dictionary, key): def has_key(dictionary, key):
''' '''
For cross compatibility between Python 2 and Python 3 dictionaries. For cross compatibility between Python 2 and Python 3 dictionaries.
''' '''
if PY_MAJOR_VERSION > 2: if PY_MAJOR_VERSION > 2:
return key in dictionary return key in dictionary
else: else:
return dictionary.has_key(key) return dictionary.has_key(key)
def get_keys(dictionary): def get_keys(dictionary):
''' '''
For cross compatibility between Python 2 and Python 3 dictionaries. For cross compatibility between Python 2 and Python 3 dictionaries.
''' '''
if PY_MAJOR_VERSION > 2: if PY_MAJOR_VERSION > 2:
return list(dictionary.keys()) return list(dictionary.keys())
else: else:
return dictionary.keys() return dictionary.keys()
def str2bytes(string): def str2bytes(string):
''' '''
For cross compatibility between Python 2 and Python 3 strings. For cross compatibility between Python 2 and Python 3 strings.
''' '''
if isinstance(string, type('')) and PY_MAJOR_VERSION > 2: if isinstance(string, type('')) and PY_MAJOR_VERSION > 2:
return bytes(string, 'latin1') return bytes(string, 'latin1')
else: else:
return string return string
def bytes2str(bs): def bytes2str(bs):
''' '''
For cross compatibility between Python 2 and Python 3 strings. For cross compatibility between Python 2 and Python 3 strings.
''' '''
if isinstance(bs, type(b'')) and PY_MAJOR_VERSION > 2: if isinstance(bs, type(b'')) and PY_MAJOR_VERSION > 2:
return bs.decode('latin1') return bs.decode('latin1')
else: else:
return bs return bs
def string_decode(string): def string_decode(string):
''' '''
For cross compatibility between Python 2 and Python 3 strings. For cross compatibility between Python 2 and Python 3 strings.
''' '''
if PY_MAJOR_VERSION > 2: if PY_MAJOR_VERSION > 2:
return bytes(string, 'utf-8').decode('unicode_escape') return bytes(string, 'utf-8').decode('unicode_escape')
else: else:
return string.decode('string_escape') return string.decode('string_escape')
def user_input(prompt=''): def user_input(prompt=''):
''' '''
For getting raw user input in Python 2 and 3. For getting raw user input in Python 2 and 3.
''' '''
if PY_MAJOR_VERSION > 2: if PY_MAJOR_VERSION > 2:
return input(prompt) return input(prompt)
else: else:
return raw_input(prompt) return raw_input(prompt)
0 belong x Hex: 0x%.8X
#0 string x String: %s
#0 lequad x Little Endian Quad: %lld
#0 bequad x Big Endian Quad: %lld
0 lelong x Little Endian Long: %d
0 belong x Big Endian Long: %d
0 leshort x Little Endian Short: %d
0 beshort x Big Endian Short: %d
0 ledate x Little Endian Date: %s
0 bedate x Big Endian Date: %s
...@@ -5,87 +5,87 @@ import binwalk.core.C ...@@ -5,87 +5,87 @@ import binwalk.core.C
from binwalk.core.module import Option, Kwarg, Module from binwalk.core.module import Option, Kwarg, Module
class Deflate(object): class Deflate(object):
''' '''
Finds and extracts raw deflate compression streams. Finds and extracts raw deflate compression streams.
''' '''
ENABLED = False ENABLED = False
BLOCK_SIZE = 33*1024 BLOCK_SIZE = 33*1024
# To prevent many false positives, only show data that decompressed to a reasonable size and didn't just result in a bunch of NULL bytes # To prevent many false positives, only show data that decompressed to a reasonable size and didn't just result in a bunch of NULL bytes
MIN_DECOMP_SIZE = 32*1024 MIN_DECOMP_SIZE = 32*1024
DESCRIPTION = "Raw deflate compression stream" DESCRIPTION = "Raw deflate compression stream"
TINFL_NAME = "tinfl" TINFL_NAME = "tinfl"
TINFL_FUNCTIONS = [ TINFL_FUNCTIONS = [
binwalk.core.C.Function(name="is_deflated", type=int), binwalk.core.C.Function(name="is_deflated", type=int),
binwalk.core.C.Function(name="inflate_raw_file", type=None), binwalk.core.C.Function(name="inflate_raw_file", type=None),
] ]
def __init__(self, module): def __init__(self, module):
self.module = module self.module = module
# The tinfl library is built and installed with binwalk # The tinfl library is built and installed with binwalk
self.tinfl = binwalk.core.C.Library(self.TINFL_NAME, self.TINFL_FUNCTIONS) self.tinfl = binwalk.core.C.Library(self.TINFL_NAME, self.TINFL_FUNCTIONS)
# Add an extraction rule # Add an extraction rule
if self.module.extractor.enabled: if self.module.extractor.enabled:
self.module.extractor.add_rule(regex='^%s' % self.DESCRIPTION.lower(), extension="deflate", cmd=self._extractor) self.module.extractor.add_rule(regex='^%s' % self.DESCRIPTION.lower(), extension="deflate", cmd=self._extractor)
def _extractor(self, file_name): def _extractor(self, file_name):
out_file = os.path.splitext(file_name)[0] out_file = os.path.splitext(file_name)[0]
self.tinfl.inflate_raw_file(file_name, out_file) self.tinfl.inflate_raw_file(file_name, out_file)
def decompress(self, data): def decompress(self, data):
description = None description = None
decomp_size = self.tinfl.is_deflated(data, len(data), 0) decomp_size = self.tinfl.is_deflated(data, len(data), 0)
if decomp_size >= self.MIN_DECOMP_SIZE: if decomp_size >= self.MIN_DECOMP_SIZE:
description = self.DESCRIPTION + ', uncompressed size >= %d' % decomp_size description = self.DESCRIPTION + ', uncompressed size >= %d' % decomp_size
return description return description
class RawCompression(Module): class RawCompression(Module):
DECOMPRESSORS = { DECOMPRESSORS = {
'deflate' : Deflate, 'deflate' : Deflate,
} }
TITLE = 'Raw Compression' TITLE = 'Raw Compression'
CLI = [ CLI = [
Option(short='X', Option(short='X',
long='deflate', long='deflate',
kwargs={'enabled' : True, 'decompressor_class' : 'deflate'}, kwargs={'enabled' : True, 'decompressor_class' : 'deflate'},
description='Scan for raw deflate compression streams'), description='Scan for raw deflate compression streams'),
] ]
KWARGS = [ KWARGS = [
Kwarg(name='enabled', default=False), Kwarg(name='enabled', default=False),
Kwarg(name='decompressor_class', default=None), Kwarg(name='decompressor_class', default=None),
] ]
def init(self): def init(self):
self.decompressor = self.DECOMPRESSORS[self.decompressor_class](self) self.decompressor = self.DECOMPRESSORS[self.decompressor_class](self)
def run(self): def run(self):
for fp in iter(self.next_file, None): for fp in iter(self.next_file, None):
fp.set_block_size(peek=self.decompressor.BLOCK_SIZE) fp.set_block_size(peek=self.decompressor.BLOCK_SIZE)
self.header() self.header()
while True: while True:
(data, dlen) = fp.read_block() (data, dlen) = fp.read_block()
if not data: if not data:
break break
for i in range(0, dlen): for i in range(0, dlen):
description = self.decompressor.decompress(data[i:i+self.decompressor.BLOCK_SIZE]) description = self.decompressor.decompress(data[i:i+self.decompressor.BLOCK_SIZE])
if description: if description:
self.result(description=description, file=fp, offset=fp.tell()-dlen+i) self.result(description=description, file=fp, offset=fp.tell()-dlen+i)
self.status.completed = fp.tell() - fp.offset self.status.completed = fp.tell() - fp.offset
self.footer() self.footer()
...@@ -8,186 +8,186 @@ from binwalk.core.compat import * ...@@ -8,186 +8,186 @@ from binwalk.core.compat import *
from binwalk.core.module import Module, Kwarg, Option, Dependency from binwalk.core.module import Module, Kwarg, Option, Dependency
class ChiSquare(object): class ChiSquare(object):
''' '''
Performs a Chi Squared test against the provided data. Performs a Chi Squared test against the provided data.
''' '''
IDEAL = 256.0 IDEAL = 256.0
def __init__(self): def __init__(self):
''' '''
Class constructor. Class constructor.
Returns None. Returns None.
''' '''
self.bytes = {} self.bytes = {}
self.freedom = self.IDEAL - 1 self.freedom = self.IDEAL - 1
# Initialize the self.bytes dictionary with keys for all possible byte values (0 - 255) # Initialize the self.bytes dictionary with keys for all possible byte values (0 - 255)
for i in range(0, int(self.IDEAL)): for i in range(0, int(self.IDEAL)):
self.bytes[chr(i)] = 0 self.bytes[chr(i)] = 0
self.reset() self.reset()
def reset(self): def reset(self):
self.xc2 = 0.0 self.xc2 = 0.0
self.byte_count = 0 self.byte_count = 0
for key in self.bytes.keys(): for key in self.bytes.keys():
self.bytes[key] = 0 self.bytes[key] = 0
def update(self, data): def update(self, data):
''' '''
Updates the current byte counts with new data. Updates the current byte counts with new data.
@data - String of bytes to update. @data - String of bytes to update.
Returns None. Returns None.
''' '''
# Count the number of occurances of each byte value # Count the number of occurances of each byte value
for i in data: for i in data:
self.bytes[i] += 1 self.bytes[i] += 1
self.byte_count += len(data) self.byte_count += len(data)
def chisq(self): def chisq(self):
''' '''
Calculate the Chi Square critical value. Calculate the Chi Square critical value.
Returns the critical value. Returns the critical value.
''' '''
expected = self.byte_count / self.IDEAL expected = self.byte_count / self.IDEAL
if expected: if expected:
for byte in self.bytes.values(): for byte in self.bytes.values():
self.xc2 += ((byte - expected) ** 2 ) / expected self.xc2 += ((byte - expected) ** 2 ) / expected
return self.xc2 return self.xc2
class EntropyBlock(object): class EntropyBlock(object):
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.start = None self.start = None
self.end = None self.end = None
self.length = None self.length = None
for (k,v) in iterator(kwargs): for (k,v) in iterator(kwargs):
setattr(self, k, v) setattr(self, k, v)
class HeuristicCompressionAnalyzer(Module): class HeuristicCompressionAnalyzer(Module):
''' '''
Performs analysis and attempts to interpret the results. Performs analysis and attempts to interpret the results.
''' '''
BLOCK_SIZE = 32 BLOCK_SIZE = 32
CHI_CUTOFF = 512 CHI_CUTOFF = 512
ENTROPY_TRIGGER = .90 ENTROPY_TRIGGER = .90
MIN_BLOCK_SIZE = 4096 MIN_BLOCK_SIZE = 4096
BLOCK_OFFSET = 1024 BLOCK_OFFSET = 1024
ENTROPY_BLOCK_SIZE = 1024 ENTROPY_BLOCK_SIZE = 1024
TITLE = "Heuristic Compression" TITLE = "Heuristic Compression"
DEPENDS = [ DEPENDS = [
Dependency(name='Entropy', Dependency(name='Entropy',
attribute='entropy', attribute='entropy',
kwargs={'enabled' : True, 'do_plot' : False, 'display_results' : False, 'block_size' : ENTROPY_BLOCK_SIZE}), kwargs={'enabled' : True, 'do_plot' : False, 'display_results' : False, 'block_size' : ENTROPY_BLOCK_SIZE}),
] ]
CLI = [ CLI = [
Option(short='H', Option(short='H',
long='heuristic', long='heuristic',
kwargs={'enabled' : True}, kwargs={'enabled' : True},
description='Heuristically classify high entropy data'), description='Heuristically classify high entropy data'),
Option(short='a', Option(short='a',
long='trigger', long='trigger',
kwargs={'trigger_level' : 0}, kwargs={'trigger_level' : 0},
type=float, type=float,
description='Set the entropy trigger level (0.0 - 1.0)'), description='Set the entropy trigger level (0.0 - 1.0)'),
] ]
KWARGS = [ KWARGS = [
Kwarg(name='enabled', default=False), Kwarg(name='enabled', default=False),
Kwarg(name='trigger_level', default=ENTROPY_TRIGGER), Kwarg(name='trigger_level', default=ENTROPY_TRIGGER),
] ]
def init(self): def init(self):
self.blocks = {} self.blocks = {}
self.HEADER[-1] = "HEURISTIC ENTROPY ANALYSIS" self.HEADER[-1] = "HEURISTIC ENTROPY ANALYSIS"
if self.config.block: if self.config.block:
self.block_size = self.config.block self.block_size = self.config.block
else: else:
self.block_size = self.BLOCK_SIZE self.block_size = self.BLOCK_SIZE
for result in self.entropy.results: for result in self.entropy.results:
if not has_key(self.blocks, result.file.name): if not has_key(self.blocks, result.file.name):
self.blocks[result.file.name] = [] self.blocks[result.file.name] = []
if result.entropy >= self.trigger_level and (not self.blocks[result.file.name] or self.blocks[result.file.name][-1].end is not None): if result.entropy >= self.trigger_level and (not self.blocks[result.file.name] or self.blocks[result.file.name][-1].end is not None):
self.blocks[result.file.name].append(EntropyBlock(start=result.offset + self.BLOCK_OFFSET)) self.blocks[result.file.name].append(EntropyBlock(start=result.offset + self.BLOCK_OFFSET))
elif result.entropy < self.trigger_level and self.blocks[result.file.name] and self.blocks[result.file.name][-1].end is None: elif result.entropy < self.trigger_level and self.blocks[result.file.name] and self.blocks[result.file.name][-1].end is None:
self.blocks[result.file.name][-1].end = result.offset - self.BLOCK_OFFSET self.blocks[result.file.name][-1].end = result.offset - self.BLOCK_OFFSET
def run(self): def run(self):
for fp in iter(self.next_file, None): for fp in iter(self.next_file, None):
if has_key(self.blocks, fp.name): if has_key(self.blocks, fp.name):
self.header() self.header()
for block in self.blocks[fp.name]: for block in self.blocks[fp.name]:
if block.end is None: if block.end is None:
block.length = fp.offset + fp.length - block.start block.length = fp.offset + fp.length - block.start
else: else:
block.length = block.end - block.start block.length = block.end - block.start
if block.length >= self.MIN_BLOCK_SIZE: if block.length >= self.MIN_BLOCK_SIZE:
self.analyze(fp, block) self.analyze(fp, block)
self.footer() self.footer()
def analyze(self, fp, block): def analyze(self, fp, block):
''' '''
Perform analysis and interpretation. Perform analysis and interpretation.
''' '''
i = 0 i = 0
num_error = 0 num_error = 0
analyzer_results = [] analyzer_results = []
chi = ChiSquare() chi = ChiSquare()
fp.seek(block.start) fp.seek(block.start)
while i < block.length: while i < block.length:
j = 0 j = 0
(d, dlen) = fp.read_block() (d, dlen) = fp.read_block()
if not d: if not d:
break break
while j < dlen: while j < dlen:
chi.reset() chi.reset()
data = d[j:j+self.block_size] data = d[j:j+self.block_size]
if len(data) < self.block_size: if len(data) < self.block_size:
break break
chi.update(data) chi.update(data)
if chi.chisq() >= self.CHI_CUTOFF: if chi.chisq() >= self.CHI_CUTOFF:
num_error += 1 num_error += 1
j += self.block_size j += self.block_size
if (j + i) > block.length: if (j + i) > block.length:
break break
i += dlen i += dlen
if num_error > 0: if num_error > 0:
verdict = 'Moderate entropy data, best guess: compressed' verdict = 'Moderate entropy data, best guess: compressed'
else: else:
verdict = 'High entropy data, best guess: encrypted' verdict = 'High entropy data, best guess: encrypted'
desc = '%s, size: %d, %d low entropy blocks' % (verdict, block.length, num_error) desc = '%s, size: %d, %d low entropy blocks' % (verdict, block.length, num_error)
self.result(offset=block.start, description=desc, file=fp) self.result(offset=block.start, description=desc, file=fp)
...@@ -2,32 +2,32 @@ import binwalk.core.C ...@@ -2,32 +2,32 @@ import binwalk.core.C
from binwalk.core.common import * from binwalk.core.common import *
class Plugin(object): class Plugin(object):
''' '''
Searches for and validates compress'd data. Searches for and validates compress'd data.
''' '''
READ_SIZE = 64 READ_SIZE = 64
COMPRESS42 = "compress42" COMPRESS42 = "compress42"
COMPRESS42_FUNCTIONS = [ COMPRESS42_FUNCTIONS = [
binwalk.core.C.Function(name="is_compressed", type=bool), binwalk.core.C.Function(name="is_compressed", type=bool),
] ]
def __init__(self, module): def __init__(self, module):
self.fd = None self.fd = None
self.comp = None self.comp = None
if module.name == 'Signature': if module.name == 'Signature':
self.comp = binwalk.core.C.Library(self.COMPRESS42, self.COMPRESS42_FUNCTIONS) self.comp = binwalk.core.C.Library(self.COMPRESS42, self.COMPRESS42_FUNCTIONS)
def scan(self, result): def scan(self, result):
if self.comp: if self.comp:
if result.file and result.description.lower().startswith("compress'd data"): if result.file and result.description.lower().startswith("compress'd data"):
fd = BlockFile(result.file.name, "r", offset=result.offset, length=self.READ_SIZE) fd = BlockFile(result.file.name, "r", offset=result.offset, length=self.READ_SIZE)
compressed_data = fd.read(self.READ_SIZE) compressed_data = fd.read(self.READ_SIZE)
fd.close() fd.close()
if not self.comp.is_compressed(compressed_data, len(compressed_data)): if not self.comp.is_compressed(compressed_data, len(compressed_data)):
result.valid = False result.valid = False
class Plugin(object): class Plugin(object):
''' '''
Ensures that ASCII CPIO archive entries only get extracted once. Ensures that ASCII CPIO archive entries only get extracted once.
''' '''
def __init__(self, module): def __init__(self, module):
self.found_archive = False self.found_archive = False
self.enabled = (module.name == 'Signature') self.enabled = (module.name == 'Signature')
def pre_scan(self, module): def pre_scan(self, module):
# Be sure to re-set this at the beginning of every scan # Be sure to re-set this at the beginning of every scan
self.found_archive = False self.found_archive = False
def scan(self, result): def scan(self, result):
if self.enabled and result.valid: if self.enabled and result.valid:
# ASCII CPIO archives consist of multiple entries, ending with an entry named 'TRAILER!!!'. # ASCII CPIO archives consist of multiple entries, ending with an entry named 'TRAILER!!!'.
# Displaying each entry is useful, as it shows what files are contained in the archive, # Displaying each entry is useful, as it shows what files are contained in the archive,
# but we only want to extract the archive when the first entry is found. # but we only want to extract the archive when the first entry is found.
if result.description.startswith('ASCII cpio archive'): if result.description.startswith('ASCII cpio archive'):
if not self.found_archive: if not self.found_archive:
# This is the first entry. Set found_archive and allow the scan to continue normally. # This is the first entry. Set found_archive and allow the scan to continue normally.
self.found_archive = True self.found_archive = True
result.extract = True result.extract = True
elif 'TRAILER!!!' in results['description']: elif 'TRAILER!!!' in results['description']:
# This is the last entry, un-set found_archive. # This is the last entry, un-set found_archive.
self.found_archive = False self.found_archive = False
# The first entry has already been found and this is the last entry, or the last entry # The first entry has already been found and this is the last entry, or the last entry
# has not yet been found. Don't extract. # has not yet been found. Don't extract.
result.extract = False result.extract = False
...@@ -4,61 +4,61 @@ from binwalk.core.compat import * ...@@ -4,61 +4,61 @@ from binwalk.core.compat import *
from binwalk.core.common import BlockFile from binwalk.core.common import BlockFile
class Plugin(object): class Plugin(object):
''' '''
Finds and extracts modified LZMA files commonly found in cable modems. Finds and extracts modified LZMA files commonly found in cable modems.
Based on Bernardo Rodrigues' work: http://w00tsec.blogspot.com/2013/11/unpacking-firmware-images-from-cable.html Based on Bernardo Rodrigues' work: http://w00tsec.blogspot.com/2013/11/unpacking-firmware-images-from-cable.html
''' '''
FAKE_LZMA_SIZE = "\x00\x00\x00\x10\x00\x00\x00\x00" FAKE_LZMA_SIZE = "\x00\x00\x00\x10\x00\x00\x00\x00"
SIGNATURE = "lzma compressed data" SIGNATURE = "lzma compressed data"
def __init__(self, module): def __init__(self, module):
self.original_cmd = '' self.original_cmd = ''
self.enabled = (module.name == 'Signature') self.enabled = (module.name == 'Signature')
self.module = module self.module = module
if self.enabled: if self.enabled:
# Replace the existing LZMA extraction command with our own # Replace the existing LZMA extraction command with our own
rules = self.module.extractor.get_rules() rules = self.module.extractor.get_rules()
for i in range(0, len(rules)): for i in range(0, len(rules)):
if rules[i]['regex'].match(self.SIGNATURE): if rules[i]['regex'].match(self.SIGNATURE):
self.original_cmd = rules[i]['cmd'] self.original_cmd = rules[i]['cmd']
rules[i]['cmd'] = self.lzma_cable_extractor rules[i]['cmd'] = self.lzma_cable_extractor
break break
def lzma_cable_extractor(self, fname): def lzma_cable_extractor(self, fname):
# Try extracting the LZMA file without modification first # Try extracting the LZMA file without modification first
if not self.module.extractor.execute(self.original_cmd, fname): if not self.module.extractor.execute(self.original_cmd, fname):
out_name = os.path.splitext(fname)[0] + '-patched' + os.path.splitext(fname)[1] out_name = os.path.splitext(fname)[0] + '-patched' + os.path.splitext(fname)[1]
fp_out = BlockFile(out_name, 'w') fp_out = BlockFile(out_name, 'w')
# Use self.module.config.open_file here to ensure that other config settings (such as byte-swapping) are honored # Use self.module.config.open_file here to ensure that other config settings (such as byte-swapping) are honored
fp_in = self.module.config.open_file(fname, offset=0, length=0) fp_in = self.module.config.open_file(fname, offset=0, length=0)
fp_in.set_block_size(peek=0) fp_in.set_block_size(peek=0)
i = 0 i = 0
while i < fp_in.length: while i < fp_in.length:
(data, dlen) = fp_in.read_block() (data, dlen) = fp_in.read_block()
if i == 0: if i == 0:
out_data = data[0:5] + self.FAKE_LZMA_SIZE + data[5:] out_data = data[0:5] + self.FAKE_LZMA_SIZE + data[5:]
else: else:
out_data = data out_data = data
fp_out.write(out_data) fp_out.write(out_data)
i += dlen i += dlen
fp_in.close() fp_in.close()
fp_out.close() fp_out.close()
# Overwrite the original file so that it can be cleaned up if -r was specified # Overwrite the original file so that it can be cleaned up if -r was specified
shutil.move(out_name, fname) shutil.move(out_name, fname)
self.module.extractor.execute(self.original_cmd, fname) self.module.extractor.execute(self.original_cmd, fname)
def scan(self, result): def scan(self, result):
# The modified cable modem LZMA headers all have valid dictionary sizes and a properties byte of 0x5D. # The modified cable modem LZMA headers all have valid dictionary sizes and a properties byte of 0x5D.
if self.enabled and result.description.lower().startswith(self.SIGNATURE) and "invalid uncompressed size" in result.description: if self.enabled and result.description.lower().startswith(self.SIGNATURE) and "invalid uncompressed size" in result.description:
if "properties: 0x5D" in result.description and "invalid dictionary size" not in result.description: if "properties: 0x5D" in result.description and "invalid dictionary size" not in result.description:
result.valid = True result.valid = True
result.description = result.description.split("invalid uncompressed size")[0] + "missing uncompressed size" result.description = result.description.split("invalid uncompressed size")[0] + "missing uncompressed size"
...@@ -2,39 +2,39 @@ import binwalk.core.C ...@@ -2,39 +2,39 @@ import binwalk.core.C
from binwalk.core.common import BlockFile from binwalk.core.common import BlockFile
class Plugin(object): class Plugin(object):
''' '''
Searches for and validates zlib compressed data. Searches for and validates zlib compressed data.
''' '''
MIN_DECOMP_SIZE = 16 * 1024 MIN_DECOMP_SIZE = 16 * 1024
MAX_DATA_SIZE = 33 * 1024 MAX_DATA_SIZE = 33 * 1024
TINFL = "tinfl" TINFL = "tinfl"
TINFL_FUNCTIONS = [ TINFL_FUNCTIONS = [
binwalk.core.C.Function(name="is_deflated", type=int), binwalk.core.C.Function(name="is_deflated", type=int),
] ]
def __init__(self, module): def __init__(self, module):
self.tinfl = None self.tinfl = None
self.module = module self.module = module
# Only initialize this plugin if this is a signature scan # Only initialize this plugin if this is a signature scan
if module.name == 'Signature': if module.name == 'Signature':
# Load libtinfl.so # Load libtinfl.so
self.tinfl = binwalk.core.C.Library(self.TINFL, self.TINFL_FUNCTIONS) self.tinfl = binwalk.core.C.Library(self.TINFL, self.TINFL_FUNCTIONS)
def scan(self, result): def scan(self, result):
# If this result is a zlib signature match, try to decompress the data # If this result is a zlib signature match, try to decompress the data
if self.tinfl and result.file and result.description.lower().startswith('zlib'): if self.tinfl and result.file and result.description.lower().startswith('zlib'):
# Seek to and read the suspected zlib data # Seek to and read the suspected zlib data
fd = self.module.config.open_file(result.file.name, offset=result.offset, length=self.MAX_DATA_SIZE) fd = self.module.config.open_file(result.file.name, offset=result.offset, length=self.MAX_DATA_SIZE)
data = fd.read(self.MAX_DATA_SIZE) data = fd.read(self.MAX_DATA_SIZE)
fd.close() fd.close()
# Check if this is valid zlib data # Check if this is valid zlib data
decomp_size = self.tinfl.is_deflated(data, len(data), 1) decomp_size = self.tinfl.is_deflated(data, len(data), 1)
if decomp_size > 0: if decomp_size > 0:
result.description += ", uncompressed size >= %d" % decomp_size result.description += ", uncompressed size >= %d" % decomp_size
else: else:
result.valid = False result.valid = False
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment