zsteg 0.2.12 → 0.2.14

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b50a98cd405613a299041cc60c497f4f9a73a099e3112f44b27dee3fddf4831d
4
- data.tar.gz: 8c58dd150ce32f42cc381a67592eded9c9c38e7dc0c4026642ec4773081f4bea
3
+ metadata.gz: 517af804bb6892894d053fe80139d31ca23746c02c42f33dd5574bf25e41f8c2
4
+ data.tar.gz: 399a3f35dc307eb44d1b3e4cb776e32887428d25f311b42a8facb33ecb8ba7f0
5
5
  SHA512:
6
- metadata.gz: 726aa00d6e7cc127072b7dbebe5418d668bc7d62dd119267ab05341b96d63ca71ce23cb2e2cb189e5b59cb8cbd48c09700d736b0d581b32b1feeb30a9ac3b390
7
- data.tar.gz: 22811b2e9c2ffc3e4ce09aa67d3a65c7dd3636d84740b09664e80dca20d02456f5632214a7df8354affac0f9e8ffd7ccaeb466606608f3bd8e1222ca3dec34c9
6
+ metadata.gz: cf416358da42bab09505d7f54d3e5df0b9e26cc4b29e3b6e0aa73bb13900c7367d5e941298917bb6286a6e8b5f430eca36547a4f66efd4d06b3c2a8316b17ffd
7
+ data.tar.gz: 49f2ac63384acc20790882c5542375b655c4462a6099d0ba999fe300ca91766c9d65ec08f2288f9e628c6c11b552f91490ae24be55c6ffbd96b0c94998a64002
data/Gemfile CHANGED
@@ -1,7 +1,7 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- gem "zpng", ">= 0.4.4"
4
- gem "iostruct"
3
+ gem "zpng", ">= 0.4.6"
4
+ gem "iostruct", ">= 0.7.0"
5
5
  gem "prime"
6
6
 
7
7
  group :development do
data/Gemfile.lock CHANGED
@@ -1,13 +1,36 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
- addressable (2.8.0)
5
- public_suffix (>= 2.0.2, < 5.0)
6
- builder (3.2.4)
4
+ activesupport (7.1.6)
5
+ base64
6
+ benchmark (>= 0.3)
7
+ bigdecimal
8
+ concurrent-ruby (~> 1.0, >= 1.0.2)
9
+ connection_pool (>= 2.2.5)
10
+ drb
11
+ i18n (>= 1.6, < 2)
12
+ logger (>= 1.4.2)
13
+ minitest (>= 5.1)
14
+ mutex_m
15
+ securerandom (>= 0.3)
16
+ tzinfo (~> 2.0)
17
+ addressable (2.8.8)
18
+ public_suffix (>= 2.0.2, < 8.0)
19
+ base64 (0.3.0)
20
+ benchmark (0.5.0)
21
+ bigdecimal (4.0.1)
22
+ builder (3.3.0)
23
+ cgi (0.5.1)
24
+ concurrent-ruby (1.3.6)
25
+ connection_pool (2.5.5)
26
+ date (3.5.1)
7
27
  descendants_tracker (0.0.4)
8
28
  thread_safe (~> 0.3, >= 0.3.1)
9
- diff-lcs (1.5.0)
10
- faraday (1.10.0)
29
+ diff-lcs (1.6.2)
30
+ drb (2.2.3)
31
+ erb (4.0.4)
32
+ cgi (>= 0.3.3)
33
+ faraday (1.10.4)
11
34
  faraday-em_http (~> 1.0)
12
35
  faraday-em_synchrony (~> 1.0)
13
36
  faraday-excon (~> 1.1)
@@ -20,19 +43,21 @@ GEM
20
43
  faraday-retry (~> 1.0)
21
44
  ruby2_keywords (>= 0.0.4)
22
45
  faraday-em_http (1.0.0)
23
- faraday-em_synchrony (1.0.0)
46
+ faraday-em_synchrony (1.0.1)
24
47
  faraday-excon (1.1.0)
25
48
  faraday-httpclient (1.0.1)
26
- faraday-multipart (1.0.4)
27
- multipart-post (~> 2)
28
- faraday-net_http (1.0.1)
49
+ faraday-multipart (1.2.0)
50
+ multipart-post (~> 2.0)
51
+ faraday-net_http (1.0.2)
29
52
  faraday-net_http_persistent (1.2.0)
30
53
  faraday-patron (1.0.0)
31
54
  faraday-rack (1.0.0)
32
55
  faraday-retry (1.0.3)
33
- forwardable (1.3.3)
34
- git (1.13.1)
56
+ forwardable (1.4.0)
57
+ git (2.3.3)
58
+ activesupport (>= 5.0)
35
59
  addressable (~> 2.8)
60
+ process_executer (~> 1.1)
36
61
  rchardet (~> 1.8)
37
62
  github_api (0.19.0)
38
63
  addressable (~> 2.4)
@@ -41,8 +66,12 @@ GEM
41
66
  hashie (~> 3.5, >= 3.5.2)
42
67
  oauth2 (~> 1.0)
43
68
  hashie (3.6.0)
44
- highline (2.0.3)
45
- iostruct (0.0.4)
69
+ highline (3.1.2)
70
+ reline
71
+ i18n (1.14.8)
72
+ concurrent-ruby (~> 1.0)
73
+ io-console (0.8.2)
74
+ iostruct (0.7.0)
46
75
  juwelier (2.4.9)
47
76
  builder
48
77
  bundler
@@ -55,15 +84,19 @@ GEM
55
84
  rake
56
85
  rdoc
57
86
  semver2
