Skip to content

Commit 30bcd3a

Browse files
peter50216david942j
authored andcommitted
Feature/hexdump (#10)
* First commit for hexdump, need test. * Update hexdump format. * gray -> dimgray * Small fix when highlight string given is not binary * Update * Fix hexdump doc. * Add hexdump test, also add some newlines. * downgrade codeclimate-test-reporter New version is buggy. * fix merge failure. * Fix hexdump test. * ClassMethod -> ClassMethods, fix example.
1 parent fcc11f9 commit 30bcd3a

File tree

15 files changed

+354
-10
lines changed

15 files changed

+354
-10
lines changed

Gemfile.lock

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ PATH
22
remote: .
33
specs:
44
pwntools (0.1.0)
5+
rainbow (~> 2.2)
56

67
GEM
78
remote: https://www.rubygems.org/
89
specs:
910
ast (2.3.0)
10-
codeclimate-test-reporter (1.0.4)
11-
simplecov
11+
codeclimate-test-reporter (0.6.0)
12+
simplecov (>= 0.7.1, < 1.0.0)
1213
coderay (1.1.1)
1314
docile (1.1.5)
1415
json (2.0.2)
@@ -38,19 +39,21 @@ GEM
3839
slop (3.6.0)
3940
tty-platform (0.1.0)
4041
unicode-display_width (1.1.2)
42+
yard (0.9.7)
4143

4244
PLATFORMS
4345
ruby
4446

4547
DEPENDENCIES
46-
codeclimate-test-reporter (~> 1.0)
48+
codeclimate-test-reporter (~> 0.6)
4749
minitest (~> 5.8)
4850
pry (~> 0.10)
4951
pwntools!
50-
rainbow (~> 2.2)
5152
rake (~> 12.0)
5253
rubocop (~> 0.46)
54+
simplecov (~> 0.12)
5355
tty-platform (~> 0.1)
56+
yard (~> 0.9)
5457

5558
BUNDLED WITH
5659
1.13.7

lib/pwnlib/pwn.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,19 @@
77
require 'pwnlib/constants/constants'
88
require 'pwnlib/context'
99
require 'pwnlib/dynelf'
10+
1011
require 'pwnlib/util/cyclic'
1112
require 'pwnlib/util/fiddling'
13+
require 'pwnlib/util/hexdump'
1214
require 'pwnlib/util/packing'
1315

1416
# include this module in a class to use all pwnlib functions in that class
1517
# instance.
1618
module Pwn
1719
include ::Pwnlib::Context
1820

19-
include ::Pwnlib::Util::Packing::ClassMethods
2021
include ::Pwnlib::Util::Cyclic::ClassMethods
2122
include ::Pwnlib::Util::Fiddling::ClassMethods
23+
include ::Pwnlib::Util::HexDump::ClassMethods
24+
include ::Pwnlib::Util::Packing::ClassMethods
2225
end

lib/pwnlib/util/hexdump.rb

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# encoding: ASCII-8BIT
2+
require 'rainbow'
3+
4+
module Pwnlib
5+
module Util
6+
# Method for output a pretty hexdump.
7+
# Since this may be used in log module, to avoid cyclic dependency, it is put in a separate module as {Fiddling}
8+
# See {ClassMethods} for method details.
9+
# @todo Control coloring by context?
10+
# @example Call by specifying full module path.
11+
# require 'pwnlib/util/hexdump'
12+
# Pwnlib::Util::HexDump.hexdump('217')
13+
# #=> "00000000 32 31 37 │217│\n00000003"
14+
# @example require 'pwn' and have all methods.
15+
# require 'pwn'
16+
# hexdump('217')
17+
# #=> "00000000 32 31 37 │217│\n00000003"
18+
module HexDump
19+
# @note Do not create and call instance method here. Instead, call module method on {HexDump}.
20+
module ClassMethods
21+
MARKER = "\u2502".freeze
22+
HIGHLIGHT_STYLE = ->(s) { Rainbow(s).bg(:red) }
23+
DEFAULT_STYLE = {
24+
0x00 => ->(s) { Rainbow(s).red },
25+
0x0a => ->(s) { Rainbow(s).red },
26+
0xff => ->(s) { Rainbow(s).green },
27+
marker: ->(s) { Rainbow(s).dimgray },
28+
printable: ->(s) { s },
29+
unprintable: ->(s) { Rainbow(s).dimgray }
30+
}.freeze
31+
32+
# @!macro [new] hexdump_options
33+
# Color is provided using +rainbow+ gem and only when output is a tty.
34+
# To force enable/disable coloring, call <tt>Rainbow.enabled = true / false</tt>.
35+
# @param [Integer] width
36+
# The max number of characters per line.
37+
# @param [Boolean] skip
38+
# Whether repeated lines should be replaced by a +"*"+.
39+
# @param [Integer] offset
40+
# Offset of the first byte to print in the left column.
41+
# @param [Hash{Integer, Symbol => Proc}] style
42+
# Color scheme to use.
43+
#
44+
# Possible keys are:
45+
# * <tt>0x00..0xFF</tt>, for specified byte.
46+
# * +:marker+, for the separator in right column.
47+
# * +:printable+, for printable bytes that don't have style specified.
48+
# * +:unprintable+, for unprintable bytes that don't have style specified.
49+
# The proc is called with a single argument, the string to be formatted.
50+
# @param [String] highlight
51+
# Convenient argument to highlight (red background) some bytes in style.
52+
53+
# Yields lines of a hexdump-dump of a string. Unless you have massive
54+
# amounts of data you probably want to use {#hexdump}.
55+
# Returns an Enumerator if no block given.
56+
#
57+
# @param [#read] io
58+
# The object to be dumped.
59+
# @!macro hexdump_options
60+
# @return [Enumerator<String>]
61+
# The resulting hexdump, line by line.
62+
def hexdump_iter(io, width: 16, skip: true, offset: 0, style: {}, highlight: '')
63+
Enumerator.new do |y|
64+
style = DEFAULT_STYLE.merge(style)
65+
highlight.bytes.each { |b| style[b] = HIGHLIGHT_STYLE }
66+
(0..255).each do |b|
67+
next if style.include?(b)
68+
style[b] = (b.chr =~ /[[:print:]]/ ? style[:printable] : style[:unprintable])
69+
end
70+
71+
styled_bytes = (0..255).map do |b|
72+
left_hex = format('%02x', b)
73+
c = b.chr
74+
right_char = (c =~ /[[:print:]]/ ? c : "\u00b7")
75+
[style[b].call(left_hex), style[b].call(right_char)]
76+
end
77+
78+
marker = style[:marker].call(MARKER)
79+
spacer = ' '
80+
81+
byte_index = offset
82+
skipping = false
83+
last_chunk = ''
84+
85+
loop do
86+
# We assume that chunk is in ASCII-8BIT encoding.
87+
chunk = io.read(width)
88+
break unless chunk
89+
chunk_bytes = chunk.bytes
90+
start_byte_index = byte_index
91+
byte_index += chunk_bytes.size
92+
93+
# Yield * once for repeated lines.
94+
if skip && last_chunk == chunk
95+
y << '*' unless skipping
96+
skipping = true
97+
next
98+
end
99+
skipping = false
100+
last_chunk = chunk
101+
102+
hex_bytes = ''
103+
printable = ''
104+
chunk_bytes.each_with_index do |b, i|
105+
left_hex, right_char = styled_bytes[b]
106+
hex_bytes << left_hex
107+
printable << right_char
108+
if i % 4 == 3 && i != chunk_bytes.size - 1
109+
hex_bytes << spacer
110+
printable << marker
111+
end
112+
hex_bytes << ' '
113+
end
114+
115+
if chunk_bytes.size < width
116+
padded_hex_length = 3 * width + (width - 1) / 4
117+
hex_length = 3 * chunk_bytes.size + (chunk_bytes.size - 1) / 4
118+
hex_bytes << ' ' * (padded_hex_length - hex_length)
119+
end
120+
121+
y << format("%08x %s #{MARKER}%s#{MARKER}", start_byte_index, hex_bytes, printable)
122+
end
123+
124+
y << format('%08x', byte_index)
125+
end
126+
end
127+
128+
# Returns a hexdump-dump of a string.
129+
#
130+
# @param [String] str
131+
# The string to be hexdumped.
132+
# @!macro hexdump_options
133+
# @return [String]
134+
# The resulting hexdump.
135+
def hexdump(str, width: 16, skip: true, offset: 0, style: {}, highlight: '')
136+
hexdump_iter(StringIO.new(str),
137+
width: width, skip: skip, offset: offset, style: style,
138+
highlight: highlight).to_a.join("\n")
139+
end
140+
end
141+
142+
extend ClassMethods
143+
end
144+
end
145+
end

pwntools.gemspec

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@ Gem::Specification.new do |s|
2121

2222
s.required_ruby_version = '>= 2.1.0'
2323

24-
s.add_development_dependency 'codeclimate-test-reporter', '~> 1.0'
24+
s.add_runtime_dependency 'rainbow', '~> 2.2'
25+
26+
s.add_development_dependency 'codeclimate-test-reporter', '~> 0.6'
2527
s.add_development_dependency 'minitest', '~> 5.8'
26-
s.add_development_dependency 'rainbow', '~> 2.2'
2728
s.add_development_dependency 'pry', '~> 0.10'
2829
s.add_development_dependency 'rake', '~> 12.0'
2930
s.add_development_dependency 'rubocop', '~> 0.46'
31+
s.add_development_dependency 'simplecov', '~> 0.12'
3032
s.add_development_dependency 'tty-platform', '~> 0.1'
33+
s.add_development_dependency 'yard', '~> 0.9'
3134
end

test/constants/constant_test.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# encoding: ASCII-8BIT
2+
23
require 'test_helper'
34
require 'pwnlib/constants/constant'
45

test/constants/constants_test.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# encoding: ASCII-8BIT
2+
23
require 'test_helper'
34
require 'pwnlib/constants/constants'
45
require 'pwnlib/context'

test/context_test.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# encoding: ASCII-8BIT
2+
23
require 'test_helper'
34
require 'pwnlib/context'
45

test/dynelf_test.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# encoding: ASCII-8BIT
2+
23
require 'open3'
34

45
require 'tty-platform'

test/ext_test.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# encoding: ASCII-8BIT
2+
23
require 'test_helper'
3-
require 'pwnlib/ext/string'
4-
require 'pwnlib/ext/integer'
54
require 'pwnlib/ext/array'
5+
require 'pwnlib/ext/integer'
6+
require 'pwnlib/ext/string'
67

78
class ExtTest < MiniTest::Test
89
# Thought that test one method in each module for each type is enough, since it's quite

test/full_file_test.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# encoding: ASCII-8BIT
2-
require 'test_helper'
2+
33
require 'open3'
44

5+
require 'test_helper'
6+
57
class FullFileTest < MiniTest::Test
68
parallelize_me!
79
Dir['test/files/*.rb'].each do |f|

0 commit comments

Comments
 (0)