zsteg 0.0.0 → 0.0.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/Gemfile +2 -7
 - data/Gemfile.lock +2 -4
 - data/README.md +72 -1
 - data/README.md.tpl +23 -0
 - data/Rakefile +5 -3
 - data/TODO +5 -2
 - data/VERSION +1 -1
 - data/bin/zsteg-mask +7 -0
 - data/lib/zsteg/checker/wbstego.rb +69 -14
 - data/lib/zsteg/checker.rb +137 -34
 - data/lib/zsteg/cli.rb +92 -35
 - data/lib/zsteg/extractor/byte_extractor.rb +36 -21
 - data/lib/zsteg/extractor/color_extractor.rb +68 -34
 - data/lib/zsteg/extractor.rb +36 -1
 - data/lib/zsteg/file_cmd.rb +64 -1
 - data/lib/zsteg/mask_cli.rb +268 -0
 - data/lib/zsteg/masker.rb +52 -0
 - data/lib/zsteg/result.rb +27 -32
 - data/lib/zsteg.rb +2 -0
 - data/samples/hackquest/crypt.bmp +0 -0
 - data/samples/hackquest/square.bmp +0 -0
 - data/samples/wbstego/wbsteg_blowfish_pass_1.bmp +0 -0
 - data/samples/wbstego/wbsteg_cast128_pass_1.bmp +0 -0
 - data/samples/wbstego/wbsteg_enc_pass_pass.bmp +0 -0
 - data/samples/wbstego/wbsteg_enc_pass_pass_even.bmp +0 -0
 - data/samples/wbstego/wbsteg_mix_pass_1.bmp +0 -0
 - data/samples/wbstego/wbsteg_mix_pass_1_even.bmp +0 -0
 - data/samples/wbstego/wbsteg_mix_pass_foobar.bmp +0 -0
 - data/samples/wbstego/wbsteg_mix_pass_pass.bmp +0 -0
 - data/samples/wbstego/wbsteg_mixenc_pass_pass_even.bmp +0 -0
 - data/samples/{wbsteg_noenc.bmp → wbstego/wbsteg_noenc.bmp} +0 -0
 - data/samples/wbstego/wbsteg_noenc.png +0 -0
 - data/samples/wbstego/wbsteg_noenc_.bmp +0 -0
 - data/samples/{wbsteg_noenc_17.bmp → wbstego/wbsteg_noenc_17.bmp} +0 -0
 - data/samples/wbstego/wbsteg_noenc__.bmp +0 -0
 - data/samples/{wbsteg_noenc_even.bmp → wbstego/wbsteg_noenc_even.bmp} +0 -0
 - data/samples/wbstego/wbsteg_noenc_even2.bmp +0 -0
 - data/samples/{wbsteg_noenc_even_17.bmp → wbstego/wbsteg_noenc_even_17.bmp} +0 -0
 - data/samples/wbstego/wbsteg_noenc_even_17_.bmp +0 -0
 - data/samples/wbstego/wbsteg_noenc_ext_ABC.bmp +0 -0
 - data/samples/wbstego/wbsteg_rijndael_pass_1.bmp +0 -0
 - data/samples/wbstego/wbsteg_rijndael_pass_pass.bmp +0 -0
 - data/samples/wbstego/wbsteg_rijndael_pass_pass_even.bmp +0 -0
 - data/samples/wbstego/wbsteg_twofish_pass_1.bmp +0 -0
 - data/samples/wechall/5ZMGcCLxpcpsru03.g00000010.png +0 -0
 - data/samples/wechall/5ZMGcCLxpcpsru03.png +0 -0
 - data/samples/wechall/stegano1.bmp +0 -0
 - data/spec/checker_spec.rb +47 -0
 - data/spec/easybmp_spec.rb +9 -0
 - data/spec/hackquest_spec.rb +18 -0
 - data/spec/mask_spec.rb +14 -0
 - data/spec/polictf2012_spec.rb +48 -0
 - data/spec/prime_spec.rb +9 -0
 - data/spec/r3g2b3_spec.rb +9 -0
 - data/spec/spec_helper.rb +21 -4
 - data/spec/wbstego_spec.rb +21 -3
 - data/spec/wechall_spec.rb +26 -0
 - data/tmp/.keep +0 -0
 - data/zsteg.gemspec +121 -0
 - metadata +47 -43
 - data/samples/06_enc.png +0 -0
 - data/samples/Code.png +0 -0
 - data/samples/README +0 -4
 - data/samples/camouflage-password.png +0 -0
 - data/samples/camouflage.png +0 -0
 - data/samples/cats.png +0 -0
 - data/samples/flower.png +0 -0
 - data/samples/flower_rgb1.png +0 -0
 - data/samples/flower_rgb2.png +0 -0
 - data/samples/flower_rgb3.png +0 -0
 - data/samples/flower_rgb4.png +0 -0
 - data/samples/flower_rgb5.png +0 -0
 - data/samples/flower_rgb6.png +0 -0
 - data/samples/montenach-enc.png +0 -0
 - data/samples/ndh2k12_sp113.bmp.7z +0 -0
 - data/samples/openstego_q2.png +0 -0
 - data/samples/openstego_send.png +0 -0
 - data/samples/stg300.png +0 -0
 
    
        data/lib/zsteg/file_cmd.rb
    CHANGED
    
    | 
         @@ -8,6 +8,7 @@ module ZSteg 
     | 
|
| 
       8 
8 
     | 
    
         
             
                  'data',
         
     | 
| 
       9 
9 
     | 
    
         
             
                  'empty',
         
     | 
| 
       10 
10 
     | 
    
         
             
                  'Sendmail frozen configuration',
         
     | 
| 
      
 11 
     | 
    
         
            +
                  'DBase 3 data file',
         
     | 
