zsteg 0.0.0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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/Gemfile
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
|
-
|
3
|
-
|
4
|
-
# gem "activesupport", ">= 2.3.5"
|
5
|
-
gem 'zpng', ">= 0.2.1"
|
6
|
-
gem "awesome_print"
|
2
|
+
|
3
|
+
gem 'zpng', ">= 0.2.2"
|
7
4
|
gem "iostruct"
|
8
5
|
|
9
|
-
# Add dependencies to develop your gem here.
|
10
|
-
# Include everything needed to run rake, tests, features, etc.
|
11
6
|
group :development do
|
12
7
|
gem "rspec", ">= 2.8.0"
|
13
8
|
gem "bundler", ">= 1.0.0"
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
awesome_print (1.1.0)
|
5
4
|
diff-lcs (1.1.3)
|
6
5
|
git (1.2.5)
|
7
6
|
iostruct (0.0.1)
|
@@ -23,16 +22,15 @@ GEM
|
|
23
22
|
rspec-expectations (2.12.1)
|
24
23
|
diff-lcs (~> 1.1.3)
|
25
24
|
rspec-mocks (2.12.1)
|
26
|
-
zpng (0.2.
|
25
|
+
zpng (0.2.2)
|
27
26
|
rainbow
|
28
27
|
|
29
28
|
PLATFORMS
|
30
29
|
ruby
|
31
30
|
|
32
31
|
DEPENDENCIES
|
33
|
-
awesome_print
|
34
32
|
bundler (>= 1.0.0)
|
35
33
|
iostruct
|
36
34
|
jeweler (~> 1.8.4)
|
37
35
|
rspec (>= 2.8.0)
|
38
|
-
zpng (>= 0.2.
|
36
|
+
zpng (>= 0.2.2)
|
data/README.md
CHANGED
@@ -18,6 +18,7 @@ Detects:
|
|
18
18
|
* zlib-compressed data
|
19
19
|
* [OpenStego](http://openstego.sourceforge.net/)
|
20
20
|
* [Camouflage 1.2.1](http://camouflage.unfiction.com/)
|
21
|
+
* [LSB with The Eratosthenes set](http://wiki.cedricbonhomme.org/security:steganography)
|
21
22
|
|
22
23
|
|
23
24
|
Usage
|
@@ -25,7 +26,7 @@ Usage
|
|
25
26
|
|
26
27
|
# zsteg -h
|
27
28
|
|
28
|
-
Usage: zsteg [options] filename.png
|
29
|
+
Usage: zsteg [options] filename.png [param_string]
|
29
30
|
|
30
31
|
-c, --channels X channels (R/G/B/A) or any combination, comma separated
|
31
32
|
valid values: r,g,b,a,rg,rgb,bgr,rgba,...
|
@@ -33,13 +34,83 @@ Usage
|
|
33
34
|
-b, --bits N number of bits (1..8), single value or '1,3,5' or '1-8'
|
34
35
|
--lsb least significant BIT comes first
|
35
36
|
--msb most significant BIT comes first
|
37
|
+
-P, --prime analyze/extract only prime bytes/pixels
|
38
|
+
-a, --all try all known methods
|
36
39
|
-o, --order X pixel iteration order (default: 'auto')
|
37
40
|
valid values: ALL,xy,yx,XY,YX,xY,Xy,bY,...
|
38
41
|
-E, --extract NAME extract specified payload, NAME is like '1b,rgb,lsb'
|
39
42
|
|
40
43
|
-v, --verbose Run verbosely (can be used multiple times)
|
41
44
|
-q, --quiet Silent any warnings (can be used multiple times)
|
45
|
+
-C, --[no-]color Force (or disable) color output (default: auto)
|
46
|
+
|
47
|
+
PARAMS SHORTCUT
|
48
|
+
zsteg fname.png 2b,b,lsb,xy ==> --bits 2 --channel b --lsb --order xy
|
49
|
+
|
50
|
+
Examples
|
51
|
+
--------
|
52
|
+
|
53
|
+
### Simple LSB
|
54
|
+
|
55
|
+
# zsteg flower_rgb3.png
|
56
|
+
|
57
|
+
3b,rgb,lsb,xy .. text: "SuperSecretMessage"
|
58
|
+
|
59
|
+
### Multi-result file
|
60
|
+
|
61
|
+
# zsteg cats.png
|
62
|
+
|
63
|
+
meta F .. ["Z" repeated 14999985 times]
|
64
|
+
meta C .. text: "Fourth and last cat is Luke"
|
65
|
+
meta A .. [same as "meta F"]
|
66
|
+
meta date:create .. text: "2012-03-15T23:32:46+07:00"
|
67
|
+
meta date:modify .. text: "2012-03-15T23:32:14+07:00"
|
68
|
+
1b,r,lsb,xy .. text: "Second cat is Marussia"
|
69
|
+
1b,g,lsb,xy .. text: "Good, but look a bit deeper..."
|
70
|
+
1b,bgr,lsb,xy .. text: "MF_WIhf>"
|
71
|
+
2b,g,lsb,xy .. text: "VHello, third kitten is Bessy"
|
72
|
+
|
73
|
+
### wbStego even distributed
|
74
|
+
|
75
|
+
# zsteg wbstego/wbsteg_noenc_even.bmp 1b,lsb,bY -v
|
76
|
+
|
77
|
+
1b,lsb,bY .. <wbStego size=22, data="xtSuperSecretMessage\n", even=true, mix=true, controlbyte="t">
|
78
|
+
00000000: 51 00 00 16 00 00 74 0d b5 78 1e a1 39 74 e8 38 |Q.....t..x..9t.8|
|
79
|
+
00000010: 53 c6 56 94 75 d1 a5 70 84 c8 27 65 fe 08 72 35 |S.V.u..p..'e..r5|
|
80
|
+
00000020: 1f 3e 53 5d a7 65 8b 6e 3b 63 6b 1d bf 72 ee 27 |.>S].e.n;ck..r.'|
|
81
|
+
00000030: 65 8d ee 82 74 da 8d 4d b3 8a 06 65 7e f8 73 9c |e...t..M...e~.s.|
|
82
|
+
00000040: 36 0c 73 aa bd 61 67 29 37 67 5f 0b 06 65 1f a4 |6.s..ag)7g_..e..|
|
83
|
+
00000050: 0a a1 f8 35 |...5 |
|
84
|
+
|
85
|
+
### wbStego encrypted
|
86
|
+
|
87
|
+
# zsteg wbstego/wbsteg_blowfish_pass_1.bmp 1b,lsb,bY -v
|
88
|
+
|
89
|
+
1b,lsb,bY .. <wbStego size=26, data="\rC\xF5\xBF#\xFF[6\e\xB3"..., even=false, hdr="\x01", enc="Blowfish">
|
90
|
+
00000000: 1a 00 00 00 ff 01 01 0d 43 f5 bf 23 ff 5b 36 1b |........C..#.[6.|
|
91
|
+
00000010: b3 17 42 4a 3f ba eb c7 ee 9c d7 7a 2b |..BJ?......z+ |
|
92
|
+
|
93
|
+
### zlib
|
94
|
+
|
95
|
+
# zsteg ndh2k12_sp113.bmp -b 1 -o yx -v
|
42
96
|
|
97
|
+
1b,rgb,lsb,yx .. zlib: data="%PDF-1.4\n%\xC3\xA4\xC3\xBC\xC3\xB6\xC3\x9F\n2 0 obj\n<</Length 3 0 R/Filter/FlateDecode>>\nstream\nx\x9C\x8DT\xC9n\xDB@\f\xBD\xCFW\xF0\x1C \x13\x92\xB3\x03\x86\x80\xC8K\xD1\xDE\\\b\xE8\xA1\xE8)K\x8B\xA2n\x91\\\xF2\xFB!5Zl\xD5v\v\x01\xD4\x90C\xBE\xF7\x86\x1A\n-\xC1\x9By\x01\x94'\x94`=d\xCF\xF0\xFA\x04_n\xE0\xF7\x10Gx\xFDn\xDA\xCE\xB0\x8F6\x80s$Y\xDD#\xDC\xED\b\x1CC\xF7\xBCBBF\x87^\xDE\xA1\xE9~\x9Amg\xF6\x8BZ\xCAYj", offset=4
|
98
|
+
00000000: 00 02 eb 9b 78 9c d4 b9 65 54 24 cc 92 36 58 b8 |....x...eT$..6X.|
|
99
|
+
00000010: d3 68 e3 ee ee 4e e3 ee ee 0e 85 bb 3b dd 68 23 |.h...N......;.h#|
|
100
|
+
00000020: 8d bb bb bb 3b 8d bb bb 3b 34 ee 6e 1f ef 7b ef |....;...;4.n..{.|
|
101
|
+
00000030: 9d 3b b3 e7 cc 9e d9 3d df 9e dd cd 8a 1f 99 19 |.;.....=........|
|
102
|
+
00000040: 99 55 11 99 4f 58 25 99 82 88 18 1d 13 3d 2b 2c |.U..OX%......=+,|
|
103
|
+
00000050: 59 6f 7e 6f 7b 6f 63 6f 16 2c 33 21 23 a1 9d 91 |Yo~o{oco.,3!#...|
|
104
|
+
00000060: 25 2c 2f 2f 83 0c d0 d6 cc d9 9c 90 e5 73 46 89 |%,//.........sF.|
|
105
|
+
00000070: 41 cc c2 da 19 e8 c8 20 66 6d e8 0c 14 01 1a db |A...... fm......|
|
106
|
+
00000080: 99 00 f9 f8 60 9d 9c 1d 81 86 36 b0 ee e9 bf 54 |....`.....6....T|
|
107
|
+
00000090: 86 6d 57 05 e0 3b 26 d5 2f 71 09 51 63 eb c0 82 |.mW..;&./q.Qc...|
|
108
|
+
000000a0: bf 0f 49 4f 6f e8 40 ff c9 f9 43 25 1d 9e 6b 1b |..IOo.@...C%..k.|
|
109
|
+
000000b0: a3 73 fd 42 c4 a6 65 3d ef 0a 07 32 17 2d dc f9 |.s.B..e=...2.-..|
|
110
|
+
000000c0: 10 8c 0d 4b d7 9d e6 01 12 4f 11 6f f0 cd 64 f2 |...K.....O.o..d.|
|
111
|
+
000000d0: f2 19 5c df 76 eb 01 49 dc fd cd 76 65 a2 3a 8a |..\.v..I...ve.:.|
|
112
|
+
000000e0: fd bb 13 a9 e6 3a c9 da 19 34 ae f0 43 bb 90 90 |.....:...4..C...|
|
113
|
+
000000f0: 58 88 de 46 ce 91 6f aa 8d d9 7d b8 d6 88 a6 65 |X..F..o...}....e|
|
43
114
|
|
44
115
|
License
|
45
116
|
-------
|
data/README.md.tpl
CHANGED
@@ -18,6 +18,7 @@ Detects:
|
|
18
18
|
* zlib-compressed data
|
19
19
|
* [OpenStego](http://openstego.sourceforge.net/)
|
20
20
|
* [Camouflage 1.2.1](http://camouflage.unfiction.com/)
|
21
|
+
* [LSB with The Eratosthenes set](http://wiki.cedricbonhomme.org/security:steganography)
|
21
22
|
|
22
23
|
|
23
24
|
Usage
|
@@ -25,6 +26,28 @@ Usage
|
|
25
26
|
|
26
27
|
% zsteg -h
|
27
28
|
|
29
|
+
Examples
|
30
|
+
--------
|
31
|
+
|
32
|
+
### Simple LSB
|
33
|
+
|
34
|
+
% zsteg flower_rgb3.png
|
35
|
+
|
36
|
+
### Multi-result file
|
37
|
+
|
38
|
+
% zsteg cats.png
|
39
|
+
|
40
|
+
### wbStego even distributed
|
41
|
+
|
42
|
+
% zsteg wbstego/wbsteg_noenc_even.bmp 1b,lsb,bY -v
|
43
|
+
|
44
|
+
### wbStego encrypted
|
45
|
+
|
46
|
+
% zsteg wbstego/wbsteg_blowfish_pass_1.bmp 1b,lsb,bY -v
|
47
|
+
|
48
|
+
### zlib
|
49
|
+
|
50
|
+
% zsteg ndh2k12_sp113.bmp -b 1 -o yx -v
|
28
51
|
|
29
52
|
License
|
30
53
|
-------
|
data/Rakefile
CHANGED
@@ -23,7 +23,8 @@ Jeweler::Tasks.new do |gem|
|
|
23
23
|
gem.authors = ["Andrey \"Zed\" Zaikin"]
|
24
24
|
#gem.executables = %w'zsteg'
|
25
25
|
gem.files.include "lib/**/*.rb"
|
26
|
-
gem.files.include "bin
|
26
|
+
gem.files.include "bin/*"
|
27
|
+
gem.files.exclude "samples/*"
|
27
28
|
# dependencies defined in Gemfile
|
28
29
|
end
|
29
30
|
Jeweler::RubygemsDotOrgTasks.new
|
@@ -53,9 +54,10 @@ task :readme do
|
|
53
54
|
puts "[.] #{cmd} ..."
|
54
55
|
r = " # #{cmd}\n\n"
|
55
56
|
cmd.sub! /^zsteg/,"../bin/zsteg"
|
56
|
-
lines = `#{cmd}`.sub(/\A\n+/m,'').
|
57
|
+
lines = `#{cmd}`.sub(/\A\n+/m,'').split("\n")
|
58
|
+
lines.map!{ |l| l.split("\r").last } # emulate CR's
|
57
59
|
lines = lines[0,25] + ['...'] if lines.size > 50
|
58
|
-
r << lines.map{|x| " #{x}"}.join("\n")
|
60
|
+
r << lines.map{|x| " #{x}"}.join("\n").rstrip
|
59
61
|
r << "\n"
|
60
62
|
end
|
61
63
|
Dir.chdir 'samples'
|
data/TODO
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
[ ] make 'extract' cmd stream its data directly to stdout
|
2
2
|
[ ] gzip
|
3
|
-
[
|
3
|
+
[ ] wbStego: 4bpp/8bpp BMP
|
4
4
|
[ ] openstego
|
5
5
|
[ ] tmp/steg*/*.bmp
|
6
6
|
[ ] 4bpp/8bpp BMP
|
7
|
+
[ ] http://search.cpan.org/~nwclark/Acme-Steganography-Image-Png-0.06/Png.pm
|
8
|
+
[ ] http://registry.gimp.org/node/25988 - GIMP stego plugin
|
9
|
+
[ ] zsteg-mask: normalize all to white
|
7
10
|
|
8
11
|
[+] auto pixel order for BMP
|
9
12
|
[+] BMP
|
@@ -11,4 +14,4 @@
|
|
11
14
|
|
12
15
|
[ ] detect AES from http://punkroy.drque.net/PNG_Steganography/Steganography5.php
|
13
16
|
[?] http://tobyinkster.co.uk/article/steg-encode/
|
14
|
-
[
|
17
|
+
[+] Sieve of Eratosthenes: http://wiki.cedricbonhomme.org/security:steganography
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.1
|
data/bin/zsteg-mask
ADDED
@@ -2,17 +2,60 @@ module ZSteg
|
|
2
2
|
class Checker
|
3
3
|
module WBStego
|
4
4
|
|
5
|
-
|
5
|
+
ENCRYPTIONS = [
|
6
|
+
nil, # 0
|
7
|
+
"Blowfish", # 1
|
8
|
+
"Twofish", # 2
|
9
|
+
"CAST128", # 3
|
10
|
+
"Rijndael", # 4
|
11
|
+
]
|
12
|
+
|
13
|
+
class Result < IOStruct.new "a3a3a*", :size, :ext, :data, :even, :hdr, :enc, :mix, :controlbyte
|
14
|
+
attr_accessor :color
|
15
|
+
|
6
16
|
def initialize *args
|
7
17
|
super
|
8
18
|
if self.size.is_a?(String)
|
9
19
|
self.size = (self.size[0,3] + "\x00").unpack('V')[0]
|
10
20
|
end
|
11
|
-
self.even
|
21
|
+
self.even ||= false
|
22
|
+
#self.encrypted ||= false
|
23
|
+
if ext[0,2] == "\x00\xff"
|
24
|
+
# wbStego 4.x header
|
25
|
+
self.hdr = data[0,ext[2].ord] # 3rd ext byte is hdr len
|
26
|
+
self.data = data[hdr.size..-1]
|
27
|
+
self.ext = nil # encrypted files have no ext
|
28
|
+
self.enc = ENCRYPTIONS[hdr[0].ord] || "unknown ##{hdr[0].ord}"
|
29
|
+
elsif (cb=ext[0].ord) & 0xc0 != 0
|
30
|
+
# wbStego 2.x/3.x controlbyte
|
31
|
+
self.controlbyte = ext[0]
|
32
|
+
self.data = ext[1..-1] + data
|
33
|
+
self.ext = nil # have ext but its encrypted/mixed with data
|
34
|
+
self.mix = true if cb & 0x40 != 0
|
35
|
+
self.enc = "wbStego 2.x/3.x" if cb & 0x80 != 0
|
36
|
+
end
|
12
37
|
end
|
13
38
|
|
14
39
|
def to_s
|
15
|
-
|
40
|
+
s = inspect.
|
41
|
+
sub("#<struct #{self.class.to_s}", "<wbStego").
|
42
|
+
gsub(/, \w+=nil/,'')
|
43
|
+
|
44
|
+
color = @color
|
45
|
+
|
46
|
+
if ext && !valid_ext?
|
47
|
+
s.sub!(data.inspect, data[0,10].inspect+"...") if data && data.size>13
|
48
|
+
color ||= :gray
|
49
|
+
else
|
50
|
+
s.sub!(data.inspect, data[0,10].inspect+"...") if data && data.size>13 && enc
|
51
|
+
color ||= :bright_red
|
52
|
+
end
|
53
|
+
s.send(color)
|
54
|
+
end
|
55
|
+
|
56
|
+
# XXX require that file extension be 7-bit ASCII
|
57
|
+
def valid_ext?
|
58
|
+
ext =~ /\A[\x20-\x7e]+\Z/ && !ext['*'] && !ext['?']
|
16
59
|
end
|
17
60
|
end
|
18
61
|
|
@@ -49,8 +92,15 @@ module ZSteg
|
|
49
92
|
def check data, params = {}
|
50
93
|
return if data.size < 4
|
51
94
|
return if params[:bit_order] != :lsb
|
95
|
+
|
96
|
+
force_color = nil
|
97
|
+
|
52
98
|
if params[:image].format == :bmp
|
53
99
|
return if params[:order] !~ /b/i
|
100
|
+
else
|
101
|
+
# PNG
|
102
|
+
return if Array(params[:channels]).join != 'bgr'
|
103
|
+
force_color = :gray if params[:order] != 'xY'
|
54
104
|
end
|
55
105
|
|
56
106
|
size1 = (data[0,3] + "\x00").unpack('V')[0]
|
@@ -61,9 +111,16 @@ module ZSteg
|
|
61
111
|
params[:max_hidden_size]
|
62
112
|
end
|
63
113
|
return if size1 == 0 || size1 > avail_size
|
114
|
+
|
115
|
+
# check if too many zeroes, prevent false positive
|
116
|
+
nzeroes = data[3..-1].count("\x00")
|
117
|
+
return if nzeroes > 10 && data.size-3-nzeroes < 4
|
118
|
+
|
119
|
+
result = nil
|
120
|
+
|
64
121
|
size2 = (data[3,3] + "\x00").unpack('V')[0]
|
65
122
|
# p [size1, size2, avail_size]
|
66
|
-
if size2 < avail_size
|
123
|
+
if size2 < avail_size && size2 > 0
|
67
124
|
spacing = 1.0*avail_size/(size2+5) - 1
|
68
125
|
# puts "[d] spacing=#{spacing}"
|
69
126
|
if spacing > 0
|
@@ -77,21 +134,19 @@ module ZSteg
|
|
77
134
|
error -= 1
|
78
135
|
end
|
79
136
|
end
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
137
|
+
if r.size > 4
|
138
|
+
ext = r[0,3]
|
139
|
+
result = Result.new(size2, ext, r[3..-1], true)
|
140
|
+
end
|
84
141
|
end
|
85
142
|
end
|
86
143
|
# no even distribution
|
87
|
-
return unless valid_ext?(data[3,3])
|
88
|
-
|
144
|
+
#return unless valid_ext?(data[3,3])
|
145
|
+
result ||= Result.read(data)
|
146
|
+
result.color = force_color if result && force_color
|
147
|
+
result
|
89
148
|
end
|
90
149
|
|
91
|
-
# XXX require that file extension be 7-bit ASCII
|
92
|
-
def valid_ext? ext
|
93
|
-
ext =~ /\A[\x20-\x7e]+\Z/
|
94
|
-
end
|
95
150
|
end
|
96
151
|
end
|
97
152
|
end
|
data/lib/zsteg/checker.rb
CHANGED
@@ -1,16 +1,21 @@
|
|
1
1
|
require 'stringio'
|
2
2
|
require 'zlib'
|
3
|
+
require 'set'
|
3
4
|
|
4
5
|
module ZSteg
|
5
6
|
class Checker
|
6
|
-
attr_accessor :params, :channels, :verbose
|
7
|
+
attr_accessor :params, :channels, :verbose, :results
|
7
8
|
|
8
|
-
MIN_TEXT_LENGTH
|
9
|
+
MIN_TEXT_LENGTH = 8
|
10
|
+
MIN_WHOLETEXT_LENGTH = 6 # when entire data is a text
|
11
|
+
DEFAULT_BITS = [1,2,3,4]
|
12
|
+
DEFAULT_ORDER = 'auto'
|
13
|
+
DEFAULT_LIMIT = 256 # number of checked bytes, 0 = no limit
|
9
14
|
|
10
15
|
# image can be either filename or ZPNG::Image
|
11
16
|
def initialize image, params = {}
|
12
17
|
@params = params
|
13
|
-
@cache = {}
|
18
|
+
@cache = {}; @wastitles = Set.new
|
14
19
|
@image = image.is_a?(ZPNG::Image) ? image : ZPNG::Image.load(image)
|
15
20
|
@extractor = Extractor.new(@image, params)
|
16
21
|
@channels = params[:channels] ||
|
@@ -19,32 +24,67 @@ module ZSteg
|
|
19
24
|
else
|
20
25
|
%w'r g b rgb bgr'
|
21
26
|
end
|
22
|
-
@verbose = params[:verbose] ||
|
27
|
+
@verbose = params[:verbose] || -2
|
23
28
|
@file_cmd = FileCmd.new
|
29
|
+
@results = []
|
30
|
+
|
31
|
+
@params[:bits] ||= DEFAULT_BITS
|
32
|
+
@params[:order] ||= DEFAULT_ORDER
|
33
|
+
@params[:limit] ||= DEFAULT_LIMIT
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# catch Kernel#print for easier verbosity handling
|
39
|
+
def print *args
|
40
|
+
Kernel.print(*args) if @verbose >= 0
|
41
|
+
end
|
42
|
+
|
43
|
+
# catch Kernel#printf for easier verbosity handling
|
44
|
+
def printf *args
|
45
|
+
Kernel.printf(*args) if @verbose >= 0
|
46
|
+
end
|
47
|
+
|
48
|
+
# catch Kernel#puts for easier verbosity handling
|
49
|
+
def puts *args
|
50
|
+
Kernel.puts(*args) if @verbose >= 0
|
24
51
|
end
|
25
52
|
|
53
|
+
public
|
54
|
+
|
26
55
|
def check
|
27
56
|
@found_anything = false
|
28
57
|
@file_cmd.start!
|
29
58
|
|
30
59
|
check_extradata
|
31
60
|
check_metadata
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
params[:order]
|
36
|
-
|
37
|
-
|
61
|
+
check_imagedata
|
62
|
+
|
63
|
+
if @image.format == :bmp
|
64
|
+
case params[:order].to_s.downcase
|
65
|
+
when /all/
|
66
|
+
params[:order] = %w'bY xY xy yx XY YX Xy yX Yx'
|
67
|
+
when /auto/
|
68
|
+
params[:order] = %w'bY xY'
|
69
|
+
end
|
70
|
+
else
|
71
|
+
case params[:order].to_s.downcase
|
72
|
+
when /all/
|
73
|
+
params[:order] = %w'xy yx XY YX Xy yX xY Yx'
|
74
|
+
when /auto/
|
75
|
+
params[:order] = 'xy'
|
76
|
+
end
|
38
77
|
end
|
39
78
|
|
40
79
|
Array(params[:order]).uniq.each do |order|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
80
|
+
(params[:prime] == :all ? [false,true] : [params[:prime]]).each do |prime|
|
81
|
+
Array(params[:bits]).uniq.each do |bits|
|
82
|
+
p1 = @params.merge :bits => bits, :order => order, :prime => prime
|
83
|
+
if order[/b/i]
|
84
|
+
# byte iterator does not need channels
|
85
|
+
check_channels nil, p1
|
86
|
+
else
|
87
|
+
channels.each{ |c| check_channels c, p1 }
|
48
88
|
end
|
49
89
|
end
|
50
90
|
end
|
@@ -55,15 +95,22 @@ module ZSteg
|
|
55
95
|
else
|
56
96
|
puts "\r[=] nothing :(" + " "*20 # line cleanup
|
57
97
|
end
|
98
|
+
|
99
|
+
@results
|
58
100
|
ensure
|
59
101
|
@file_cmd.stop!
|
60
102
|
end
|
61
103
|
|
104
|
+
def check_imagedata
|
105
|
+
h = { :title => "imagedata", :show_title => true }
|
106
|
+
process_result @image.imagedata, h
|
107
|
+
end
|
108
|
+
|
62
109
|
def check_extradata
|
63
110
|
if @image.extradata
|
64
111
|
@found_anything = true
|
65
112
|
title = "data after IEND"
|
66
|
-
show_title title, :
|
113
|
+
show_title title, :bright_red
|
67
114
|
process_result @image.extradata, :special => true, :title => title
|
68
115
|
end
|
69
116
|
end
|
@@ -83,24 +130,73 @@ module ZSteg
|
|
83
130
|
return
|
84
131
|
end
|
85
132
|
|
86
|
-
title = ["#{params[:bits]}b",channels,params[:bit_order],params[:order]].compact.join(',')
|
87
|
-
show_title title
|
88
|
-
|
89
133
|
p1 = params.clone
|
90
|
-
p1.delete :channel
|
91
|
-
p1[:title] = title
|
92
134
|
|
135
|
+
# number of bits
|
136
|
+
# equals to params[:bits] if in range 1..8
|
137
|
+
# otherwise equals to number of 1's, like 0b1000_0001
|
138
|
+
nbits = p1[:bits] <= 8 ? p1[:bits] : (p1[:bits]&0xff).to_s(2).count("1")
|
139
|
+
|
140
|
+
show_bits = true
|
141
|
+
# channels is a String
|
93
142
|
if channels
|
94
|
-
p1[:channels] =
|
95
|
-
|
143
|
+
p1[:channels] =
|
144
|
+
if channels[1] && channels[1] =~ /\A\d\Z/
|
145
|
+
# 'r3g2b3'
|
146
|
+
a=[]
|
147
|
+
cbits = 0
|
148
|
+
(channels.size/2).times do |i|
|
149
|
+
a << (t=channels[i*2,2])
|
150
|
+
cbits += t[1].to_i
|
151
|
+
end
|
152
|
+
show_bits = false
|
153
|
+
@max_hidden_size = cbits * @image.width
|
154
|
+
a
|
155
|
+
else
|
156
|
+
# 'rgb'
|
157
|
+
a = channels.chars.to_a
|
158
|
+
@max_hidden_size = a.size * @image.width * nbits
|
159
|
+
a
|
160
|
+
end
|
161
|
+
# p1[:channels] is an Array
|
96
162
|
elsif params[:order] =~ /b/i
|
97
163
|
# byte extractor
|
98
|
-
@max_hidden_size = @image.scanlines[0].decoded_bytes.size
|
164
|
+
@max_hidden_size = @image.scanlines[0].decoded_bytes.size * nbits
|
99
165
|
else
|
100
166
|
raise "invalid params #{params.inspect}"
|
101
167
|
end
|
102
|
-
@max_hidden_size *=
|
168
|
+
@max_hidden_size *= @image.height/8
|
169
|
+
|
170
|
+
bits_tag =
|
171
|
+
if show_bits
|
172
|
+
if params[:bits] > 0x100
|
173
|
+
if params[:bits].to_s(2) =~ /(1{1,8})$/
|
174
|
+
# mask => number of bits
|
175
|
+
"b#{$1.size}"
|
176
|
+
else
|
177
|
+
# mask
|
178
|
+
"b#{(params[:bits]&0xff).to_s(2)}"
|
179
|
+
end
|
180
|
+
else
|
181
|
+
# number of bits
|
182
|
+
"b#{params[:bits]}"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
title = [
|
187
|
+
bits_tag,
|
188
|
+
channels,
|
189
|
+
params[:bit_order],
|
190
|
+
params[:order],
|
191
|
+
params[:prime] ? 'prime' : nil
|
192
|
+
].compact.join(',')
|
103
193
|
|
194
|
+
return if @wastitles.include?(title)
|
195
|
+
@wastitles << title
|
196
|
+
|
197
|
+
show_title title
|
198
|
+
|
199
|
+
p1[:title] = title
|
104
200
|
data = @extractor.extract p1
|
105
201
|
|
106
202
|
@need_cr = !process_result(data, p1) # carriage return needed?
|
@@ -108,7 +204,7 @@ module ZSteg
|
|
108
204
|
end
|
109
205
|
|
110
206
|
def show_title title, color = :gray
|
111
|
-
printf "\r
|
207
|
+
printf "\r%-20s.. ".send(color), title
|
112
208
|
$stdout.flush
|
113
209
|
end
|
114
210
|
|
@@ -129,12 +225,15 @@ module ZSteg
|
|
129
225
|
# TODO: store hash of data for large datas
|
130
226
|
@cache[data] = params[:title]
|
131
227
|
|
132
|
-
result = data2result
|
228
|
+
if result = data2result(data, params)
|
229
|
+
@results << result
|
230
|
+
end
|
133
231
|
|
134
232
|
case verbose
|
135
233
|
when -999..0
|
136
234
|
# verbosity=0: only show result if anything interesting found
|
137
235
|
if result && !result.is_a?(Result::OneChar)
|
236
|
+
show_title params[:title] if params[:show_title]
|
138
237
|
puts result
|
139
238
|
return true
|
140
239
|
else
|
@@ -146,6 +245,7 @@ module ZSteg
|
|
146
245
|
end
|
147
246
|
|
148
247
|
# verbosity>1: always show hexdump
|
248
|
+
show_title params[:title] if params[:show_title]
|
149
249
|
|
150
250
|
if params[:special]
|
151
251
|
puts result.is_a?(Result::PartialText) ? nil : result
|
@@ -183,24 +283,27 @@ module ZSteg
|
|
183
283
|
end
|
184
284
|
end
|
185
285
|
|
186
|
-
if data =~ /\A[\x20-\x7e\r\n\t]+\Z/
|
286
|
+
if data.size >= MIN_WHOLETEXT_LENGTH && data =~ /\A[\x20-\x7e\r\n\t]+\Z/
|
187
287
|
# whole ASCII
|
188
288
|
return Result::WholeText.new(data, 0)
|
189
289
|
end
|
190
290
|
|
191
|
-
|
192
|
-
|
291
|
+
# XXX TODO refactor params hack
|
292
|
+
if !params.key?(:no_check_file) && (r = @file_cmd.data2result(data))
|
293
|
+
return r
|
193
294
|
end
|
194
295
|
|
296
|
+
# try to find zlib
|
195
297
|
# http://blog.w3challs.com/index.php?post/2012/03/25/NDH2k12-Prequals-We-are-looking-for-a-real-hacker-Wallpaper-image
|
196
298
|
# http://blog.w3challs.com/public/ndh2k12_prequalls/sp113.bmp
|
197
|
-
|
299
|
+
# XXX TODO refactor params hack
|
300
|
+
if !params.key?(:no_check_zlib) && (idx = data.index(/\x78[\x9c\xda\x01]/))
|
198
301
|
begin
|
199
302
|
# x = Zlib::Inflate.inflate(data[idx,4096])
|
200
303
|
zi = Zlib::Inflate.new(Zlib::MAX_WBITS)
|
201
304
|
x = zi.inflate data[idx..-1]
|
202
305
|
# decompress OK
|
203
|
-
return Result::Zlib.new x, idx
|
306
|
+
return Result::Zlib.new x, idx if x.size > 2
|
204
307
|
rescue Zlib::BufError
|
205
308
|
# tried to decompress, but got EOF - need more data
|
206
309
|
return Result::Zlib.new x, idx
|