zsteg 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -1
- data/Gemfile.lock +2 -2
- data/TODO +6 -0
- data/VERSION +1 -1
- data/bin/zsteg +5 -4
- data/bin/zsteg-mask +5 -4
- data/bin/zsteg-reflow +8 -0
- data/lib/zsteg.rb +25 -0
- data/lib/zsteg/analyzer.rb +63 -0
- data/lib/zsteg/checker.rb +108 -38
- data/lib/zsteg/checker/scanline_checker.rb +82 -0
- data/lib/zsteg/checker/wbstego.rb +3 -0
- data/lib/zsteg/checker/zlib.rb +41 -0
- data/lib/zsteg/{cli.rb → cli/cli.rb} +63 -11
- data/lib/zsteg/{mask_cli.rb → cli/mask.rb} +6 -4
- data/lib/zsteg/cli/reflow.rb +241 -0
- data/lib/zsteg/extractor.rb +9 -9
- data/lib/zsteg/extractor/byte_extractor.rb +4 -4
- data/lib/zsteg/extractor/color_extractor.rb +10 -5
- data/lib/zsteg/result.rb +4 -9
- data/samples/newbiecontest/alph1-surprise.bmp.7z +0 -0
- data/spec/bin_spec.rb +10 -0
- data/spec/checker_spec.rb +3 -3
- data/spec/extradata_spec.rb +28 -0
- data/spec/mask_spec.rb +1 -1
- data/spec/newbiecontest_spec.rb +18 -0
- data/spec/spec_helper.rb +10 -5
- data/spec/wechall_spec.rb +1 -1
- data/writers/chunk_append.rb +48 -0
- data/writers/zlib_append.rb +64 -0
- data/zsteg.gemspec +19 -8
- metadata +41 -29
data/lib/zsteg/extractor.rb
CHANGED
@@ -35,22 +35,22 @@ module ZSteg
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
def
|
39
|
-
masks = []
|
38
|
+
def bit_indexes bits
|
40
39
|
if (1..8).include?(bits)
|
41
40
|
# number of bits
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
47
|
+
mask = bits & 0xff
|
48
|
+
r = []
|
48
49
|
8.times do |i|
|
49
|
-
mask
|
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
|
-
|
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/
|
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
|
-
|
24
|
-
a <<
|
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],
|
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],
|
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,
|
44
|
+
ch_masks.each do |c,bidxs|
|
40
45
|
value = color.send(c)
|
41
|
-
|
42
|
-
a <<
|
46
|
+
bidxs.each do |bidx|
|
47
|
+
a << value[bidx]
|
43
48
|
end
|
44
49
|
end
|
45
50
|
#p [x,y,a.size,a]
|
data/lib/zsteg/result.rb
CHANGED
@@ -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
|
Binary file
|
data/spec/bin_spec.rb
ADDED
data/spec/checker_spec.rb
CHANGED
@@ -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::
|
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::
|
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::
|
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
|
data/spec/mask_spec.rb
CHANGED
@@ -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(
|
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
|
data/spec/spec_helper.rb
CHANGED
@@ -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 =
|
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#{
|
58
|
+
system "7z", "x", fname, "-o#{File.dirname(fname)}"
|
54
59
|
end
|
55
60
|
end
|
56
61
|
end
|
data/spec/wechall_spec.rb
CHANGED
@@ -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(
|
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
|
data/zsteg.gemspec
CHANGED
@@ -5,13 +5,13 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "zsteg"
|
8
|
-
s.version = "0.0
|
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-
|
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/
|
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.
|
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.
|
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.
|
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"])
|