zsteg 0.0.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 +15 -0
- data/Gemfile.lock +38 -0
- data/README.md +46 -0
- data/README.md.tpl +31 -0
- data/Rakefile +99 -0
- data/TODO +14 -0
- data/VERSION +1 -0
- data/bin/zsteg +7 -0
- data/cmp_bmp.rb +47 -0
- data/cmp_png.rb +42 -0
- data/lib/zsteg.rb +12 -0
- data/lib/zsteg/checker.rb +228 -0
- data/lib/zsteg/checker/wbstego.rb +98 -0
- data/lib/zsteg/cli.rb +132 -0
- data/lib/zsteg/extractor.rb +21 -0
- data/lib/zsteg/extractor/byte_extractor.rb +94 -0
- data/lib/zsteg/extractor/color_extractor.rb +95 -0
- data/lib/zsteg/file_cmd.rb +63 -0
- data/lib/zsteg/result.rb +90 -0
- data/pngsteg.gemspec +65 -0
- data/samples/06_enc.png +0 -0
- data/samples/Code.png +0 -0
- data/samples/README +4 -0
- 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/samples/wbsteg_noenc.bmp +0 -0
- data/samples/wbsteg_noenc_17.bmp +0 -0
- data/samples/wbsteg_noenc_even.bmp +0 -0
- data/samples/wbsteg_noenc_even_17.bmp +0 -0
- data/spec/camouflage_spec.rb +9 -0
- data/spec/cats_spec.rb +23 -0
- data/spec/flowers_spec.rb +11 -0
- data/spec/openstego_spec.rb +21 -0
- data/spec/simple_spec.rb +22 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/wbstego_spec.rb +10 -0
- data/spec/zlib_spec.rb +6 -0
- metadata +198 -0
@@ -0,0 +1,98 @@
|
|
1
|
+
module ZSteg
|
2
|
+
class Checker
|
3
|
+
module WBStego
|
4
|
+
|
5
|
+
class Result < IOStruct.new "a3a3a*", :size, :ext, :data, :even
|
6
|
+
def initialize *args
|
7
|
+
super
|
8
|
+
if self.size.is_a?(String)
|
9
|
+
self.size = (self.size[0,3] + "\x00").unpack('V')[0]
|
10
|
+
end
|
11
|
+
self.even = false if self.even.nil?
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
inspect.sub("#<struct #{self.class.to_s}", "<wbStego").red
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def used_colors
|
21
|
+
raise "TODO"
|
22
|
+
end
|
23
|
+
|
24
|
+
# from wbStego4open sources
|
25
|
+
def calc_avail_size image
|
26
|
+
space = 0
|
27
|
+
biHeader = image.hdr
|
28
|
+
if biHeader.biCompression == 0
|
29
|
+
case biHeader.biBitCount
|
30
|
+
when 4
|
31
|
+
space = 2*image.imagedata_size if used_colors < 9
|
32
|
+
when 8
|
33
|
+
space = image.imagedata_size if used_colors < 129
|
34
|
+
when 24
|
35
|
+
space = image.imagedata_size
|
36
|
+
end
|
37
|
+
else
|
38
|
+
raise "TODO"
|
39
|
+
# if biHeader.biBitCount=4 then begin
|
40
|
+
# if UsedColors<9 then space:=GetAvailSizeRLE else space:=0;
|
41
|
+
# end;
|
42
|
+
# if biHeader.biBitCount=8 then begin
|
43
|
+
# if UsedColors<129 then space:=GetAvailSizeRLE else space:=0;
|
44
|
+
# end;
|
45
|
+
end
|
46
|
+
space/8
|
47
|
+
end
|
48
|
+
|
49
|
+
def check data, params = {}
|
50
|
+
return if data.size < 4
|
51
|
+
return if params[:bit_order] != :lsb
|
52
|
+
if params[:image].format == :bmp
|
53
|
+
return if params[:order] !~ /b/i
|
54
|
+
end
|
55
|
+
|
56
|
+
size1 = (data[0,3] + "\x00").unpack('V')[0]
|
57
|
+
avail_size =
|
58
|
+
if params[:image].format == :bmp
|
59
|
+
calc_avail_size(params[:image])
|
60
|
+
else
|
61
|
+
params[:max_hidden_size]
|
62
|
+
end
|
63
|
+
return if size1 == 0 || size1 > avail_size
|
64
|
+
size2 = (data[3,3] + "\x00").unpack('V')[0]
|
65
|
+
# p [size1, size2, avail_size]
|
66
|
+
if size2 < avail_size
|
67
|
+
spacing = 1.0*avail_size/(size2+5) - 1
|
68
|
+
# puts "[d] spacing=#{spacing}"
|
69
|
+
if spacing > 0
|
70
|
+
error = 0
|
71
|
+
r = ''
|
72
|
+
6.upto(data.size-1) do |idx|
|
73
|
+
if error < 1
|
74
|
+
r << data[idx]
|
75
|
+
error += spacing
|
76
|
+
else
|
77
|
+
error -= 1
|
78
|
+
end
|
79
|
+
end
|
80
|
+
# puts "[d] r=#{r.inspect} (#{r.size})"
|
81
|
+
ext = r[0,3]
|
82
|
+
return unless valid_ext?(ext)
|
83
|
+
return Result.new(size2, ext, r[3..-1], true)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
# no even distribution
|
87
|
+
return unless valid_ext?(data[3,3])
|
88
|
+
return Result.read(data)
|
89
|
+
end
|
90
|
+
|
91
|
+
# XXX require that file extension be 7-bit ASCII
|
92
|
+
def valid_ext? ext
|
93
|
+
ext =~ /\A[\x20-\x7e]+\Z/
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/lib/zsteg/cli.rb
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'awesome_print'
|
3
|
+
|
4
|
+
module ZSteg
|
5
|
+
class CLI
|
6
|
+
DEFAULT_ACTIONS = %w'check'
|
7
|
+
DEFAULT_LIMIT = 256
|
8
|
+
DEFAULT_ORDER = 'auto'
|
9
|
+
|
10
|
+
def initialize argv = ARGV
|
11
|
+
@argv = argv
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
@actions = []
|
16
|
+
@options = {
|
17
|
+
:verbose => 0,
|
18
|
+
:limit => DEFAULT_LIMIT,
|
19
|
+
:bits => [1,2,3,4],
|
20
|
+
:order => DEFAULT_ORDER
|
21
|
+
}
|
22
|
+
optparser = OptionParser.new do |opts|
|
23
|
+
opts.banner = "Usage: zsteg [options] filename.png"
|
24
|
+
opts.separator ""
|
25
|
+
|
26
|
+
opts.on("-c", "--channels X", /[rgba,]+/,
|
27
|
+
"channels (R/G/B/A) or any combination, comma separated",
|
28
|
+
"valid values: r,g,b,a,rg,rgb,bgr,rgba,..."
|
29
|
+
){ |x| @options[:channels] = x.split(',') }
|
30
|
+
|
31
|
+
opts.on("-l", "--limit N", Integer,
|
32
|
+
"limit bytes checked, 0 = no limit (default: #{DEFAULT_LIMIT})"
|
33
|
+
){ |n| @options[:limit] = n }
|
34
|
+
|
35
|
+
opts.on("-b", "--bits N", /[\d,-]+/,
|
36
|
+
"number of bits (1..8), single value or '1,3,5' or '1-8'") do |n|
|
37
|
+
if n['-']
|
38
|
+
@options[:bits] = Range.new(*n.split('-').map(&:to_i)).to_a
|
39
|
+
else
|
40
|
+
@options[:bits] = n.split(',').map(&:to_i)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on "--lsb", "least significant BIT comes first" do
|
45
|
+
@options[:bit_order] = :lsb
|
46
|
+
end
|
47
|
+
opts.on "--msb", "most significant BIT comes first" do
|
48
|
+
@options[:bit_order] = :msb
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on("-o", "--order X", /all|auto|[bxy,]+/i,
|
52
|
+
"pixel iteration order (default: '#{DEFAULT_ORDER}')",
|
53
|
+
"valid values: ALL,xy,yx,XY,YX,xY,Xy,bY,...",
|
54
|
+
){ |x| @options[:order] = x.split(',') }
|
55
|
+
|
56
|
+
opts.on "-E", "--extract NAME", "extract specified payload, NAME is like '1b,rgb,lsb'" do |x|
|
57
|
+
@actions << [:extract, x]
|
58
|
+
end
|
59
|
+
|
60
|
+
opts.separator ""
|
61
|
+
opts.on "-v", "--verbose", "Run verbosely (can be used multiple times)" do |v|
|
62
|
+
@options[:verbose] += 1
|
63
|
+
end
|
64
|
+
opts.on "-q", "--quiet", "Silent any warnings (can be used multiple times)" do |v|
|
65
|
+
@options[:verbose] -= 1
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
if (argv = optparser.parse(@argv)).empty?
|
70
|
+
puts optparser.help
|
71
|
+
return
|
72
|
+
end
|
73
|
+
|
74
|
+
@actions = DEFAULT_ACTIONS if @actions.empty?
|
75
|
+
|
76
|
+
argv.each_with_index do |fname,idx|
|
77
|
+
if argv.size > 1 && @options[:verbose] >= 0
|
78
|
+
puts if idx > 0
|
79
|
+
puts "[.] #{fname}".green
|
80
|
+
end
|
81
|
+
@fname = fname
|
82
|
+
|
83
|
+
@actions.each do |action|
|
84
|
+
if action.is_a?(Array)
|
85
|
+
self.send(*action) if self.respond_to?(action.first)
|
86
|
+
else
|
87
|
+
self.send(action) if self.respond_to?(action)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
rescue Errno::EPIPE
|
92
|
+
# output interrupt, f.ex. when piping output to a 'head' command
|
93
|
+
# prevents a 'Broken pipe - <STDOUT> (Errno::EPIPE)' message
|
94
|
+
end
|
95
|
+
|
96
|
+
###########################################################################
|
97
|
+
# actions
|
98
|
+
|
99
|
+
def check
|
100
|
+
Checker.new(@fname, @options).check
|
101
|
+
end
|
102
|
+
|
103
|
+
def extract name
|
104
|
+
if ['extradata', 'data after IEND'].include?(name)
|
105
|
+
img = ZPNG::Image.load(@fname)
|
106
|
+
print img.extradata
|
107
|
+
return
|
108
|
+
end
|
109
|
+
|
110
|
+
h = {}
|
111
|
+
name.split(',').each do |x|
|
112
|
+
case x
|
113
|
+
when 'lsb'
|
114
|
+
h[:bit_order] = :lsb
|
115
|
+
when 'msb'
|
116
|
+
h[:bit_order] = :msb
|
117
|
+
when /(\d)b/
|
118
|
+
h[:bits] = $1.to_i
|
119
|
+
when /\A[rgba]+\Z/
|
120
|
+
h[:channels] = x.split('')
|
121
|
+
when /\Axy|yx\Z/i
|
122
|
+
h[:order] = x
|
123
|
+
else
|
124
|
+
raise "uknown param #{x.inspect}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
h[:limit] = @options[:limit] if @options[:limit] != DEFAULT_LIMIT
|
128
|
+
print Extractor.new(@fname, @options).extract(h)
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ZSteg
|
2
|
+
class Extractor
|
3
|
+
|
4
|
+
include ByteExtractor
|
5
|
+
include ColorExtractor
|
6
|
+
|
7
|
+
# image can be either filename or ZPNG::Image
|
8
|
+
def initialize image, params = {}
|
9
|
+
@image = image.is_a?(ZPNG::Image) ? image : ZPNG::Image.load(image)
|
10
|
+
@verbose = params[:verbose]
|
11
|
+
end
|
12
|
+
|
13
|
+
def extract params = {}
|
14
|
+
if params[:order] =~ /b/i
|
15
|
+
byte_extract params
|
16
|
+
else
|
17
|
+
color_extract params
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module ZSteg
|
2
|
+
class Extractor
|
3
|
+
# ByteExtractor extracts bits from each scanline bytes
|
4
|
+
# actual for BMP+wbStego combination
|
5
|
+
module ByteExtractor
|
6
|
+
|
7
|
+
def byte_extract params = {}
|
8
|
+
limit = params[:limit].to_i
|
9
|
+
limit = 2**32 if limit <= 0
|
10
|
+
|
11
|
+
bits = params[:bits]
|
12
|
+
raise "invalid bits value #{bits.inspect}" unless (1..8).include?(bits)
|
13
|
+
mask = 2**bits - 1
|
14
|
+
|
15
|
+
|
16
|
+
data = ''.force_encoding('binary')
|
17
|
+
a = []
|
18
|
+
byte_iterator(params[:order]) do |x,y|
|
19
|
+
sl = @image.scanlines[y]
|
20
|
+
|
21
|
+
value = sl.decoded_bytes[x].ord
|
22
|
+
bits.times do |bidx|
|
23
|
+
a << ((value & (1<<(bits-bidx-1))) == 0 ? 0 : 1)
|
24
|
+
end
|
25
|
+
|
26
|
+
if a.size >= 8
|
27
|
+
byte = 0
|
28
|
+
if params[:bit_order] == :msb
|
29
|
+
8.times{ |i| byte |= (a.shift<<i)}
|
30
|
+
else
|
31
|
+
8.times{ |i| byte |= (a.shift<<(7-i))}
|
32
|
+
end
|
33
|
+
#printf "[d] %02x %08b\n", byte, byte
|
34
|
+
data << byte.chr
|
35
|
+
if data.size >= limit
|
36
|
+
print "[limit #{params[:limit]}]".gray if @verbose > 1
|
37
|
+
break
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
if params[:strip_tail_zeroes] != false && data[-1,1] == "\x00"
|
42
|
+
oldsz = data.size
|
43
|
+
data.sub!(/\x00+\Z/,'')
|
44
|
+
print "[zerotail #{oldsz-data.size}]".gray if @verbose > 1
|
45
|
+
end
|
46
|
+
data
|
47
|
+
end
|
48
|
+
|
49
|
+
# 'xy': b=0,y=0; b=1,y=0; b=2,y=0; ...
|
50
|
+
# 'yx': b=0,y=0; b=0,y=1; b=0,y=2; ...
|
51
|
+
# ...
|
52
|
+
# 'xY': b=0, y=MAX; b=1, y=MAX; b=2, y=MAX; ...
|
53
|
+
# 'XY': b=MAX,y=MAX; b=MAX-1,y=MAX; b=MAX-2,y=MAX; ...
|
54
|
+
def byte_iterator type = nil
|
55
|
+
if type.nil? || type == 'auto'
|
56
|
+
type = @image.format == :bmp ? 'bY' : 'by'
|
57
|
+
end
|
58
|
+
raise "invalid iterator type #{type}" unless type =~ /\A(by|yb)\Z/i
|
59
|
+
|
60
|
+
sl0 = @image.scanlines.first
|
61
|
+
|
62
|
+
x0,x1,xstep =
|
63
|
+
if type.index('b')
|
64
|
+
[0, sl0.decoded_bytes.size-1, 1]
|
65
|
+
else
|
66
|
+
[sl0.decoded_bytes.size-1, 0, -1]
|
67
|
+
end
|
68
|
+
|
69
|
+
y0,y1,ystep =
|
70
|
+
if type.index('y')
|
71
|
+
[0, @image.height-1, 1]
|
72
|
+
else
|
73
|
+
[@image.height-1, 0, -1]
|
74
|
+
end
|
75
|
+
|
76
|
+
if type[0,1].downcase == 'b'
|
77
|
+
# ROW iterator
|
78
|
+
y0.step(y1,ystep) do |y|
|
79
|
+
x0.step(x1,xstep) do |x|
|
80
|
+
yield x,y
|
81
|
+
end
|
82
|
+
end
|
83
|
+
else
|
84
|
+
# COLUMN iterator
|
85
|
+
x0.step(x1,xstep) do |x|
|
86
|
+
y0.step(y1,ystep) do |y|
|
87
|
+
yield x,y
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module ZSteg
|
2
|
+
class Extractor
|
3
|
+
# ColorExtractor extracts bits from each pixel's color
|
4
|
+
module ColorExtractor
|
5
|
+
|
6
|
+
def color_extract params = {}
|
7
|
+
channels = (Array(params[:channels]) + Array(params[:channel])).compact
|
8
|
+
|
9
|
+
limit = params[:limit].to_i
|
10
|
+
limit = 2**32 if limit <= 0
|
11
|
+
|
12
|
+
bits = params[:bits]
|
13
|
+
raise "invalid bits value #{bits.inspect}" unless (1..8).include?(bits)
|
14
|
+
mask = 2**bits - 1
|
15
|
+
|
16
|
+
|
17
|
+
data = ''.force_encoding('binary')
|
18
|
+
a = []
|
19
|
+
coord_iterator(params[:order]) do |x,y|
|
20
|
+
color = @image[x,y]
|
21
|
+
|
22
|
+
channels.each do |c|
|
23
|
+
value = color.send(c)
|
24
|
+
bits.times do |bidx|
|
25
|
+
a << ((value & (1<<(bits-bidx-1))) == 0 ? 0 : 1)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
if a.size >= 8
|
30
|
+
byte = 0
|
31
|
+
if params[:bit_order] == :msb
|
32
|
+
8.times{ |i| byte |= (a.shift<<i)}
|
33
|
+
else
|
34
|
+
8.times{ |i| byte |= (a.shift<<(7-i))}
|
35
|
+
end
|
36
|
+
#printf "[d] %02x %08b\n", byte, byte
|
37
|
+
data << byte.chr
|
38
|
+
if data.size >= limit
|
39
|
+
print "[limit #{params[:limit]}]".gray if @verbose > 1
|
40
|
+
break
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
if params[:strip_tail_zeroes] != false && data[-1,1] == "\x00"
|
45
|
+
oldsz = data.size
|
46
|
+
data.sub!(/\x00+\Z/,'')
|
47
|
+
print "[zerotail #{oldsz-data.size}]".gray if @verbose > 1
|
48
|
+
end
|
49
|
+
data
|
50
|
+
end
|
51
|
+
|
52
|
+
# 'xy': x=0,y=0; x=1,y=0; x=2,y=0; ...
|
53
|
+
# 'yx': x=0,y=0; x=0,y=1; x=0,y=2; ...
|
54
|
+
# ...
|
55
|
+
# 'xY': x=0, y=MAX; x=1, y=MAX; x=2, y=MAX; ...
|
56
|
+
# 'XY': x=MAX,y=MAX; x=MAX-1,y=MAX; x=MAX-2,y=MAX; ...
|
57
|
+
def coord_iterator type = nil
|
58
|
+
if type.nil? || type == 'auto'
|
59
|
+
type = @image.format == :bmp ? 'xY' : 'xy'
|
60
|
+
end
|
61
|
+
raise "invalid iterator type #{type}" unless type =~ /\A(xy|yx)\Z/i
|
62
|
+
|
63
|
+
x0,x1,xstep =
|
64
|
+
if type.index('x')
|
65
|
+
[0, @image.width-1, 1]
|
66
|
+
else
|
67
|
+
[@image.width-1, 0, -1]
|
68
|
+
end
|
69
|
+
|
70
|
+
y0,y1,ystep =
|
71
|
+
if type.index('y')
|
72
|
+
[0, @image.height-1, 1]
|
73
|
+
else
|
74
|
+
[@image.height-1, 0, -1]
|
75
|
+
end
|
76
|
+
|
77
|
+
if type[0,1].downcase == 'x'
|
78
|
+
# ROW iterator
|
79
|
+
y0.step(y1,ystep) do |y|
|
80
|
+
x0.step(x1,xstep) do |x|
|
81
|
+
yield x,y
|
82
|
+
end
|
83
|
+
end
|
84
|
+
else
|
85
|
+
# COLUMN iterator
|
86
|
+
x0.step(x1,xstep) do |x|
|
87
|
+
y0.step(y1,ystep) do |y|
|
88
|
+
yield x,y
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
module ZSteg
|
5
|
+
class FileCmd
|
6
|
+
|
7
|
+
IGNORES = [
|
8
|
+
'data',
|
9
|
+
'empty',
|
10
|
+
'Sendmail frozen configuration',
|
11
|
+
'DOS executable',
|
12
|
+
'Dyalog APL',
|
13
|
+
'8086 relocatable',
|
14
|
+
'SysEx File',
|
15
|
+
'COM executable',
|
16
|
+
'Non-ISO extended-ASCII text',
|
17
|
+
'ISO-8859 text',
|
18
|
+
'very short file',
|
19
|
+
'International EBCDIC text',
|
20
|
+
'lif file',
|
21
|
+
'AmigaOS bitmap font'
|
22
|
+
]
|
23
|
+
|
24
|
+
def start!
|
25
|
+
@stdin, @stdout, @stderr, @wait_thr = Open3.popen3("file -n -b -f -")
|
26
|
+
end
|
27
|
+
|
28
|
+
def check_file fname
|
29
|
+
@stdin.puts fname
|
30
|
+
r = @stdout.gets.force_encoding('binary').strip
|
31
|
+
IGNORES.any?{ |x| r.index(x) == 0 } ? nil : r
|
32
|
+
end
|
33
|
+
|
34
|
+
def check_data data
|
35
|
+
@tempfile ||= Tempfile.new('zsteg', :encoding => 'binary')
|
36
|
+
@tempfile.rewind
|
37
|
+
@tempfile.write data
|
38
|
+
@tempfile.flush
|
39
|
+
check_file @tempfile.path
|
40
|
+
end
|
41
|
+
|
42
|
+
def stop!
|
43
|
+
@stdin.close
|
44
|
+
@stdout.close
|
45
|
+
@stderr.close
|
46
|
+
ensure
|
47
|
+
if @tempfile
|
48
|
+
@tempfile.close
|
49
|
+
@tempfile.unlink
|
50
|
+
@tempfile = nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
if __FILE__ == $0
|
57
|
+
filecmd = ZSteg::FileCmd.new
|
58
|
+
ARGV.each do |fname|
|
59
|
+
p filecmd.check_file fname
|
60
|
+
p filecmd.check_data File.binread(fname)
|
61
|
+
end
|
62
|
+
filecmd.stop!
|
63
|
+
end
|