class ZSteg::Checker
Constants
- CAMOUFLAGE_SIG1
- CAMOUFLAGE_SIG2
- DEFAULT_BITS
- DEFAULT_EXTRA_CHECKS
- DEFAULT_LIMIT
- DEFAULT_MIN_STR_LEN
- DEFAULT_ORDER
Attributes
channels[RW]
params[RW]
results[RW]
verbose[RW]
Public Class Methods
new(image, params = {})
click to toggle source
image can be either filename or ZPNG::Image
# File lib/zsteg/checker.rb, line 22 def initialize image, params = {} @params = params @cache = {}; @wastitles = Set.new @image = image.is_a?(ZPNG::Image) ? image : ZPNG::Image.load(image) @extractor = Extractor.new(@image, params) @channels = params[:channels] || if @image.alpha_used? %w'r g b a rgb bgr rgba abgr' else %w'r g b rgb bgr' end @verbose = params[:verbose] || -2 @file_cmd = FileCmd.new if params.fetch(:file, true) @results = [] @params[:bits] ||= DEFAULT_BITS @params[:order] ||= DEFAULT_ORDER @params[:limit] ||= DEFAULT_LIMIT if @params[:min_str_len] @min_str_len = @min_wholetext_len = @params[:min_str_len] else @min_str_len = DEFAULT_MIN_STR_LEN @min_wholetext_len = @min_str_len - 2 end @strings_re = /[\x20-\x7e\r\n\t]{#@min_str_len,}/ @extra_checks = params.fetch(:extra_checks, DEFAULT_EXTRA_CHECKS) end
Public Instance Methods
check()
click to toggle source
# catch Kernel#print for easier verbosity handling
def print *args Kernel.print(*args) if @verbose >= 0 end # catch Kernel#printf for easier verbosity handling def printf *args Kernel.printf(*args) if @verbose >= 0 end # catch Kernel#puts for easier verbosity handling def puts *args Kernel.puts(*args) if @verbose >= 0 end
# File lib/zsteg/checker.rb, line 71 def check @found_anything = false @file_cmd.start! if @file_cmd if @extra_checks check_extradata check_metadata check_imagedata end if @image.format == :bmp case params[:order].to_s.downcase when /all/ params[:order] = %w'bY xY xy yx XY YX Xy yX Yx' when /auto/ params[:order] = %w'bY xY' end else case params[:order].to_s.downcase when /all/ params[:order] = %w'xy yx XY YX Xy yX xY Yx' when /auto/ params[:order] = 'xy' end end Array(params[:order]).uniq.each do |order| (params[:prime] == :all ? [false,true] : [params[:prime]]).each do |prime| Array(params[:bits]).uniq.each do |bits| if params[:pixel_align] == :all [false, true].each do |pixel_align| # skip cases when output will be identical for pixel_align true/false next if pixel_align && (8%bits) == 0 p1 = @params.merge bits: bits, order: order, prime: prime, pixel_align: pixel_align if order[/b/i] # byte iterator does not need channels check_channels nil, p1 else channels.each{ |c| check_channels c, p1 } end end else p1 = @params.merge bits: bits, order: order, prime: prime if order[/b/i] # byte iterator does not need channels check_channels nil, p1 else channels.each{ |c| check_channels c, p1 } end end end end end if @found_anything print "\r" + " "*20 + "\r" if @need_cr else puts "\r[=] nothing :(" + " "*20 # line cleanup end if @extra_checks Analyzer.new(@image).analyze! end # return everything found if this method was called from some code @results ensure @file_cmd.stop! if @file_cmd end
check_channels(channels, params)
click to toggle source
# File lib/zsteg/checker.rb, line 189 def check_channels channels, params unless params[:bit_order] check_channels(channels, params.merge(:bit_order => :lsb)) check_channels(channels, params.merge(:bit_order => :msb)) return end p1 = params.clone # number of bits # equals to params[:bits] if in range 1..8 # otherwise equals to number of 1's, like 0b1000_0001 nbits = p1[:bits] <= 8 ? p1[:bits] : (p1[:bits]&0xff).to_s(2).count("1") show_bits = true # channels is a String if channels p1[:channels] = if channels[1] && channels[1] =~ /\A\d\Z/ # 'r3g2b3' a=[] cbits = 0 (channels.size/2).times do |i| a << (t=channels[i*2,2]) cbits += t[1].to_i end show_bits = false @max_hidden_size = cbits * @image.width a else # 'rgb' a = channels.chars.to_a @max_hidden_size = a.size * @image.width * nbits a end # p1[:channels] is an Array elsif params[:order] =~ /b/i # byte extractor @max_hidden_size = @image.scanlines[0].decoded_bytes.size * nbits else raise "invalid params #{params.inspect}" end @max_hidden_size *= @image.height/8 bits_tag = if show_bits if params[:bits] > 0x100 if params[:bits].to_s(2) =~ /(1{1,8})$/ # mask => number of bits "b#{$1.size}" else # mask "b#{(params[:bits]&0xff).to_s(2)}" end else # number of bits "b#{params[:bits]}" end end bits_tag << "p" if params[:pixel_align] title = [ bits_tag, channels, params[:bit_order], params[:order], params[:prime] ? 'prime' : nil ].compact.join(',') return if @wastitles.include?(title) @wastitles << title show_title title p1[:title] = title data = @extractor.extract p1 if p1[:invert] data.size.times{ |i| data.setbyte(i, data.getbyte(i)^0xff) } end @need_cr = !process_result(data, p1) # carriage return needed? @found_anything ||= !@need_cr end
check_extradata()
click to toggle source
# File lib/zsteg/checker.rb, line 146 def check_extradata # accessing imagedata implicitly unpacks zlib stream # zlib stream may contain extradata if @image.imagedata.size > (t=@image.scanlines.map(&:size).inject(&:+)) @found_anything = true data = @image.imagedata[t..-1] title = "extradata:imagedata" show_title title, :bright_red process_result data, :special => true, :title => title end if @image.extradata.any? @found_anything = true @image.extradata.each_with_index do |data,idx| title = "extradata:#{idx}" show_title title, :bright_red process_result data, :special => true, :title => title end end if data = ScanlineChecker.check_image(@image, @params) @found_anything = true title = "scanline extradata" show_title title, :bright_red process_result data, :special => true, :title => title end if r = SteganographyPNG.check_image(@image, @params) @found_anything = true title = "image" show_title title, :bright_red process_result nil, title: title, result: r end end
check_imagedata()
click to toggle source
# File lib/zsteg/checker.rb, line 141 def check_imagedata h = { :title => "imagedata", :show_title => true } process_result @image.imagedata, h end
check_metadata()
click to toggle source
# File lib/zsteg/checker.rb, line 181 def check_metadata @image.metadata.each do |k,v| @found_anything = true show_title(title = "meta #{k}") process_result v, :special => true, :title => title end end
data2result(data, params)
click to toggle source
# File lib/zsteg/checker.rb, line 358 def data2result data, params if one_char?(data) return Result::OneChar.new(data[0,1], data.size) end if idx = data.index('OPENSTEGO') io = StringIO.new(data) io.seek(idx+9) return Result::OpenStego.read(io) end # only in extradata if params[:title]['extradata'] if data[0,2] == CAMOUFLAGE_SIG1 && data[3,3] == CAMOUFLAGE_SIG2 return Result::Camouflage.new(data) end end # only BMP & 1-bit-per-channel if params[:bits] == 1 && params[:bit_order] == :lsb if x = WBStego.check(data, params.merge( :image => @image, :max_hidden_size => @max_hidden_size )) return x end end if data.size >= @min_wholetext_len && data =~ /\A[\x20-\x7e\r\n\t]+\Z/ # whole ASCII return Result::WholeText.new(data, 0) end if @file_cmd && (r = @file_cmd.data2result(data)) return r end if r = Checker::Zlib.check_data(data) return r end case params.fetch(:strings, :first) when :all r=[] data.scan(@strings_re) do r << Result::PartialText.from_matchdata($~) end return r if r.any? when :first if data[@strings_re] return Result::PartialText.from_matchdata($~) end when :longest r=[] data.scan(@strings_re){ r << $~ } return Result::PartialText.from_matchdata(r.sort_by(&:size).last) if r.any? end # utf-8 string matching, may be slow, may throw exceptions # begin # t = data. # encode('UTF-16', 'UTF-8', :invalid => :replace, :replace => ''). # encode!('UTF-8', 'UTF-16') # r = t.scan(/\p{Word}{#{DEFAULT_MIN_STR_LEN},}/) # r if r.any? # rescue # end end
process_result(data, params)
click to toggle source
returns true if was any output
# File lib/zsteg/checker.rb, line 296 def process_result data, params verbose = params[:special] ? [@verbose,1.5].max : @verbose result = nil if data if @cache[data] if verbose > 1 puts "[same as #{@cache[data].inspect}]".gray return true else # silent return return false end end # TODO: store hash of data for large datas @cache[data] = params[:title] if result = data2result(data, params) @results << result end elsif !(result = params[:result]) raise "[?] No data nor result" end case verbose when -999..0 # verbosity=0: only show result if anything interesting found if result && !result.is_a?(Result::OneChar) show_title params[:title] if params[:show_title] show_result result, params return true else return false end when 1 # verbosity=1: if anything interesting found show result & hexdump return false unless result else # verbosity>1: always show hexdump end show_title params[:title] if params[:show_title] if params[:special] puts result.is_a?(Result::PartialText) ? nil : result else show_result result, params end if data && data.size > 0 && !result.is_a?(Result::OneChar) && !result.is_a?(Result::WholeText) # newline if no results and want hexdump puts if !result || result == [] limit = (params[:limit] || @params[:limit]).to_i t = limit > 0 ? data[0,limit] : data print ZPNG::Hexdump.dump(t){ |x| x.prepend(" "*4) } end true end
show_result(result, params)
click to toggle source
# File lib/zsteg/checker.rb, line 280 def show_result result, params case result when Array result.each_with_index do |r,idx| # empty title for multiple results from same title show_title(" ") if idx > 0 puts r end when nil, false # do nothing? else puts result end end
show_title(title, color = :gray)
click to toggle source
# File lib/zsteg/checker.rb, line 275 def show_title title, color = :gray printf "\r%-20s.. ".send(color), title $stdout.flush end
Private Instance Methods
one_char?(s)
click to toggle source
returns true if String s consists of one repeating character performance-optimized 16Mb string = 0.7s on Core i5 1.7GHz
# File lib/zsteg/checker.rb, line 432 def one_char? s (s =~ /\A(.)\1+\Z/m) == 0 end