zsteg 0.0.1 → 0.1.0

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.
@@ -35,22 +35,22 @@ module ZSteg
35
35
  end
36
36
  end
37
37
 
38
- def bits2masks bits
39
- masks = []
38
+ def bit_indexes bits
40
39
  if (1..8).include?(bits)
41
40
  # number of bits
42
- bits.times do |i|
43
- masks << (1<<(bits-i-1))
44
- end
41
+ # 1 => [0]
42
+ # ...
43
+ # 8 => [7,6,5,4,3,2,1,0]
44
+ bits.times.to_a.reverse
45
45
  else
46
46
  # mask
47
- bits &= 0xff
47
+ mask = bits & 0xff
48
+ r = []
48
49
  8.times do |i|
49
- mask = (1<<(bits-i-1))
50
- masks << mask if (bits & mask) != 0
50
+ r << i if mask[i] == 1
51
51
  end
52
+ r.reverse
52
53
  end
53
- masks
54
54
  end
55
55
  end
56
56
  end
@@ -5,12 +5,12 @@ module ZSteg
5
5
  module ByteExtractor
6
6
 
7
7
  def byte_extract params = {}
8
- masks = bits2masks params[:bits]
8
+ bidxs = bit_indexes params[:bits]
9
9
 
10
10
  if params[:prime]
11
11
  pregenerate_primes(
12
12
  :max => @image.scanlines[0].size * @image.height,
13
- :count => (@limit*8.0/masks.size).ceil
13
+ :count => (@limit*8.0/bidxs.size).ceil
14
14
  )
15
15
  end
16
16
 
@@ -20,8 +20,8 @@ module ZSteg
20
20
  sl = @image.scanlines[y]
21
21
 
22
22
  value = sl.decoded_bytes.getbyte(x)
23
- masks.each do |mask|
24
- a << ((value & mask) == 0 ? 0 : 1)
23
+ bidxs.each do |bidx|
24
+ a << value[bidx]
25
25
  end
26
26
 
27
27
  if a.size >= 8
@@ -11,11 +11,16 @@ module ZSteg
11
11
  case channels.first.size
12
12
  when 1
13
13
  # ['r', 'g', 'b']
14
- channels.each{ |c| ch_masks << [c[0], bits2masks(params[:bits])] }
14
+ channels.each{ |c| ch_masks << [c[0], bit_indexes(params[:bits])] }
15
15
  when 2
16
16
  # ['r3', 'g2', 'b3']
17
- channels.each{ |c| ch_masks << [c[0], bits2masks(c[1].to_i)] }
17
+ channels.each{ |c| ch_masks << [c[0], bit_indexes(c[1].to_i)] }
18
18
  else
19
+ raise "invalid channels: #{channels.inspect}" if channels.size != 1
20
+ t = channels.first
21
+ if t =~ /\A[rgba]+\Z/
22
+ return color_extract(params.merge(:channels => t.split('')))
23
+ end
19
24
  raise "invalid channels: #{channels.inspect}"
20
25
  end
21
26
 
@@ -36,10 +41,10 @@ module ZSteg
36
41
  coord_iterator(params) do |x,y|
37
42
  color = @image[x,y]
38
43
 
39
- ch_masks.each do |c,masks|
44
+ ch_masks.each do |c,bidxs|
40
45
  value = color.send(c)
41
- masks.each do |mask|
42
- a << ((value & mask) == 0 ? 0 : 1)
46
+ bidxs.each do |bidx|
47
+ a << value[bidx]
43
48
  end
44
49
  end
45
50
  #p [x,y,a.size,a]
@@ -42,6 +42,10 @@ module ZSteg
42
42
  text.inspect
43
43
  end
44
44
  end
45
+
46
+ def self.from_matchdata m
47
+ self.new m[0], m.begin(0)
48
+ end
45
49
  end
46
50
 
47
51
  # whole data is text
@@ -53,15 +57,6 @@ module ZSteg
53
57
  # unicode text
54
58
  class UnicodeText < Text; end
55
59
 
56
- class Zlib < Struct.new(:data, :offset)
57
- MAX_SHOW_SIZE = 100
58
- def to_s
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}"
62
- end
63
- end
64
-
65
60
  class OneChar < Struct.new(:char, :size)
66
61
  def to_s
67
62
  "[#{char.inspect} repeated #{size} times]".gray
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ Dir['bin/*'].each do |fname|
4
+ describe fname do
5
+ it "should run" do
6
+ system "#{fname} > /dev/null"
7
+ $?.should be_success
8
+ end
9
+ end
10
+ end
@@ -30,13 +30,13 @@ describe Checker do
30
30
  end
31
31
 
32
32
  describe "results" do
33
- it "should not have text results shorter than #{Checker::MIN_TEXT_LENGTH}" do
33
+ it "should not have text results shorter than #{Checker::DEFAULT_MIN_STR_LEN}" do
34
34
  @results.each do |result|