| 
       11 
12 
     | 
    
         
             
                  'DOS executable',
         
     | 
| 
       12 
13 
     | 
    
         
             
                  'Dyalog APL',
         
     | 
| 
       13 
14 
     | 
    
         
             
                  '8086 relocatable',
         
     | 
| 
         @@ -18,9 +19,50 @@ module ZSteg 
     | 
|
| 
       18 
19 
     | 
    
         
             
                  'very short file',
         
     | 
| 
       19 
20 
     | 
    
         
             
                  'International EBCDIC text',
         
     | 
| 
       20 
21 
     | 
    
         
             
                  'lif file',
         
     | 
| 
       21 
     | 
    
         
            -
                  'AmigaOS bitmap font'
         
     | 
| 
      
 22 
     | 
    
         
            +
                  'AmigaOS bitmap font',
         
     | 
| 
      
 23 
     | 
    
         
            +
                  'a python script text executable' # common false positive
         
     | 
| 
       22 
24 
     | 
    
         
             
                ]
         
     | 
| 
       23 
25 
     | 
    
         | 
| 
      
 26 
     | 
    
         
            +
                MIN_DATA_SIZE = 5
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                class Result < Struct.new(:title, :data)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  COLORMAP_TEXT = {
         
     | 
| 
      
 30 
     | 
    
         
            +
                    /DBase 3 data/i               => :gray
         
     | 
| 
      
 31 
     | 
    
         
            +
                  }
         
     | 
| 
      
 32 
     | 
    
         
            +
                  COLORMAP_WORD = {
         
     | 
| 
      
 33 
     | 
    
         
            +
                    /bitmap|jpeg|pdf|zip|rar|7-?z/i => :bright_red,
         
     | 
| 
      
 34 
     | 
    
         
            +
                  }
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  def to_s
         
     | 
| 
      
 37 
     | 
    
         
            +
                    if title[/UTF-8 Unicode text/i]
         
     | 
| 
      
 38 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 39 
     | 
    
         
            +
                        t = data.force_encoding("UTF-8").encode("UTF-32LE").encode("UTF-8")
         
     | 
| 
      
 40 
     | 
    
         
            +
                      rescue
         
     | 
| 
      
 41 
     | 
    
         
            +
                        t = data.force_encoding('binary')
         
     | 
| 
      
 42 
     | 
    
         
            +
                      end
         
     | 
| 
      
 43 
     | 
    
         
            +
                      return "utf8: " + t
         
     | 
| 
      
 44 
     | 
    
         
            +
                    end
         
     | 
| 
      
 45 
     | 
    
         
            +
                    COLORMAP_TEXT.each do |re,color|
         
     | 
| 
      
 46 
     | 
    
         
            +
                      return colorize(color) if title[re]
         
     | 
| 
      
 47 
     | 
    
         
            +
                    end
         
     | 
| 
      
 48 
     | 
    
         
            +
                    title.downcase.split.each do |word|
         
     | 
| 
      
 49 
     | 
    
         
            +
                      COLORMAP_WORD.each do |re,color|
         
     | 
| 
      
 50 
     | 
    
         
            +
                        return colorize(color) if title.index(re) == 0
         
     | 
| 
      
 51 
     | 
    
         
            +
                      end
         
     | 
| 
      
 52 
     | 
    
         
            +
                    end
         
     | 
| 
      
 53 
     | 
    
         
            +
                    colorize(:yellow)
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                  def colorize color
         
     | 
| 
      
 57 
     | 
    
         
            +
                    if color == :gray
         
     | 
| 
      
 58 
     | 
    
         
            +
                      # gray whole string
         
     | 
| 
      
 59 
     | 
    
         
            +
                      "file: #{title}".send(color)
         
     | 
| 
      
 60 
     | 
    
         
            +
                    else
         
     | 
| 
      
 61 
     | 
    
         
            +
                      "file: " + title.send(color)
         
     | 
| 
      
 62 
     | 
    
         
            +
                    end
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
       24 
66 
     | 
    
         
             
                def start!
         
     | 
| 
       25 
67 
     | 
    
         
             
                  @stdin, @stdout, @stderr, @wait_thr = Open3.popen3("file -n -b -f -")
         
     | 
| 
       26 
68 
     | 
    
         
             
                end
         
     | 
| 
         @@ -39,6 +81,27 @@ module ZSteg 
     | 
|
| 
       39 
81 
     | 
    
         
             
                  check_file @tempfile.path
         
     | 
| 
       40 
82 
     | 
    
         
             
                end
         
     | 
| 
       41 
83 
     | 
    
         | 
| 
      
 84 
     | 
    
         
            +
                # checks data and resurns Result, if any
         
     | 
| 
      
 85 
     | 
    
         
            +
                def data2result data
         
     | 
| 
      
 86 
     | 
    
         
            +
                  return if data.size < MIN_DATA_SIZE
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                  title = check_data data
         
     | 
| 
      
 89 
     | 
    
         
            +
                  return unless title
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                  if title[/UTF-8 Unicode text/i]
         
     | 
| 
      
 92 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 93 
     | 
    
         
            +
                      t = data.force_encoding("UTF-8")
         
     | 
| 
      
 94 
     | 
    
         
            +
                    rescue
         
     | 
| 
      
 95 
     | 
    
         
            +
                      t = data.force_encoding('binary')
         
     | 
| 
      
 96 
     | 
    
         
            +
                    end
         
     | 
| 
      
 97 
     | 
    
         
            +
                    if t.size >= Checker::MIN_TEXT_LENGTH
         
     | 
| 
      
 98 
     | 
    
         
            +
                      ZSteg::Result::UnicodeText.new(t,0)
         
     | 
| 
      
 99 
     | 
    
         
            +
                    end
         
     | 