58
- jwt (2.4.1)
87
+ jwt (2.10.2)
88
+ base64
59
89
  kamelcase (0.0.2)
60
90
  semver2 (~> 3)
61
- mini_portile2 (2.8.1)
62
- multi_json (1.15.0)
91
+ logger (1.7.0)
92
+ mini_portile2 (2.8.9)
93
+ minitest (5.26.1)
94
+ multi_json (1.19.1)
63
95
  multi_xml (0.6.0)
64
- multipart-post (2.2.3)
65
- nokogiri (1.14.2)
66
- mini_portile2 (~> 2.8.0)
96
+ multipart-post (2.4.1)
97
+ mutex_m (0.3.0)
98
+ nokogiri (1.17.2)
99
+ mini_portile2 (~> 2.8.2)
67
100
  racc (~> 1.4)
68
101
  oauth2 (1.4.11)
69
102
  faraday (>= 0.17.3, < 3.0)
@@ -71,49 +104,60 @@ GEM
71
104
  multi_json (~> 1.3)
72
105
  multi_xml (~> 0.5)
73
106
  rack (>= 1.2, < 4)
74
- prime (0.1.2)
107
+ prime (0.1.4)
75
108
  forwardable
76
109
  singleton
77
- psych (4.0.4)
110
+ process_executer (1.1.2)
111
+ psych (5.3.1)
112
+ date
78
113
  stringio
79
- public_suffix (4.0.7)
80
- racc (1.6.2)
81
- rack (3.0.4.1)
114
+ public_suffix (6.0.2)
115
+ racc (1.8.1)
116
+ rack (3.2.4)
82
117
  rainbow (3.1.1)
83
- rake (13.0.6)
84
- rchardet (1.8.0)
85
- rdoc (6.4.0)
118
+ rake (13.3.1)
119
+ rchardet (1.10.0)
120
+ rdoc (7.1.0)
121
+ erb
86
122
  psych (>= 4.0.0)
87
- rspec (3.11.0)
88
- rspec-core (~> 3.11.0)
89
- rspec-expectations (~> 3.11.0)
90
- rspec-mocks (~> 3.11.0)
91
- rspec-core (3.11.0)
92
- rspec-support (~> 3.11.0)
93
- rspec-expectations (3.11.0)
123
+ tsort
124
+ reline (0.6.3)
125
+ io-console (~> 0.5)
126
+ rspec (3.13.2)
127
+ rspec-core (~> 3.13.0)
128
+ rspec-expectations (~> 3.13.0)
129
+ rspec-mocks (~> 3.13.0)
130
+ rspec-core (3.13.6)
131
+ rspec-support (~> 3.13.0)
132
+ rspec-expectations (3.13.5)
94
133
  diff-lcs (>= 1.2.0, < 2.0)
95
- rspec-support (~> 3.11.0)
96
- rspec-mocks (3.11.1)
134
+ rspec-support (~> 3.13.0)
135
+ rspec-mocks (3.13.7)
97
136
  diff-lcs (>= 1.2.0, < 2.0)
98
- rspec-support (~> 3.11.0)
99
- rspec-support (3.11.0)
137
+ rspec-support (~> 3.13.0)
138
+ rspec-support (3.13.6)
100
139
  ruby2_keywords (0.0.5)
140
+ securerandom (0.3.2)
101
141
  semver2 (3.4.2)
102
- singleton (0.1.1)
103
- stringio (3.0.2)
142
+ singleton (0.3.0)
143
+ stringio (3.2.0)
104
144
  thread_safe (0.3.6)
105
- zpng (0.4.4)
145
+ tsort (0.2.0)
146
+ tzinfo (2.0.6)
147
+ concurrent-ruby (~> 1.0)
148
+ zpng (0.4.6)
149
+ iostruct (>= 0.7.0)
106
150
  rainbow (~> 3.1.1)
107
151
 
108
152
  PLATFORMS
109
153
  ruby
110
154
 
111
155
  DEPENDENCIES
112
- iostruct
156
+ iostruct (>= 0.7.0)
113
157
  juwelier
114
158
  prime
115
159
  rspec
116
- zpng (>= 0.4.4)
160
+ zpng (>= 0.4.6)
117
161
 
118
162
  BUNDLED WITH
119
- 2.3.12
163
+ 2.4.22
data/README.md CHANGED
@@ -28,26 +28,32 @@ Usage
28
28
 
29
29
  Usage: zsteg [options] filename.png [param_string]
30
30
 
31
+ -a, --all try all known methods
32
+ -E, --extract NAME extract specified payload, NAME is like '1b,rgb,lsb'
33
+
34
+ Iteration/extraction params:
35
+ -o, --order X pixel iteration order (default: 'auto')
36
+ valid values: ALL,xy,yx,XY,YX,xY,Xy,bY,...
31
37
  -c, --channels X channels (R/G/B/A) or any combination, comma separated
32
38
  valid values: r,g,b,a,rg,bgr,rgba,r3g2b3,...
33
- -l, --limit N limit bytes checked, 0 = no limit (default: 256)
34
39
  -b, --bits N number of bits, single int value or '1,3,5' or range '1-8'
35
40
  advanced: specify individual bits like '00001110' or '0x88'
36
- --lsb least significant BIT comes first
37
- --msb most significant BIT comes first
41
+ --lsb least significant bit comes first
42
+ --msb most significant bit comes first
38
43
  -P, --prime analyze/extract only prime bytes/pixels
44
+ --shift N prepend N zero bits
45
+ --step N step
39
46
  --invert invert bits (XOR 0xff)