35
35
  case result
36
36
  when Result::WholeText
37
- result.text.size.should(be >= Checker::MIN_WHOLETEXT_LENGTH, result.inspect)
37
+ result.text.size.should(be >= Checker::DEFAULT_MIN_STR_LEN-2, result.inspect)
38
38
  when Result::Text
39
- result.text.size.should(be >= Checker::MIN_TEXT_LENGTH, result.inspect)
39
+ result.text.size.should(be >= Checker::DEFAULT_MIN_STR_LEN, result.inspect)
40
40
  end
41
41
  end
42
42
  end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe "samples/extradata.png" do
4
+ subject{ cli(sample("extradata.png")) }
5
+ it { should include("foobar1") }
6
+ it { should include("foobar2") }
7
+ it { should include("foobar3") }
8
+
9
+ describe "--extract" do
10
+ before do
11
+ @out = subject
12
+ end
13
+ it "should extract all" do
14
+ keys = []
15
+ @out.split(/[\r\n]+/).each do |line|
16
+ if line[/foobar\d/]
17
+ keys << line.split.first
18
+ end
19
+ end
20
+ keys.size.should == 3
21
+ r = cli(sample("extradata.png"), *keys.map{|k| "--extract #{k}"} )
22
+ r.should include("foobar1")
23
+ r.should include("foobar2")
24
+ r.should include("foobar3")
25
+ r.size.should == 7*3
26
+ end
27
+ end
28
+ end
@@ -5,7 +5,7 @@ sample("Steganography_original.png") do |fname|
5
5
  it "extracts hidden image" do
6
6
  tname = "tmp/mask.tmp.png"
7
7
  File.unlink(tname) if File.exist?(tname)
8
- cli(ZSteg::MaskCLI, fname, "-m 00000011 -O #{tname}")
8
+ cli(:mask, fname, "-m 00000011 -O #{tname}")
9
9
  img1 = ZPNG::Image.load tname
10
10
  img2 = ZPNG::Image.load fname.sub(/\.png$/,".00000011.png")
11
11
  img1.should == img2
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ # scanline extradata
4
+ sample("newbiecontest/alph1-surprise.bmp") do |fname|
5
+ describe fname do
6
+ subject{ cli(fname) }
7
+
8
+ it { should include "PE32 executable for MS Windows" }
9
+ it { should include "is program canno" }
10
+
11
+ describe "--extract" do
12
+ subject{ cli(fname, "--extract scanline") }
13
+
14
+ it { should include "MessageBoxA" }
15
+ it { should include "PVUAC PY HCHY UCYL AOPCISV WJHXY JDVYZJI YXH NIDSYRFBRCASVMFWVY" }
16
+ end
17
+ end
18
+ end
@@ -1,7 +1,5 @@
1
1
  $:.unshift(File.expand_path("../lib", File.dirname(__FILE__)))
2
2
  require 'zsteg'
3
- require 'zsteg/cli'
4
- require 'zsteg/mask_cli'
5
3
 
6
4
  SAMPLES_DIR = File.expand_path("../samples", File.dirname(__FILE__))
7
5
 
@@ -33,7 +31,14 @@ def cli *args
33
31
  args.flatten!
34
32
  @@cli_cache[args.inspect] ||=
35
33
  begin
36
- klass = args.first.is_a?(Class) ? args.shift : ZSteg::CLI
34
+ klass =
35
+ if args.first.is_a?(Symbol)
36
+ cli_name = args.shift.to_s
37
+ require "zsteg/cli/#{cli_name}"
38
+ ZSteg::CLI.const_get(cli_name.capitalize)
39
+ else
40
+ ZSteg::CLI
41
+ end
37
42
  args << "--no-color" unless args.any?{|x| x['color']}
38
43
  orig_stdout, out = $stdout, ""
39
44
  begin
@@ -48,9 +53,9 @@ end
48
53
 
49
54
  RSpec.configure do |config|
50
55
  config.before :suite do
51
- Dir[File.join(SAMPLES_DIR, "*.7z")].each do |fname|
56
+ Dir[File.join(SAMPLES_DIR, "**", "*.7z")].each do |fname|
52
57
  next if File.exist?(fname.sub(/\.7z$/,''))
53
- system "7z", "x", fname, "-o#{SAMPLES_DIR}"
58
+ system "7z", "x", fname, "-o#{File.dirname(fname)}"
54
59
  end
55
60
  end
56
61
  end
@@ -17,7 +17,7 @@ sample("wechall/5ZMGcCLxpcpsru03.png") do |fname|
17
17
  it "extracts hidden image" do
18
18
  tname = "tmp/wechall.tmp.png"
19
19
  File.unlink(tname) if File.exist?(tname)
