zsteg 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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"])