zsteg 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|