| 
      
 100 
     | 
    
         
            +
                  else
         
     | 
| 
      
 101 
     | 
    
         
            +
                    Result.new(title,data)
         
     | 
| 
      
 102 
     | 
    
         
            +
                  end
         
     | 
| 
      
 103 
     | 
    
         
            +
                end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
       42 
105 
     | 
    
         
             
                def stop!
         
     | 
| 
       43 
106 
     | 
    
         
             
                  @stdin.close
         
     | 
| 
       44 
107 
     | 
    
         
             
                  @stdout.close
         
     | 
| 
         @@ -0,0 +1,268 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'optparse'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'set'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'digest/md5'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module ZSteg
         
     | 
| 
      
 6 
     | 
    
         
            +
              class MaskCLI
         
     | 
| 
      
 7 
     | 
    
         
            +
                DEFAULT_ACTIONS = %w'mask'
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                COMMON_MASKS = [
         
     | 
| 
      
 10 
     | 
    
         
            +
                  0b0000_0001, 0b0000_0011, 0b0000_0111, 0b0000_1111,
         
     | 
| 
      
 11 
     | 
    
         
            +
                               0b0000_0010, 0b0000_0100, 0b0000_1000,
         
     | 
| 
      
 12 
     | 
    
         
            +
                  0b0001_0000, 0b0010_0000, 0b0100_0000, 0b1000_0000,
         
     | 
| 
      
 13 
     | 
    
         
            +
                ]
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                CHANNELS = [:r, :g, :b, :a]
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def initialize argv = ARGV
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @argv = argv
         
     | 
| 
      
 19 
     | 
    
         
            +
                  @wasfiles = Set.new
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @cache = {}
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def run
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @actions = []
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @options = {
         
     | 
| 
      
 26 
     | 
    
         
            +
                    :verbose   => 0,
         
     | 
| 
      
 27 
     | 
    
         
            +
                    :masks     => Hash.new{|k,v| k[v] = [] },
         
     | 
| 
      
 28 
     | 
    
         
            +
                    :normalize => true
         
     | 
| 
      
 29 
     | 
    
         
            +
                  }
         
     | 
| 
      
 30 
     | 
    
         
            +
                  optparser = OptionParser.new do |opts|
         
     | 
| 
      
 31 
     | 
    
         
            +
                    opts.banner = "Usage: zsteg-mask [options] filename.png [param_string]"
         
     | 
| 
      
 32 
     | 
    
         
            +
                    opts.separator ""
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                    opts.on("-m", "--mask M", "apply mask to all channels",
         
     | 
| 
      
 35 
     | 
    
         
            +
                            "mask: 0-255 OR 0x00-0xff OR 00000000-11111111",
         
     | 
| 
      
 36 
     | 
    
         
            +
                            "OR 'all' for all common masks"
         
     | 
| 
      
 37 
     | 
    
         
            +
                    ){ |x| @options[:masks][:all] << parse_mask(x) }
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                    opts.on("-R", "--red M", "red channel mask"){ |x|
         
     | 
| 
      
 40 
     | 
    
         
            +
                      @options[:masks][:r] << parse_mask(x) }
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                    opts.on("-G", "--green M", "green channel mask"){ |x|
         
     | 
| 
      
 43 
     | 
    
         
            +
                      @options[:masks][:g] << parse_mask(x) }
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                    opts.on("-B", "--blue M", "blue channel mask"){ |x|
         
     | 
| 
      
 46 
     | 
    
         
            +
                      @options[:masks][:b] << parse_mask(x) }
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                    opts.on("-A", "--alpha M", "alpha channel mask"){ |x|
         
     | 
| 
      
 49 
     | 
    
         
            +
                      @options[:masks][:a] << parse_mask(x) }
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                    opts.separator ""
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                    opts.on "-a", "--all", "try all common masks (default)" do
         
     | 
| 
      
 54 
     | 
    
         
            +
                      @options[:try_all] = true
         
     | 
| 
      
 55 
     | 
    
         
            +
                    end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                    opts.separator ""
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                    opts.on "-N", "--[no-]normalize", "normalize color value after applying mask",
         
     | 
| 
      
 60 
     | 
    
         
            +
                      "(default: normalize)" do |x|
         
     | 
| 
      
 61 
     | 
    
         
            +
                      @options[:normalize] = x
         
     | 
| 
      
 62 
     | 
    
         
            +
                    end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                    opts.on "-O", "--outfile FILENAME", "output single result to specified file" do |x|
         
     | 
| 
      
 65 
     | 
    
         
            +
                      @options[:outfile] = x
         
     | 
| 
      
 66 
     | 
    
         
            +
                    end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                    opts.on "-D", "--dir DIRNAME", "output multiple results to specified dir" do |x|
         
     | 
| 
      
 69 
     | 
    
         
            +
                      @options[:dir] = x
         
     | 
| 
      
 70 
     | 
    
         
            +
                    end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                    opts.separator ""
         
     | 
| 
      
 73 
     | 
    
         
            +
                    opts.on "-v", "--verbose", "Run verbosely (can be used multiple times)" do |v|
         
     | 
| 
      
 74 
     | 
    
         
            +
                      @options[:verbose] += 1
         
     | 
| 
      
 75 
     | 
    
         
            +
                    end
         
     | 
| 
      
 76 
     | 
    
         
            +
                    opts.on "-q", "--quiet", "Silent any warnings (can be used multiple times)" do |v|
         
     | 
| 
      
 77 
     | 
    
         
            +
                      @options[:verbose] -= 1
         
     | 
| 
      
 78 
     | 
    
         
            +
                    end
         
     | 
| 
      
 79 
     | 
    
         
            +
                    opts.on "-C", "--[no-]color", "Force (or disable) color output (default: auto)" do |x|
         
     | 