40
- -a, --all try all known methods
41
- -o, --order X pixel iteration order (default: 'auto')
42
- valid values: ALL,xy,yx,XY,YX,xY,Xy,bY,...
43
- -E, --extract NAME extract specified payload, NAME is like '1b,rgb,lsb'
47
+ --pixel-align pixel-align hidden data
48
+
49
+ Analysis params:
50
+ -l, --limit N limit bytes checked, 0 = no limit (default: 256)
44
51
 
45
52
  --[no-]file use 'file' command to detect data type (default: YES)
46
53
  --no-strings disable ASCII strings finding (default: enabled)
47
54
  -s, --strings X ASCII strings find mode: first, all, longest, none
48
55
  (default: first)
49
56
  -n, --min-str-len X minimum string length (default: 8)
50
- --shift N prepend N zero bits
51
57
 
52
58
  -v, --verbose Run verbosely (can be used multiple times)
53
59
  -q, --quiet Silent any warnings (can be used multiple times)
@@ -85,23 +91,6 @@ Examples
85
91
 
86
92
  # zsteg wbstego/wbsteg_noenc_even.bmp 1b,lsb,bY -v
87
93
 
88
- imagedata .. file: FoxPro FPT, blocks size 1, next free block index 65537
89
- 00000000: 00 01 00 01 00 00 00 01 00 00 00 00 00 00 00 00 |................|
90
- 00000010: 00 00 00 00 00 00 00 00 00 00 00 01 00 01 01 00 |................|
91
- 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
92
- 00000030: 00 01 01 01 00 01 00 00 00 00 00 00 01 01 00 01 |................|
93
- 00000040: 01 00 01 01 00 01 00 01 00 01 01 01 01 00 00 00 |................|
94
- 00000050: 00 00 00 01 01 01 01 00 01 00 01 00 00 00 00 01 |................|
95
- 00000060: 00 00 01 01 01 00 00 01 00 01 01 01 00 01 00 00 |................|
96
- 00000070: 01 01 01 00 01 00 00 00 00 00 01 01 01 00 00 00 |................|
97
- 00000080: 00 01 00 01 00 00 01 01 01 01 00 00 00 01 01 00 |................|
98
- 00000090: 00 01 00 01 00 01 01 00 01 00 00 01 00 01 00 00 |................|
99
- 000000a0: 00 01 01 01 00 01 00 01 01 01 00 01 00 00 00 01 |................|
100
- 000000b0: 01 00 01 00 00 01 00 01 00 01 01 01 00 00 00 00 |................|
101
- 000000c0: 01 00 00 00 00 01 00 00 01 01 00 00 01 00 00 00 |................|
102
- 000000d0: 00 00 01 00 00 01 01 01 00 01 01 00 00 01 00 01 |................|
103
- 000000e0: 01 01 01 01 01 01 01 00 00 00 00 00 01 00 00 00 |................|
104
- 000000f0: 00 01 01 01 00 00 01 00 00 00 01 01 00 01 00 01 |................|
105
94
  b1,lsb,bY .. <wbStego size=22, data="xtSuperSecretMessage\n", even=true, mix=true, controlbyte="t">
106
95
  00000000: 51 00 00 16 00 00 74 0d b5 78 1e a1 39 74 e8 38 |Q.....t..x..9t.8|
107
96
  00000010: 53 c6 56 94 75 d1 a5 70 84 c8 27 65 fe 08 72 35 |S.V.u..p..'e..r5|
data/TODO CHANGED
@@ -11,6 +11,8 @@
11
11
  [ ] advices on what tool to use
12
12
  [ ] SilentEye
13
13
  [ ] chunks length check, as in pngcheck
14
+ [ ] hiding data in iCCP
15
+ [ ] hiding data before/after ICC apply
14
16
 
15
17
  [ ] CLI: self-describe
16
18
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.12
1
+ 0.2.14
data/cmp_png.rb CHANGED
@@ -5,7 +5,7 @@ require 'awesome_print'
5
5
  images = ARGV.map{ |fname| ZPNG::Image.load(fname) }
6
6
  raise "need at least 2 images" if images.size < 2
7
7
 
8
- limit = 20
8
+ limit = 100
9
9
  alpha_used = images.any?(&:alpha_used?)
10
10
  channels = alpha_used ? %w'r g b a' : %w'r g b'
11
11
 
@@ -0,0 +1,36 @@
1
+ # coding: binary
2
+ module ZSteg
3
+ class Checker
4
+ module SteganographyPNG
5
+
6
+ URL = "https://github.com/pedrooaugusto/steganography-png"
7
+
8
+ # https://github.com/pedrooaugusto/steganography-png/blob/2a0e038c135e41438b4c2c93821227a2289b4203/scanlines/scanlines.go#L234
9
+ #
10
+ # The secret metadata is stored in the last bytes of the last scanline in the form of:
11
+ # 17 107 [bitloss] [secret size - 4 bytes] [secret type] [secret type length]
12
+ # 17 107 1 4096 "text/plain" 10
13
+
14
+ class Result < IOStruct.new("nCNa*", :magic, :bitloss, :secret_size, :secret_type, struct_name: 'SteganographyPNG')
15
+ def valid?
16
+ magic == 0x116b && (1..8).include?(bitloss)
17
+ end
18
+
19
+ def to_s
20
+ super.sub(/>$/,'').bright_red
21
+ end
22
+ end
23
+
24
+ def self.check_image image, _params = {}
25
+ ls = image.scanlines.last
26
+ data = ls.decoded_bytes
27
+ secret_type_length = data[-1].ord
28
+ return nil if secret_type_length > data.size - 8
29
+ data = data[ -secret_type_length-8 .. -2 ]
30
+ # data.size to prevent "want 8 bytes, got 7" IOStruct warning when secret_type_length == 0
31
+ r = Result.read(data, data.size)
32
+ r.valid? && [r, URL]
33
+ end
34
+ end
35
+ end
36
+ end
@@ -11,7 +11,7 @@ module ZSteg
11
11
  "Rijndael", # 4
