From 406a8a3c8eab4691ff32271ca906e93556d845dc Mon Sep 17 00:00:00 2001 From: Cyrille Bagard Date: Thu, 31 Dec 2020 12:42:08 +0100 Subject: Completed the Python plugin displaying file entropy. --- plugins/python/cglimpse/core.py | 19 ++++- plugins/python/cglimpse/distro.py | 61 +++++++++------- plugins/python/cglimpse/entropy.py | 137 ++++++++++++++++++++++++++---------- plugins/python/cglimpse/method.py | 10 ++- plugins/python/cglimpse/panel.py | 130 ++++++++++++++++++++++++++++------ plugins/python/cglimpse/panel.ui | 140 ++++++++++++++++++++++++++++--------- 6 files changed, 380 insertions(+), 117 deletions(-) diff --git a/plugins/python/cglimpse/core.py b/plugins/python/cglimpse/core.py index d2cfdc4..29fb535 100644 --- a/plugins/python/cglimpse/core.py +++ b/plugins/python/cglimpse/core.py @@ -1,5 +1,6 @@ from pychrysalide import PluginModule +from pychrysalide.glibext import ConfigParam from pychrysalide.gui import core from .panel import CGlimpsePanel @@ -13,7 +14,7 @@ class ContentGlimpse(PluginModule): _version = '0.1' _url = 'https://www.chrysalide.re/' - _actions = ( ) + _actions = ( PluginModule.PluginAction.PLUGIN_LOADED, PluginModule.PluginAction.PANEL_CREATION, ) def __init__(self): @@ -22,3 +23,19 @@ class ContentGlimpse(PluginModule): super(ContentGlimpse, self).__init__() core.register_panel(CGlimpsePanel) + + + def _manage(self, action): + """React to several steps of the plugin life.""" + + CGlimpsePanel.setup_config(self.config) + + return True + + + def _on_panel_creation(self, action, item): + """Get notified of a new panel creation.""" + + if type(item) == CGlimpsePanel: + + item.attach_config(self.config) diff --git a/plugins/python/cglimpse/distro.py b/plugins/python/cglimpse/distro.py index 5ffc523..4b490e6 100644 --- a/plugins/python/cglimpse/distro.py +++ b/plugins/python/cglimpse/distro.py @@ -1,20 +1,28 @@ +from gi.repository import Gdk +from pychrysalide.glibext import ConfigParam + from .method import GlimpseMethod class ByteDistribution(GlimpseMethod): - def __init__(self, builder): - """Prepare a Shannon entropy display.""" - super(ByteDistribution, self).__init__(builder) + @staticmethod + def setup_config(config): + """Register the configuration parameters for the method.""" - button = builder.get_object('shannon_color') - button.connect('color-set', self._on_color_set) + color = Gdk.RGBA() + color.parse('#3465A4') + + param = ConfigParam('cglimpse.distrib.color', ConfigParam.ConfigParamType.COLOR, color) + config.add(param) - self._on_color_set(button) - self._step = 0x80 + def __init__(self, builder, config, update_cb): + """Prepare a Distrib entropy display.""" + + super(ByteDistribution, self).__init__(builder, config, update_cb) self._v_legend = 'Quantity' self._h_legend = 'Byte values' @@ -24,41 +32,42 @@ class ByteDistribution(GlimpseMethod): self._values = {} + button = builder.get_object('distrib_color') + button.connect('color-set', self._on_color_set) - def _on_color_set(self, button): - """React on color chosen for the rendering.""" + param = config.search('cglimpse.distrib.color') + color = param.value - color = button.get_rgba() self._color = [ color.red, color.green, color.blue, color.alpha ] self._shadow_color = [ color.red * 0.5, color.green * 0.5, color.blue * 0.5, color.alpha ] + button.set_rgba(param.value) - def format_legend(self, value, vert): - """Build the label used for a rule.""" - if vert: - text = str(value) + def _on_color_set(self, button): + """React on color chosen for the rendering.""" - else: + color = button.get_rgba() - scale = [ ' kb', ' Mb', ' Gb', ' Tb' ] - suffix = '' + self._color = [ color.red, color.green, color.blue, color.alpha ] + self._shadow_color = [ color.red * 0.5, color.green * 0.5, color.blue * 0.5, color.alpha ] + + param = self._config.search('cglimpse.distrib.color') + param.value = color - for i in range(len(scale)): + self._update_cb() - if value < 1024: - break - value /= 1024 - suffix = scale[i] + def format_legend(self, value, vert): + """Build the label used for a rule.""" - text = '%u%s' % (value, suffix) + text = str(int(value)) return text - def update(self, data): - """Provide a description for the method.""" + def update(self, data, coverage): + """Compute internal values for the method.""" max_count = 0 @@ -67,7 +76,7 @@ class ByteDistribution(GlimpseMethod): for i in range(256): self._values[i] = 0 - for b in data: + for b in data[coverage[0] : coverage[1]]: if b in self._values.keys(): self._values[b] += 1 diff --git a/plugins/python/cglimpse/entropy.py b/plugins/python/cglimpse/entropy.py index f090624..5c4df5a 100644 --- a/plugins/python/cglimpse/entropy.py +++ b/plugins/python/cglimpse/entropy.py @@ -1,22 +1,32 @@ import math +from gi.repository import Gdk +from pychrysalide.glibext import ConfigParam from .method import GlimpseMethod class ShannonEntropy(GlimpseMethod): - def __init__(self, builder): - """Prepare a Shannon entropy display.""" - super(ShannonEntropy, self).__init__(builder) + @staticmethod + def setup_config(config): + """Register the configuration parameters for the method.""" - button = builder.get_object('shannon_color') - button.connect('color-set', self._on_color_set) + color = Gdk.RGBA() + color.parse('#3465A4') - self._on_color_set(button) + param = ConfigParam('cglimpse.shannon.color', ConfigParam.ConfigParamType.COLOR, color) + config.add(param) - self._step = 0x80 + param = ConfigParam('cglimpse.shannon.step', ConfigParam.ConfigParamType.ULONG, 0x80) + config.add(param) + + + def __init__(self, builder, config, update_cb): + """Prepare a Shannon entropy display.""" + + super(ShannonEntropy, self).__init__(builder, config, update_cb) self._v_legend = 'Entropy' self._h_legend = 'Byte offsets' @@ -24,17 +34,64 @@ class ShannonEntropy(GlimpseMethod): self._x_range = [ 0, 1024, 10240 ] self._y_range = [ 0.0, 0.25, 1.0 ] - self._size = None + self._coverage = None self._values = [] + button = builder.get_object('shannon_color') + button.connect('color-set', self._on_color_set) + + param = config.search('cglimpse.shannon.color') + color = param.value + + self._color = [ color.red, color.green, color.blue, color.alpha ] + self._shadow_color = [ color.red * 0.5, color.green * 0.5, color.blue * 0.5, color.alpha ] + + button.set_rgba(param.value) + + scale = builder.get_object('shannon_step') + scale.connect('format-value', self._on_scale_format_value) + scale.connect('value-changed', self._on_scale_value_changed) + + param = config.search('cglimpse.shannon.step') + step = param.value + + self._step = step + + scale.set_value(step) + def _on_color_set(self, button): """React on color chosen for the rendering.""" color = button.get_rgba() + self._color = [ color.red, color.green, color.blue, color.alpha ] self._shadow_color = [ color.red * 0.5, color.green * 0.5, color.blue * 0.5, color.alpha ] + param = self._config.search('cglimpse.shannon.color') + param.value = color + + self._update_cb() + + + def _on_scale_value_changed(self, scale): + """React when the range value changes.""" + + step = int(scale.get_value()) + + self._step = step + + param = self._config.search('cglimpse.shannon.step') + param.value = step + + self._update_cb() + + + def _on_scale_format_value(self, scale, value): + """Change how the scale value is displayed.""" + + return '0x%x' % int(value) + def format_legend(self, value, vert): """Build the label used for a rule.""" @@ -44,42 +101,53 @@ class ShannonEntropy(GlimpseMethod): else: + multi = 1 + scale = [ ' kb', ' Mb', ' Gb', ' Tb' ] suffix = '' + remaining = value + for i in range(len(scale)): - if value < 1024: + if remaining < 1024: break - value /= 1024 + multi *= 1024 + + remaining /= 1024 suffix = scale[i] - text = '%u%s' % (value, suffix) + if value % multi == 0: + text = '%u%s' % (remaining, suffix) + else: + text = '%.1f%s' % (value / multi, suffix) return text - def update(self, data): - """Provide a description for the method.""" + def update(self, data, coverage): + """Compute internal values for the method.""" - self._size = len(data) + self._coverage = coverage - step = 2 ** math.ceil(math.log(self._size / 10, 2)) + size = self._coverage[1] - self._coverage[0] - self._x_range = [ 0, step, 10 * step ] + step = math.ceil(size / 10) + + self._x_range = [ coverage[0], step, coverage[0] + 10 * step ] self._values = [] - for i in range(0, self._size, self._step): + for i in range(self._x_range[0], self._x_range[2], self._step): counter = [ 0 for i in range(256) ] start = i end = i + self._step - if end > self._size: - end = self._size + if end > self._coverage[1]: + end = self._coverage[1] for b in data[start : end]: counter[b] += 1 @@ -97,31 +165,29 @@ class ShannonEntropy(GlimpseMethod): def render(self, cr, area): """Draw the bytes distribution for the current binary, if any.""" - step = 2 ** math.ceil(math.log(self._size / 10, 2)) + size = self._coverage[1] - self._coverage[0] - if self._size % step == 0: - full_size = self._size - else: - full_size = (self._size + step - 1) & ~(step - 1) + step = math.ceil(size / 10) - start = 0 + start = self._coverage[0] last_x = area[0] last_y = area[1] + area[3] - (area[3] * self._values[0]) cr.move_to(last_x, last_y + 2) - for i in range(0, self._size, self._step): + for i in range(self._x_range[0], self._x_range[2], self._step): end = i + self._step - if end > self._size: - end = self._size + if end > self._coverage[1]: + end = self._coverage[1] - x = area[0] + ((end - start) * area[2]) / full_size - y = area[1] + area[3] - (area[3] * self._values[int(i / self._step)]) + x = area[0] + ((end - start) * area[2]) / (10 * step) + y = area[1] + area[3] - (area[3] * self._values[int((i - self._x_range[0]) / self._step)]) if last_y != y: cr.line_to(last_x, y + 2) + last_y = y cr.line_to(x, y + 2) @@ -136,18 +202,19 @@ class ShannonEntropy(GlimpseMethod): last_y = area[1] + area[3] - (area[3] * self._values[0]) cr.move_to(last_x, last_y) - for i in range(0, self._size, self._step): + for i in range(self._x_range[0], self._x_range[2], self._step): end = i + self._step - if end > self._size: - end = self._size + if end > self._coverage[1]: + end = self._coverage[1] - x = area[0] + ((end - start) * area[2]) / full_size - y = area[1] + area[3] - (area[3] * self._values[int(i / self._step)]) + x = area[0] + ((end - start) * area[2]) / (10 * step) + y = area[1] + area[3] - (area[3] * self._values[int((i - self._x_range[0]) / self._step)]) if last_y != y: cr.line_to(last_x, y) + last_y = y cr.line_to(x, y) diff --git a/plugins/python/cglimpse/method.py b/plugins/python/cglimpse/method.py index f2e9fdb..731271e 100644 --- a/plugins/python/cglimpse/method.py +++ b/plugins/python/cglimpse/method.py @@ -3,10 +3,12 @@ class GlimpseMethod(): """Abstract class for gimpses.""" - def __init__(self, builder): + def __init__(self, builder, config, update_cb): """Populate the class with its attributes.""" self._builder = builder + self._config = config + self._update_cb = update_cb self._v_legend = None self._h_legend = None @@ -37,6 +39,12 @@ class GlimpseMethod(): return str(value) + def update(self, data, coverage): + """Compute internal values for the method.""" + + pass + + def render(self, cr, area): """Draw the bytes distribution for the current binary, if any.""" diff --git a/plugins/python/cglimpse/panel.py b/plugins/python/cglimpse/panel.py index 2245e3a..a195b0d 100644 --- a/plugins/python/cglimpse/panel.py +++ b/plugins/python/cglimpse/panel.py @@ -2,6 +2,7 @@ import os from gi.repository import Gtk from pychrysalide import core +from pychrysalide.glibext import ConfigParam from pychrysalide.gtkext import BuiltNamedWidget from pychrysalide.gtkext import EasyGtk from pychrysalide.gui import PanelItem @@ -21,6 +22,18 @@ class CGlimpsePanel(PanelItem, UpdatablePanel): _working_group_id = core.setup_tiny_global_work_group() + @staticmethod + def setup_config(config): + """Register the configuration parameters for all the methods.""" + + param = ConfigParam('cglimpse.selected', ConfigParam.ConfigParamType.ULONG, 0) + config.add(param) + + ShannonEntropy.setup_config(config) + + ByteDistribution.setup_config(config) + + def __init__(self): """Initialize the GUI panel.""" @@ -35,10 +48,35 @@ class CGlimpsePanel(PanelItem, UpdatablePanel): self._builder.connect_signals(self) + self._start_changed_sid = -1 + self._end_changed_sid = -1 + self._content = None + self._start = 0 + self._end = 0 + + self._config = None + self._methods = {} + self._current = None + + + def attach_config(self, config): + """Attach a loaded configuration to the displayed panel.""" + + self._config = config + + self._methods['shannon'] = ShannonEntropy(self._builder, config, self._force_update) + self._methods['distrib'] = ByteDistribution(self._builder, config, self._force_update) + + param = config.search('cglimpse.selected') + selected = param.value combo = self._builder.get_object('method_sel') - self._on_method_changed(combo) + + if selected == combo.get_active(): + self._on_method_changed(combo) + else: + combo.set_active(selected) def _change_content(self, old, new): @@ -46,6 +84,23 @@ class CGlimpsePanel(PanelItem, UpdatablePanel): self._content = new.content if new else None + length = len(self._content.data) if self._content else 1 + + self._start = 0 + self._end = length + + scale = self._builder.get_object('start_pos') + scale.set_range(0, length - 1) + scale.set_value(0) + + self._start_changed_sid = scale.connect('value-changed', self._on_position_value_changed) + + scale = self._builder.get_object('end_pos') + scale.set_range(1, length) + scale.set_value(length) + + self._end_changed_sid = scale.connect('value-changed', self._on_position_value_changed) + combo = self._builder.get_object('method_sel') self._on_method_changed(combo) @@ -71,7 +126,7 @@ class CGlimpsePanel(PanelItem, UpdatablePanel): assert(uid == 0) - self._current.update(self._content.data) + self._current.update(self._content.data, [ self._start, self._end ]) area = self._builder.get_object('content') area.queue_draw() @@ -93,6 +148,28 @@ class CGlimpsePanel(PanelItem, UpdatablePanel): assert(uid == 0) + def _on_options_toggled(self, button): + """React on options display/hide order.""" + + lbl = self._builder.get_object('options_label') + + common = self._builder.get_object('common_options') + + if button.get_active(): + + button.get_parent().child_set_property(button, 'expand', False) + lbl.set_angle(0) + + common.show() + + else: + + button.get_parent().child_set_property(button, 'expand', True) + lbl.set_angle(90) + + common.hide() + + def _on_method_changed(self, combo): """React on method selection change.""" @@ -103,40 +180,49 @@ class CGlimpsePanel(PanelItem, UpdatablePanel): model = combo.get_model() key = model[tree_iter][1] - if key == 'shanon': - self._current = ShannonEntropy(self._builder) - - else: - assert(key == 'distrib') - self._current = ByteDistribution(self._builder) + assert(key in self._methods.keys()) + self._current = self._methods[key] self._current.switch() - if self._content: + stack = self._builder.get_object('specific_options') + stack.set_visible_child_name('%s_page' % key) - self.run_update(0) + param = self._config.search('cglimpse.selected') + param.value = combo.get_active() + self._force_update() - def _on_options_toggled(self, button): - """React on options display/hide order.""" - lbl = self._builder.get_object('options_label') + def _on_position_value_changed(self, scale): + """React when the data range value changes.""" - common = self._builder.get_object('common_options') + start_scale = self._builder.get_object('start_pos') + self._start = int(start_scale.get_value()) - if button.get_active(): + end_scale = self._builder.get_object('end_pos') + self._end = int(end_scale.get_value()) - button.get_parent().child_set_property(button, 'expand', False) - lbl.set_angle(0) + length = len(self._content.data) if self._content else 1 - common.show() + start_scale.disconnect(self._start_changed_sid) + end_scale.disconnect(self._end_changed_sid) - else: + start_scale.set_range(0, self._end - 1) + end_scale.set_range(self._start + 1, length) - button.get_parent().child_set_property(button, 'expand', True) - lbl.set_angle(90) + self._start_changed_sid = start_scale.connect('value-changed', self._on_position_value_changed) + self._end_changed_sid = end_scale.connect('value-changed', self._on_position_value_changed) - common.hide() + self._force_update() + + + def _force_update(self): + """Force the update of the rendering.""" + + if self._content: + + self.run_update(0) def _render_grid(self, widget, cr): diff --git a/plugins/python/cglimpse/panel.ui b/plugins/python/cglimpse/panel.ui index 0ec6dc5..8299484 100644 --- a/plugins/python/cglimpse/panel.ui +++ b/plugins/python/cglimpse/panel.ui @@ -2,6 +2,22 @@ + + 100 + 1 + 10 + + + 16 + 1024 + 128 + 1 + + + 100 + 1 + 10 + True False @@ -38,22 +54,24 @@ 8 8 8 + False vertical 8 - True False + True 8 8 True False + True 0 - Shanon entropy + Shannon entropy Bytes distribution @@ -89,9 +107,16 @@ - + + 200 True True + True + start_pos_adj + True + 0 + 0 + right 1 @@ -99,9 +124,16 @@ - + + 200 True True + True + end_pos_adj + True + 0 + 0 + right 1 @@ -116,28 +148,30 @@ - + + True + False + + + False + True + 1 + + + + True False + True - True False 8 8 - - True - True - - - 1 - 2 - - - + 200 True True True @@ -145,26 +179,26 @@ 1 - 1 + 0 - + True False - vertical + Color: + 1 0 0 - 2 True False - Color: + Step: 1 @@ -173,28 +207,67 @@ + + True + True + True + shannon_step_adj + True + 0 + right + + + 1 + 1 + + + + + shannon_page + + + + + True + False + 8 + 8 + + + 200 + True + True + True + rgb(52,101,164) + + + 1 + 0 + + + True False - Step: + Color: 1 0 - 2 + 0 - page0 - page0 + distrib_page + 1 False True - 1 + 2 @@ -204,9 +277,6 @@ 1 - - - False @@ -215,7 +285,6 @@ - True False @@ -254,9 +323,9 @@ True False - 4 + 8 8 - 8 + 4 4 True True @@ -271,6 +340,12 @@ True False + 8 + 8 + 4 + 4 + True + True True @@ -292,6 +367,7 @@ True True + end 1 -- cgit v0.11.2-87-g4458