20
- cli(ZSteg::MaskCLI, fname, "--green 00000010 -O #{tname}")
20
+ cli(:mask, fname, "--green 00000010 -O #{tname}")
21
21
  img1 = ZPNG::Image.load tname
22
22
  img2 = ZPNG::Image.load fname.sub(/\.png$/,".g00000010.png")
23
23
  img1.should == img2
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env ruby
2
+ require 'zpng'
3
+
4
+ class ChunkAppender
5
+ def initialize image
6
+ @image = image.is_a?(ZPNG::Image) ? image : ZPNG::Image.load(image)
7
+ end
8
+
9
+ def list_chunks
10
+ @image.chunks.each_with_index do |c, idx|
11
+ printf "%3d: type=%4s size=%d\n", idx, c.type, c.size
12
+ end
13
+ end
14
+
15
+ def append chunk_no, data
16
+ @image.chunks[chunk_no].define_singleton_method :export_data do
17
+ super() + data
18
+ end
19
+ self
20
+ end
21
+
22
+ def save fname
23
+ @image.save(fname, :repack => false)
24
+ end
25
+ end
26
+
27
+ if $0 == __FILE__
28
+ case ARGV.size
29
+ when 1
30
+ ChunkAppender.new(ARGV[0]).list_chunks
31
+ when 3,4
32
+ fname, chunk_no, data, oname = ARGV
33
+ oname ||= fname.chomp(File.extname(fname)) + ".out" + File.extname(fname)
34
+ ChunkAppender.new(fname).
35
+ append(chunk_no.to_i, data).
36
+ save(oname)
37
+ puts "[=] #{oname} saved"
38
+ else
39
+ bname = File.basename($0)
40
+ puts "USAGE:"
41
+ puts " Append data to specified chunk:"
42
+ puts " #{bname} input.png <chunk_no> <data> [output.png]"
43
+ puts
44
+ puts " List chunks:"
45
+ puts " #{bname} input.png"
46
+ exit
47
+ end
48
+ end
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env ruby
2
+ require 'zpng'
3
+
4
+ class ZlibAppender
5
+ def initialize image
6
+ @image = image.is_a?(ZPNG::Image) ? image : ZPNG::Image.load(image)
7
+ end
8
+
9
+ def _guess_compress_method
10
+ zdata = @image.chunks.find_all{ |c| c.is_a?(ZPNG::Chunk::IDAT) }.map(&:data).join
11
+ puts "[.] old zdata size = #{zdata.size}"
12
+ 9.downto(0) do |i|
13
+ if zdata == Zlib::Deflate.deflate(@image.imagedata, i)
14
+ puts "[.] compress_method = #{i}"
15
+ return i
16
+ end
17
+ end
18
+ 9.downto(0) do |i|
19
+ if zdata.size == Zlib::Deflate.deflate(@image.imagedata, i).size
20
+ puts "[.] compress_method = #{i}"
21
+ return i
22
+ end
23
+ end
24
+ puts "[?] failed to guess compress method, using default".yellow
25
+ nil
26
+ end
27
+
28
+ def append appendum
29
+ m = _guess_compress_method
30
+ new_data = @image.imagedata + appendum
31
+ new_zdata = Zlib::Deflate.deflate(new_data, m)
32
+ puts "[.] new zdata size = #{new_zdata.size}"
33
+
34
+ idats = @image.chunks.find_all{ |c| c.is_a?(ZPNG::Chunk::IDAT) }
35
+ idats[0].data = new_zdata
36
+
37
+ # delete other IDAT chunks, if any
38
+ image.chunks -= idats[1..-1] if idats.size > 1
39
+
40
+ self
41
+ end
42
+
43
+ def save fname
44
+ @image.save(fname, :repack => false)
45
+ end
46
+ end
47
+
48
+ if $0 == __FILE__
49
+ case ARGV.size
50
+ when 2,3
51
+ fname, data, oname = ARGV
52
+ oname ||= fname.chomp(File.extname(fname)) + ".out" + File.extname(fname)
53
+ ZlibAppender.new(fname).
54
+ append(data).
55
+ save(oname)
56
+ puts "[=] #{oname} saved"
57
+ else
58
+ bname = File.basename($0)
59
+ puts "USAGE:"
60
+ puts " Append data to zlib stream:"
61
+ puts " #{bname} input.png <data> [output.png]"
62
+ exit
63
+ end
64
+ end
@@ -5,13 +5,13 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "zsteg"
8
- s.version = "0.0.1"
8
+ s.version = "0.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Andrey \"Zed\" Zaikin"]
12
- s.date = "2013-01-15"
12
+ s.date = "2013-01-24"
13
13
  s.email = "zed.0xff@gmail.com"
