unveiler 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/unveiler +67 -0
- data/lib/unveiler.rb +121 -0
- metadata +47 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: deb8e270b0525876f90650a279865f89b2fda044
|
4
|
+
data.tar.gz: 637a0e13085f4577cc8eb616bcf74899763d5b88
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 46c3da0e933f6c31eee9cbe158dda816566a18cd4ff0c875de99b9f19d6163878b698e9275a1d13d6122623e287d8a517aa371dbd6c0f9c24fa537cd8bf3c4d3
|
7
|
+
data.tar.gz: 50ecc0fbfaa9db8f5e037a9ad09b81bbda9b63257bdcf77331d90cc67223ff5bffc43cc21ecf838504d219c2d1da459e0e73b6b55f1e831b334bdd42866e1b32
|
data/bin/unveiler
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'unveiler'
|
3
|
+
|
4
|
+
# Encodes the input file with the specified data, saving the manipulated data
|
5
|
+
# to the output file.
|
6
|
+
#
|
7
|
+
# +input+:: File to be encoded
|
8
|
+
# +output+:: File to write encoded data to
|
9
|
+
# +data+:: Data to be encoded
|
10
|
+
def encode(input, output, data)
|
11
|
+
begin
|
12
|
+
target = read_file(input)
|
13
|
+
data = Unveiler.encode(target, data)
|
14
|
+
write_file(output, data)
|
15
|
+
rescue
|
16
|
+
puts $!
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Decodes the provided input file and saves the decoded data to the output
|
21
|
+
# file.
|
22
|
+
#
|
23
|
+
# +input+:: File to be decoded
|
24
|
+
# +output+:: File to write decoded data to
|
25
|
+
def decode(input, output)
|
26
|
+
begin
|
27
|
+
target = read_file(input)
|
28
|
+
data = Unveiler.decode(target)
|
29
|
+
write_file(output, data + "\n")
|
30
|
+
rescue
|
31
|
+
puts $!.message
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Reads data from the specified file.
|
36
|
+
#
|
37
|
+
# +input+:: File to read
|
38
|
+
def read_file(input)
|
39
|
+
File.open(input, 'rb'){|file| return file.read}
|
40
|
+
end
|
41
|
+
|
42
|
+
# Writes data to the specified file.
|
43
|
+
#
|
44
|
+
# +output+:: File to write
|
45
|
+
def write_file(output, data)
|
46
|
+
File.open(output, 'wb'){|file| file.write(data)}
|
47
|
+
end
|
48
|
+
|
49
|
+
# Prints unveiler usage examples.
|
50
|
+
def usage
|
51
|
+
puts 'Usage:'
|
52
|
+
puts 'unveiler encode [input_file] [output_file] [data]'
|
53
|
+
puts 'unveiler decode [input_file] [output_file]'
|
54
|
+
end
|
55
|
+
|
56
|
+
# Parses ARGV to ensure the correct parameters are given.
|
57
|
+
def parse_argv
|
58
|
+
if ARGV.first == 'encode'
|
59
|
+
ARGV.length == 4 ? encode(ARGV[1], ARGV[2], ARGV[3]) : usage
|
60
|
+
elsif ARGV.first == 'decode'
|
61
|
+
ARGV.length == 3 ? decode(ARGV[1], ARGV[2]) : usage
|
62
|
+
else
|
63
|
+
usage
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
ARGV.empty? ? usage : parse_argv
|
data/lib/unveiler.rb
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
# Unveiler is a basic steganography tool that encodes data by manipulating
|
4
|
+
# existing binary data and can decode previously encoded data.
|
5
|
+
#
|
6
|
+
# Author:: Brendan Goodenough
|
7
|
+
# Copyright:: Copyright (c) 2016 Brendan Goodenough
|
8
|
+
# License:: MIT
|
9
|
+
#
|
10
|
+
# The encoding of the data is achieved by manipulating the least significant
|
11
|
+
# bits of each byte in the target data. These bits are replaced with the next
|
12
|
+
# sequential bit of the data to be encoded, looping until the end of the data
|
13
|
+
# is reached.
|
14
|
+
#
|
15
|
+
# Decoding is achieved by looping through each byte of the target data, and
|
16
|
+
# stringing together the least significant bits of each byte. This is repeated
|
17
|
+
# until the last 3 bytes in the string of bits forms the phrase 'EOF'.
|
18
|
+
|
19
|
+
class Unveiler
|
20
|
+
# Encodes the specified string of data into the target data. Upon success,
|
21
|
+
# a UTF-8 string containing the manipulated data will be returned.
|
22
|
+
#
|
23
|
+
# +target+:: Target data to be manipulated
|
24
|
+
# +data+:: Data to encode
|
25
|
+
def self.encode(target, data)
|
26
|
+
raise ArgumentError, 'target must be a string' unless target.is_a? String
|
27
|
+
raise ArgumentError, 'data must be a string' unless data.is_a? String
|
28
|
+
|
29
|
+
# Convert target data into an array of bytes
|
30
|
+
bytes = target.unpack('B*')[0].scan(/.{8}/)
|
31
|
+
|
32
|
+
# Convert data to binary
|
33
|
+
data = Base64.encode64(data)
|
34
|
+
data += 'EOF'
|
35
|
+
data = data.unpack('B*').first
|
36
|
+
|
37
|
+
# Encode the data into the array of bytes
|
38
|
+
bytes = self.manipulate_bytes(bytes, data)
|
39
|
+
|
40
|
+
# Convert binary to UTF-8
|
41
|
+
bytes.map!{|byte| byte = byte.to_i(2)}
|
42
|
+
return bytes.pack('C*').force_encoding('utf-8')
|
43
|
+
end
|
44
|
+
|
45
|
+
# Decodes any data within the specified target data. Upon success, the decoded
|
46
|
+
# string will be returned.
|
47
|
+
#
|
48
|
+
# +target+:: Target data to be decoded
|
49
|
+
def self.decode(target)
|
50
|
+
raise ArgumentError, 'target must be a String' unless target.is_a? String
|
51
|
+
|
52
|
+
# Convert the target data into an array of bytes
|
53
|
+
bytes = target.unpack('B*')[0].scan(/.{8}/)
|
54
|
+
|
55
|
+
# Obtain string from the bytes
|
56
|
+
bytes = self.process_bytes(bytes)
|
57
|
+
return Base64.decode64(bytes)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Manipulates an array of bytes by looping through and replacing the least
|
61
|
+
# significant bit of each byte with the next sequential bit in a string of
|
62
|
+
# binary data. The loop will break when the end of the data string is
|
63
|
+
# reached. Returns the manipulated array of bytes.
|
64
|
+
#
|
65
|
+
# +bytes+:: Array of bytes to manipulate
|
66
|
+
# +data+:: Binary data to insert
|
67
|
+
def self.manipulate_bytes(bytes, data)
|
68
|
+
count = 0
|
69
|
+
|
70
|
+
8.times do |index|
|
71
|
+
# Loop through each byte and replace the bit at the given index
|
72
|
+
bytes.reverse.each do |byte|
|
73
|
+
byte[7 - index] = data[count]
|
74
|
+
count += 1
|
75
|
+
return bytes if count == data.length
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Ran out of space to encode data
|
80
|
+
raise 'Input file is too small for the specified data'
|
81
|
+
end
|
82
|
+
|
83
|
+
# Processes the specified array of bytes by looping through each byte and
|
84
|
+
# appending the least significant bit onto a string of bits. At the end of
|
85
|
+
# every eighth bit, a check will be made to see if the last 3 bytes of the
|
86
|
+
# string contain the phrase 'EOF'. Only the bytes before EOF will be returned
|
87
|
+
# if the phrase is found. An error will be raised if no EOF is found.
|
88
|
+
#
|
89
|
+
# +bytes+:: Array of bytes to be processed
|
90
|
+
def self.process_bytes(bytes)
|
91
|
+
bits = ''
|
92
|
+
|
93
|
+
8.times do |index|
|
94
|
+
bytes.reverse.each do |byte|
|
95
|
+
# Least significant bit
|
96
|
+
bits += byte[7 - index]
|
97
|
+
|
98
|
+
# Check whether a full byte has been read
|
99
|
+
if bits.length % 8 == 0
|
100
|
+
# Convert bits to array of bytes
|
101
|
+
data = bits.scan(/.{8}/)
|
102
|
+
|
103
|
+
if data.length >= 3
|
104
|
+
# Convert bytes to a UTF-8 string
|
105
|
+
data.map!{|b| byte = b.to_i(2)}
|
106
|
+
data = data.pack('C*').force_encoding('utf-8')
|
107
|
+
len = data.length
|
108
|
+
|
109
|
+
if data[-3,3] == 'EOF'
|
110
|
+
# Return the UTF-8 string, excluding 'EOF'
|
111
|
+
return data[0..len - 3]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# No EOF was found, error
|
119
|
+
raise 'No phrase matching "EOF" was found'
|
120
|
+
end
|
121
|
+
end
|
metadata
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: unveiler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brendan Goodenough
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-05-22 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Unveiler encodes hidden messages within files by manipulating their least
|
14
|
+
significant bits.
|
15
|
+
email: brendan@goodenough.nz
|
16
|
+
executables:
|
17
|
+
- unveiler
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- bin/unveiler
|
22
|
+
- lib/unveiler.rb
|
23
|
+
homepage: https://github.com/nerdenough/unveiler
|
24
|
+
licenses:
|
25
|
+
- MIT
|
26
|
+
metadata: {}
|
27
|
+
post_install_message:
|
28
|
+
rdoc_options: []
|
29
|
+
require_paths:
|
30
|
+
- lib
|
31
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '0'
|
36
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
requirements: []
|
42
|
+
rubyforge_project:
|
43
|
+
rubygems_version: 2.5.1
|
44
|
+
signing_key:
|
45
|
+
specification_version: 4
|
46
|
+
summary: Encode hidden messages in files
|
47
|
+
test_files: []
|