zsteg 0.0.0
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 +15 -0
- data/Gemfile.lock +38 -0
- data/README.md +46 -0
- data/README.md.tpl +31 -0
- data/Rakefile +99 -0
- data/TODO +14 -0
- data/VERSION +1 -0
- data/bin/zsteg +7 -0
- data/cmp_bmp.rb +47 -0
- data/cmp_png.rb +42 -0
- data/lib/zsteg.rb +12 -0
- data/lib/zsteg/checker.rb +228 -0
- data/lib/zsteg/checker/wbstego.rb +98 -0
- data/lib/zsteg/cli.rb +132 -0
- data/lib/zsteg/extractor.rb +21 -0
- data/lib/zsteg/extractor/byte_extractor.rb +94 -0
- data/lib/zsteg/extractor/color_extractor.rb +95 -0
- data/lib/zsteg/file_cmd.rb +63 -0
- data/lib/zsteg/result.rb +90 -0
- data/pngsteg.gemspec +65 -0
- data/samples/06_enc.png +0 -0
- data/samples/Code.png +0 -0
- data/samples/README +4 -0
- 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/samples/wbsteg_noenc.bmp +0 -0
- data/samples/wbsteg_noenc_17.bmp +0 -0
- data/samples/wbsteg_noenc_even.bmp +0 -0
- data/samples/wbsteg_noenc_even_17.bmp +0 -0
- data/spec/camouflage_spec.rb +9 -0
- data/spec/cats_spec.rb +23 -0
- data/spec/flowers_spec.rb +11 -0
- data/spec/openstego_spec.rb +21 -0
- data/spec/simple_spec.rb +22 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/wbstego_spec.rb +10 -0
- data/spec/zlib_spec.rb +6 -0
- metadata +198 -0
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
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"
|
7
|
+
gem "iostruct"
|
8
|
+
|
9
|
+
# Add dependencies to develop your gem here.
|
10
|
+
# Include everything needed to run rake, tests, features, etc.
|
11
|
+
group :development do
|
12
|
+
gem "rspec", ">= 2.8.0"
|
13
|
+
gem "bundler", ">= 1.0.0"
|
14
|
+
gem "jeweler", "~> 1.8.4"
|
15
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
awesome_print (1.1.0)
|
5
|
+
diff-lcs (1.1.3)
|
6
|
+
git (1.2.5)
|
7
|
+
iostruct (0.0.1)
|
8
|
+
jeweler (1.8.4)
|
9
|
+
bundler (~> 1.0)
|
10
|
+
git (>= 1.2.5)
|
11
|
+
rake
|
12
|
+
rdoc
|
13
|
+
json (1.7.5)
|
14
|
+
rainbow (1.1.4)
|
15
|
+
rake (10.0.3)
|
16
|
+
rdoc (3.12)
|
17
|
+
json (~> 1.4)
|
18
|
+
rspec (2.12.0)
|
19
|
+
rspec-core (~> 2.12.0)
|
20
|
+
rspec-expectations (~> 2.12.0)
|
21
|
+
rspec-mocks (~> 2.12.0)
|
22
|
+
rspec-core (2.12.2)
|
23
|
+
rspec-expectations (2.12.1)
|
24
|
+
diff-lcs (~> 1.1.3)
|
25
|
+
rspec-mocks (2.12.1)
|
26
|
+
zpng (0.2.1)
|
27
|
+
rainbow
|
28
|
+
|
29
|
+
PLATFORMS
|
30
|
+
ruby
|
31
|
+
|
32
|
+
DEPENDENCIES
|
33
|
+
awesome_print
|
34
|
+
bundler (>= 1.0.0)
|
35
|
+
iostruct
|
36
|
+
jeweler (~> 1.8.4)
|
37
|
+
rspec (>= 2.8.0)
|
38
|
+
zpng (>= 0.2.1)
|
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
zsteg
|
2
|
+
======
|
3
|
+
|
4
|
+
|
5
|
+
Description
|
6
|
+
-----------
|
7
|
+
detect stegano-hidden data in PNG & BMP
|
8
|
+
|
9
|
+
|
10
|
+
Installation
|
11
|
+
------------
|
12
|
+
gem install zsteg
|
13
|
+
|
14
|
+
|
15
|
+
Detects:
|
16
|
+
--------
|
17
|
+
* LSB steganography in PNG & BMP
|
18
|
+
* zlib-compressed data
|
19
|
+
* [OpenStego](http://openstego.sourceforge.net/)
|
20
|
+
* [Camouflage 1.2.1](http://camouflage.unfiction.com/)
|
21
|
+
|
22
|
+
|
23
|
+
Usage
|
24
|
+
-----
|
25
|
+
|
26
|
+
# zsteg -h
|
27
|
+
|
28
|
+
Usage: zsteg [options] filename.png
|
29
|
+
|
30
|
+
-c, --channels X channels (R/G/B/A) or any combination, comma separated
|
31
|
+
valid values: r,g,b,a,rg,rgb,bgr,rgba,...
|
32
|
+
-l, --limit N limit bytes checked, 0 = no limit (default: 256)
|
33
|
+
-b, --bits N number of bits (1..8), single value or '1,3,5' or '1-8'
|
34
|
+
--lsb least significant BIT comes first
|
35
|
+
--msb most significant BIT comes first
|
36
|
+
-o, --order X pixel iteration order (default: 'auto')
|
37
|
+
valid values: ALL,xy,yx,XY,YX,xY,Xy,bY,...
|
38
|
+
-E, --extract NAME extract specified payload, NAME is like '1b,rgb,lsb'
|
39
|
+
|
40
|
+
-v, --verbose Run verbosely (can be used multiple times)
|
41
|
+
-q, --quiet Silent any warnings (can be used multiple times)
|
42
|
+
|
43
|
+
|
44
|
+
License
|
45
|
+
-------
|
46
|
+
Released under the MIT License. See the [LICENSE](https://github.com/zed-0xff/zsteg/blob/master/LICENSE.txt) file for further details.
|
data/README.md.tpl
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
zsteg
|
2
|
+
======
|
3
|
+
|
4
|
+
|
5
|
+
Description
|
6
|
+
-----------
|
7
|
+
detect stegano-hidden data in PNG & BMP
|
8
|
+
|
9
|
+
|
10
|
+
Installation
|
11
|
+
------------
|
12
|
+
gem install zsteg
|
13
|
+
|
14
|
+
|
15
|
+
Detects:
|
16
|
+
--------
|
17
|
+
* LSB steganography in PNG & BMP
|
18
|
+
* zlib-compressed data
|
19
|
+
* [OpenStego](http://openstego.sourceforge.net/)
|
20
|
+
* [Camouflage 1.2.1](http://camouflage.unfiction.com/)
|
21
|
+
|
22
|
+
|
23
|
+
Usage
|
24
|
+
-----
|
25
|
+
|
26
|
+
% zsteg -h
|
27
|
+
|
28
|
+
|
29
|
+
License
|
30
|
+
-------
|
31
|
+
Released under the MIT License. See the [LICENSE](https://github.com/zed-0xff/zsteg/blob/master/LICENSE.txt) file for further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "zsteg"
|
18
|
+
gem.homepage = "http://github.com/zed-0xff/zsteg"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Detect stegano-hidden data in PNG & BMP files.}
|
21
|
+
#gem.description = %Q{TODO: longer description of your gem}
|
22
|
+
gem.email = "zed.0xff@gmail.com"
|
23
|
+
gem.authors = ["Andrey \"Zed\" Zaikin"]
|
24
|
+
#gem.executables = %w'zsteg'
|
25
|
+
gem.files.include "lib/**/*.rb"
|
26
|
+
gem.files.include "bin/zsteg"
|
27
|
+
# dependencies defined in Gemfile
|
28
|
+
end
|
29
|
+
Jeweler::RubygemsDotOrgTasks.new
|
30
|
+
|
31
|
+
require 'rspec/core'
|
32
|
+
require 'rspec/core/rake_task'
|
33
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
34
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
35
|
+
end
|
36
|
+
|
37
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
38
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
39
|
+
spec.rcov = true
|
40
|
+
end
|
41
|
+
|
42
|
+
task :default => :spec
|
43
|
+
|
44
|
+
desc "build readme"
|
45
|
+
task :readme do
|
46
|
+
require 'erb'
|
47
|
+
tpl = File.read('README.md.tpl').gsub(/^%\s+(.+)/) do |x|
|
48
|
+
x.sub! /^%/,''
|
49
|
+
"<%= run(\"#{x}\") %>"
|
50
|
+
end
|
51
|
+
def run cmd
|
52
|
+
cmd.strip!
|
53
|
+
puts "[.] #{cmd} ..."
|
54
|
+
r = " # #{cmd}\n\n"
|
55
|
+
cmd.sub! /^zsteg/,"../bin/zsteg"
|
56
|
+
lines = `#{cmd}`.sub(/\A\n+/m,'').sub(/\s+\Z/,'').split("\n")
|
57
|
+
lines = lines[0,25] + ['...'] if lines.size > 50
|
58
|
+
r << lines.map{|x| " #{x}"}.join("\n")
|
59
|
+
r << "\n"
|
60
|
+
end
|
61
|
+
Dir.chdir 'samples'
|
62
|
+
result = ERB.new(tpl,nil,'%>').result
|
63
|
+
Dir.chdir '..'
|
64
|
+
File.open('README.md','w'){ |f| f << result }
|
65
|
+
end
|
66
|
+
|
67
|
+
Rake::Task[:console].clear
|
68
|
+
|
69
|
+
# from /usr/local/lib64/ruby/gems/1.9.1/gems/jeweler-1.8.4/lib/jeweler/tasks.rb
|
70
|
+
desc "Start IRB with all runtime dependencies loaded"
|
71
|
+
task :console, [:script] do |t,args|
|
72
|
+
dirs = ['./ext', './lib'].select { |dir| File.directory?(dir) }
|
73
|
+
|
74
|
+
original_load_path = $LOAD_PATH
|
75
|
+
|
76
|
+
cmd = if File.exist?('Gemfile')
|
77
|
+
require 'bundler'
|
78
|
+
Bundler.setup(:default)
|
79
|
+
end
|
80
|
+
|
81
|
+
# add the project code directories
|
82
|
+
$LOAD_PATH.unshift(*dirs)
|
83
|
+
|
84
|
+
# clear ARGV so IRB is not confused
|
85
|
+
ARGV.clear
|
86
|
+
|
87
|
+
require 'irb'
|
88
|
+
|
89
|
+
# ZZZ actually added only these 2 lines
|
90
|
+
require 'zsteg'
|
91
|
+
include ZSteg
|
92
|
+
|
93
|
+
# set the optional script to run
|
94
|
+
IRB.conf[:SCRIPT] = args.script
|
95
|
+
IRB.start
|
96
|
+
|
97
|
+
# return the $LOAD_PATH to it's original state
|
98
|
+
$LOAD_PATH.reject! { |path| !(original_load_path.include?(path)) }
|
99
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
[ ] make 'extract' cmd stream its data directly to stdout
|
2
|
+
[ ] gzip
|
3
|
+
[*] wbStego
|
4
|
+
[ ] openstego
|
5
|
+
[ ] tmp/steg*/*.bmp
|
6
|
+
[ ] 4bpp/8bpp BMP
|
7
|
+
|
8
|
+
[+] auto pixel order for BMP
|
9
|
+
[+] BMP
|
10
|
+
[+] zlib
|
11
|
+
|
12
|
+
[ ] detect AES from http://punkroy.drque.net/PNG_Steganography/Steganography5.php
|
13
|
+
[?] http://tobyinkster.co.uk/article/steg-encode/
|
14
|
+
[ ] Sieve of Eratosthenes: http://wiki.cedricbonhomme.org/security:steganography
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.0
|
data/bin/zsteg
ADDED
data/cmp_bmp.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'zpng'
|
3
|
+
require 'awesome_print'
|
4
|
+
|
5
|
+
@show_all = true
|
6
|
+
|
7
|
+
images = ARGV.map{ |fname| ZPNG::Image.load(fname) }
|
8
|
+
raise "need at least 2 images" if images.size < 2
|
9
|
+
|
10
|
+
limit = 25
|
11
|
+
alpha_used = images.any?(&:alpha_used?)
|
12
|
+
channels = alpha_used ? %w'r g b a' : %w'r g b'
|
13
|
+
channels.reverse!
|
14
|
+
|
15
|
+
printf "%6s %4s %4s : %s ...\n".magenta, "#", "X", "Y", (alpha_used ? "RRGGBBAA":"RRGGBB").reverse
|
16
|
+
|
17
|
+
idx = ndiff = 0
|
18
|
+
(images[0].height-1).downto(0) do |y|
|
19
|
+
0.upto(images[0].width-1) do |x|
|
20
|
+
colors = images.map{ |img| img[x,y] }
|
21
|
+
if colors.uniq.size > 1 || @show_all
|
22
|
+
ndiff += 1
|
23
|
+
printf "%6d %4d %4d : ", idx, x, y
|
24
|
+
t = Array.new(images.size){ '' }
|
25
|
+
channels.each do |channel|
|
26
|
+
values = colors.map{ |color| color.send(channel) }
|
27
|
+
if values.uniq.size == 1
|
28
|
+
# all equal
|
29
|
+
values.each_with_index do |value,idx|
|
30
|
+
t[idx] << "%02x".gray % value
|
31
|
+
end
|
32
|
+
else
|
33
|
+
# got diff
|
34
|
+
values.each_with_index do |value,idx|
|
35
|
+
t[idx] << "%02x".red % value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
puts t.join(' ')
|
40
|
+
end
|
41
|
+
idx += 1
|
42
|
+
if limit && ndiff >= limit
|
43
|
+
puts "[.] diff limit #{limit} reached"
|
44
|
+
exit
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/cmp_png.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'zpng'
|
3
|
+
require 'awesome_print'
|
4
|
+
|
5
|
+
images = ARGV.map{ |fname| ZPNG::Image.load(fname) }
|
6
|
+
raise "need at least 2 images" if images.size < 2
|
7
|
+
|
8
|
+
limit = 20
|
9
|
+
alpha_used = images.any?(&:alpha_used?)
|
10
|
+
channels = alpha_used ? %w'r g b a' : %w'r g b'
|
11
|
+
|
12
|
+
printf "%6s %4s %4s : %s ...\n".magenta, "#", "X", "Y", (alpha_used ? "RRGGBBAA":"RRGGBB")
|
13
|
+
|
14
|
+
idx = ndiff = 0
|
15
|
+
images[0].each_pixel do |c,x,y|
|
16
|
+
colors = images.map{ |img| img[x,y] }
|
17
|
+
if colors.uniq.size > 1
|
18
|
+
ndiff += 1
|
19
|
+
printf "%6d %4d %4d : ", idx, x, y
|
20
|
+
t = Array.new(images.size){ '' }
|
21
|
+
channels.each do |channel|
|
22
|
+
values = colors.map{ |color| color.send(channel) }
|
23
|
+
if values.uniq.size == 1
|
24
|
+
# all equal
|
25
|
+
values.each_with_index do |value,idx|
|
26
|
+
t[idx] << "%02x".gray % value
|
27
|
+
end
|
28
|
+
else
|
29
|
+
# got diff
|
30
|
+
values.each_with_index do |value,idx|
|
31
|
+
t[idx] << "%02x".red % value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
puts t.join(' ')
|
36
|
+
end
|
37
|
+
idx += 1
|
38
|
+
if limit && ndiff >= limit
|
39
|
+
puts "[.] diff limit #{limit} reached"
|
40
|
+
break
|
41
|
+
end
|
42
|
+
end
|
data/lib/zsteg.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'zpng'
|
2
|
+
require 'iostruct'
|
3
|
+
|
4
|
+
require 'zsteg/extractor/byte_extractor'
|
5
|
+
require 'zsteg/extractor/color_extractor'
|
6
|
+
require 'zsteg/extractor'
|
7
|
+
|
8
|
+
require 'zsteg/checker'
|
9
|
+
require 'zsteg/result'
|
10
|
+
require 'zsteg/file_cmd'
|
11
|
+
|
12
|
+
require 'zsteg/checker/wbstego'
|
@@ -0,0 +1,228 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'zlib'
|
3
|
+
|
4
|
+
module ZSteg
|
5
|
+
class Checker
|
6
|
+
attr_accessor :params, :channels, :verbose
|
7
|
+
|
8
|
+
MIN_TEXT_LENGTH = 8
|
9
|
+
|
10
|
+
# image can be either filename or ZPNG::Image
|
11
|
+
def initialize image, params = {}
|
12
|
+
@params = params
|
13
|
+
@cache = {}
|
14
|
+
@image = image.is_a?(ZPNG::Image) ? image : ZPNG::Image.load(image)
|
15
|
+
@extractor = Extractor.new(@image, params)
|
16
|
+
@channels = params[:channels] ||
|
17
|
+
if @image.alpha_used?
|
18
|
+
%w'r g b a rgb bgr rgba abgr'
|
19
|
+
else
|
20
|
+
%w'r g b rgb bgr'
|
21
|
+
end
|
22
|
+
@verbose = params[:verbose] || 0
|
23
|
+
@file_cmd = FileCmd.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def check
|
27
|
+
@found_anything = false
|
28
|
+
@file_cmd.start!
|
29
|
+
|
30
|
+
check_extradata
|
31
|
+
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'
|
38
|
+
end
|
39
|
+
|
40
|
+
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 )
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
if @found_anything
|
54
|
+
print "\r" + " "*20 + "\r" if @need_cr
|
55
|
+
else
|
56
|
+
puts "\r[=] nothing :(" + " "*20 # line cleanup
|
57
|
+
end
|
58
|
+
ensure
|
59
|
+
@file_cmd.stop!
|
60
|
+
end
|
61
|
+
|
62
|
+
def check_extradata
|
63
|
+
if @image.extradata
|
64
|
+
@found_anything = true
|
65
|
+
title = "data after IEND"
|
66
|
+
show_title title, :red
|
67
|
+
process_result @image.extradata, :special => true, :title => title
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def check_metadata
|
72
|
+
@image.metadata.each do |k,v|
|
73
|
+
@found_anything = true
|
74
|
+
show_title(title = "meta #{k}")
|
75
|
+
process_result v, :special => true, :title => title
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def check_channels channels, params
|
80
|
+
unless params[:bit_order]
|
81
|
+
check_channels(channels, params.merge(:bit_order => :lsb))
|
82
|
+
check_channels(channels, params.merge(:bit_order => :msb))
|
83
|
+
return
|
84
|
+
end
|
85
|
+
|
86
|
+
title = ["#{params[:bits]}b",channels,params[:bit_order],params[:order]].compact.join(',')
|
87
|
+
show_title title
|
88
|
+
|
89
|
+
p1 = params.clone
|
90
|
+
p1.delete :channel
|
91
|
+
p1[:title] = title
|
92
|
+
|
93
|
+
if channels
|
94
|
+
p1[:channels] = channels.split('')
|
95
|
+
@max_hidden_size = p1[:channels].size*@image.width
|
96
|
+
elsif params[:order] =~ /b/i
|
97
|
+
# byte extractor
|
98
|
+
@max_hidden_size = @image.scanlines[0].decoded_bytes.size
|
99
|
+
else
|
100
|
+
raise "invalid params #{params.inspect}"
|
101
|
+
end
|
102
|
+
@max_hidden_size *= p1[:bits]*@image.height/8
|
103
|
+
|
104
|
+
data = @extractor.extract p1
|
105
|
+
|
106
|
+
@need_cr = !process_result(data, p1) # carriage return needed?
|
107
|
+
@found_anything ||= !@need_cr
|
108
|
+
end
|
109
|
+
|
110
|
+
def show_title title, color = :gray
|
111
|
+
printf "\r[.] %-14s.. ".send(color), title
|
112
|
+
$stdout.flush
|
113
|
+
end
|
114
|
+
|
115
|
+
# returns true if was any output
|
116
|
+
def process_result data, params
|
117
|
+
verbose = params[:special] ? [@verbose,1.5].max : @verbose
|
118
|
+
|
119
|
+
if @cache[data]
|
120
|
+
if verbose > 1
|
121
|
+
puts "[same as #{@cache[data].inspect}]".gray
|
122
|
+
return true
|
123
|
+
else
|
124
|
+
# silent return
|
125
|
+
return false
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# TODO: store hash of data for large datas
|
130
|
+
@cache[data] = params[:title]
|
131
|
+
|
132
|
+
result = data2result data, params
|
133
|
+
|
134
|
+
case verbose
|
135
|
+
when -999..0
|
136
|
+
# verbosity=0: only show result if anything interesting found
|
137
|
+
if result && !result.is_a?(Result::OneChar)
|
138
|
+
puts result
|
139
|
+
return true
|
140
|
+
else
|
141
|
+
return false
|
142
|
+
end
|
143
|
+
when 1
|
144
|
+
# verbosity=1: if anything interesting found show result & hexdump
|
145
|
+
return false unless result
|
146
|
+
end
|
147
|
+
|
148
|
+
# verbosity>1: always show hexdump
|
149
|
+
|
150
|
+
if params[:special]
|
151
|
+
puts result.is_a?(Result::PartialText) ? nil : result
|
152
|
+
else
|
153
|
+
puts result
|
154
|
+
end
|
155
|
+
if data.size > 0 && !result.is_a?(Result::OneChar) && !result.is_a?(Result::WholeText)
|
156
|
+
print ZPNG::Hexdump.dump(data){ |x| x.prepend(" "*4) }
|
157
|
+
end
|
158
|
+
true
|
159
|
+
end
|
160
|
+
|
161
|
+
def data2result data, params
|
162
|
+
if one_char?(data)
|
163
|
+
return Result::OneChar.new(data[0,1], data.size)
|
164
|
+
end
|
165
|
+
|
166
|
+
if idx = data.index('OPENSTEGO')
|
167
|
+
io = StringIO.new(data)
|
168
|
+
io.seek(idx+9)
|
169
|
+
return Result::OpenStego.read(io)
|
170
|
+
end
|
171
|
+
|
172
|
+
if data[0,2] == "\x00\x00" && data[3,3] == "\xed\xcd\x01"
|
173
|
+
return Result::Camouflage.new(data)
|
174
|
+
end
|
175
|
+
|
176
|
+
# only BMP & 1-bit-per-channel
|
177
|
+
if params[:bits] == 1 && params[:bit_order] == :lsb
|
178
|
+
if x = WBStego.check(data, params.merge(
|
179
|
+
:image => @image,
|
180
|
+
:max_hidden_size => @max_hidden_size
|
181
|
+
))
|
182
|
+
return x
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
if data =~ /\A[\x20-\x7e\r\n\t]+\Z/
|
187
|
+
# whole ASCII
|
188
|
+
return Result::WholeText.new(data, 0)
|
189
|
+
end
|
190
|
+
|
191
|
+
if r = @file_cmd.check_data(data)
|
192
|
+
return Result::FileCmd.new(r, data)
|
193
|
+
end
|
194
|
+
|
195
|
+
# http://blog.w3challs.com/index.php?post/2012/03/25/NDH2k12-Prequals-We-are-looking-for-a-real-hacker-Wallpaper-image
|
196
|
+
# http://blog.w3challs.com/public/ndh2k12_prequalls/sp113.bmp
|
197
|
+
if idx = data.index(/\x78[\x9c\xda\x01]/)
|
198
|
+
begin
|
199
|
+
# x = Zlib::Inflate.inflate(data[idx,4096])
|
200
|
+
zi = Zlib::Inflate.new(Zlib::MAX_WBITS)
|
201
|
+
x = zi.inflate data[idx..-1]
|
202
|
+
# decompress OK
|
203
|
+
return Result::Zlib.new x, idx
|
204
|
+
rescue Zlib::BufError
|
205
|
+
# tried to decompress, but got EOF - need more data
|
206
|
+
return Result::Zlib.new x, idx
|
207
|
+
rescue Zlib::DataError, Zlib::NeedDict
|
208
|
+
# not a zlib
|
209
|
+
ensure
|
210
|
+
zi.close if zi && !zi.closed?
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
if (r=data[/[\x20-\x7e\r\n\t]{#{MIN_TEXT_LENGTH},}/])
|
215
|
+
return Result::PartialText.new(r, data.index(r))
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
private
|
220
|
+
|
221
|
+
# returns true if String s consists of one repeating character
|
222
|
+
# performance-optimized
|
223
|
+
# 16Mb string = 0.7s on Core i5 1.7GHz
|
224
|
+
def one_char? s
|
225
|
+
(s =~ /\A(.)\1+\Z/m) == 0
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|