emonti-rbkb 0.6.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+
@@ -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
+