emonti-rbkb 0.6.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +39 -0
- data/bin/b64 +57 -0
- data/bin/bgrep +91 -0
- data/bin/blit +97 -0
- data/bin/c +14 -0
- data/bin/crc32 +60 -0
- data/bin/d64 +41 -0
- data/bin/dedump +53 -0
- data/bin/hexify +85 -0
- data/bin/len +74 -0
- data/bin/rex +10 -0
- data/bin/rstrings +123 -0
- data/bin/slice +74 -0
- data/bin/telson +109 -0
- data/bin/unhexify +65 -0
- data/bin/urldec +56 -0
- data/bin/urlenc +55 -0
- data/bin/xor +60 -0
- data/lib/rbkb/command_line.rb +41 -0
- data/lib/rbkb/extends.rb +672 -0
- data/lib/rbkb/plug/blit.rb +220 -0
- data/lib/rbkb/plug/peer.rb +64 -0
- data/lib/rbkb/plug/plug.rb +195 -0
- data/lib/rbkb/plug.rb +6 -0
- data/lib/rbkb.rb +6 -0
- data/usage.txt +224 -0
- metadata +109 -0
data/bin/unhexify
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Author Eric Monti (emonti at matasano)
|
3
|
+
#
|
4
|
+
# unhexify converts a string of hex bytes back to raw data. Input can be
|
5
|
+
# supplied via stdin, a hex-string argument, or a file containing hex (use -f).
|
6
|
+
#
|
7
|
+
# Usage: unhexify [options] <data | blank for stdin>
|
8
|
+
# -h, --help Show this message
|
9
|
+
# -v, --version Show version and exit
|
10
|
+
# -f, --file FILENAME Input from FILENAME
|
11
|
+
# -d, --delim DELIMITER DELIMITER regex between hex chunks
|
12
|
+
#
|
13
|
+
|
14
|
+
require 'rbkb'
|
15
|
+
require 'rbkb/command_line'
|
16
|
+
|
17
|
+
include RBkB::CommandLine
|
18
|
+
|
19
|
+
#-------------------------------------------------------------------------------
|
20
|
+
# Init options and arg parsing
|
21
|
+
OPTS = {}
|
22
|
+
arg = bkb_stdargs(nil, OPTS)
|
23
|
+
|
24
|
+
arg.banner += " <data | blank for stdin>"
|
25
|
+
|
26
|
+
arg = bkb_inputargs(arg, OPTS)
|
27
|
+
|
28
|
+
#------------------------------------------------------------------------------
|
29
|
+
# Add local options
|
30
|
+
|
31
|
+
arg.on("-d", "--delim DELIMITER",
|
32
|
+
"DELIMITER regex between hex chunks") do |d|
|
33
|
+
OPTS[:delim] = Regexp.new(d.gsub('\\\\', '\\'))
|
34
|
+
end
|
35
|
+
|
36
|
+
#------------------------------------------------------------------------------
|
37
|
+
# Parse arguments
|
38
|
+
arg.parse!(ARGV) rescue bail "Error: #{$!}\n#{arg}"
|
39
|
+
|
40
|
+
# default string arg
|
41
|
+
if OPTS[:indat].nil? and a=ARGV.shift
|
42
|
+
OPTS[:indat] = a.dup
|
43
|
+
end
|
44
|
+
|
45
|
+
# catchall
|
46
|
+
if ARGV.length != 0
|
47
|
+
bail "Error: bad arguments - #{ARGV.join(' ')}\n#{arg}"
|
48
|
+
end
|
49
|
+
|
50
|
+
OPTS[:indat] ||= STDIN.read()
|
51
|
+
|
52
|
+
#------------------------------------------------------------------------------
|
53
|
+
# Do stuff
|
54
|
+
|
55
|
+
OPTS[:indat].delete!("\r\n")
|
56
|
+
OPTS[:indat]
|
57
|
+
|
58
|
+
OPTS[:delim] ||= /\s*/
|
59
|
+
|
60
|
+
unless out = OPTS[:indat].unhexify(OPTS[:delim])
|
61
|
+
bail "Error: Failed parsing as hex"
|
62
|
+
end
|
63
|
+
|
64
|
+
STDOUT.write(out)
|
65
|
+
|
data/bin/urldec
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Author Eric Monti (emonti at matasano)
|
3
|
+
#
|
4
|
+
# urldec converts a url-encoded string back to its raw form.
|
5
|
+
# (url encoding is really just fancy hex encoding)
|
6
|
+
#
|
7
|
+
# Usage: urldec [options] <data | blank for stdin>
|
8
|
+
# -h, --help Show this message
|
9
|
+
# -v, --version Show version and exit
|
10
|
+
# -f, --file FILENAME Input from FILENAME
|
11
|
+
# -p, --[no-]plus Convert '+' to space (default is true)
|
12
|
+
#
|
13
|
+
|
14
|
+
require 'rbkb'
|
15
|
+
require 'rbkb/command_line'
|
16
|
+
|
17
|
+
include RBkB::CommandLine
|
18
|
+
|
19
|
+
#-------------------------------------------------------------------------------
|
20
|
+
# Init options and arg parsing
|
21
|
+
OPTS = {}
|
22
|
+
arg = bkb_stdargs(nil, OPTS)
|
23
|
+
|
24
|
+
arg.banner += " <data | blank for stdin>"
|
25
|
+
|
26
|
+
arg = bkb_inputargs(arg, OPTS)
|
27
|
+
|
28
|
+
arg.on("-p", "--[no-]plus", "Convert '+' to space (default is true)") do |p|
|
29
|
+
OPTS[:noplus] = (not p)
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
#------------------------------------------------------------------------------
|
34
|
+
# Add local options here
|
35
|
+
|
36
|
+
#------------------------------------------------------------------------------
|
37
|
+
# Parse arguments
|
38
|
+
arg.parse!(ARGV) rescue bail "Error: #{$!}\n#{arg}"
|
39
|
+
|
40
|
+
# default string arg
|
41
|
+
if OPTS[:indat].nil? and a=ARGV.shift
|
42
|
+
OPTS[:indat] = a
|
43
|
+
end
|
44
|
+
|
45
|
+
# catchall
|
46
|
+
if ARGV.length != 0
|
47
|
+
bail "Error: bad arguments - #{ARGV.join(' ')}\n#{arg}"
|
48
|
+
end
|
49
|
+
|
50
|
+
OPTS[:indat] ||= STDIN.read()
|
51
|
+
|
52
|
+
#------------------------------------------------------------------------------
|
53
|
+
# Do stuff
|
54
|
+
|
55
|
+
STDOUT.write(OPTS[:indat].urldec(:noplus => OPTS[:noplus]))
|
56
|
+
|
data/bin/urlenc
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Author Eric Monti (emonti at matasano)
|
3
|
+
#
|
4
|
+
# urlenc converts a string or raw data to a url-encoded string
|
5
|
+
# (url encoding is really just fancy hex encoding)
|
6
|
+
#
|
7
|
+
# Usage: urlenc [options] <data | blank for stdin>
|
8
|
+
# -h, --help Show this message
|
9
|
+
# -v, --version Show version and exit
|
10
|
+
# -f, --file FILENAME Input from FILENAME
|
11
|
+
# -p, --[no-]plus Convert spaces to '+' (default is false)
|
12
|
+
#
|
13
|
+
|
14
|
+
require 'rbkb'
|
15
|
+
require 'rbkb/command_line'
|
16
|
+
|
17
|
+
include RBkB::CommandLine
|
18
|
+
|
19
|
+
#------------------------------------------------------------------------------
|
20
|
+
# Init options and arg parsing
|
21
|
+
OPTS = {}
|
22
|
+
arg = bkb_stdargs(nil, OPTS)
|
23
|
+
arg = bkb_inputargs(arg, OPTS)
|
24
|
+
|
25
|
+
arg.banner += " <data | blank for stdin>"
|
26
|
+
|
27
|
+
arg.on("-p", "--[no-]plus", "Convert spaces to '+' (default is false)") do |p|
|
28
|
+
OPTS[:plus] = p
|
29
|
+
end
|
30
|
+
|
31
|
+
#------------------------------------------------------------------------------
|
32
|
+
# Add local options here
|
33
|
+
|
34
|
+
#------------------------------------------------------------------------------
|
35
|
+
# Parse arguments
|
36
|
+
arg.parse!(ARGV) rescue bail "Error: #{$!}\nUse -h|--help for more info."
|
37
|
+
|
38
|
+
# default string arg
|
39
|
+
if OPTS[:indat].nil? and a=ARGV.shift
|
40
|
+
OPTS[:indat] = a.dup
|
41
|
+
end
|
42
|
+
|
43
|
+
# catchall
|
44
|
+
if ARGV.length != 0
|
45
|
+
bail "Error: bad arguments - #{ARGV.join(' ')}\n-h|--help for more info."
|
46
|
+
end
|
47
|
+
|
48
|
+
# Default to standard input
|
49
|
+
OPTS[:indat] ||= STDIN.read()
|
50
|
+
|
51
|
+
#------------------------------------------------------------------------------
|
52
|
+
# Do stuff
|
53
|
+
|
54
|
+
puts OPTS[:indat].urlenc(:plus => OPTS[:plus])
|
55
|
+
|
data/bin/xor
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Author Eric Monti (emonti at matasano)
|
3
|
+
#
|
4
|
+
# repeating string xor
|
5
|
+
#
|
6
|
+
# Usage: xor [options] -k|-s <key> <data | stdin>
|
7
|
+
# -h, --help Show this message
|
8
|
+
# -v, --version Show version and exit
|
9
|
+
# -f, --file FILENAME Input from FILENAME
|
10
|
+
#
|
11
|
+
# Key options (one of the following is required):
|
12
|
+
# -s, --strkey STRING xor against bare STRING
|
13
|
+
# -x, --hexkey HEXSTR xor against decoded HEXSTR
|
14
|
+
#
|
15
|
+
|
16
|
+
require 'rbkb'
|
17
|
+
require 'rbkb/command_line'
|
18
|
+
|
19
|
+
include RBkB::CommandLine
|
20
|
+
|
21
|
+
#-------------------------------------------------------------------------------
|
22
|
+
# Init options and arg parsing
|
23
|
+
OPTS = {}
|
24
|
+
arg = bkb_stdargs(nil, OPTS)
|
25
|
+
arg = bkb_inputargs(arg, OPTS)
|
26
|
+
|
27
|
+
arg.banner += " -k|-s <key> <data | stdin>"
|
28
|
+
|
29
|
+
#------------------------------------------------------------------------------
|
30
|
+
# Add local options
|
31
|
+
arg.separator ""
|
32
|
+
arg.separator " Key options (one of the following is required):"
|
33
|
+
|
34
|
+
arg.on("-s", "--strkey STRING", "xor against bare STRING") do |s|
|
35
|
+
bail "only one key option can be specified with -s or -x" if OPTS[:key]
|
36
|
+
OPTS[:key] = s
|
37
|
+
end
|
38
|
+
|
39
|
+
arg.on("-x", "--hexkey HEXSTR", "xor against decoded HEXSTR") do |x|
|
40
|
+
bail "only one key option can be specified with -s or -x" if OPTS[:key]
|
41
|
+
x.sub!(/^0[xX]/, '')
|
42
|
+
bail "Unable to parse hex string" unless OPTS[:key] = x.unhexify
|
43
|
+
end
|
44
|
+
|
45
|
+
#------------------------------------------------------------------------------
|
46
|
+
# Parse arguments
|
47
|
+
arg.parse!(ARGV) rescue bail "Error: #{$!}\n#{arg}"
|
48
|
+
|
49
|
+
bail "You must specify a key with -s or -x\n#{arg}" unless OPTS[:key]
|
50
|
+
|
51
|
+
OPTS[:indat] ||= ARGV.shift
|
52
|
+
|
53
|
+
if ARGV.length != 0
|
54
|
+
bail "Error: bad arguments - #{ARGV.join(' ')}\n-h|--help for more info."
|
55
|
+
end
|
56
|
+
|
57
|
+
OPTS[:indat] ||= STDIN.read()
|
58
|
+
|
59
|
+
STDOUT.write OPTS[:indat].xor(OPTS[:key])
|
60
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Author Eric Monti (emonti at matasano)
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
module RBkB
|
5
|
+
module CommandLine
|
6
|
+
# exits with a message on stderr
|
7
|
+
def bail(msg)
|
8
|
+
STDERR.puts msg if msg
|
9
|
+
exit 1
|
10
|
+
end
|
11
|
+
|
12
|
+
# returns a OptionsParser object with blackbag standard options
|
13
|
+
def bkb_stdargs(o=OptionParser.new, cfg=OPTS)
|
14
|
+
o=OptionParser.new
|
15
|
+
o.banner = "Usage: #{File.basename $0} [options]"
|
16
|
+
|
17
|
+
o.on("-h", "--help", "Show this message") do
|
18
|
+
bail(o)
|
19
|
+
end
|
20
|
+
|
21
|
+
o.on("-v", "--version", "Show version and exit") do
|
22
|
+
bail("Ruby BlackBag version #{RBkB::VERSION}")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# returns a OptionsParser object with blackbag input options
|
27
|
+
def bkb_inputargs(o=OptionParser.new, cfg=OPTS)
|
28
|
+
o.on("-f", "--file FILENAME",
|
29
|
+
"Input from FILENAME") do |f|
|
30
|
+
begin
|
31
|
+
cfg[:indat] = File.read(f)
|
32
|
+
rescue
|
33
|
+
bail "File Error: #{$!}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
return o
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
data/lib/rbkb/extends.rb
ADDED
@@ -0,0 +1,672 @@
|
|
1
|
+
# Author Eric Monti (emonti at matasano)
|
2
|
+
require "base64"
|
3
|
+
require "stringio"
|
4
|
+
require 'zlib'
|
5
|
+
require 'open3'
|
6
|
+
require 'sha1'
|
7
|
+
|
8
|
+
module RBkB
|
9
|
+
DEFAULT_BYTE_ORDER=:big
|
10
|
+
HEXCHARS = [("0".."9").to_a, ("a".."f").to_a].flatten
|
11
|
+
end
|
12
|
+
|
13
|
+
# Generates a "universally unique identifier"
|
14
|
+
def uuid
|
15
|
+
(SHA1::sha1(rand.to_s)).to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
# Generates a random alphanumeric string of 'size' bytes (8 by default)
|
19
|
+
def random_string(size = 8)
|
20
|
+
chars = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a
|
21
|
+
(1..size).collect{|a| chars[rand(chars.size)]}.join
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
# Simple syntactic sugar to pass any object to a block
|
26
|
+
def with(x)
|
27
|
+
yield x if block_given?; x
|
28
|
+
end if not defined? with
|
29
|
+
|
30
|
+
|
31
|
+
#-----------------------------------------------------------------------------
|
32
|
+
|
33
|
+
# Mixins and class-specific items
|
34
|
+
|
35
|
+
class String
|
36
|
+
|
37
|
+
# shortcut for hex sanity with regex
|
38
|
+
def ishex? ; (self =~ /^[a-f0-9]+$/i)? true : false ; end
|
39
|
+
|
40
|
+
# Encode into percent-hexify url encoding format
|
41
|
+
def urlenc(opts={})
|
42
|
+
s=self
|
43
|
+
plus = opts[:plus]
|
44
|
+
unless (opts[:rx] ||= /[^A-Za-z0-9_\.~-]/).kind_of? Regexp
|
45
|
+
raise "rx must be a regular expression for a character class"
|
46
|
+
end
|
47
|
+
hx = RBkB::HEXCHARS
|
48
|
+
|
49
|
+
s.gsub(opts[:rx]) do |c|
|
50
|
+
c=c[0]
|
51
|
+
(plus and c==32)? '+' : "%" + (hx[(c >> 4)] + hx[(c & 0xf )])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Undo percent-hexified url encoding data
|
56
|
+
def urldec(opts={})
|
57
|
+
s=self
|
58
|
+
s.gsub!('+', ' ') unless opts[:noplus]
|
59
|
+
s.gsub(/%([A-Fa-f0-9]{2})/) {$1.hex.chr}
|
60
|
+
end
|
61
|
+
|
62
|
+
# Base64 encode
|
63
|
+
def b64; Base64.encode64(self).tr("\n", ""); end
|
64
|
+
|
65
|
+
# Base64 decode
|
66
|
+
def d64; Base64.decode64(self); end
|
67
|
+
|
68
|
+
# right-align to 'a' alignment padded with 'p'
|
69
|
+
def ralign(a, p=' ')
|
70
|
+
s=self
|
71
|
+
p ||= ' '
|
72
|
+
l = s.length
|
73
|
+
pad = l.pad(a)
|
74
|
+
s.rjust(pad+l, p)
|
75
|
+
end
|
76
|
+
|
77
|
+
# left-align to 'a' alignment padded with 'p'
|
78
|
+
def lalign(a, p=' ')
|
79
|
+
s=self
|
80
|
+
p ||= ' '
|
81
|
+
l = s.length
|
82
|
+
pad = l.pad(a)
|
83
|
+
s.ljust(pad+l, p)
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
# Convert a string to ASCII hex string. Supports a few options for format:
|
88
|
+
#
|
89
|
+
# :delim - delimter between each hex byte
|
90
|
+
# :prefix - prefix before each hex byte
|
91
|
+
# :suffix - suffix after each hex byte
|
92
|
+
#
|
93
|
+
def hexify(opts={})
|
94
|
+
s=self
|
95
|
+
delim = opts[:delim]
|
96
|
+
pre = (opts[:prefix] || "")
|
97
|
+
suf = (opts[:suffix] || "")
|
98
|
+
|
99
|
+
if (rx=opts[:rx]) and not rx.kind_of? Regexp
|
100
|
+
raise "rx must be a regular expression for a character class"
|
101
|
+
end
|
102
|
+
|
103
|
+
hx=RBkB::HEXCHARS
|
104
|
+
|
105
|
+
out=Array.new
|
106
|
+
|
107
|
+
s.each_byte do |c|
|
108
|
+
hc = if (rx and not rx.match c.chr)
|
109
|
+
c.chr
|
110
|
+
else
|
111
|
+
pre + (hx[(c >> 4)] + hx[(c & 0xf )]) + suf
|
112
|
+
end
|
113
|
+
out << (hc)
|
114
|
+
end
|
115
|
+
out.join(delim)
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
# Convert ASCII hex string to raw.
|
120
|
+
#
|
121
|
+
# Parameters:
|
122
|
+
#
|
123
|
+
# d = optional 'delimiter' between hex bytes (zero+ spaces by default)
|
124
|
+
def unhexify(d=/\s*/)
|
125
|
+
self.strip.gsub(/([A-Fa-f0-9]{1,2})#{d}?/) { $1.hex.chr }
|
126
|
+
end
|
127
|
+
|
128
|
+
# Converts a hex value to numeric.
|
129
|
+
#
|
130
|
+
# Parameters:
|
131
|
+
#
|
132
|
+
# order => :big or :little endian (default is :big)
|
133
|
+
#
|
134
|
+
def hex_to_num(order=:big)
|
135
|
+
s=self
|
136
|
+
raise "invalid hex value: '#{s.inspect}'" unless s.ishex?
|
137
|
+
|
138
|
+
r = if order == :little
|
139
|
+
s.scan(/.{2}/).reverse.join
|
140
|
+
elsif order == :big
|
141
|
+
s
|
142
|
+
else
|
143
|
+
raise "Invalid byte order #{order.inspect}"
|
144
|
+
end.hex
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
# A "generalized" lazy bytestring -> numeric converter.
|
149
|
+
#
|
150
|
+
# Parameters:
|
151
|
+
#
|
152
|
+
# order => :big or :little endian (default is :big)
|
153
|
+
#
|
154
|
+
# Bonus: should work seamlessly with really large strings.
|
155
|
+
#
|
156
|
+
# >> ("\xFF"*10).dat_to_num
|
157
|
+
# => 1208925819614629174706175
|
158
|
+
# >> ("\xFF"*20).dat_to_num
|
159
|
+
# => 1461501637330902918203684832716283019655932542975
|
160
|
+
#
|
161
|
+
def dat_to_num(order=:big)
|
162
|
+
s=self
|
163
|
+
s.reverse! if order == :little
|
164
|
+
r = 0
|
165
|
+
s.each_byte {|c| r = ((r << 8) | c)}
|
166
|
+
r
|
167
|
+
end
|
168
|
+
alias lazy_to_n dat_to_num
|
169
|
+
alias lazy_to_num dat_to_num
|
170
|
+
alias dat_to_n dat_to_num
|
171
|
+
|
172
|
+
|
173
|
+
#### Crypto'ey stuff
|
174
|
+
|
175
|
+
# calculates entropy in string
|
176
|
+
#
|
177
|
+
# TQBF's description:
|
178
|
+
# "I also added a chi-squared test to quickly figure out entropy of a
|
179
|
+
# string, in "bits of randomness per byte". This is useful, so..."
|
180
|
+
def entropy
|
181
|
+
e = 0
|
182
|
+
0.upto(255) do |i|
|
183
|
+
x = count(i.chr)/size.to_f
|
184
|
+
if x > 0
|
185
|
+
e += - x * x.log2
|
186
|
+
end
|
187
|
+
end
|
188
|
+
e
|
189
|
+
end
|
190
|
+
|
191
|
+
# xor against a key. key will be repeated or truncated to self.size.
|
192
|
+
def xor(k)
|
193
|
+
s=self
|
194
|
+
out=StringIO.new ; i=0;
|
195
|
+
s.each_byte do |x|
|
196
|
+
out.write((x ^ (k[i] || k[i=0]) ).chr)
|
197
|
+
i+=1
|
198
|
+
end
|
199
|
+
out.string
|
200
|
+
end
|
201
|
+
|
202
|
+
# convert bytes to number then xor against another byte-string or number
|
203
|
+
def ^(x)
|
204
|
+
x = x.dat_to_num unless x.is_a? Numeric
|
205
|
+
(self.dat_to_num ^ x)#.to_bytes
|
206
|
+
end
|
207
|
+
|
208
|
+
# String randomizer
|
209
|
+
def randomize ; self.split('').randomize.to_s ; end
|
210
|
+
|
211
|
+
# In-place string randomizer
|
212
|
+
def randomize! ; self.replace(randomize) end
|
213
|
+
|
214
|
+
|
215
|
+
# Returns or prints a hexdump in the style of 'hexdump -C'
|
216
|
+
#
|
217
|
+
# :len => optionally specify a length other than 16 for a wider or thinner
|
218
|
+
# dump. If length is an odd number, it will be rounded up.
|
219
|
+
#
|
220
|
+
# :out => optionally specify an alternate IO object for output. By default,
|
221
|
+
# hexdump will output to STDOUT. Pass a StringIO object and it will return
|
222
|
+
# it as a string.
|
223
|
+
#
|
224
|
+
# Example:
|
225
|
+
#
|
226
|
+
# Here's the default behavior done explicitely:
|
227
|
+
#
|
228
|
+
# >> xxd = dat.hexdump(:len => 16, :out => StringIO.new)
|
229
|
+
# => <a string containing hexdump>
|
230
|
+
#
|
231
|
+
# Here's how to change it to STDERR
|
232
|
+
#
|
233
|
+
# >> xxd = dat.hexdump(:len => 16, :out => STDERR)
|
234
|
+
# <prints hexdump on STDERR>
|
235
|
+
# -> nil # return value is nil!
|
236
|
+
#
|
237
|
+
def hexdump(opt={})
|
238
|
+
s=self
|
239
|
+
out = opt[:out] || StringIO.new
|
240
|
+
len = (opt[:len] and opt[:len] > 0)? opt[:len] + (opt[:len] % 2) : 16
|
241
|
+
|
242
|
+
off = opt[:start_addr] || 0
|
243
|
+
offlen = opt[:start_len] || 8
|
244
|
+
|
245
|
+
hlen=len/2
|
246
|
+
|
247
|
+
s.scan(/(?:.|\n){1,#{len}}/) do |m|
|
248
|
+
out.write(off.to_s(16).rjust(offlen, "0") + ' ')
|
249
|
+
|
250
|
+
i=0
|
251
|
+
m.each_byte do |c|
|
252
|
+
out.write c.to_s(16).rjust(2,"0") + " "
|
253
|
+
out.write(' ') if (i+=1) == hlen
|
254
|
+
end
|
255
|
+
|
256
|
+
out.write(" " * (len-i) ) # pad
|
257
|
+
out.write(" ") if i < hlen
|
258
|
+
|
259
|
+
out.write(" |" + m.tr("\0-\37\177-\377", '.') + "|\n")
|
260
|
+
off += m.length
|
261
|
+
end
|
262
|
+
|
263
|
+
out.write(off.to_s(16).rjust(offlen,'0') + "\n")
|
264
|
+
|
265
|
+
if out.class == StringIO
|
266
|
+
out.string
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
|
271
|
+
# Converts a hexdump back to binary - takes the same options as hexdump().
|
272
|
+
# Fairly flexible. Should work both with 'xxd' and 'hexdump -C' style dumps.
|
273
|
+
def dehexdump(opt={})
|
274
|
+
s=self
|
275
|
+
out = opt[:out] || StringIO.new
|
276
|
+
len = (opt[:len] and opt[:len] > 0)? opt[:len] : 16
|
277
|
+
|
278
|
+
hcrx = /[A-Fa-f0-9]/
|
279
|
+
dumprx = /^(#{hcrx}+):?\s*((?:#{hcrx}{2}\s*){0,#{len}})/
|
280
|
+
off = opt[:start_addr] || 0
|
281
|
+
|
282
|
+
i=1
|
283
|
+
# iterate each line of hexdump
|
284
|
+
s.split(/\r?\n/).each do |hl|
|
285
|
+
# match and check offset
|
286
|
+
if m = dumprx.match(hl) and $1.hex == off
|
287
|
+
i+=1
|
288
|
+
# take the data chunk and unhexify it
|
289
|
+
raw = $2.unhexify
|
290
|
+
off += out.write(raw)
|
291
|
+
else
|
292
|
+
raise "Hexdump parse error on line #{i} #{s}"
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
if out.class == StringIO
|
297
|
+
out.string
|
298
|
+
end
|
299
|
+
end
|
300
|
+
alias dedump dehexdump
|
301
|
+
alias undump dehexdump
|
302
|
+
alias unhexdump dehexdump
|
303
|
+
|
304
|
+
|
305
|
+
# Binary grep
|
306
|
+
#
|
307
|
+
# Parameters:
|
308
|
+
#
|
309
|
+
# find : A Regexp or string to search for in self
|
310
|
+
# align : nil | numeric alignment (matches only made if aligned)
|
311
|
+
def bgrep(find, align=nil)
|
312
|
+
if align and (not align.is_a?(Integer) or align < 0)
|
313
|
+
raise "alignment must be a integer >= 0"
|
314
|
+
end
|
315
|
+
|
316
|
+
dat=self
|
317
|
+
if find.kind_of? Regexp
|
318
|
+
search = lambda do |find, buf|
|
319
|
+
if m = find.match(buf)
|
320
|
+
mtch = m[0]
|
321
|
+
off,endoff = m.offset(0)
|
322
|
+
return off, endoff, mtch
|
323
|
+
end
|
324
|
+
end
|
325
|
+
else
|
326
|
+
search = lambda do |find, buf|
|
327
|
+
if off = buf.index(find)
|
328
|
+
return off, off+find.size, find
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
ret=[]
|
334
|
+
pos = 0
|
335
|
+
while (res = search.call(find, dat[pos..-1]))
|
336
|
+
off, endoff, match = res
|
337
|
+
if align and ( pad = (pos+off).pad(align) ) != 0
|
338
|
+
pos += pad
|
339
|
+
else
|
340
|
+
hit = [pos+off, pos+endoff, match]
|
341
|
+
if not block_given? or yield([pos+off, pos+endoff, match])
|
342
|
+
ret << hit
|
343
|
+
end
|
344
|
+
pos += endoff
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
# A 'strings' method a-la unix strings utility. Finds printable strings in
|
350
|
+
# a binary blob.
|
351
|
+
# Supports ASCII and little endian unicode (though only for ASCII printable
|
352
|
+
# character.)
|
353
|
+
#
|
354
|
+
# === Parameters and options:
|
355
|
+
#
|
356
|
+
# * Use the :minimum parameter to specify minimum number of characters
|
357
|
+
# to match. (default = 6)
|
358
|
+
#
|
359
|
+
# * Use the :encoding parameter as one of :ascii, :unicode, or :both
|
360
|
+
# (default = :ascii)
|
361
|
+
#
|
362
|
+
# * The 'strings' method uses Regexp under the hood. Therefore
|
363
|
+
# you can pass a character class for "valid characters" with :valid
|
364
|
+
# (default = /[\r\n [:print:]]/)
|
365
|
+
#
|
366
|
+
# * Supports an optional block, which will be passed |offset, type, string|
|
367
|
+
# for each match.
|
368
|
+
# The block's boolean return value also determines whether the match
|
369
|
+
# passes or fails (true or false/nil) and gets returned by the function.
|
370
|
+
#
|
371
|
+
# === Return Value:
|
372
|
+
#
|
373
|
+
# Returns an array consisting of matches with the following elements:
|
374
|
+
#
|
375
|
+
# [[start_offset, end_offset, string_type, string], ...]
|
376
|
+
#
|
377
|
+
# * string_type will be one of :ascii or :unicode
|
378
|
+
# * end_offset will include the terminating null character
|
379
|
+
# * end_offset will include all null bytes in unicode strings (including
|
380
|
+
# * both terminating nulls)
|
381
|
+
#
|
382
|
+
# If strings are null terminated, the trailing null *IS* included
|
383
|
+
# in the end_offset. Unicode matches will also include null bytes.
|
384
|
+
#
|
385
|
+
# TODO?
|
386
|
+
# - better unicode support (i.e. not using half-assed unicode)
|
387
|
+
# - support other encodings such as all those the binutils strings does?
|
388
|
+
# - not sure if we want the trailing null in null terminated strings
|
389
|
+
# - not sure if we want wide characters to include their null bytes
|
390
|
+
def strings(opts={})
|
391
|
+
opts[:encoding] ||= :both
|
392
|
+
prx = (opts[:valid] || /[\r\n [:print:]]/)
|
393
|
+
min = (opts[:minimum] || 6)
|
394
|
+
align = opts[:align]
|
395
|
+
|
396
|
+
raise "Minimum must be numeric and > 0" unless min.kind_of? Numeric and min > 0
|
397
|
+
|
398
|
+
arx = /(#{prx}{#{min}}?#{prx}*\x00?)/
|
399
|
+
urx = /((?:#{prx}\x00){#{min}}(?:#{prx}\x00)*(?:\x00\x00)?)/
|
400
|
+
|
401
|
+
rx = case (opts[:encoding] || :both).to_sym
|
402
|
+
when :ascii : arx
|
403
|
+
when :unicode : urx
|
404
|
+
when :both : Regexp.union( arx, urx )
|
405
|
+
else
|
406
|
+
raise "Encoding must be :unicode, :ascii, or :both"
|
407
|
+
end
|
408
|
+
|
409
|
+
off=0
|
410
|
+
ret = []
|
411
|
+
|
412
|
+
while mtch = rx.match(self[off..-1])
|
413
|
+
# calculate relative offsets
|
414
|
+
rel_off = mtch.offset(0)
|
415
|
+
startoff = off + rel_off[0]
|
416
|
+
endoff = off + rel_off[1]
|
417
|
+
off += rel_off[1]
|
418
|
+
|
419
|
+
if align and (pad=startoff.pad(align)) != 0
|
420
|
+
off = startoff + pad
|
421
|
+
next
|
422
|
+
end
|
423
|
+
|
424
|
+
stype = if mtch[1]
|
425
|
+
:ascii
|
426
|
+
elsif mtch[2]
|
427
|
+
:unicode
|
428
|
+
end
|
429
|
+
|
430
|
+
|
431
|
+
mret = [startoff, endoff, stype, mtch[0] ]
|
432
|
+
|
433
|
+
# yield to a block for additional criteria
|
434
|
+
next if block_given? and not yield( *mret )
|
435
|
+
|
436
|
+
ret << mret
|
437
|
+
end
|
438
|
+
|
439
|
+
return ret
|
440
|
+
end
|
441
|
+
|
442
|
+
# Does string "start with" dat?
|
443
|
+
# No clue whether/when this is faster than a regex, but it is easier to type.
|
444
|
+
def starts_with?(dat)
|
445
|
+
self[0,dat.size] == dat
|
446
|
+
end
|
447
|
+
|
448
|
+
# Returns a single null-terminated ascii string from beginning of self.
|
449
|
+
# This will return the entire string if no null is encountered.
|
450
|
+
#
|
451
|
+
# Parameters:
|
452
|
+
#
|
453
|
+
# off = specify an optional beggining offset
|
454
|
+
#
|
455
|
+
def cstring(off=0)
|
456
|
+
self[ off, self.index("\x00") || self.size ]
|
457
|
+
end
|
458
|
+
|
459
|
+
# returns CRC32 checksum for the string object
|
460
|
+
def crc32
|
461
|
+
## XXX slower, but here for reference (found on some forum)
|
462
|
+
# r = 0xFFFFFFFF
|
463
|
+
# self.each_byte do |b|
|
464
|
+
# r ^= b
|
465
|
+
# 8.times do
|
466
|
+
# r = (r>>1) ^ (0xEDB88320 * (r & 1))
|
467
|
+
# end
|
468
|
+
# end
|
469
|
+
# r ^ 0xFFFFFFFF
|
470
|
+
## or... we can just use:
|
471
|
+
Zlib.crc32 self
|
472
|
+
end
|
473
|
+
|
474
|
+
# This attempts to identify a blob of data using 'file(1)' via popen3
|
475
|
+
# (using popen3 because IO.popen blows)
|
476
|
+
# Tried doing this with a fmagic ruby extention to libmagic, but it was
|
477
|
+
# a whole lot slower.
|
478
|
+
def pipe_magick(arg="")
|
479
|
+
ret=""
|
480
|
+
Open3.popen3("file #{arg} -") do |w,r,e|
|
481
|
+
w.write self; w.close
|
482
|
+
ret = r.read ; r.close
|
483
|
+
ret.sub!(/^\/dev\/stdin: /, "")
|
484
|
+
end
|
485
|
+
ret
|
486
|
+
end
|
487
|
+
|
488
|
+
# convert a string to its idiomatic ruby class name
|
489
|
+
def class_name
|
490
|
+
r = ""
|
491
|
+
up = true
|
492
|
+
each_byte do |c|
|
493
|
+
if c == 95
|
494
|
+
if up
|
495
|
+
r << "::"
|
496
|
+
else
|
497
|
+
up = true
|
498
|
+
end
|
499
|
+
else
|
500
|
+
m = up ? :upcase : :to_s
|
501
|
+
r << (c.chr.send(m))
|
502
|
+
up = false
|
503
|
+
end
|
504
|
+
end
|
505
|
+
r
|
506
|
+
end
|
507
|
+
|
508
|
+
|
509
|
+
|
510
|
+
# Returns a reference to actual constant for a given name in namespace
|
511
|
+
# can be used to lookup classes from enums and such
|
512
|
+
def const_lookup(ns=Object)
|
513
|
+
if c=ns.constants.select {|n| n == self.class_name } and not c.empty?
|
514
|
+
ns.const_get(c.first)
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
end # class String
|
519
|
+
|
520
|
+
class Symbol
|
521
|
+
# looks up this symbol as a constant defined in 'ns' (Object by default)
|
522
|
+
def const_lookup(ns=Object)
|
523
|
+
self.to_s.const_lookup(ns)
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
class Array
|
528
|
+
# randomizes the order of contents in the Array (self)
|
529
|
+
def randomize ; self.sort_by { rand } ; end
|
530
|
+
end
|
531
|
+
|
532
|
+
|
533
|
+
class Float
|
534
|
+
def log2; Math.log(self)/Math.log(2); end
|
535
|
+
end
|
536
|
+
|
537
|
+
|
538
|
+
class Numeric
|
539
|
+
|
540
|
+
# calculate padding based on alignment(a)
|
541
|
+
def pad(a)
|
542
|
+
raise "bad alignment #{a.inspect}" unless a.kind_of? Numeric and a > 0
|
543
|
+
return self < 1 ? a + self : (a-1) - (self-1) % a
|
544
|
+
end
|
545
|
+
|
546
|
+
# tells you whether a number is within printable range
|
547
|
+
def printable?; self >= 0x20 and self <= 0x7e; end
|
548
|
+
|
549
|
+
# just to go with the flow
|
550
|
+
def randomize ; rand(self) ; end
|
551
|
+
|
552
|
+
# shortcut for packing a single number... wtf...
|
553
|
+
def pack(arg) ; [self].pack(arg) ; end
|
554
|
+
|
555
|
+
def clear_bits(c) ; (self ^ (self & c)) ; end
|
556
|
+
|
557
|
+
# Returns an array of chars per 8-bit break-up.
|
558
|
+
# Accepts a block for some transformation on each byte.
|
559
|
+
# (used by to_bytes and to_hex under the hood)
|
560
|
+
#
|
561
|
+
# args:
|
562
|
+
# order: byte order - :big or :little
|
563
|
+
# (only :big has meaning)
|
564
|
+
# siz: pack to this size. larger numbers will wrap
|
565
|
+
def to_chars(order=nil, siz=nil)
|
566
|
+
order ||= RBkB::DEFAULT_BYTE_ORDER
|
567
|
+
n=self
|
568
|
+
siz ||= self.size
|
569
|
+
ret=[]
|
570
|
+
siz.times do
|
571
|
+
c = (n % 256)
|
572
|
+
if block_given? then (c = yield(c)) end
|
573
|
+
ret << c
|
574
|
+
n=(n >> 8)
|
575
|
+
end
|
576
|
+
return ((order == :big)? ret.reverse : ret)
|
577
|
+
end
|
578
|
+
|
579
|
+
# "packs" a number into bytes using bit-twiddling instead of pack()
|
580
|
+
#
|
581
|
+
# Uses to_chars under the hood. See also: to_hex
|
582
|
+
#
|
583
|
+
# args:
|
584
|
+
# siz: pack to this size. larger numbers will wrap
|
585
|
+
# order: byte order - :big or :little
|
586
|
+
# (only :big has meaning)
|
587
|
+
def to_bytes(order=nil, siz=nil)
|
588
|
+
to_chars(order,siz) {|c| c.chr }.join
|
589
|
+
end
|
590
|
+
|
591
|
+
# Converts a number to hex string with width and endian options.
|
592
|
+
# "packs" a number into bytes using bit-twiddling instead of pack()
|
593
|
+
#
|
594
|
+
# Uses to_chars under the hood. See also: to_bytes
|
595
|
+
#
|
596
|
+
# args:
|
597
|
+
# siz: pack to this size. larger numbers will wrap
|
598
|
+
# order: byte order - :big or :little
|
599
|
+
# (only :big has meaning)
|
600
|
+
#
|
601
|
+
def to_hex(o=nil, s=nil)
|
602
|
+
to_chars(o,s) {|c|
|
603
|
+
RBkB::HEXCHARS[c.clear_bits(0xf) >> 4]+RBkB::HEXCHARS[c.clear_bits(0xf0)]
|
604
|
+
}.join
|
605
|
+
end
|
606
|
+
|
607
|
+
# XXX TODO Fixme for new to_bytes/char etc.
|
608
|
+
# def to_guid(order=RBkB::DEFAULT_BYTE_ORDER)
|
609
|
+
# raw = self.to_bytes(order, 16)
|
610
|
+
# a,b,c,d,*e = raw.unpack("VvvnC6").map{|x| x.to_hex}
|
611
|
+
# e = e.join
|
612
|
+
# [a,b,c,d,e].join("-").upcase
|
613
|
+
# end
|
614
|
+
|
615
|
+
end # class Numeric
|
616
|
+
|
617
|
+
|
618
|
+
# some extra features for zlib... more to come?
|
619
|
+
module Zlib
|
620
|
+
OSMAP = {
|
621
|
+
OS_MSDOS => :msdos,
|
622
|
+
OS_AMIGA => :amiga,
|
623
|
+
OS_VMS => :vms,
|
624
|
+
OS_UNIX => :unix,
|
625
|
+
OS_ATARI => :atari,
|
626
|
+
OS_OS2 => :os2,
|
627
|
+
OS_TOPS20 => :tops20,
|
628
|
+
OS_WIN32 => :win32,
|
629
|
+
OS_VMCMS => :vmcms,
|
630
|
+
OS_ZSYSTEM => :zsystem,
|
631
|
+
OS_CPM => :cpm,
|
632
|
+
OS_RISCOS => :riscos,
|
633
|
+
OS_UNKNOWN => :unknown
|
634
|
+
}
|
635
|
+
|
636
|
+
# Helpers for Zlib::GzipFile... more to come?
|
637
|
+
class GzipFile
|
638
|
+
|
639
|
+
## extra info dump for gzipped files
|
640
|
+
def get_xtra_info
|
641
|
+
info = {
|
642
|
+
:file_crc => crc.to_hex,
|
643
|
+
:file_comment => comment,
|
644
|
+
:file_name => orig_name,
|
645
|
+
:level => level,
|
646
|
+
:mtime => mtime,
|
647
|
+
:os => (Zlib::OSMAP[os_code] || os_code)
|
648
|
+
}
|
649
|
+
end
|
650
|
+
end
|
651
|
+
end
|
652
|
+
|
653
|
+
class Object
|
654
|
+
## This is from Topher Cyll's Stupd IRB tricks
|
655
|
+
def mymethods
|
656
|
+
(self.methods - self.class.superclass.methods).sort
|
657
|
+
end
|
658
|
+
end
|
659
|
+
|
660
|
+
|
661
|
+
module Enumerable
|
662
|
+
def each_recursive(&block)
|
663
|
+
self.each do |n|
|
664
|
+
block.call(n)
|
665
|
+
n.each_recursive(&block) if n.kind_of? Array or n.kind_of? Hash
|
666
|
+
end
|
667
|
+
end
|
668
|
+
end
|
669
|
+
|
670
|
+
__END__
|
671
|
+
|
672
|
+
|