#!/usr/bin/ruby # # scans a binary file for a list of integral values that all appear in a small window # # options: # -f filename (file to scan) # -l width (window size - hex allowed) # --format fmtstring (pack-style integer encoding to search - defaults to 's', littleendian short) # arglist is the sequence of ints to search # # all indexes of the 1st pattern are searched # for each, a window is taken (width bytes before..width after) # windows not containing all patterns are rejected # matching windows are shown in hex # # (c) Yoann Guillot, 2008 # Distributes under the WTFPL v2 # require 'optparse' file = nil format = 's' # pack argument width = 1024 OptionParser.new { |opt| opt.on('-f file') { |f| file = f } opt.on('-l length') { |l| width = Integer(l) } opt.on('--format fmt') { |f| format = f } opt.on('-1') { format = 'C' } }.parse!(ARGV) raise 'filename not specified' if not file class String # returns all indexes of str (iterate #index) def indexes(str) i = -1 ret = [] ret << i while i = index(str, i+1) ret end # outputs a hexdump (byte/long/char) def hexdump(hlpats=[]) # no cross-line hl hlpats = hlpats.map { |pat| pat.unpack('C*').map { |c| '%02x '%c }.join } off = 0 while off < length s = self[off, 16] chr = s.unpack('C*').map{ |c| '%02x '%c }.join.ljust(16*3+2) dwd = s.unpack('L*').map{ |l| '%08x '%l }.join.ljust(4*9+2) asc = s.tr("^\x20-\x7e", '.') hlpats.each_with_index { |p,i| chr.gsub!(p, "\e[#{31+i%7}m" + p + "\e[0m" ) } if $stdout.tty? puts chr+dwd+asc off += 16 end nil end end file = File.read(file) patterns = ARGV.map { |i| [Integer(i)].pack(format) } list = file.indexes(patterns.first) list.delete_if { |off| chunk = file[off-width, 2*width] patterns.find { |p| not chunk.index(p) } } puts "found #{list.length} offsets" list.each { |off| off -= width chunk = file[off, 2*width] puts off.to_s 16 chunk.hexdump(patterns) }