1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# Code to handle displaying and logging of results.
# Anything in binwalk that prints results to screen should use this class.
import sys
import csv as pycsv
import datetime
import binwalk.core.common
from binwalk.core.compat import *
class Display(object):
'''
Class to handle display of output and writing to log files.
This class is instantiated for all modules implicitly and should not need to be invoked directly by most modules.
'''
SCREEN_WIDTH = 0
HEADER_WIDTH = 80
DEFAULT_FORMAT = "%s\n"
def __init__(self, quiet=False, verbose=False, log=None, csv=False, fit_to_screen=False):
self.quiet = quiet
self.verbose = verbose
self.fit_to_screen = fit_to_screen
self.fp = None
self.csv = None
self.num_columns = 0
self.custom_verbose_format = ""
self.custom_verbose_args = []
self._configure_formatting()
if log:
self.fp = open(log, "a")
if csv:
self.csv = pycsv.writer(self.fp)
def _fix_unicode(self, line):
'''
This is a hack, there must be a better way to handle it.
In Python3, if the environment variable LANG=C is set, indicating
that the terminal is ASCII only, but unicode characters need to be
printed to the screen or to a file (e.g., file path, magic result
format string), then an UnicodeEncodError exception will be raised.
This converts the given line to ASCII, ignoring conversion errors,
and returns a str.
'''
return bytes2str(line.encode('ascii', 'ignore'))
def _fix_unicode_list(self, columns):
'''
Convenience wrapper for self.log which is passed a list of format arguments.
'''
if type(columns) in [list, tuple]:
for i in range(0, len(columns)):
try:
columns[i] = self._fix_unicode(columns[i])
except AttributeError:
pass
return columns
def format_strings(self, header, result):
self.result_format = result
self.header_format = header
if self.num_columns == 0:
self.num_columns = len(header.split())
def log(self, fmt, columns):
if self.fp:
if self.csv:
try:
self.csv.writerow(columns)
except UnicodeEncodeError:
self.csv.writerow(self._fix_unicode_list(columns))
else:
try:
self.fp.write(fmt % tuple(columns))
except UnicodeEncodeError:
self.fp.write(fmt % tuple(self._fix_unicode_list(columns)))
self.fp.flush()
def add_custom_header(self, fmt, args):
self.custom_verbose_format = fmt
self.custom_verbose_args = args
def header(self, *args, **kwargs):
file_name = None
self.num_columns = len(args)
if has_key(kwargs, 'file_name'):
file_name = kwargs['file_name']
if self.verbose and file_name:
md5sum = binwalk.core.common.file_md5(file_name)
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
if self.csv:
self.log("", ["FILE", "MD5SUM", "TIMESTAMP"])
self.log("", [file_name, md5sum, timestamp])
self._fprint("%s", "\n", csv=False)
self._fprint("Scan Time: %s\n", [timestamp], csv=False, filter=False)
self._fprint("Target File: %s\n", [file_name], csv=False, filter=False)
self._fprint("MD5 Checksum: %s\n", [md5sum], csv=False, filter=False)
if self.custom_verbose_format and self.custom_verbose_args:
self._fprint(self.custom_verbose_format, self.custom_verbose_args, csv=False, filter=False)
self._fprint("%s", "\n", csv=False, filter=False)
self._fprint(self.header_format, args, filter=False)
self._fprint("%s", ["-" * self.HEADER_WIDTH + "\n"], csv=False, filter=False)
def result(self, *args):
# Convert to list for item assignment
args = list(args)
# Replace multiple spaces with single spaces. This is to prevent accidentally putting
# four spaces in the description string, which would break auto-formatting.
for i in range(len(args)):
if isinstance(args[i], str):
while " " in args[i]:
args[i] = args[i].replace(" " , " ")
self._fprint(self.result_format, tuple(args))
def footer(self):
self._fprint("%s", "\n", csv=False, filter=False)
def _fprint(self, fmt, columns, csv=True, stdout=True, filter=True):
line = fmt % tuple(columns)
if not self.quiet and stdout:
try:
try:
sys.stdout.write(self._format_line(line.strip()) + "\n")
except UnicodeEncodeError:
line = self._fix_unicode(line)
sys.stdout.write(self._format_line(line.strip()) + "\n")
sys.stdout.flush()
except IOError as e:
pass
if self.fp and not (self.csv and not csv):
self.log(fmt, columns)
def _append_to_data_parts(self, data, start, end):
'''
Intelligently appends data to self.string_parts.
For use by self._format.
'''
try:
while data[start] == ' ':
start += 1
if start == end:
end = len(data[start:])
self.string_parts.append(data[start:end])
except KeyboardInterrupt as e:
raise e
except Exception:
try:
self.string_parts.append(data[start:])
except KeyboardInterrupt as e:
raise e
except Exception:
pass
return start
def _format_line(self, line):
'''
Formats a line of text to fit in the terminal window.
For Tim.
'''
delim = '\n'
offset = 0
self.string_parts = []
# Split the line into an array of columns, e.g., ['0', '0x00000000', 'Some description here']
line_columns = line.split(None, self.num_columns-1)
if line_columns:
# Find where the start of the last column (description) starts in the line of text.
# All line wraps need to be aligned to this offset.
offset = line.rfind(line_columns[-1])
# The delimiter will be a newline followed by spaces padding out the line wrap to the alignment offset.
delim += ' ' * offset
if line_columns and self.fit_to_screen and len(line) > self.SCREEN_WIDTH:
# Calculate the maximum length that each wrapped line can be
max_line_wrap_length = self.SCREEN_WIDTH - offset
# Append all but the last column to formatted_line
formatted_line = line[:offset]
# Loop to split up line into multiple max_line_wrap_length pieces
while len(line[offset:]) > max_line_wrap_length:
# Find the nearest space to wrap the line at (so we don't split a word across two lines)
split_offset = line[offset:offset+max_line_wrap_length].rfind(' ')
# If there were no good places to split the line, just truncate it at max_line_wrap_length
if split_offset < 1:
split_offset = max_line_wrap_length
self._append_to_data_parts(line, offset, offset+split_offset)
offset += split_offset
# Add any remaining data (guarunteed to be max_line_wrap_length long or shorter) to self.string_parts
self._append_to_data_parts(line, offset, offset+len(line[offset:]))
# Append self.string_parts to formatted_line; each part seperated by delim
formatted_line += delim.join(self.string_parts)
else:
formatted_line = line
return formatted_line
def _configure_formatting(self):
'''
Configures output formatting, and fitting output to the current terminal width.
Returns None.
'''
self.format_strings(self.DEFAULT_FORMAT, self.DEFAULT_FORMAT)
if self.fit_to_screen:
try:
import fcntl
import struct
import termios
# Get the terminal window width
hw = struct.unpack('hh', fcntl.ioctl(1, termios.TIOCGWINSZ, '1234'))
self.SCREEN_WIDTH = self.HEADER_WIDTH = hw[1]
except KeyboardInterrupt as e:
raise e
except Exception:
pass