zsteg 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|