| 
      
 80 
     | 
    
         
            +
                      Sickill::Rainbow.enabled = x
         
     | 
| 
      
 81 
     | 
    
         
            +
                    end
         
     | 
| 
      
 82 
     | 
    
         
            +
                  end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                  if (argv = optparser.parse(@argv)).empty?
         
     | 
| 
      
 85 
     | 
    
         
            +
                    puts optparser.help
         
     | 
| 
      
 86 
     | 
    
         
            +
                    return
         
     | 
| 
      
 87 
     | 
    
         
            +
                  end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                  # default :all mask if none specified
         
     | 
| 
      
 90 
     | 
    
         
            +
                  if @options[:masks].empty?
         
     | 
| 
      
 91 
     | 
    
         
            +
                    @options[:try_all] = true
         
     | 
| 
      
 92 
     | 
    
         
            +
                  end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                  @actions = DEFAULT_ACTIONS if @actions.empty?
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                  argv.each do |arg|
         
     | 
| 
      
 97 
     | 
    
         
            +
                    if arg[','] && !File.exist?(arg)
         
     | 
| 
      
 98 
     | 
    
         
            +
                      @options.merge!(decode_param_string(arg))
         
     | 
| 
      
 99 
     | 
    
         
            +
                      argv.delete arg
         
     | 
| 
      
 100 
     | 
    
         
            +
                    end
         
     | 
| 
      
 101 
     | 
    
         
            +
                  end
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                  argv.each_with_index do |fname,idx|
         
     | 
| 
      
 104 
     | 
    
         
            +
                    if argv.size > 1 && @options[:verbose] >= 0
         
     | 
| 
      
 105 
     | 
    
         
            +
                      puts if idx > 0
         
     | 
| 
      
 106 
     | 
    
         
            +
                      puts "[.] #{fname}".green
         
     | 
| 
      
 107 
     | 
    
         
            +
                    end
         
     | 
| 
      
 108 
     | 
    
         
            +
                    next unless @image=load_image(@fname=fname)
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
                    @actions.each do |action|
         
     | 
| 
      
 111 
     | 
    
         
            +
                      if action.is_a?(Array)
         
     | 
| 
      
 112 
     | 
    
         
            +
                        self.send(*action) if self.respond_to?(action.first)
         
     | 
| 
      
 113 
     | 
    
         
            +
                      else
         
     | 
| 
      
 114 
     | 
    
         
            +
                        self.send(action) if self.respond_to?(action)
         
     | 
| 
      
 115 
     | 
    
         
            +
                      end
         
     | 
| 
      
 116 
     | 
    
         
            +
                    end
         
     | 
| 
      
 117 
     | 
    
         
            +
                  end
         
     | 
| 
      
 118 
     | 
    
         
            +
                rescue Errno::EPIPE
         
     | 
| 
      
 119 
     | 
    
         
            +
                  # output interrupt, f.ex. when piping output to a 'head' command
         
     | 
| 
      
 120 
     | 
    
         
            +
                  # prevents a 'Broken pipe - <STDOUT> (Errno::EPIPE)' message
         
     | 
| 
      
 121 
     | 
    
         
            +
                end
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
                def parse_mask x
         
     | 
| 
      
 124 
     | 
    
         
            +
                  case x
         
     | 
| 
      
 125 
     | 
    
         
            +
                  when /0x/i
         
     | 
| 
      
 126 
     | 
    
         
            +
                    x.to_i(16)
         
     | 
| 
      
 127 
     | 
    
         
            +
                  when /^[01]{8}$/
         
     | 
| 
      
 128 
     | 
    
         
            +
                    x.to_i(2)
         
     | 
| 
      
 129 
     | 
    
         
            +
                  when /^\d{1,3}$/
         
     | 
| 
      
 130 
     | 
    
         
            +
                    x.to_i
         
     | 
| 
      
 131 
     | 
    
         
            +
                  when /^all$/
         
     | 
| 
      
 132 
     | 
    
         
            +
                    COMMON_MASKS
         
     | 
| 
      
 133 
     | 
    
         
            +
                  else raise "invalid mask #{x.inspect}"
         
     | 
| 
      
 134 
     | 
    
         
            +
                  end
         
     | 
| 
      
 135 
     | 
    
         
            +
                end
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                def load_image fname
         
     | 
| 
      
 138 
     | 
    
         
            +
                  if File.directory?(fname)
         
     | 
| 
      
 139 
     | 
    
         
            +
                    puts "[?] #{fname} is a directory".yellow
         
     | 
| 
      
 140 
     | 
    
         
            +
                  else
         
     | 
| 
      
 141 
     | 
    
         
            +
                    ZPNG::Image.load(fname)
         
     | 
| 
      
 142 
     | 
    
         
            +
                  end
         
     | 
| 
      
 143 
     | 
    
         
            +
                rescue ZPNG::Exception, Errno::ENOENT
         
     | 
| 
      
 144 
     | 
    
         
            +
                  puts "[!] #{$!.inspect}".red
         
     | 
| 
      
 145 
     | 
    
         
            +
                end
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
      
 147 
     | 
    
         
            +
                ###########################################################################
         
     | 
| 
      
 148 
     | 
    
         
            +
                # actions
         
     | 
| 
      
 149 
     | 
    
         
            +
             
     | 
| 
      
 150 
     | 
    
         
            +
                def mask
         
     | 
| 
      
 151 
     | 
    
         
            +
                  masks = @options[:masks]
         
     | 
| 
      
 152 
     | 
    
         
            +
                  masks.each{ |k,v| v.flatten!; v.uniq! }
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
                  if @options[:try_all]
         
     | 
