#!/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 } }.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 off = 0 while off < length s = self[off, 16] puts s.unpack('C*').map{ |c| '%02x '%c }.join.ljust(16*3+2) + # s.unpack('S*').map{ |s| '%04x '%s }.join.ljust(8*5+2) + s.unpack('L*').map{ |l| '%08x '%l }.join.ljust(4*9+2) + s.tr('^a-zA-Z0-9', '.') 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) } } list = list.sort.map { |off| [off-width, off+width] }.inject([]) { |ary, (newlo, newhi)| if ary.last and newlo <= ary.last.last ary.last[1] = newhi else ary << [newlo, newhi] end ary } puts "found #{list.length} offsets" list.each { |lo, hi| chunk = file[lo..hi] puts lo.to_s(16) chunk.hexdump }