zsteg 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/Gemfile +2 -7
  2. data/Gemfile.lock +2 -4
  3. data/README.md +72 -1
  4. data/README.md.tpl +23 -0
  5. data/Rakefile +5 -3
  6. data/TODO +5 -2
  7. data/VERSION +1 -1
  8. data/bin/zsteg-mask +7 -0
  9. data/lib/zsteg/checker/wbstego.rb +69 -14
  10. data/lib/zsteg/checker.rb +137 -34
  11. data/lib/zsteg/cli.rb +92 -35
  12. data/lib/zsteg/extractor/byte_extractor.rb +36 -21
  13. data/lib/zsteg/extractor/color_extractor.rb +68 -34
  14. data/lib/zsteg/extractor.rb +36 -1
  15. data/lib/zsteg/file_cmd.rb +64 -1
  16. data/lib/zsteg/mask_cli.rb +268 -0
  17. data/lib/zsteg/masker.rb +52 -0
  18. data/lib/zsteg/result.rb +27 -32
  19. data/lib/zsteg.rb +2 -0
  20. data/samples/hackquest/crypt.bmp +0 -0
  21. data/samples/hackquest/square.bmp +0 -0
  22. data/samples/wbstego/wbsteg_blowfish_pass_1.bmp +0 -0
  23. data/samples/wbstego/wbsteg_cast128_pass_1.bmp +0 -0
  24. data/samples/wbstego/wbsteg_enc_pass_pass.bmp +0 -0
  25. data/samples/wbstego/wbsteg_enc_pass_pass_even.bmp +0 -0
  26. data/samples/wbstego/wbsteg_mix_pass_1.bmp +0 -0
  27. data/samples/wbstego/wbsteg_mix_pass_1_even.bmp +0 -0
  28. data/samples/wbstego/wbsteg_mix_pass_foobar.bmp +0 -0
  29. data/samples/wbstego/wbsteg_mix_pass_pass.bmp +0 -0
  30. data/samples/wbstego/wbsteg_mixenc_pass_pass_even.bmp +0 -0
  31. data/samples/{wbsteg_noenc.bmp → wbstego/wbsteg_noenc.bmp} +0 -0
  32. data/samples/wbstego/wbsteg_noenc.png +0 -0
  33. data/samples/wbstego/wbsteg_noenc_.bmp +0 -0
  34. data/samples/{wbsteg_noenc_17.bmp → wbstego/wbsteg_noenc_17.bmp} +0 -0
  35. data/samples/wbstego/wbsteg_noenc__.bmp +0 -0
  36. data/samples/{wbsteg_noenc_even.bmp → wbstego/wbsteg_noenc_even.bmp} +0 -0
  37. data/samples/wbstego/wbsteg_noenc_even2.bmp +0 -0
  38. data/samples/{wbsteg_noenc_even_17.bmp → wbstego/wbsteg_noenc_even_17.bmp} +0 -0
  39. data/samples/wbstego/wbsteg_noenc_even_17_.bmp +0 -0
  40. data/samples/wbstego/wbsteg_noenc_ext_ABC.bmp +0 -0
  41. data/samples/wbstego/wbsteg_rijndael_pass_1.bmp +0 -0
  42. data/samples/wbstego/wbsteg_rijndael_pass_pass.bmp +0 -0
  43. data/samples/wbstego/wbsteg_rijndael_pass_pass_even.bmp +0 -0
  44. data/samples/wbstego/wbsteg_twofish_pass_1.bmp +0 -0
  45. data/samples/wechall/5ZMGcCLxpcpsru03.g00000010.png +0 -0
  46. data/samples/wechall/5ZMGcCLxpcpsru03.png +0 -0
  47. data/samples/wechall/stegano1.bmp +0 -0
  48. data/spec/checker_spec.rb +47 -0
  49. data/spec/easybmp_spec.rb +9 -0
  50. data/spec/hackquest_spec.rb +18 -0
  51. data/spec/mask_spec.rb +14 -0
  52. data/spec/polictf2012_spec.rb +48 -0
  53. data/spec/prime_spec.rb +9 -0
  54. data/spec/r3g2b3_spec.rb +9 -0
  55. data/spec/spec_helper.rb +21 -4
  56. data/spec/wbstego_spec.rb +21 -3
  57. data/spec/wechall_spec.rb +26 -0
  58. data/tmp/.keep +0 -0
  59. data/zsteg.gemspec +121 -0
  60. metadata +47 -43
  61. data/samples/06_enc.png +0 -0
  62. data/samples/Code.png +0 -0
  63. data/samples/README +0 -4
  64. data/samples/camouflage-password.png +0 -0
  65. data/samples/camouflage.png +0 -0
  66. data/samples/cats.png +0 -0
  67. data/samples/flower.png +0 -0
  68. data/samples/flower_rgb1.png +0 -0
  69. data/samples/flower_rgb2.png +0 -0
  70. data/samples/flower_rgb3.png +0 -0
  71. data/samples/flower_rgb4.png +0 -0
  72. data/samples/flower_rgb5.png +0 -0
  73. data/samples/flower_rgb6.png +0 -0
  74. data/samples/montenach-enc.png +0 -0
  75. data/samples/ndh2k12_sp113.bmp.7z +0 -0
  76. data/samples/openstego_q2.png +0 -0
  77. data/samples/openstego_send.png +0 -0
  78. data/samples/stg300.png +0 -0
data/Gemfile CHANGED
@@ -1,13 +1,8 @@
1
1
  source "http://rubygems.org"
2
- # Add dependencies required to use your gem here.
3
- # Example:
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.1)
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.1)
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/zsteg"
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,'').sub(/\s+\Z/,'').split("\n")
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
- [*] wbStego
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
- [ ] Sieve of Eratosthenes: http://wiki.cedricbonhomme.org/security:steganography
17
+ [+] Sieve of Eratosthenes: http://wiki.cedricbonhomme.org/security:steganography
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0
1
+ 0.0.1
data/bin/zsteg-mask ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
4
+ require 'zsteg'
5
+ require 'zsteg/mask_cli'
6
+
7
+ ZSteg::MaskCLI.new.run
@@ -2,17 +2,60 @@ module ZSteg
2
2
  class Checker
3
3
  module WBStego
4
4
 
5
- class Result < IOStruct.new "a3a3a*", :size, :ext, :data, :even
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 = false if self.even.nil?
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
- inspect.sub("#<struct #{self.class.to_s}", "<wbStego").red
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
- # 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)
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
- return Result.read(data)
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 = 8
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] || 0
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
- case params[:order].to_s.downcase
34
- when /all/
35
- params[:order] = %w'xy yx XY YX Xy yX xY Yx'
36
- when /auto/
37
- params[:order] = @image.format == :bmp ? %w'bY xY' : 'xy'
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
- Array(params[:bits]).uniq.each do |bits|
42
- if order[/b/i]
43
- # byte iterator does not need channels
44
- check_channels nil, @params.merge( :bits => bits, :order => order )
45
- else
46
- channels.each do |c|
47
- check_channels c, @params.merge( :bits => bits, :order => order )
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, :red
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] = channels.split('')
95
- @max_hidden_size = p1[:channels].size*@image.width
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 *= p1[:bits]*@image.height/8
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[.] %-14s.. ".send(color), title
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 data, params
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
- if r = @file_cmd.check_data(data)
192
- return Result::FileCmd.new(r, data)
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
- if idx = data.index(/\x78[\x9c\xda\x01]/)
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