| 
      
 155 
     | 
    
         
            +
                    # try all common masks
         
     | 
| 
      
 156 
     | 
    
         
            +
                    masks = masks[:all] || []
         
     | 
| 
      
 157 
     | 
    
         
            +
                    masks = COMMON_MASKS if masks.empty?
         
     | 
| 
      
 158 
     | 
    
         
            +
                    masks.each{ |x| run_masker x,x,x,x    }
         
     | 
| 
      
 159 
     | 
    
         
            +
                    masks.each{ |x| run_masker x,0,0,0xff }
         
     | 
| 
      
 160 
     | 
    
         
            +
                    masks.each{ |x| run_masker 0,x,0,0xff }
         
     | 
| 
      
 161 
     | 
    
         
            +
                    masks.each{ |x| run_masker 0,0,x,0xff }
         
     | 
| 
      
 162 
     | 
    
         
            +
                    if @image.alpha_used?
         
     | 
| 
      
 163 
     | 
    
         
            +
                      masks.each{ |x| run_masker 0,0,0,x    }
         
     | 
| 
      
 164 
     | 
    
         
            +
                    end
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
      
 166 
     | 
    
         
            +
                  elsif CHANNELS.all?{ |c| !masks[c] || masks[c].empty? }
         
     | 
| 
      
 167 
     | 
    
         
            +
                    # no specific channels
         
     | 
| 
      
 168 
     | 
    
         
            +
                    masks[:all].each do |x|
         
     | 
| 
      
 169 
     | 
    
         
            +
                      run_masker x,x,x,x
         
     | 
| 
      
 170 
     | 
    
         
            +
                    end
         
     | 
| 
      
 171 
     | 
    
         
            +
             
     | 
| 
      
 172 
     | 
    
         
            +
                  else
         
     | 
| 
      
 173 
     | 
    
         
            +
                    # specific channels
         
     | 
| 
      
 174 
     | 
    
         
            +
                    CHANNELS.each{ |x| masks[x] = [x==:a ? 0xff : 0] if !masks[x] || masks[x].empty? }
         
     | 
| 
      
 175 
     | 
    
         
            +
                    masks[:r].each do |r|
         
     | 
| 
      
 176 
     | 
    
         
            +
                      masks[:g].each do |g|
         
     | 
| 
      
 177 
     | 
    
         
            +
                        masks[:b].each do |b|
         
     | 
| 
      
 178 
     | 
    
         
            +
                          if @image.alpha_used?
         
     | 
| 
      
 179 
     | 
    
         
            +
                            masks[:a].each do |a|
         
     | 
| 
      
 180 
     | 
    
         
            +
                              run_masker r,g,b,a
         
     | 
| 
      
 181 
     | 
    
         
            +
                            end
         
     | 
| 
      
 182 
     | 
    
         
            +
                          else
         
     | 
| 
      
 183 
     | 
    
         
            +
                            run_masker r,g,b,0xff
         
     | 
| 
      
 184 
     | 
    
         
            +
                          end
         
     | 
| 
      
 185 
     | 
    
         
            +
                        end
         
     | 
| 
      
 186 
     | 
    
         
            +
                      end
         
     | 
| 
      
 187 
     | 
    
         
            +
                    end
         
     | 
| 
      
 188 
     | 
    
         
            +
                  end
         
     | 
| 
      
 189 
     | 
    
         
            +
                end
         
     | 
| 
      
 190 
     | 
    
         
            +
             
     | 
| 
      
 191 
     | 
    
         
            +
                private
         
     | 
| 
      
 192 
     | 
    
         
            +
             
     | 
| 
      
 193 
     | 
    
         
            +
                def _all_pixels_same img
         
     | 
| 
      
 194 
     | 
    
         
            +
                  sl0 = img.scanlines.first
         
     | 
| 
      
 195 
     | 
    
         
            +
                  return false if sl0.pixels.to_a.uniq.size != 1
         
     | 
| 
      
 196 
     | 
    
         
            +
             
     | 
| 
      
 197 
     | 
    
         
            +
                  db0 = sl0.decoded_bytes
         
     | 
| 
      
 198 
     | 
    
         
            +
                  img.scanlines[1..-1].each do |sl|
         
     | 
| 
      
 199 
     | 
    
         
            +
                    return false if sl.decoded_bytes != db0
         
     | 
| 
      
 200 
     | 
    
         
            +
                  end
         
     | 
| 
      
 201 
     | 
    
         
            +
                  true
         
     | 
| 
      
 202 
     | 
    
         
            +
                end
         
     | 
| 
      
 203 
     | 
    
         
            +
             
     | 
| 
      
 204 
     | 
    
         
            +
                def run_masker r,g,b,a
         
     | 
| 
      
 205 
     | 
    
         
            +
                  params = @options.dup
         
     | 
| 
      
 206 
     | 
    
         
            +
                  params[:masks] = params[:masks].merge( :r => r, :g => g, :b => b, :a => a)
         
     | 
| 
      
 207 
     | 
    
         
            +
                  fname,color = @options[:outfile],nil
         
     | 
| 
      
 208 
     | 
    
         
            +
                  fname,color = masks2fname(params[:masks]) unless fname
         
     | 
| 
      
 209 
     | 
    
         
            +
             
     | 
| 
      
 210 
     | 
    
         
            +
                  print "[.] #{fname.send(color||:to_s)} .. "
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
                  raise "already written to #{fname}" if @wasfiles.include?(fname)
         
     | 
| 
      
 213 
     | 
    
         
            +
                  @wasfiles << fname
         
     | 
| 
      
 214 
     | 
    
         
            +
             
     | 
| 
      
 215 
     | 
    
         
            +
                  dst = Masker.new(@image, params).mask
         
     | 
| 
      
 216 
     | 
    
         
            +
             
     | 
