emonti-rbkb 0.6.1.1

Sign up to get free protection for your applications and to get access to all the features.
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
+