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/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
|