| 
      
 217 
     | 
    
         
            +
                  if _all_pixels_same(dst)
         
     | 
| 
      
 218 
     | 
    
         
            +
                    puts "all pixels = #{dst[0,0].inspect}".gray
         
     | 
| 
      
 219 
     | 
    
         
            +
                    return
         
     | 
| 
      
 220 
     | 
    
         
            +
                  end
         
     | 
| 
      
 221 
     | 
    
         
            +
             
     | 
| 
      
 222 
     | 
    
         
            +
                  data = dst.export
         
     | 
| 
      
 223 
     | 
    
         
            +
             
     | 
| 
      
 224 
     | 
    
         
            +
                  md5 = Digest::MD5.hexdigest(data)
         
     | 
| 
      
 225 
     | 
    
         
            +
                  if @cache[md5]
         
     | 
| 
      
 226 
     | 
    
         
            +
                    puts "same as #{File.basename(@cache[md5])}".gray
         
     | 
| 
      
 227 
     | 
    
         
            +
                    return
         
     | 
| 
      
 228 
     | 
    
         
            +
                  end
         
     | 
| 
      
 229 
     | 
    
         
            +
                  @cache[md5] = fname
         
     | 
| 
      
 230 
     | 
    
         
            +
             
     | 
| 
      
 231 
     | 
    
         
            +
                  File.open(fname, "wb"){ |f| f<<data }
         
     | 
| 
      
 232 
     | 
    
         
            +
                  printf "%6d bytes\n".green, File.size(fname)
         
     | 
| 
      
 233 
     | 
    
         
            +
                end
         
     | 
| 
      
 234 
     | 
    
         
            +
             
     | 
| 
      
 235 
     | 
    
         
            +
                def masks2fname masks
         
     | 
| 
      
 236 
     | 
    
         
            +
                  masks = masks.dup.delete_if{ |k,v| !CHANNELS.include?(k) }
         
     | 