12
12
  ]
13
13
 
14
- class Result < IOStruct.new "a3a3a*", :size, :ext, :data, :even, :hdr, :enc, :mix, :controlbyte
14
+ class Result < IOStruct.new("a3a3a*", :size, :ext, :data, :even, :hdr, :enc, :mix, :controlbyte, struct_name: 'wbStego')
15
15
  attr_accessor :color
16
16
 
17
17
  def initialize *args
@@ -39,7 +39,6 @@ module ZSteg
39
39
 
40
40
  def to_s
41
41
  s = inspect.
42
- sub("#<struct #{self.class.to_s}", "<wbStego").
43
42
  gsub(/, \w+=nil/,'')
44
43
 
45
44
  color = @color
@@ -21,6 +21,7 @@ module ZSteg
21
21
  # http://blog.w3challs.com/index.php?post/2012/03/25/NDH2k12-Prequals-We-are-looking-for-a-real-hacker-Wallpaper-image
22
22
  # http://blog.w3challs.com/public/ndh2k12_prequalls/sp113.bmp
23
23
  def self.check_data data
24
+ data = data.force_encoding('ASCII-8BIT')
24
25
  return unless idx = data.index(/\x78[\x9c\xda\x01]/n)
25
26
 
26
27
  zi = ::Zlib::Inflate.new
data/lib/zsteg/checker.rb CHANGED
@@ -3,6 +3,11 @@ require 'stringio'
3
3
  require 'zlib'
4
4
  require 'set'
5
5
 
6
+ require 'zsteg/checker/scanline_checker'
7
+ require 'zsteg/checker/steganography_png'
8
+ require 'zsteg/checker/wbstego'
9
+ require 'zsteg/checker/zlib'
10
+
6
11
  module ZSteg
7
12
  class Checker
8
13
  attr_accessor :params, :channels, :verbose, :results
@@ -46,20 +51,20 @@ module ZSteg
46
51
 
47
52
  private
48
53
 
49
- # catch Kernel#print for easier verbosity handling
50
- def print *args
51
- Kernel.print(*args) if @verbose >= 0
52
- end
53
-
54
- # catch Kernel#printf for easier verbosity handling
55
- def printf *args
56
- Kernel.printf(*args) if @verbose >= 0
57
- end
58
-
59
- # catch Kernel#puts for easier verbosity handling
60
- def puts *args
61
- Kernel.puts(*args) if @verbose >= 0
62
- end
54
+ # # catch Kernel#print for easier verbosity handling
55
+ # def print *args
56
+ # Kernel.print(*args) if @verbose >= 0
57
+ # end
58
+ #
59
+ # # catch Kernel#printf for easier verbosity handling
60
+ # def printf *args
61
+ # Kernel.printf(*args) if @verbose >= 0
62
+ # end
63
+ #
64
+ # # catch Kernel#puts for easier verbosity handling
65
+ # def puts *args
66
+ # Kernel.puts(*args) if @verbose >= 0
67
+ # end
63
68
 
64
69
  public
65
70
 
@@ -71,6 +76,7 @@ module ZSteg
71
76
  check_extradata
72
77
  check_metadata
73
78
  check_imagedata
79
+ check_chunks
74
80
  end
75
81
 
76
82
  if @image.format == :bmp
@@ -92,12 +98,26 @@ module ZSteg
92
98
  Array(params[:order]).uniq.each do |order|
93
99
  (params[:prime] == :all ? [false,true] : [params[:prime]]).each do |prime|
94
100
  Array(params[:bits]).uniq.each do |bits|
95
- p1 = @params.merge :bits => bits, :order => order, :prime => prime
96
- if order[/b/i]
97
- # byte iterator does not need channels
98
- check_channels nil, p1
101
+ if params[:pixel_align] == :all
102
+ [false, true].each do |pixel_align|
103
+ # skip cases when output will be identical for pixel_align true/false
104
+ next if pixel_align && (8%bits) == 0
105
+ p1 = @params.merge bits: bits, order: order, prime: prime, pixel_align: pixel_align
106
+ if order[/b/i]
107
+ # byte iterator does not need channels
108
+ check_channels nil, p1
109
+ else
110
+ channels.each{ |c| check_channels c, p1 }
111
+ end
112
+ end
99
113
  else
100
- channels.each{ |c| check_channels c, p1 }
114
+ p1 = @params.merge bits: bits, order: order, prime: prime
115
+ if order[/b/i]
116
+ # byte iterator does not need channels
117
+ check_channels nil, p1
118
+ else
119
+ channels.each{ |c| check_channels c, p1 }
120
+ end
101
121
  end
102
122
  end
103
123
  end
@@ -124,6 +144,16 @@ module ZSteg
124
144
  process_result @image.imagedata, h
125
145
  end
126
146
 
147
+ def check_chunks
148
+ @image.chunks.each_with_index do |chunk, idx|
149
+ next unless chunk.respond_to?(:size) && chunk.respond_to?(:data)
150
+ next unless chunk.size && chunk.data
151
+ next if chunk.size < 5 || chunk.is_a?(ZPNG::TextChunk) || chunk.is_a?(ZPNG::Chunk::IDAT)
152
+ h = { :title => "chunk:#{idx}:#{chunk.type}", :show_title => true }
153
+ process_result chunk.data, h
154
+ end
155
+ end
156
+
127
157
  def check_extradata
