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.
- 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"])
|