| 
      
 237 
     | 
    
         
            +
                  bname = @fname.sub(/#{Regexp.escape(File.extname(@fname))}$/,'')
         
     | 
| 
      
 238 
     | 
    
         
            +
                  color = nil
         
     | 
| 
      
 239 
     | 
    
         
            +
                  raise "TODO" if masks.values.all?(&:nil?)
         
     | 
| 
      
 240 
     | 
    
         
            +
                  if masks.values.uniq.size == 1
         
     | 
| 
      
 241 
     | 
    
         
            +
                    tail = "%08b" % masks.values.first
         
     | 
| 
      
 242 
     | 
    
         
            +
                  else
         
     | 
| 
      
 243 
     | 
    
         
            +
                    a = []
         
     | 
| 
      
 244 
     | 
    
         
            +
                    masks.each do |c,mask|
         
     | 
| 
      
 245 
     | 
    
         
            +
                      a << "%s%08b" % [c,mask] if mask && mask != 0
         
     | 
| 
      
 246 
     | 
    
         
            +
                    end
         
     | 
| 
      
 247 
     | 
    
         
            +
                    raise "TODO" if a.empty?
         
     | 
| 
      
 248 
     | 
    
         
            +
                    a -= ['a11111111'] if a.size > 1 # fully opaque alpha is OK
         
     | 
| 
      
 249 
     | 
    
         
            +
                    if a.size == 1
         
     | 
| 
      
 250 
     | 
    
         
            +
                      color =
         
     | 
| 
      
 251 
     | 
    
         
            +
                        case a[0][0,1]
         
     | 
| 
      
 252 
     | 
    
         
            +
                        when 'r'; :red
         
     | 
| 
      
 253 
     | 
    
         
            +
                        when 'g'; :green
         
     | 
| 
      
 254 
     | 
    
         
            +
                        when 'b'; :blue
         
     | 
| 
      
 255 
     | 
    
         
            +
                        when 'a'; :gray
         
     | 
| 
      
 256 
     | 
    
         
            +
                        else nil
         
     | 
| 
      
 257 
     | 
    
         
            +
                        end
         
     | 
| 
      
 258 
     | 
    
         
            +
                    end
         
     | 
| 
      
 259 
     | 
    
         
            +
                    tail = a.join(".")
         
     | 
| 
      
 260 
     | 
    
         
            +
                  end
         
     | 
| 
      
 261 
     | 
    
         
            +
             
     | 
| 
      
 262 
     | 
    
         
            +
                  fname = [bname, tail, "png"].join('.')
         
     | 
| 
      
 263 
     | 
    
         
            +
                  fname = File.join(@options[:dir], File.basename(fname)) if @options[:dir]
         
     | 
| 
      
 264 
     | 
    
         
            +
                  [fname, color]
         
     | 
| 
      
 265 
     | 
    
         
            +
                end
         
     | 
| 
      
 266 
     | 
    
         
            +
             
     | 
| 
      
 267 
     | 
    
         
            +
              end
         
     | 
| 
      
 268 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/zsteg/masker.rb
    ADDED
    
    | 
         @@ -0,0 +1,52 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'set'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module ZSteg
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Masker
         
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize image, params = {}
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @src = image.is_a?(ZPNG::Image) ? image : ZPNG::Image.load(image)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @masks = params[:masks] || {}
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @mask = params[:mask] || @masks[:all]
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @normalize = params[:normalize]
         
     | 
| 
      
 10 
     | 
    
         
            +
                  [:r, :g, :b, :a].each{ |x| @masks[x] ||= @mask }
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def mask params = {}
         
     | 
| 
      
 14 
     | 
    
         
            +
                  dst = ZPNG::Image.new :width => @src.width, :height => @src.height
         
     | 
| 
      
 15 
     | 
    
         
            +
                  rm, gm, bm, am = @masks[:r], @masks[:g], @masks[:b], @masks[:a]
         
     | 
| 
      
 16 
     | 
    
         
            +
                  rd, gd, bd, ad = rm==0?1:rm, gm==0?1:gm, bm==0?1:bm, am==0?1:am
         
     | 
| 
      
 17 
     | 
    
         
            +
                  # duplicate loops for performance reason
         
     | 
| 
      
 18 
     | 
    
         
            +
                  if @normalize
         
     | 
| 
      
 19 
     | 
    
         
            +
                    if rm == 0 && gm == 0 && bm == 0 && am != 0
         
     | 
| 
      
 20 
     | 
    
         
            +
                      # alpha2grayscale
         
     | 
| 
      
 21 
     | 
    
         
            +
                      @src.each_pixel do |c,x,y|
         
     | 
| 
      
 22 
     | 
    
         
            +
                        c.r = c.g = c.b = (c.a & am) * 255 / ad
         
     | 
| 
      
 23 
     | 
    
         
            +
                        c.a = 0xff
         
     | 
| 
      
 24 
     | 
    
         
            +
                        dst[x,y] = c
         
     | 
| 
      
 25 
     | 
    
         
            +
                      end
         
     | 
| 
      
 26 
     | 
    
         
            +
                    else
         
     | 
| 
      
 27 
     | 
    
         
            +
                      # normal operation
         
     | 
| 
      
 28 
     | 
    
         
            +
                      @src.each_pixel do |c,x,y|
         
     | 
| 
      
 29 
     | 
    
         
            +
                        #TODO: c.to_depth(8)
         
     | 
| 
      
 30 
     | 
    
         
            +
                        # further possible optimizations:
         
     | 
| 
      
 31 
     | 
    
         
            +
                        # a) precalculate (255 / Xm)
         
     | 
| 
      
 32 
     | 
    
         
            +
                        c.r = (c.r & rm) * 255 / rd
         
     | 
| 
      
 33 
     | 
    
         
            +
                        c.g = (c.g & gm) * 255 / gd
         
     | 
| 
      
 34 
     | 
    
         
            +
                        c.b = (c.b & bm) * 255 / bd
         
     | 
| 
      
 35 
     | 
    
         
            +
                        c.a = (c.a & am) * 255 / ad
         
     | 
| 
      
 36 
     | 
    
         
            +
                        dst[x,y] = c
         
     | 
| 
      
 37 
     | 
    
         
            +
                      end
         
     | 
| 
      
 38 
     | 
    
         
            +
                    end
         
     | 
| 
      
 39 
     | 
    
         
            +
                  else
         
     | 
| 
      
 40 
     | 
    
         
            +
                    @src.each_pixel do |c,x,y|
         
     | 
| 
      
 41 
     | 
    
         
            +
                      #TODO: c.to_depth(8)
         
     | 
| 
      
 42 
     | 
    
         
            +
                      c.r &= rm
         
     | 
| 
      
 43 
     | 
    
         
            +
                      c.g &= gm
         
     | 
| 
      
 44 
     | 
    
         
            +
                      c.b &= bm
         
     | 
| 
      
 45 
     | 
    
         
            +
                      c.a &= am
         
     | 
| 
      
 46 
     | 
    
         
            +
                      dst[x,y] = c
         
     | 
| 
      
 47 
     | 
    
         
            +
                    end
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
                  dst
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
              end
         
     | 
| 
      
 52 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/zsteg/result.rb
    CHANGED
    
    | 
         @@ -17,13 +17,30 @@ module ZSteg 
     | 
|
| 
       17 
17 
     | 
    
         
             
                  end
         
     | 
| 
       18 
18 
     | 
    
         | 
| 
       19 
19 
     | 
    
         
             
                  def to_s
         
     | 
| 
       20 
     | 
    
         
            -
                    super.sub(/^<Result::/,'').sub(/>$/,''). 
     | 
| 
      
 20 
     | 
    
         
            +
                    super.sub(/^<Result::/,'').sub(/>$/,'').bright_red
         
     | 
| 
       21 
21 
     | 
    
         
             
                  end
         
     | 
| 
       22 
22 
     | 
    
         
             
                end
         
     | 
| 
       23 
23 
     | 
    
         | 
| 
       24 
24 
     | 
    
         
             
                class Text < Struct.new(:text, :offset)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  def one_char?
         
     | 
| 
      
 26 
     | 
    
         
            +
                    (text =~ /\A(.)\1+\Z/m) == 0
         
     | 
| 
      
 27 
     | 
    
         
            +
                  rescue # invalid byte sequence in UTF-8
         
     | 
| 
      
 28 
     | 
    
         
            +
                    text.chars.to_a.uniq.size == 1 # ~10x slower than regexp
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
       25 
31 
     | 
    
         
             
                  def to_s
         
     | 
| 
       26 
     | 
    
         
            -
                    "text: ".gray + 
     | 
| 
      
 32 
     | 
    
         
            +
                    "text: ".gray +
         
     | 
| 
      
 33 
     | 
    
         
            +
                      if one_char?
         
     | 
| 
      
 34 
     | 
    
         
            +
                        "[#{text[0].inspect} repeated #{text.size} times]".gray
         
     | 
| 
      
 35 
     | 
    
         
            +
                      elsif offset == 0
         
     | 
| 
      
 36 
     | 
    
         
            +
                        # first byte of data is also first char of text
         
     | 
| 
      
 37 
     | 
    
         
            +
                        text.inspect.bright_red
         
     | 
| 
      
 38 
     | 
    
         
            +
                      elsif text.size > 10 && text[' '] && text =~ /\A[a-z0-9 .,:!_-]+\Z/i
         
     | 
| 
      
 39 
     | 
    
         
            +
                        # text is ASCII with spaces
         
     | 
| 
      
 40 
     | 
    
         
            +
                        text.inspect.bright_red
         
     | 
| 
      
 41 
     | 
    
         
            +
                      else
         
     | 
| 
      
 42 
     | 
    
         
            +
                        text.inspect
         
     | 
| 
      
 43 
     | 
    
         
            +
                      end
         
     | 
| 
       27 
44 
     | 
    
         
             
                  end
         
     | 
| 
       28 
45 
     | 
    
         
             
                end
         
     | 
| 
       29 
46 
     | 
    
         | 
| 
         @@ -33,9 +50,15 @@ module ZSteg 
     | 
|
| 
       33 
50 
     | 
    
         
             
                # part of data is text
         
     | 
| 
       34 
51 
     | 
    
         
             
                class PartialText < Text; end
         
     | 
| 
       35 
52 
     | 
    
         | 
| 
      
 53 
     | 
    
         
            +
                # unicode text
         
     | 
| 
      
 54 
     | 
    
         
            +
                class UnicodeText < Text; end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
       36 
56 
     | 
    
         
             
                class Zlib < Struct.new(:data, :offset)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  MAX_SHOW_SIZE = 100
         
     | 
| 
       37 
58 
     | 
    
         
             
                  def to_s
         
     | 
| 
       38 
     | 
    
         
            -
                     
     | 
| 
      
 59 
     | 
    
         
            +
                    x = data
         
     | 
| 
      
 60 
     | 
    
         
            +
                    x=x[0,MAX_SHOW_SIZE] + "..." if x.size > MAX_SHOW_SIZE
         
     | 
| 
      
 61 
     | 
    
         
            +
                    "zlib: data=#{x.inspect.bright_red}, offset=#{offset}, size=#{data.size}"
         
     | 
| 
       39 
62 
     | 
    
         
             
                  end
         
     | 
| 
       40 
63 
     | 
    
         
             
                end
         
     | 
| 
       41 
64 
     | 
    
         | 
| 
         @@ -45,34 +68,6 @@ module ZSteg 
     | 
|
| 
       45 
68 
     | 
    
         
             
                  end
         
     | 
| 
       46 
69 
     | 
    
         
             
                end
         
     | 
| 
       47 
70 
     | 
    
         | 
| 
       48 
     | 
    
         
            -
                class FileCmd < Struct.new(:title, :data)
         
     | 
| 
       49 
     | 
    
         
            -
                  COLORMAP = {
         
     | 
| 
       50 
     | 
    
         
            -
                    /bitmap|jpeg|pdf|zip|rar|7z/i => :red,
         
     | 
| 
       51 
     | 
    
         
            -
                    /DBase 3 data/i               => :gray
         
     | 
| 
       52 
     | 
    
         
            -
                  }
         
     | 
| 
       53 
     | 
    
         
            -
             
     | 
| 
       54 
     | 
    
         
            -
                  def to_s
         
     | 
| 
       55 
     | 
    
         
            -
                    if title[/UTF-8 Unicode text/i]
         
     | 
| 
       56 
     | 
    
         
            -
                      begin
         
     | 
| 
       57 
     | 
    
         
            -
                        t = data.force_encoding("UTF-8").encode("UTF-32LE").encode("UTF-8")
         
     | 
| 
       58 
     | 
    
         
            -
                      rescue
         
     | 
| 
       59 
     | 
    
         
            -
                        t = data.force_encoding('binary')
         
     | 
| 
       60 
     | 
    
         
            -
                      end
         
     | 
| 
       61 
     | 
    
         
            -
                      return "utf8: " + t
         
     | 
| 
       62 
     | 
    
         
            -
                    end
         
     | 
| 
       63 
     | 
    
         
            -
                    COLORMAP.each do |re,color|
         
     | 
| 
       64 
     | 
    
         
            -
                      if title[re]
         
     | 
| 
       65 
     | 
    
         
            -
                        if color == :gray
         
     | 
| 
       66 
     | 
    
         
            -
                          return "file: #{title}".send(color)
         
     | 
| 
       67 
     | 
    
         
            -
                        else
         
     | 
| 
       68 
     | 
    
         
            -
                          return "file: " + title.send(color)
         
     | 
| 
       69 
     | 
    
         
            -
                        end
         
     | 
| 
       70 
     | 
    
         
            -
                      end
         
     | 
| 
       71 
     | 
    
         
            -
                    end
         
     | 
| 
       72 
     | 
    
         
            -
                    "file: " + title.yellow
         
     | 
| 
       73 
     | 
    
         
            -
                  end
         
     | 
| 
       74 
     | 
    
         
            -
                end
         
     | 
| 
       75 
     | 
    
         
            -
             
     | 
| 
       76 
71 
     | 
    
         
             
                class Camouflage < Struct.new(:hidden_data_len, :host_orig_len)
         
     | 
| 
       77 
72 
     | 
    
         
             
                  def initialize(data)
         
     | 
| 
       78 
73 
     | 
    
         
             
                    self.hidden_data_len = (data[0x1a,4] || '').unpack('V').first
         
     | 
| 
         @@ -83,7 +78,7 @@ module ZSteg 
     | 
|
| 
       83 
78 
     | 
    
         
             
                  end
         
     | 
| 
       84 
79 
     | 
    
         | 
| 
       85 
80 
     | 
    
         
             
                  def to_s
         
     | 
| 
       86 
     | 
    
         
            -
                    super. 
     | 
| 
      
 81 
     | 
    
         
            +
                    super.bright_red
         
     | 
| 
       87 
82 
     | 
    
         
             
                  end
         
     | 
| 
       88 
83 
     | 
    
         
             
                end
         
     | 
| 
       89 
84 
     | 
    
         
             
              end
         
     |