zsteg 0.0.0 → 0.0.1
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 +2 -7
- data/Gemfile.lock +2 -4
- data/README.md +72 -1
- data/README.md.tpl +23 -0
- data/Rakefile +5 -3
- data/TODO +5 -2
- data/VERSION +1 -1
- data/bin/zsteg-mask +7 -0
- data/lib/zsteg/checker/wbstego.rb +69 -14
- data/lib/zsteg/checker.rb +137 -34
- data/lib/zsteg/cli.rb +92 -35
- data/lib/zsteg/extractor/byte_extractor.rb +36 -21
- data/lib/zsteg/extractor/color_extractor.rb +68 -34
- data/lib/zsteg/extractor.rb +36 -1
- data/lib/zsteg/file_cmd.rb +64 -1
- data/lib/zsteg/mask_cli.rb +268 -0
- data/lib/zsteg/masker.rb +52 -0
- data/lib/zsteg/result.rb +27 -32
- data/lib/zsteg.rb +2 -0
- data/samples/hackquest/crypt.bmp +0 -0
- data/samples/hackquest/square.bmp +0 -0
- data/samples/wbstego/wbsteg_blowfish_pass_1.bmp +0 -0
- data/samples/wbstego/wbsteg_cast128_pass_1.bmp +0 -0
- data/samples/wbstego/wbsteg_enc_pass_pass.bmp +0 -0
- data/samples/wbstego/wbsteg_enc_pass_pass_even.bmp +0 -0
- data/samples/wbstego/wbsteg_mix_pass_1.bmp +0 -0
- data/samples/wbstego/wbsteg_mix_pass_1_even.bmp +0 -0
- data/samples/wbstego/wbsteg_mix_pass_foobar.bmp +0 -0
- data/samples/wbstego/wbsteg_mix_pass_pass.bmp +0 -0
- data/samples/wbstego/wbsteg_mixenc_pass_pass_even.bmp +0 -0
- data/samples/{wbsteg_noenc.bmp → wbstego/wbsteg_noenc.bmp} +0 -0
- data/samples/wbstego/wbsteg_noenc.png +0 -0
- data/samples/wbstego/wbsteg_noenc_.bmp +0 -0
- data/samples/{wbsteg_noenc_17.bmp → wbstego/wbsteg_noenc_17.bmp} +0 -0
- data/samples/wbstego/wbsteg_noenc__.bmp +0 -0
- data/samples/{wbsteg_noenc_even.bmp → wbstego/wbsteg_noenc_even.bmp} +0 -0
- data/samples/wbstego/wbsteg_noenc_even2.bmp +0 -0
- data/samples/{wbsteg_noenc_even_17.bmp → wbstego/wbsteg_noenc_even_17.bmp} +0 -0
- data/samples/wbstego/wbsteg_noenc_even_17_.bmp +0 -0
- data/samples/wbstego/wbsteg_noenc_ext_ABC.bmp +0 -0
- data/samples/wbstego/wbsteg_rijndael_pass_1.bmp +0 -0
- data/samples/wbstego/wbsteg_rijndael_pass_pass.bmp +0 -0
- data/samples/wbstego/wbsteg_rijndael_pass_pass_even.bmp +0 -0
- data/samples/wbstego/wbsteg_twofish_pass_1.bmp +0 -0
- data/samples/wechall/5ZMGcCLxpcpsru03.g00000010.png +0 -0
- data/samples/wechall/5ZMGcCLxpcpsru03.png +0 -0
- data/samples/wechall/stegano1.bmp +0 -0
- data/spec/checker_spec.rb +47 -0
- data/spec/easybmp_spec.rb +9 -0
- data/spec/hackquest_spec.rb +18 -0
- data/spec/mask_spec.rb +14 -0
- data/spec/polictf2012_spec.rb +48 -0
- data/spec/prime_spec.rb +9 -0
- data/spec/r3g2b3_spec.rb +9 -0
- data/spec/spec_helper.rb +21 -4
- data/spec/wbstego_spec.rb +21 -3
- data/spec/wechall_spec.rb +26 -0
- data/tmp/.keep +0 -0
- data/zsteg.gemspec +121 -0
- metadata +47 -43
- data/samples/06_enc.png +0 -0
- data/samples/Code.png +0 -0
- data/samples/README +0 -4
- 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/lib/zsteg/file_cmd.rb
CHANGED
@@ -8,6 +8,7 @@ module ZSteg
|
|
8
8
|
'data',
|
9
9
|
'empty',
|
10
10
|
'Sendmail frozen configuration',
|
11
|
+
'DBase 3 data file',
|
11
12
|
'DOS executable',
|
12
13
|
'Dyalog APL',
|
13
14
|
'8086 relocatable',
|
@@ -18,9 +19,50 @@ module ZSteg
|
|
18
19
|
'very short file',
|
19
20
|
'International EBCDIC text',
|
20
21
|
'lif file',
|
21
|
-
'AmigaOS bitmap font'
|
22
|
+
'AmigaOS bitmap font',
|
23
|
+
'a python script text executable' # common false positive
|
22
24
|
]
|
23
25
|
|
26
|
+
MIN_DATA_SIZE = 5
|
27
|
+
|
28
|
+
class Result < Struct.new(:title, :data)
|
29
|
+
COLORMAP_TEXT = {
|
30
|
+
/DBase 3 data/i => :gray
|
31
|
+
}
|
32
|
+
COLORMAP_WORD = {
|
33
|
+
/bitmap|jpeg|pdf|zip|rar|7-?z/i => :bright_red,
|
34
|
+
}
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
if title[/UTF-8 Unicode text/i]
|
38
|
+
begin
|
39
|
+
t = data.force_encoding("UTF-8").encode("UTF-32LE").encode("UTF-8")
|
40
|
+
rescue
|
41
|
+
t = data.force_encoding('binary')
|
42
|
+
end
|
43
|
+
return "utf8: " + t
|
44
|
+
end
|
45
|
+
COLORMAP_TEXT.each do |re,color|
|
46
|
+
return colorize(color) if title[re]
|
47
|
+
end
|
48
|
+
title.downcase.split.each do |word|
|
49
|
+
COLORMAP_WORD.each do |re,color|
|
50
|
+
return colorize(color) if title.index(re) == 0
|
51
|
+
end
|
52
|
+
end
|
53
|
+
colorize(:yellow)
|
54
|
+
end
|
55
|
+
|
56
|
+
def colorize color
|
57
|
+
if color == :gray
|
58
|
+
# gray whole string
|
59
|
+
"file: #{title}".send(color)
|
60
|
+
else
|
61
|
+
"file: " + title.send(color)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
24
66
|
def start!
|
25
67
|
@stdin, @stdout, @stderr, @wait_thr = Open3.popen3("file -n -b -f -")
|
26
68
|
end
|
@@ -39,6 +81,27 @@ module ZSteg
|
|
39
81
|
check_file @tempfile.path
|
40
82
|
end
|
41
83
|
|
84
|
+
# checks data and resurns Result, if any
|
85
|
+
def data2result data
|
86
|
+
return if data.size < MIN_DATA_SIZE
|
87
|
+
|
88
|
+
title = check_data data
|
89
|
+
return unless title
|
90
|
+
|
91
|
+
if title[/UTF-8 Unicode text/i]
|
92
|
+
begin
|
93
|
+
t = data.force_encoding("UTF-8")
|
94
|
+
rescue
|
95
|
+
t = data.force_encoding('binary')
|
96
|
+
end
|
97
|
+
if t.size >= Checker::MIN_TEXT_LENGTH
|
98
|
+
ZSteg::Result::UnicodeText.new(t,0)
|
99
|
+
end
|
100
|
+
else
|
101
|
+
Result.new(title,data)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
42
105
|
def stop!
|
43
106
|
@stdin.close
|
44
107
|
@stdout.close
|
@@ -0,0 +1,268 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'set'
|
3
|
+
require 'digest/md5'
|
4
|
+
|
5
|
+
module ZSteg
|
6
|
+
class MaskCLI
|
7
|
+
DEFAULT_ACTIONS = %w'mask'
|
8
|
+
|
9
|
+
COMMON_MASKS = [
|
10
|
+
0b0000_0001, 0b0000_0011, 0b0000_0111, 0b0000_1111,
|
11
|
+
0b0000_0010, 0b0000_0100, 0b0000_1000,
|
12
|
+
0b0001_0000, 0b0010_0000, 0b0100_0000, 0b1000_0000,
|
13
|
+
]
|
14
|
+
|
15
|
+
CHANNELS = [:r, :g, :b, :a]
|
16
|
+
|
17
|
+
def initialize argv = ARGV
|
18
|
+
@argv = argv
|
19
|
+
@wasfiles = Set.new
|
20
|
+
@cache = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def run
|
24
|
+
@actions = []
|
25
|
+
@options = {
|
26
|
+
:verbose => 0,
|
27
|
+
:masks => Hash.new{|k,v| k[v] = [] },
|
28
|
+
:normalize => true
|
29
|
+
}
|
30
|
+
optparser = OptionParser.new do |opts|
|
31
|
+
opts.banner = "Usage: zsteg-mask [options] filename.png [param_string]"
|
32
|
+
opts.separator ""
|
33
|
+
|
34
|
+
opts.on("-m", "--mask M", "apply mask to all channels",
|
35
|
+
"mask: 0-255 OR 0x00-0xff OR 00000000-11111111",
|
36
|
+
"OR 'all' for all common masks"
|
37
|
+
){ |x| @options[:masks][:all] << parse_mask(x) }
|
38
|
+
|
39
|
+
opts.on("-R", "--red M", "red channel mask"){ |x|
|
40
|
+
@options[:masks][:r] << parse_mask(x) }
|
41
|
+
|
42
|
+
opts.on("-G", "--green M", "green channel mask"){ |x|
|
43
|
+
@options[:masks][:g] << parse_mask(x) }
|
44
|
+
|
45
|
+
opts.on("-B", "--blue M", "blue channel mask"){ |x|
|
46
|
+
@options[:masks][:b] << parse_mask(x) }
|
47
|
+
|
48
|
+
opts.on("-A", "--alpha M", "alpha channel mask"){ |x|
|
49
|
+
@options[:masks][:a] << parse_mask(x) }
|
50
|
+
|
51
|
+
opts.separator ""
|
52
|
+
|
53
|
+
opts.on "-a", "--all", "try all common masks (default)" do
|
54
|
+
@options[:try_all] = true
|
55
|
+
end
|
56
|
+
|
57
|
+
opts.separator ""
|
58
|
+
|
59
|
+
opts.on "-N", "--[no-]normalize", "normalize color value after applying mask",
|
60
|
+
"(default: normalize)" do |x|
|
61
|
+
@options[:normalize] = x
|
62
|
+
end
|
63
|
+
|
64
|
+
opts.on "-O", "--outfile FILENAME", "output single result to specified file" do |x|
|
65
|
+
@options[:outfile] = x
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.on "-D", "--dir DIRNAME", "output multiple results to specified dir" do |x|
|
69
|
+
@options[:dir] = x
|
70
|
+
end
|
71
|
+
|
72
|
+
opts.separator ""
|
73
|
+
opts.on "-v", "--verbose", "Run verbosely (can be used multiple times)" do |v|
|
74
|
+
@options[:verbose] += 1
|
75
|
+
end
|
76
|
+
opts.on "-q", "--quiet", "Silent any warnings (can be used multiple times)" do |v|
|
77
|
+
@options[:verbose] -= 1
|
78
|
+
end
|
79
|
+
opts.on "-C", "--[no-]color", "Force (or disable) color output (default: auto)" do |x|
|
80
|
+
Sickill::Rainbow.enabled = x
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
if (argv = optparser.parse(@argv)).empty?
|
85
|
+
puts optparser.help
|
86
|
+
return
|
87
|
+
end
|
88
|
+
|
89
|
+
# default :all mask if none specified
|
90
|
+
if @options[:masks].empty?
|
91
|
+
@options[:try_all] = true
|
92
|
+
end
|
93
|
+
|
94
|
+
@actions = DEFAULT_ACTIONS if @actions.empty?
|
95
|
+
|
96
|
+
argv.each do |arg|
|
97
|
+
if arg[','] && !File.exist?(arg)
|
98
|
+
@options.merge!(decode_param_string(arg))
|
99
|
+
argv.delete arg
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
argv.each_with_index do |fname,idx|
|
104
|
+
if argv.size > 1 && @options[:verbose] >= 0
|
105
|
+
puts if idx > 0
|
106
|
+
puts "[.] #{fname}".green
|
107
|
+
end
|
108
|
+
next unless @image=load_image(@fname=fname)
|
109
|
+
|
110
|
+
@actions.each do |action|
|
111
|
+
if action.is_a?(Array)
|
112
|
+
self.send(*action) if self.respond_to?(action.first)
|
113
|
+
else
|
114
|
+
self.send(action) if self.respond_to?(action)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
rescue Errno::EPIPE
|
119
|
+
# output interrupt, f.ex. when piping output to a 'head' command
|
120
|
+
# prevents a 'Broken pipe - <STDOUT> (Errno::EPIPE)' message
|
121
|
+
end
|
122
|
+
|
123
|
+
def parse_mask x
|
124
|
+
case x
|
125
|
+
when /0x/i
|
126
|
+
x.to_i(16)
|
127
|
+
when /^[01]{8}$/
|
128
|
+
x.to_i(2)
|
129
|
+
when /^\d{1,3}$/
|
130
|
+
x.to_i
|
131
|
+
when /^all$/
|
132
|
+
COMMON_MASKS
|
133
|
+
else raise "invalid mask #{x.inspect}"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def load_image fname
|
138
|
+
if File.directory?(fname)
|
139
|
+
puts "[?] #{fname} is a directory".yellow
|
140
|
+
else
|
141
|
+
ZPNG::Image.load(fname)
|
142
|
+
end
|
143
|
+
rescue ZPNG::Exception, Errno::ENOENT
|
144
|
+
puts "[!] #{$!.inspect}".red
|
145
|
+
end
|
146
|
+
|
147
|
+
###########################################################################
|
148
|
+
# actions
|
149
|
+
|
150
|
+
def mask
|
151
|
+
masks = @options[:masks]
|
152
|
+
masks.each{ |k,v| v.flatten!; v.uniq! }
|
153
|
+
|
154
|
+
if @options[:try_all]
|
155
|
+
# try all common masks
|
156
|
+
masks = masks[:all] || []
|
157
|
+
masks = COMMON_MASKS if masks.empty?
|
158
|
+
masks.each{ |x| run_masker x,x,x,x }
|
159
|
+
masks.each{ |x| run_masker x,0,0,0xff }
|
160
|
+
masks.each{ |x| run_masker 0,x,0,0xff }
|
161
|
+
masks.each{ |x| run_masker 0,0,x,0xff }
|
162
|
+
if @image.alpha_used?
|
163
|
+
masks.each{ |x| run_masker 0,0,0,x }
|
164
|
+
end
|
165
|
+
|
166
|
+
elsif CHANNELS.all?{ |c| !masks[c] || masks[c].empty? }
|
167
|
+
# no specific channels
|
168
|
+
masks[:all].each do |x|
|
169
|
+
run_masker x,x,x,x
|
170
|
+
end
|
171
|
+
|
172
|
+
else
|
173
|
+
# specific channels
|
174
|
+
CHANNELS.each{ |x| masks[x] = [x==:a ? 0xff : 0] if !masks[x] || masks[x].empty? }
|
175
|
+
masks[:r].each do |r|
|
176
|
+
masks[:g].each do |g|
|
177
|
+
masks[:b].each do |b|
|
178
|
+
if @image.alpha_used?
|
179
|
+
masks[:a].each do |a|
|
180
|
+
run_masker r,g,b,a
|
181
|
+
end
|
182
|
+
else
|
183
|
+
run_masker r,g,b,0xff
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
def _all_pixels_same img
|
194
|
+
sl0 = img.scanlines.first
|
195
|
+
return false if sl0.pixels.to_a.uniq.size != 1
|
196
|
+
|
197
|
+
db0 = sl0.decoded_bytes
|
198
|
+
img.scanlines[1..-1].each do |sl|
|
199
|
+
return false if sl.decoded_bytes != db0
|
200
|
+
end
|
201
|
+
true
|
202
|
+
end
|
203
|
+
|
204
|
+
def run_masker r,g,b,a
|
205
|
+
params = @options.dup
|
206
|
+
params[:masks] = params[:masks].merge( :r => r, :g => g, :b => b, :a => a)
|
207
|
+
fname,color = @options[:outfile],nil
|
208
|
+
fname,color = masks2fname(params[:masks]) unless fname
|
209
|
+
|
210
|
+
print "[.] #{fname.send(color||:to_s)} .. "
|
211
|
+
|
212
|
+
raise "already written to #{fname}" if @wasfiles.include?(fname)
|
213
|
+
@wasfiles << fname
|
214
|
+
|
215
|
+
dst = Masker.new(@image, params).mask
|
216
|
+
|
217
|
+
if _all_pixels_same(dst)
|
218
|
+
puts "all pixels = #{dst[0,0].inspect}".gray
|
219
|
+
return
|
220
|
+
end
|
221
|
+
|
222
|
+
data = dst.export
|
223
|
+
|
224
|
+
md5 = Digest::MD5.hexdigest(data)
|
225
|
+
if @cache[md5]
|
226
|
+
puts "same as #{File.basename(@cache[md5])}".gray
|
227
|
+
return
|
228
|
+
end
|
229
|
+
@cache[md5] = fname
|
230
|
+
|
231
|
+
File.open(fname, "wb"){ |f| f<<data }
|
232
|
+
printf "%6d bytes\n".green, File.size(fname)
|
233
|
+
end
|
234
|
+
|
235
|
+
def masks2fname masks
|
236
|
+
masks = masks.dup.delete_if{ |k,v| !CHANNELS.include?(k) }
|
237
|
+
bname = @fname.sub(/#{Regexp.escape(File.extname(@fname))}$/,'')
|
238
|
+
color = nil
|
239
|
+
raise "TODO" if masks.values.all?(&:nil?)
|
240
|
+
if masks.values.uniq.size == 1
|
241
|
+
tail = "%08b" % masks.values.first
|
242
|
+
else
|
243
|
+
a = []
|
244
|
+
masks.each do |c,mask|
|
245
|
+
a << "%s%08b" % [c,mask] if mask && mask != 0
|
246
|
+
end
|
247
|
+
raise "TODO" if a.empty?
|
248
|
+
a -= ['a11111111'] if a.size > 1 # fully opaque alpha is OK
|
249
|
+
if a.size == 1
|
250
|
+
color =
|
251
|
+
case a[0][0,1]
|
252
|
+
when 'r'; :red
|
253
|
+
when 'g'; :green
|
254
|
+
when 'b'; :blue
|
255
|
+
when 'a'; :gray
|
256
|
+
else nil
|
257
|
+
end
|
258
|
+
end
|
259
|
+
tail = a.join(".")
|
260
|
+
end
|
261
|
+
|
262
|
+
fname = [bname, tail, "png"].join('.')
|
263
|
+
fname = File.join(@options[:dir], File.basename(fname)) if @options[:dir]
|
264
|
+
[fname, color]
|
265
|
+
end
|
266
|
+
|
267
|
+
end
|
268
|
+
end
|
data/lib/zsteg/masker.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module ZSteg
|
4
|
+
class Masker
|
5
|
+
def initialize image, params = {}
|
6
|
+
@src = image.is_a?(ZPNG::Image) ? image : ZPNG::Image.load(image)
|
7
|
+
@masks = params[:masks] || {}
|
8
|
+
@mask = params[:mask] || @masks[:all]
|
9
|
+
@normalize = params[:normalize]
|
10
|
+
[:r, :g, :b, :a].each{ |x| @masks[x] ||= @mask }
|
11
|
+
end
|
12
|
+
|
13
|
+
def mask params = {}
|
14
|
+
dst = ZPNG::Image.new :width => @src.width, :height => @src.height
|
15
|
+
rm, gm, bm, am = @masks[:r], @masks[:g], @masks[:b], @masks[:a]
|
16
|
+
rd, gd, bd, ad = rm==0?1:rm, gm==0?1:gm, bm==0?1:bm, am==0?1:am
|
17
|
+
# duplicate loops for performance reason
|
18
|
+
if @normalize
|
19
|
+
if rm == 0 && gm == 0 && bm == 0 && am != 0
|
20
|
+
# alpha2grayscale
|
21
|
+
@src.each_pixel do |c,x,y|
|
22
|
+
c.r = c.g = c.b = (c.a & am) * 255 / ad
|
23
|
+
c.a = 0xff
|
24
|
+
dst[x,y] = c
|
25
|
+
end
|
26
|
+
else
|
27
|
+
# normal operation
|
28
|
+
@src.each_pixel do |c,x,y|
|
29
|
+
#TODO: c.to_depth(8)
|
30
|
+
# further possible optimizations:
|
31
|
+
# a) precalculate (255 / Xm)
|
32
|
+
c.r = (c.r & rm) * 255 / rd
|
33
|
+
c.g = (c.g & gm) * 255 / gd
|
34
|
+
c.b = (c.b & bm) * 255 / bd
|
35
|
+
c.a = (c.a & am) * 255 / ad
|
36
|
+
dst[x,y] = c
|
37
|
+
end
|
38
|
+
end
|
39
|
+
else
|
40
|
+
@src.each_pixel do |c,x,y|
|
41
|
+
#TODO: c.to_depth(8)
|
42
|
+
c.r &= rm
|
43
|
+
c.g &= gm
|
44
|
+
c.b &= bm
|
45
|
+
c.a &= am
|
46
|
+
dst[x,y] = c
|
47
|
+
end
|
48
|
+
end
|
49
|
+
dst
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/zsteg/result.rb
CHANGED
@@ -17,13 +17,30 @@ module ZSteg
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def to_s
|
20
|
-
super.sub(/^<Result::/,'').sub(/>$/,'').
|
20
|
+
super.sub(/^<Result::/,'').sub(/>$/,'').bright_red
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
24
|
class Text < Struct.new(:text, :offset)
|
25
|
+
def one_char?
|
26
|
+
(text =~ /\A(.)\1+\Z/m) == 0
|
27
|
+
rescue # invalid byte sequence in UTF-8
|
28
|
+
text.chars.to_a.uniq.size == 1 # ~10x slower than regexp
|
29
|
+
end
|
30
|
+
|
25
31
|
def to_s
|
26
|
-
"text: ".gray +
|
32
|
+
"text: ".gray +
|
33
|
+
if one_char?
|
34
|
+
"[#{text[0].inspect} repeated #{text.size} times]".gray
|
35
|
+
elsif offset == 0
|
36
|
+
# first byte of data is also first char of text
|
37
|
+
text.inspect.bright_red
|
38
|
+
elsif text.size > 10 && text[' '] && text =~ /\A[a-z0-9 .,:!_-]+\Z/i
|
39
|
+
# text is ASCII with spaces
|
40
|
+
text.inspect.bright_red
|
41
|
+
else
|
42
|
+
text.inspect
|
43
|
+
end
|
27
44
|
end
|
28
45
|
end
|
29
46
|
|
@@ -33,9 +50,15 @@ module ZSteg
|
|
33
50
|
# part of data is text
|
34
51
|
class PartialText < Text; end
|
35
52
|
|
53
|
+
# unicode text
|
54
|
+
class UnicodeText < Text; end
|
55
|
+
|
36
56
|
class Zlib < Struct.new(:data, :offset)
|
57
|
+
MAX_SHOW_SIZE = 100
|
37
58
|
def to_s
|
38
|
-
|
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}"
|
39
62
|
end
|
40
63
|
end
|
41
64
|
|
@@ -45,34 +68,6 @@ module ZSteg
|
|
45
68
|
end
|
46
69
|
end
|
47
70
|
|
48
|
-
class FileCmd < Struct.new(:title, :data)
|
49
|
-
COLORMAP = {
|
50
|
-
/bitmap|jpeg|pdf|zip|rar|7z/i => :red,
|
51
|
-
/DBase 3 data/i => :gray
|
52
|
-
}
|
53
|
-
|
54
|
-
def to_s
|
55
|
-
if title[/UTF-8 Unicode text/i]
|
56
|
-
begin
|
57
|
-
t = data.force_encoding("UTF-8").encode("UTF-32LE").encode("UTF-8")
|
58
|
-
rescue
|
59
|
-
t = data.force_encoding('binary')
|
60
|
-
end
|
61
|
-
return "utf8: " + t
|
62
|
-
end
|
63
|
-
COLORMAP.each do |re,color|
|
64
|
-
if title[re]
|
65
|
-
if color == :gray
|
66
|
-
return "file: #{title}".send(color)
|
67
|
-
else
|
68
|
-
return "file: " + title.send(color)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
"file: " + title.yellow
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
71
|
class Camouflage < Struct.new(:hidden_data_len, :host_orig_len)
|
77
72
|
def initialize(data)
|
78
73
|
self.hidden_data_len = (data[0x1a,4] || '').unpack('V').first
|
@@ -83,7 +78,7 @@ module ZSteg
|
|
83
78
|
end
|
84
79
|
|
85
80
|
def to_s
|
86
|
-
super.
|
81
|
+
super.bright_red
|
87
82
|
end
|
88
83
|
end
|
89
84
|
end
|