14
- s.executables = ["zsteg", "zsteg-mask"]
14
+ s.executables = ["zsteg", "zsteg-mask", "zsteg-reflow"]
15
15
  s.extra_rdoc_files = [
16
16
  "README.md",
17
17
  "README.md.tpl",
@@ -27,22 +27,28 @@ Gem::Specification.new do |s|
27
27
  "VERSION",
28
28
  "bin/zsteg",
29
29
  "bin/zsteg-mask",
30
+ "bin/zsteg-reflow",
30
31
  "cmp_bmp.rb",
31
32
  "cmp_png.rb",
32
33
  "lib/zsteg.rb",
34
+ "lib/zsteg/analyzer.rb",
33
35
  "lib/zsteg/checker.rb",
36
+ "lib/zsteg/checker/scanline_checker.rb",
34
37
  "lib/zsteg/checker/wbstego.rb",
35
- "lib/zsteg/cli.rb",
38
+ "lib/zsteg/checker/zlib.rb",
39
+ "lib/zsteg/cli/cli.rb",
40
+ "lib/zsteg/cli/mask.rb",
41
+ "lib/zsteg/cli/reflow.rb",
36
42
  "lib/zsteg/extractor.rb",
37
43
  "lib/zsteg/extractor/byte_extractor.rb",
38
44
  "lib/zsteg/extractor/color_extractor.rb",
39
45
  "lib/zsteg/file_cmd.rb",
40
- "lib/zsteg/mask_cli.rb",
41
46
  "lib/zsteg/masker.rb",
42
47
  "lib/zsteg/result.rb",
43
48
  "pngsteg.gemspec",
44
49
  "samples/hackquest/crypt.bmp",
45
50
  "samples/hackquest/square.bmp",
51
+ "samples/newbiecontest/alph1-surprise.bmp.7z",
46
52
  "samples/wbstego/wbsteg_blowfish_pass_1.bmp",
47
53
  "samples/wbstego/wbsteg_cast128_pass_1.bmp",
48
54
  "samples/wbstego/wbsteg_enc_pass_pass.bmp",
@@ -69,13 +75,16 @@ Gem::Specification.new do |s|
69
75
  "samples/wechall/5ZMGcCLxpcpsru03.g00000010.png",
70
76
  "samples/wechall/5ZMGcCLxpcpsru03.png",
71
77
  "samples/wechall/stegano1.bmp",
78
+ "spec/bin_spec.rb",
72
79
  "spec/camouflage_spec.rb",
73
80
  "spec/cats_spec.rb",
74
81
  "spec/checker_spec.rb",
75
82
  "spec/easybmp_spec.rb",
83
+ "spec/extradata_spec.rb",
76
84
  "spec/flowers_spec.rb",
77
85
  "spec/hackquest_spec.rb",
78
86
  "spec/mask_spec.rb",
87
+ "spec/newbiecontest_spec.rb",
79
88
  "spec/openstego_spec.rb",
80
89
  "spec/polictf2012_spec.rb",
81
90
  "spec/prime_spec.rb",
@@ -86,6 +95,8 @@ Gem::Specification.new do |s|
86
95
  "spec/wechall_spec.rb",
87
96
  "spec/zlib_spec.rb",
88
97
  "tmp/.keep",
98
+ "writers/chunk_append.rb",
99
+ "writers/zlib_append.rb",
89
100
  "zsteg.gemspec"
90
101
  ]
91
102
  s.homepage = "http://github.com/zed-0xff/zsteg"
@@ -98,20 +109,20 @@ Gem::Specification.new do |s|
98
109
  s.specification_version = 3
99
110
 
100
111
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
101
- s.add_runtime_dependency(%q<zpng>, [">= 0.2.2"])
112
+ s.add_runtime_dependency(%q<zpng>, [">= 0.2.3"])
102
113
  s.add_runtime_dependency(%q<iostruct>, [">= 0"])
103
114
  s.add_development_dependency(%q<rspec>, [">= 2.8.0"])
104
115
  s.add_development_dependency(%q<bundler>, [">= 1.0.0"])
105
116
  s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
106
117
  else
107
- s.add_dependency(%q<zpng>, [">= 0.2.2"])
118
+ s.add_dependency(%q<zpng>, [">= 0.2.3"])
108
119
  s.add_dependency(%q<iostruct>, [">= 0"])
109
120
  s.add_dependency(%q<rspec>, [">= 2.8.0"])
110
121
  s.add_dependency(%q<bundler>, [">= 1.0.0"])
111
122
  s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
112
123
  end
113
124
  else
114
- s.add_dependency(%q<zpng>, [">= 0.2.2"])
125
+ s.add_dependency(%q<zpng>, [">= 0.2.3"])
115
126
  s.add_dependency(%q<iostruct>, [">= 0"])
116
127
  s.add_dependency(%q<rspec>, [">= 2.8.0"])
117
128
  s.add_dependency(%q<bundler>, [">= 1.0.0"])