128
158
  # accessing imagedata implicitly unpacks zlib stream
129
159
  # zlib stream may contain extradata
@@ -150,6 +180,13 @@ module ZSteg
150
180
  show_title title, :bright_red
151
181
  process_result data, :special => true, :title => title
152
182
  end
183
+
184
+ if r = SteganographyPNG.check_image(@image, @params)
185
+ @found_anything = true
186
+ title = "image"
187
+ show_title title, :bright_red
188
+ process_result nil, title: title, result: r
189
+ end
153
190
  end
154
191
 
155
192
  def check_metadata
@@ -220,6 +257,8 @@ module ZSteg
220
257
  end
221
258
  end
222
259
 
260
+ bits_tag << "p" if params[:pixel_align]
261
+
223
262
  title = [
224
263
  bits_tag,
225
264
  channels,
@@ -268,21 +307,26 @@ module ZSteg
268
307
  def process_result data, params
269
308
  verbose = params[:special] ? [@verbose,1.5].max : @verbose
270
309
 
271
- if @cache[data]
272
- if verbose > 1
273
- puts "[same as #{@cache[data].inspect}]".gray
274
- return true
275
- else
276
- # silent return
277
- return false
310
+ result = nil
311
+ if data
312
+ if @cache[data]
313
+ if verbose > 1
314
+ puts "[same as #{@cache[data].inspect}]".gray
315
+ return true
316
+ else
317
+ # silent return
318
+ return false
319
+ end
278
320
  end
279
- end
280
321
 
281
- # TODO: store hash of data for large datas
282
- @cache[data] = params[:title]
322
+ # TODO: store hash of data for large datas
323
+ @cache[data] = params[:title]
283
324
 
284
- if result = data2result(data, params)
285
- @results << result
325
+ if result = data2result(data, params)
326
+ @results << result
327
+ end
328
+ elsif !(result = params[:result])
329
+ raise "[?] No data nor result"
286
330
  end
287
331
 
288
332
  case verbose
@@ -309,7 +353,7 @@ module ZSteg
309
353
  else
310
354
  show_result result, params
311
355
  end
312
- if data.size > 0 && !result.is_a?(Result::OneChar) && !result.is_a?(Result::WholeText)
356
+ if data && data.size > 0 && !result.is_a?(Result::OneChar) && !result.is_a?(Result::WholeText)
313
357
  # newline if no results and want hexdump
314
358
  puts if !result || result == []
315
359
  limit = (params[:limit] || @params[:limit]).to_i
data/lib/zsteg/cli/cli.rb CHANGED
@@ -11,14 +11,39 @@ module ZSteg
11
11
  def run
12
12
  @actions = []
13
13
  @options = {
14
- :verbose => 0,
15
- :limit => Checker::DEFAULT_LIMIT,
16
- :order => Checker::DEFAULT_ORDER
14
+ verbose: 0,
15
+ limit: Checker::DEFAULT_LIMIT,
16
+ order: Checker::DEFAULT_ORDER,
17
+ step: 1,
18
+ ystep: 1,
17
19
  }
18
20
  optparser = OptionParser.new do |opts|
19
21
  opts.banner = "Usage: zsteg [options] filename.png [param_string]"
20
22
  opts.separator ""
21
23
 
24
+ opts.on "-a", "--all", "try all known methods" do
25
+ @options[:prime] = :all
26
+ @options[:order] = :all
27
+ @options[:pixel_align] = :all
28
+ @options[:bits] = (1..8).to_a
29
+ # specifying --all on command line explicitly enables extra checks
30
+ @options[:extra_checks] = true
31
+ end
32
+
33
+ opts.on "-E", "--extract NAME", "extract specified payload, NAME is like '1b,rgb,lsb'" do |x|
34
+ @options[:verbose] = -2 # silent ZPNG warnings
35
+ @actions << [:extract, x]
36
+ end
37
+
38
+ #################################################################################
39
+ opts.separator "\nIteration/extraction params:"
40
+ #################################################################################
41
+
42
+ opts.on("-o", "--order X", /all|auto|[bxy,]+/i,
43
+ "pixel iteration order (default: '#{@options[:order]}')",
44
+ "valid values: ALL,xy,yx,XY,YX,xY,Xy,bY,...",
45
+ ){ |x| @options[:order] = x.split(',') }
46
+
22
47
  opts.on("-c", "--channels X", /[rgba,1-8]+/,
23
48
  "channels (R/G/B/A) or any combination, comma separated",
24
49
  "valid values: r,g,b,a,rg,bgr,rgba,r3g2b3,..."
@@ -28,14 +53,14 @@ module ZSteg
28
53
  @options[:extra_checks] = false
29
54
  end
30
55
 
31
- opts.on("-l", "--limit N", Integer,
32
- "limit bytes checked, 0 = no limit (default: #{@options[:limit]})"
33
- ){ |n| @options[:limit] = n }
34
-
35
56
  opts.on("-b", "--bits N", "number of bits, single int value or '1,3,5' or range '1-8'",
36
57
  "advanced: specify individual bits like '00001110' or '0x88'"
37
58
  ) do |x|
38
59
  a = []
60
+ if x[-1] == 'p'
61
+ @options[:pixel_align] = true
62
+ x = x[0..-2]
63
+ end
39
64
  x = '1-8' if x == 'all'
40
65
  x.split(',').each do |x1|
41
66
  if x1['-']
@@ -50,10 +75,10 @@ module ZSteg
50
75
  @options[:extra_checks] = false
51
76
  end
52
77
 
53
- opts.on "--lsb", "least significant BIT comes first" do
78
+ opts.on "--lsb", "least significant bit comes first" do
54
79
  @options[:bit_order] = :lsb
55
80
  end
56
- opts.on "--msb", "most significant BIT comes first" do
81
+ opts.on "--msb", "most significant bit comes first" do
57
82
  @options[:bit_order] = :msb
58
83
  end
59
84
 
@@ -62,31 +87,20 @@ module ZSteg
62
87
  # specifying prime on command line disables extra checks
63
88
  @options[:extra_checks] = false
64
89
  end
65
- opts.on "--invert", "invert bits (XOR 0xff)" do
66
- @options[:invert] = true
67
- end
68
90
 
69
- # opts.on "--pixel-align", "pixel-align hidden data (EasyBMP)" do
70
- # @options[:pixel_align] = true
71
- # end
91
+ opts.on("--shift N", Integer, "prepend N zero bits"){ |x| @options[:shift] = x }
92
+ #opts.on("--step N", Integer, "step") { |x| @options[:step] = x }
93
+ opts.on("--invert", "invert bits (XOR 0xff)") { @options[:invert] = true }
72
94
 
73
- opts.on "-a", "--all", "try all known methods" do
74
- @options[:prime] = :all
75
- @options[:order] = :all
76
- @options[:bits] = (1..8).to_a
77
- # specifying --all on command line explicitly enables extra checks
78
- @options[:extra_checks] = true
95
+ opts.on "--pixel-align", "pixel-align hidden data" do
96
+ @options[:pixel_align] = true
79
97
  end
80
98
 
81
- opts.on("-o", "--order X", /all|auto|[bxy,]+/i,
82
- "pixel iteration order (default: '#{@options[:order]}')",
83
- "valid values: ALL,xy,yx,XY,YX,xY,Xy,bY,...",
84
- ){ |x| @options[:order] = x.split(',') }
99
+ #################################################################################
100
+ opts.separator "\nAnalysis params:"
101
+ #################################################################################
85
102
 
86
- opts.on "-E", "--extract NAME", "extract specified payload, NAME is like '1b,rgb,lsb'" do |x|
87
- @options[:verbose] = -2 # silent ZPNG warnings
88
- @actions << [:extract, x]
89
- end
103
+ opts.on("-l", "--limit N", Integer, "limit bytes checked, 0 = no limit (default: #{@options[:limit]})"){ |n| @options[:limit] = n }
90
104
 
91
105
  opts.separator ""
92
106
 
@@ -112,10 +126,6 @@ module ZSteg
112
126
  @options[:min_str_len] = x
113
127
  end
114
128
 
115
- opts.on "--shift N", Integer, "prepend N zero bits" do |x|
116
- @options[:shift] = x
117
- end
118
-
119
129
  opts.separator ""
120
130
  opts.on "-v", "--verbose", "Run verbosely (can be used multiple times)" do |v|
121
131
  @options[:verbose] += 1
@@ -203,6 +213,9 @@ module ZSteg
203
213
  h[:bit_order] = :msb
204
214
  when /^(\d)b$/, /^b(\d+)$/
205
215
  h[:bits] = parse_bits($1)
216
+ when /^(\d)bp$/, /^b(\d+)p$/
217
+ h[:bits] = parse_bits($1)
218
+ h[:pixel_align] = true
206
219
  when /\A[rgba]+\Z/
207
220
  h[:channels] = [x]
208
221
  when /\Axy|yx|yb|by\Z/i
@@ -231,6 +244,17 @@ module ZSteg
231
244
  @img.extradata[$1.to_i]
232
245
  when /imagedata/
233
246
  @img.imagedata
247
+ when /\Achunk:(\d+):(.+)\Z/
248
+ # chunk with type check
249
+ idx, type = $1.to_i, $2
250
+ chunk = @img.chunks[idx]
251
+ raise "chunk ##{idx}: expected #{type} type, but got #{chunk.type}" if chunk.type != type
252
+ chunk.data
253
+ when /\Achunk:(\d+)\Z/
254
+ # chunk without type check
255
+ idx, type = $1.to_i, $2
256
+ chunk = @img.chunks[idx]
257
+ chunk.data
234
258
  else
235
259
  h = decode_param_string name
236
260
  h[:limit] = @options[:limit] if @options[:limit] != Checker::DEFAULT_LIMIT
@@ -32,8 +32,8 @@ module ZSteg
32
32
  else
33
33
  8.times{ |i| byte |= (a.shift<<(7-i))}
34
34
  end
35
- #printf "[d] %02x %08b\n", byte, byte
36
35
  data << byte.chr
36
+ #a = []
37
37
  if data.size >= @limit
38
38
  print "[limit #@limit]".gray if @verbose > 1
39
39
  break
@@ -77,12 +77,15 @@ module ZSteg
77
77
  [@image.height-1, 0, -1]
78
78
  end
79
79
 
80
+ xstep *= params[:step] if params[:step]
81
+ ystep *= params[:ystep] if params[:ystep]
82
+
80
83
  # cannot join these lines from ByteExtractor and ColorExtractor into
81
84
  # one method for performance reason:
82
85
  # it will require additional yield() for EACH BYTE iterated
83
86
 
84
87
  if type[0,1].downcase == 'b'
85
- # ROW iterator
88
+ # ROW iterator (natural)
86
89
  if params[:prime]
87
90
  idx = 0
88
91
  y0.step(y1,ystep){ |y| x0.step(x1,xstep){ |x|
@@ -6,7 +6,7 @@ module ZSteg
6
6
 
7
7
  def color_extract params = {}
8
8
  channels = Array(params[:channels])
9
- #pixel_align = params[:pixel_align]
9
+ pixel_align = params[:pixel_align]
10
10
 
11
11
  ch_masks = []
12
12
  case channels.first.size
@@ -42,28 +42,28 @@ module ZSteg
42
42
  color = @image[x,y]
43
43
 
44
44
  ch_masks.each do |c,bidxs|
45
+ bidxs = bidxs[a.size-8..] if pixel_align && a.size + bidxs.size > 8
45
46
  value = color.send(c)
46
47
  bidxs.each do |bidx|
47
48
  a << value[bidx]
48
49
  end
49
50
  end
50
- #p [x,y,a.size,a]
51
51
 
52
52
  while a.size >= 8
53
53
  byte = 0
54
- #puts a.join
54
+ # a0 = a.dup
55
55
  if params[:bit_order] == :msb
56
56
  8.times{ |i| byte |= (a.shift<<i)}
57
57
  else
58
58
  8.times{ |i| byte |= (a.shift<<(7-i))}
59
59
  end
60
- #printf "[d] %02x %08b\n", byte, byte
60
+ # printf "[d] %-10s -> %-10s : %s %02x %08b x=%d y=%d\n", a0.join, a.join, byte.chr.inspect, byte, byte, x, y
61
61
  data << byte.chr
62
62
  if data.size >= @limit
63
63
  print "[limit #@limit]".gray if @verbose > 1
64
64
  throw :limit
65
65
  end
66
- #a.clear if pixel_align
66
+ a.clear if pixel_align && a.size < 8
67
67
  end
68
68
  end
69
69
  end
data/lib/zsteg/result.rb CHANGED
@@ -8,8 +8,9 @@ module ZSteg
8
8
  end
9
9
  end
10
10
 
11
- class OpenStego < IOStruct.new "CVCCCC",
12
- :version, :data_len, :channel_bits, :fname_len, :compress, :encrypt, :fname
11
+ class OpenStego < IOStruct.new("CVCCCC",
12
+ :version, :data_len, :channel_bits, :fname_len, :compress, :encrypt, :fname,
13
+ struct_name: 'OpenStego')
13
14
 
14
15
  def self.read io
15
16
  super.tap do |r|
@@ -18,7 +19,7 @@ module ZSteg
18
19
  end
19
20
 
20
21
  def to_s
21
- super.sub(/^<Result::/,'').sub(/>$/,'').bright_red
22
+ super.sub(/>$/,'').bright_red
22
23
  end
23
24
  end
24
25
 
data/lib/zsteg.rb CHANGED
@@ -9,10 +9,6 @@ require 'zsteg/checker'
9
9
  require 'zsteg/result'
10
10
  require 'zsteg/file_cmd'
11
11
 
12
- require 'zsteg/checker/wbstego'
13
- require 'zsteg/checker/scanline_checker'
14
- require 'zsteg/checker/zlib'
15
-
16
12
  require 'zsteg/masker'
17
13
 
18
14
  require 'zsteg/analyzer'
data/spec/checker_spec.rb CHANGED
@@ -17,9 +17,9 @@ describe Checker do
17
17
  end
18
18
  end
19
19
 
20
- it "should be quiet by default" do
21
- @out.should == ""
22
- end
20
+ # it "should be quiet by default" do
21
+ # @out.should == ""
22
+ # end
23
23
 
24
24
  it "returned results should be equal to #results" do
25
25
  @results.should == @checker.results
data/spec/plte_spec.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe "samples/plte1.png" do
4
+ subject{ cli(sample("plte1.png")) }
5
+ it { should include("Zip archive data") }
6
+
7
+ describe "--extract" do
8
+ it "should extract zip file from PLTE with type check" do
9
+ r = cli(sample("plte1.png"), "--extract", "chunk:1:PLTE")
10
+ md5(r).should == 'e125f0f322fd1e99050dba688968385c'
11
+ end
12
+ it "should extract zip file from PLTE without type check" do
13
+ r = cli(sample("plte1.png"), "--extract", "chunk:1")
14
+ md5(r).should == 'e125f0f322fd1e99050dba688968385c'
15
+ end
16
+ end
17
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,9 +1,14 @@
1
1
  #coding: binary
2
2
  $:.unshift(File.expand_path("../lib", File.dirname(__FILE__)))
3
3
  require 'zsteg'
4
+ require 'digest/md5'
4
5
 
5
6
  SAMPLES_DIR = File.expand_path("../samples", File.dirname(__FILE__))
6
7
 
8
+ def md5 data
9
+ Digest::MD5.hexdigest(data)
10
+ end
11
+
7
12
  def each_sample glob="*.png"
8
13
  Dir[File.join(SAMPLES_DIR, glob)].each do |fname|
9
14
  yield fname.sub(Dir.pwd+'/','')
@@ -40,6 +45,7 @@ def cli *args
40
45
  else
41
46
  ZSteg::CLI
42
47
  end
48
+ args << "-qqq"
43
49
  args << "--no-color" unless args.any?{|x| x['color']}
44
50
  orig_stdout, out = $stdout, ""
45
51
  begin
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ # https://github.com/pedrooaugusto/steganography-png
4
+ each_sample("steganography-png/*.png") do |fname|
5
+ describe fname do
6
+ it "should reveal secret message" do
7
+ r = cli fname, "--limit", "64", "-a", "--no-file"
8
+ r.should include("SteganographyPNG")
9
+ r.should include(ZSteg::Checker::SteganographyPNG::URL)
10
+ r.should_not include("ZSteg::Checker::SteganographyPNG")
11
+ end
12
+ end
13
+ end
data/zsteg.gemspec CHANGED
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: zsteg 0.2.12 ruby lib
5
+ # stub: zsteg 0.2.14 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "zsteg".freeze
9
- s.version = "0.2.12"
9
+ s.version = "0.2.14"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["Andrey \"Zed\" Zaikin".freeze]
14
- s.date = "2023-02-14"
14
+ s.date = "2026-01-28"
15
15
  s.email = "zed.0xff@gmail.com".freeze
16
16
  s.executables = ["zsteg".freeze, "zsteg-mask".freeze, "zsteg-reflow".freeze]
17
17
  s.extra_rdoc_files = [
@@ -36,6 +36,7 @@ Gem::Specification.new do |s|
36
36
  "lib/zsteg/analyzer.rb",
37
37
  "lib/zsteg/checker.rb",
38
38
  "lib/zsteg/checker/scanline_checker.rb",
39
+ "lib/zsteg/checker/steganography_png.rb",
39
40
  "lib/zsteg/checker/wbstego.rb",
40
41
  "lib/zsteg/checker/zlib.rb",
41
42
  "lib/zsteg/cli/cli.rb",
@@ -58,11 +59,13 @@ Gem::Specification.new do |s|
58
59
  "spec/mask_spec.rb",
59
60
  "spec/newbiecontest_spec.rb",
60
61
  "spec/openstego_spec.rb",
62
+ "spec/plte_spec.rb",
61
63
  "spec/polictf2012_spec.rb",
62
64
  "spec/prime_spec.rb",
63
65
  "spec/r3g2b3_spec.rb",
64
66
  "spec/simple_spec.rb",
65
67
  "spec/spec_helper.rb",
68
+ "spec/steganography_png_spec.rb",
66
69
  "spec/wbstego_spec.rb",
67
70
  "spec/wechall_spec.rb",
68
71
  "spec/zlib_spec.rb",
@@ -73,7 +76,7 @@ Gem::Specification.new do |s|
73
76
  ]
74
77
  s.homepage = "http://github.com/zed-0xff/zsteg".freeze
75
78
  s.licenses = ["MIT".freeze]
76
- s.rubygems_version = "3.3.7".freeze
79
+ s.rubygems_version = "3.2.33".freeze
77
80
  s.summary = "Detect stegano-hidden data in PNG & BMP files.".freeze
78
81
 
79
82
  if s.respond_to? :specification_version then
@@ -81,14 +84,14 @@ Gem::Specification.new do |s|
81
84
  end
82
85
 
83
86
  if s.respond_to? :add_runtime_dependency then
84
- s.add_runtime_dependency(%q<zpng>.freeze, [">= 0.4.4"])
85
- s.add_runtime_dependency(%q<iostruct>.freeze, [">= 0"])
87
+ s.add_runtime_dependency(%q<zpng>.freeze, [">= 0.4.6"])
88
+ s.add_runtime_dependency(%q<iostruct>.freeze, [">= 0.7.0"])
86
89
  s.add_runtime_dependency(%q<prime>.freeze, [">= 0"])
87
90
  s.add_development_dependency(%q<rspec>.freeze, [">= 0"])
88
91
  s.add_development_dependency(%q<juwelier>.freeze, [">= 0"])
89
92
  else
90
- s.add_dependency(%q<zpng>.freeze, [">= 0.4.4"])
91
- s.add_dependency(%q<iostruct>.freeze, [">= 0"])
93
+ s.add_dependency(%q<zpng>.freeze, [">= 0.4.6"])
94
+ s.add_dependency(%q<iostruct>.freeze, [">= 0.7.0"])
92
95
  s.add_dependency(%q<prime>.freeze, [">= 0"])
93
96
  s.add_dependency(%q<rspec>.freeze, [">= 0"])
94
97
  s.add_dependency(%q<juwelier>.freeze, [">= 0"])
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zsteg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.12
4
+ version: 0.2.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrey "Zed" Zaikin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-02-14 00:00:00.000000000 Z
11
+ date: 2026-01-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zpng
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.4.4
19
+ version: 0.4.6
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.4.4
26
+ version: 0.4.6
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: iostruct
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: 0.7.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: 0.7.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: prime
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +108,7 @@ files:
108
108
  - lib/zsteg/analyzer.rb
109
109
  - lib/zsteg/checker.rb
110
110
  - lib/zsteg/checker/scanline_checker.rb
111
+ - lib/zsteg/checker/steganography_png.rb
111
112
  - lib/zsteg/checker/wbstego.rb
112
113
  - lib/zsteg/checker/zlib.rb
113
114
  - lib/zsteg/cli/cli.rb
@@ -130,11 +131,13 @@ files:
130
131
  - spec/mask_spec.rb
131
132
  - spec/newbiecontest_spec.rb
132
133
  - spec/openstego_spec.rb
134
+ - spec/plte_spec.rb
133
135
  - spec/polictf2012_spec.rb
134
136
  - spec/prime_spec.rb
135
137
  - spec/r3g2b3_spec.rb
136
138
  - spec/simple_spec.rb
137
139
  - spec/spec_helper.rb
140
+ - spec/steganography_png_spec.rb
138
141
  - spec/wbstego_spec.rb
139
142
  - spec/wechall_spec.rb
140
143
  - spec/zlib_spec.rb
@@ -161,7 +164,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
161
164
  - !ruby/object:Gem::Version
162
165
  version: '0'
163
166
  requirements: []
164
- rubygems_version: 3.3.7
167
+ rubygems_version: 3.2.33
165
168
  signing_key:
166
169
  specification_version: 4
167
170
  summary: Detect stegano-hidden data in PNG & BMP files.