Module: Pwnlib::Util::HexDump

Included in:
Pwn
Defined in:
lib/pwnlib/util/hexdump.rb

Overview

TODO:

Control coloring by context.

Method for output a pretty hexdump. Since this may be used in log module, to avoid cyclic dependency, it is put in a separate module as Fiddling

Examples:

Call by specifying full module path.

require 'pwnlib/util/hexdump'
Pwnlib::Util::HexDump.hexdump('217')
#=> "00000000  32 31 37                  │217│\n00000003"

require 'pwn' and have all methods.

require 'pwn'
hexdump('217')
#=> "00000000  32 31 37                  │217│\n00000003"

Constant Summary collapse

MARKER =
"\u2502"
HIGHLIGHT_STYLE =
->(s) { Rainbow(s).bg(:red) }
DEFAULT_STYLE =
{
  0x00 => ->(s) { Rainbow(s).red },
  0x0a => ->(s) { Rainbow(s).red },
  0xff => ->(s) { Rainbow(s).green },
  marker: ->(s) { Rainbow(s).dimgray },
  printable: ->(s) { s },
  unprintable: ->(s) { Rainbow(s).dimgray }
}.freeze

Class Method Summary collapse

Class Method Details

.hexdump(str, width: 16, skip: true, offset: 0, style: {}, highlight: '') ⇒ String

Returns a hexdump-dump of a string.

Color is provided using rainbow gem and only when output is a tty. To force enable/disable coloring, call Rainbow.enabled = true / false.

Parameters:

  • str (String)

    The string to be hexdumped.

  • width (Integer) (defaults to: 16)

    The max number of characters per line.

  • skip (Boolean) (defaults to: true)

    Whether repeated lines should be replaced by a "*".

  • offset (Integer) (defaults to: 0)

    Offset of the first byte to print in the left column.

  • style (Hash{Integer, Symbol => Proc}) (defaults to: {})

    Color scheme to use.

    Possible keys are:

    • 0x00..0xFF, for specified byte.

    • :marker, for the separator in right column.

    • :printable, for printable bytes that don???t have style specified.

    • :unprintable, for unprintable bytes that don???t have style specified.

    The proc is called with a single argument, the string to be formatted.

  • highlight (String) (defaults to: '')

    Convenient argument to highlight (red background) some bytes in style.

Returns:

  • (String)

    The resulting hexdump.



142
143
144
145
146
# File 'lib/pwnlib/util/hexdump.rb', line 142

def hexdump(str, width: 16, skip: true, offset: 0, style: {}, highlight: '')
  hexdump_iter(StringIO.new(str),
               width: width, skip: skip, offset: offset, style: style,
               highlight: highlight).to_a.join("\n")
end

.hexdump_iter(io, width: 16, skip: true, offset: 0, style: {}, highlight: '') ⇒ Enumerator<String>

Yields lines of a hexdump-dump of a string. Unless you have massive amounts of data you probably want to use hexdump. Returns an Enumerator if no block given.

Color is provided using rainbow gem and only when output is a tty. To force enable/disable coloring, call Rainbow.enabled = true / false.

Parameters:

  • io (#read)

    The object to be dumped.

  • width (Integer) (defaults to: 16)

    The max number of characters per line.

  • skip (Boolean) (defaults to: true)

    Whether repeated lines should be replaced by a "*".

  • offset (Integer) (defaults to: 0)

    Offset of the first byte to print in the left column.

  • style (Hash{Integer, Symbol => Proc}) (defaults to: {})

    Color scheme to use.

    Possible keys are:

    • 0x00..0xFF, for specified byte.

    • :marker, for the separator in right column.

    • :printable, for printable bytes that don???t have style specified.

    • :unprintable, for unprintable bytes that don???t have style specified.

    The proc is called with a single argument, the string to be formatted.

  • highlight (String) (defaults to: '')

    Convenient argument to highlight (red background) some bytes in style.

Returns:

  • (Enumerator<String>)

    The resulting hexdump, line by line.



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
# File 'lib/pwnlib/util/hexdump.rb', line 66

def hexdump_iter(io, width: 16, skip: true, offset: 0, style: {}, highlight: '')
  Enumerator.new do |y|
    style = DEFAULT_STYLE.merge(style)
    highlight.bytes.each { |b| style[b] = HIGHLIGHT_STYLE }
    (0..255).each do |b|
      next if style.key?(b)

      style[b] = (b.chr =~ /[[:print:]]/ ? style[:printable] : style[:unprintable])
    end

    styled_bytes = (0..255).map do |b|
      left_hex = format('%02x', b)
      c = b.chr
      right_char = (c =~ /[[:print:]]/ ? c : "\u00b7")
      [style[b].call(left_hex), style[b].call(right_char)]
    end

    marker = style[:marker].call(MARKER)
    spacer = ' '

    byte_index = offset
    skipping = false
    last_chunk = +''

    loop do
      # We assume that chunk is in ASCII-8BIT encoding.
      chunk = io.read(width)
      break unless chunk

      chunk_bytes = chunk.bytes
      start_byte_index = byte_index
      byte_index += chunk_bytes.size

      # Yield * once for repeated lines.
      if skip && last_chunk == chunk
        y << '*' unless skipping
        skipping = true
        next
      end
      skipping = false
      last_chunk = chunk

      hex_bytes = +''
      printable = +''
      chunk_bytes.each_with_index do |b, i|
        left_hex, right_char = styled_bytes[b]
        hex_bytes << left_hex
        printable << right_char
        if i % 4 == 3 && i != chunk_bytes.size - 1
          hex_bytes << spacer
          printable << marker
        end
        hex_bytes << ' '
      end

      if chunk_bytes.size < width
        padded_hex_length = 3 * width + (width - 1) / 4
        hex_length = 3 * chunk_bytes.size + (chunk_bytes.size - 1) / 4
        hex_bytes << ' ' * (padded_hex_length - hex_length)
      end

      y << format("%08x  %s #{MARKER}%s#{MARKER}", start_byte_index, hex_bytes, printable)
    end

    y << format('%08x', byte_index)
  end
end