memorandom 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +41 -0
- data/bin/memorandom.rb +70 -0
- data/lib/memorandom.rb +5 -0
- data/lib/memorandom/plugins.rb +31 -0
- data/lib/memorandom/plugins/aes.rb +188 -0
- data/lib/memorandom/plugins/capi.rb +62 -0
- data/lib/memorandom/plugins/cc.rb +36 -0
- data/lib/memorandom/plugins/der.rb +43 -0
- data/lib/memorandom/plugins/hashes.rb +36 -0
- data/lib/memorandom/plugins/pem.rb +22 -0
- data/lib/memorandom/plugins/rsa.rb +71 -0
- data/lib/memorandom/plugins/template.rb +46 -0
- data/lib/memorandom/plugins/url_params.rb +22 -0
- data/lib/memorandom/scanner.rb +152 -0
- data/lib/memorandom/version.rb +3 -0
- data/memorandom.gemspec +34 -0
- metadata +130 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Rapid7
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
Memorandom
|
2
|
+
==
|
3
|
+
|
4
|
+
Memorandom provides a command-line utility and class library from extracting secrets from binary files. Common use cases include extracting encryption keys from memory dumps and identifying sensitive data stored in block devices.
|
5
|
+
|
6
|
+
|
7
|
+
Installation
|
8
|
+
--
|
9
|
+
$ git clone https://github.com/rapid7/memorandom
|
10
|
+
$ cd memorandom
|
11
|
+
$ bundle install
|
12
|
+
|
13
|
+
Usage
|
14
|
+
--
|
15
|
+
$ bin/memorandom.rb --help
|
16
|
+
|
17
|
+
Usage: bin/memorandom.rb [options] file1 file2 ... fileN
|
18
|
+
Extracts interesting data from binary files
|
19
|
+
|
20
|
+
Options
|
21
|
+
-p [plugin1,plugin2,plugin3...], Specify a list of plugin names to use, otherwise use all plugins
|
22
|
+
--plugins
|
23
|
+
-o, --output [directory] Specify the directory in which to store found data
|
24
|
+
-w, --window [number] Specify the number of kilobytes to scan at once (default: 1024).
|
25
|
+
-x, --overlap [number] Specify the number of kilobytes to overlap between windows (default: 4).
|
26
|
+
-l, --list-plugins List all of the available plugins
|
27
|
+
-h, --help Show this message.
|
28
|
+
|
29
|
+
|
30
|
+
Memorandom can search files, block devices, and standard input for interesting things and automatically extract and save these into the specified output directory (--output). This makes it useful for processing memory dumps, network traffic logs, hard disk images, or entire filesystems.
|
31
|
+
|
32
|
+
|
33
|
+
Memorandom uses plugins to scan for specific types of data within the target files. Ny default, all plugins are enabled, which can lead to noisy and slow output. For small files, all plugins are usually fine, but when processing large amounts of data, it is better to limit the search to specific plugins.
|
34
|
+
|
35
|
+
The example below will only scan for PEM-encoded data in the target files
|
36
|
+
|
37
|
+
$ bin/memorandom.rb -p pem ~/.ssh/*
|
38
|
+
[+] file:/home/dev/.ssh/ec2.pem PEM@0 ("-----BEGIN RSA PRIVATE KEY-----\r"...)
|
39
|
+
[+] file:/home/dev/.ssh/id_dsa PEM@0 ("-----BEGIN DSA PRIVATE KEY-----\n"...)
|
40
|
+
[+] file:/home/dev/.ssh/id_rsa PEM@0 ("-----BEGIN RSA PRIVATE KEY-----\n"...)
|
41
|
+
|
data/bin/memorandom.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
|
4
|
+
require 'optparse'
|
5
|
+
require 'ostruct'
|
6
|
+
require 'memorandom'
|
7
|
+
|
8
|
+
options = {}
|
9
|
+
|
10
|
+
option_parser = OptionParser.new do |opts|
|
11
|
+
opts.banner = "Usage: #{$0} [options] file1 file2 ... fileN"
|
12
|
+
opts.separator "Extracts interesting data from binary files"
|
13
|
+
opts.separator ""
|
14
|
+
opts.separator "Options"
|
15
|
+
|
16
|
+
opts.on("-p", "--plugins [plugin1,plugin2,plugin3...]",
|
17
|
+
"Specify a list of plugin names to use, otherwise use all plugins"
|
18
|
+
) do |plugins|
|
19
|
+
options[:plugins] = plugins.split(",").map{ |x| x.downcase.strip }
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on("-o", "--output [directory]",
|
23
|
+
"Specify the directory in which to store found data") do |out|
|
24
|
+
options[:output] = out
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on("-w", "--window [number]",
|
28
|
+
"Specify the number of kilobytes to scan at once (default: 1024).") do |num|
|
29
|
+
options[:window] = (num.to_i == 0) ? (1024*1024) : (num.to_i * 1024)
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on("-x", "--overlap [number]",
|
33
|
+
"Specify the number of kilobytes to overlap between windows (default: 4).") do |num|
|
34
|
+
options[:overlap] = (num.to_i == 0) ? (4*1024) : (num.to_i * 1024)
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on("-l", "--list-plugins",
|
38
|
+
"List all of the available plugins") do
|
39
|
+
puts ""
|
40
|
+
puts "Memorandom Plugins"
|
41
|
+
puts "==============="
|
42
|
+
|
43
|
+
Memorandom::PluginManager.plugins.each_pair do |name, klass|
|
44
|
+
puts " * #{name.ljust(24)} #{klass.description}"
|
45
|
+
end
|
46
|
+
puts ""
|
47
|
+
exit
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.on("-h", "--help", "Show this message.") do
|
51
|
+
puts opts
|
52
|
+
exit
|
53
|
+
end
|
54
|
+
end
|
55
|
+
option_parser.parse!(ARGV)
|
56
|
+
|
57
|
+
if ARGV.count < 1
|
58
|
+
puts option_parser
|
59
|
+
exit
|
60
|
+
end
|
61
|
+
|
62
|
+
scanner = Memorandom::Scanner.new(options)
|
63
|
+
if scanner.plugins.length == 0
|
64
|
+
$stderr.puts "Error: No valid plugins have been selected"
|
65
|
+
exit(1)
|
66
|
+
end
|
67
|
+
|
68
|
+
ARGV.each do |target|
|
69
|
+
scanner.scan(target)
|
70
|
+
end
|
data/lib/memorandom.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Memorandom
|
2
|
+
class PluginManager
|
3
|
+
@@plugins = {}
|
4
|
+
|
5
|
+
def self.register(name, klass)
|
6
|
+
@@plugins[name.downcase.gsub(/\s+/, '_')] = klass
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.plugins
|
10
|
+
@@plugins
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.load_plugins
|
14
|
+
Memorandom::Plugins.constants.each do |c|
|
15
|
+
register(c.to_s, Memorandom::Plugins.const_get(c))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'memorandom/plugins/template'
|
23
|
+
|
24
|
+
require 'memorandom/plugins/pem'
|
25
|
+
require 'memorandom/plugins/der'
|
26
|
+
require 'memorandom/plugins/rsa'
|
27
|
+
require 'memorandom/plugins/aes'
|
28
|
+
require 'memorandom/plugins/capi'
|
29
|
+
require 'memorandom/plugins/url_params'
|
30
|
+
require 'memorandom/plugins/hashes'
|
31
|
+
require 'memorandom/plugins/cc'
|
@@ -0,0 +1,188 @@
|
|
1
|
+
module Memorandom
|
2
|
+
module Plugins
|
3
|
+
class AES < PluginTemplate
|
4
|
+
|
5
|
+
@@description = "This plugin looks for AES encryption keys (128/256)"
|
6
|
+
@@confidence = 0.50
|
7
|
+
|
8
|
+
#
|
9
|
+
# This code is a ruby implementation of AESKeyFind (C) 2008-07-18 Nadia Heninger and Ariel Feldman
|
10
|
+
# It is approximately 360 times slower than the C implementation :(
|
11
|
+
#
|
12
|
+
|
13
|
+
AES_KEYBLOCK_MIN = 176
|
14
|
+
AES_XOR_LIMIT = 10
|
15
|
+
AES_SBOX = [
|
16
|
+
# 0 1 2 3 4 5 6 7 8 9 A B C D E F
|
17
|
+
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, #0
|
18
|
+
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, #1
|
19
|
+
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, #2
|
20
|
+
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, #3
|
21
|
+
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, #4
|
22
|
+
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, #5
|
23
|
+
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, #6
|
24
|
+
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, #7
|
25
|
+
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, #8
|
26
|
+
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, #9
|
27
|
+
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, #A
|
28
|
+
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, #B
|
29
|
+
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, #C
|
30
|
+
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, #D
|
31
|
+
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, #E
|
32
|
+
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 #F
|
33
|
+
]
|
34
|
+
|
35
|
+
AES_RCON = [
|
36
|
+
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
|
37
|
+
0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39,
|
38
|
+
0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
|
39
|
+
0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,
|
40
|
+
0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef,
|
41
|
+
0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
|
42
|
+
0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b,
|
43
|
+
0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
|
44
|
+
0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
|
45
|
+
0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20,
|
46
|
+
0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
|
47
|
+
0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
|
48
|
+
0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
|
49
|
+
0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63,
|
50
|
+
0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
|
51
|
+
0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb
|
52
|
+
]
|
53
|
+
|
54
|
+
BYTE_VALUES = Array.new([0]*256)
|
55
|
+
ENTROPIC_MIN = 8
|
56
|
+
|
57
|
+
M1 = 0x55555555
|
58
|
+
M2 = 0x33333333
|
59
|
+
M4 = 0x0f0f0f0f
|
60
|
+
|
61
|
+
# Perform the AES key core operation on a word.
|
62
|
+
# (Assumes the standard byte order.)
|
63
|
+
def aes_key_core(k, i)
|
64
|
+
t = 0
|
65
|
+
0.upto(3) do |j|
|
66
|
+
t = aes_set_byte(t, (j-1) % 4, AES_SBOX[aes_get_byte(k, j)])
|
67
|
+
end
|
68
|
+
aes_set_byte(t, 0, aes_get_byte(t,0) ^ AES_RCON[i])
|
69
|
+
end
|
70
|
+
|
71
|
+
# Run each byte of a word through the sbox separately for word 4 of 256-bit AES.
|
72
|
+
def aes_sbox_bytes(k)
|
73
|
+
r = 0
|
74
|
+
0.upto(3) do |j|
|
75
|
+
r = aes_set_byte(r, j, AES_SBOX[aes_get_byte(k,j)])
|
76
|
+
end
|
77
|
+
r
|
78
|
+
end
|
79
|
+
|
80
|
+
# Return bit n of vector.
|
81
|
+
def aes_bit(vector, n)
|
82
|
+
(vector >> n) & 1
|
83
|
+
end
|
84
|
+
|
85
|
+
# Set byte n of vector to val.
|
86
|
+
def aes_set_byte(vector, n, val)
|
87
|
+
(vector & ~(0xff << (8*n))) | (val << (8*n))
|
88
|
+
end
|
89
|
+
|
90
|
+
# Return byte n of vector.
|
91
|
+
def aes_get_byte(vector, n)
|
92
|
+
(vector >> (8*n)) & 0xff
|
93
|
+
end
|
94
|
+
|
95
|
+
# Return the number of bits in x that are 1.
|
96
|
+
def aes_popcount(x)
|
97
|
+
x -= (x >> 1) & M1
|
98
|
+
x = (x & M2) + ((x >> 2) & M2)
|
99
|
+
x = (x + (x >> 4)) & M4
|
100
|
+
x += x >> 8
|
101
|
+
(x + (x >> 16)) & 0x3f
|
102
|
+
end
|
103
|
+
|
104
|
+
# Identifies byte windows that are "entropic" (< limit repeats of any byte)
|
105
|
+
# Algorithm adapted from aeskeyfind.c
|
106
|
+
def entropic(buff, window, limit)
|
107
|
+
bytes = buff.unpack("C*")
|
108
|
+
index = 0
|
109
|
+
|
110
|
+
while index < (bytes.length - window)
|
111
|
+
counts = BYTE_VALUES.dup
|
112
|
+
index.upto(index+window) {|offset| counts[ bytes[offset] ] += 1 }
|
113
|
+
mvalue = counts.max
|
114
|
+
|
115
|
+
if mvalue < limit
|
116
|
+
yield(index)
|
117
|
+
# Key-hunting time, skip ahead by one byte only
|
118
|
+
index += 1
|
119
|
+
else
|
120
|
+
# Not entropic, skip ahead by at least max - limit
|
121
|
+
index += ((mvalue + 1) - limit)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Scan takes a buffer and an offset of where this buffer starts in the source
|
127
|
+
def scan(buffer, source_offset)
|
128
|
+
|
129
|
+
blen = buffer.length
|
130
|
+
entropic(buffer, AES_KEYBLOCK_MIN, ENTROPIC_MIN) do |i|
|
131
|
+
|
132
|
+
# Exit if we dont have at least 240 bytes left
|
133
|
+
return if (i + 240) > blen
|
134
|
+
|
135
|
+
# Create a byte map to work in both little and big endian formats
|
136
|
+
#["V", "N"].each do |endian|
|
137
|
+
|
138
|
+
["V"].each do |endian|
|
139
|
+
bmap = buffer[i, 240].unpack("#{endian}*")
|
140
|
+
|
141
|
+
# Check distance from 256-bit AES key
|
142
|
+
xor_count_256 = 0
|
143
|
+
1.upto(7) do |row|
|
144
|
+
0.upto(7) do |column|
|
145
|
+
break if (row == 7 and column == 4)
|
146
|
+
case column
|
147
|
+
when 0
|
148
|
+
xor_count_256 += aes_popcount( aes_key_core( bmap[8*row-1], row) ^ bmap[8*(row-1)] ^ bmap[8*row] )
|
149
|
+
when 4
|
150
|
+
xor_count_256 += aes_popcount( aes_sbox_bytes( bmap[8*row+3]) ^ bmap[8*(row-1)+4] ^ bmap[8*row+4])
|
151
|
+
else
|
152
|
+
xor_count_256 += aes_popcount( bmap[8*row+column-1] ^ bmap[8*(row-1)+column] ^ bmap[8*row + column])
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# We found a possible AES-256 key
|
158
|
+
unless xor_count_256 > AES_XOR_LIMIT
|
159
|
+
report_hit(:type => "Key(AES-256)", :data => bmap[0, 256/32].pack("#{endian}*").unpack("C*").map{|x| "%.2x" % x }.join, :offset => source_offset + i)
|
160
|
+
next
|
161
|
+
end
|
162
|
+
|
163
|
+
# Check distance from 128-bit AES key
|
164
|
+
xor_count_128 = 0
|
165
|
+
1.upto(10) do |row|
|
166
|
+
0.upto(3) do |column|
|
167
|
+
before_count = xor_count_128
|
168
|
+
case column
|
169
|
+
when 0
|
170
|
+
xor_count_128 += aes_popcount( aes_key_core( bmap[4*row-1],row) ^ bmap[4*(row-1)] ^ bmap[4*row])
|
171
|
+
else
|
172
|
+
xor_count_128 += aes_popcount( (bmap[4*row + column-1] ^ bmap[4*(row-1)+column]) ^ bmap[4*row + column])
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# We found a possible AES-128 key
|
178
|
+
unless xor_count_128 > AES_XOR_LIMIT
|
179
|
+
report_hit(:type => "Key(AES-128)", :data => bmap[0, 128/32].pack("#{endian}*").unpack("C*").map{|x| "%.2x" % x }.join, :offset => source_offset + i)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Memorandom
|
2
|
+
module Plugins
|
3
|
+
class CAPI < PluginTemplate
|
4
|
+
|
5
|
+
require 'openssl'
|
6
|
+
|
7
|
+
@@description = "This plugin looks for Microsoft CryptoAPI encryption keys in memory (PRIVATEBLOB)"
|
8
|
+
@@confidence = 0.90
|
9
|
+
|
10
|
+
# Scan takes a buffer and an offset of where this buffer starts in the source
|
11
|
+
def scan(buffer, source_offset)
|
12
|
+
|
13
|
+
buffer.scan(
|
14
|
+
/[\x06\x07]\x02.{6}(?:RSA1|RSA2|DSS1|DSS2).{20}/
|
15
|
+
).each do |m|
|
16
|
+
# This may hit an earlier identical match, but thats ok
|
17
|
+
last_offset = buffer.index(m)
|
18
|
+
next unless last_offset
|
19
|
+
|
20
|
+
# Attempt to parse the key at the specified offset
|
21
|
+
key_candidate = buffer[last_offset, 20000]
|
22
|
+
key_type = ""
|
23
|
+
key = nil
|
24
|
+
|
25
|
+
bits = key_candidate.unpack("CCA6A4V")
|
26
|
+
key_type << ( bits[3] + "-" + bits[4].to_s + "-" )
|
27
|
+
key_type << ( (bits[0] == 0x07) ? "Private" : "Public" )
|
28
|
+
|
29
|
+
key_length = 0
|
30
|
+
nbyte = (bits[4] + 7) >> 3
|
31
|
+
hnbyte = (bits[4] + 15) >> 4
|
32
|
+
|
33
|
+
# DSA
|
34
|
+
if bits[3].index('DSS')
|
35
|
+
if bits[0] == 0x06
|
36
|
+
# Expected length: 20 for q + 3 components bitlen each + 24 for seed structure.
|
37
|
+
key_length = 44 + (3 * nbyte)
|
38
|
+
else
|
39
|
+
# Expected length: 20 for q, priv, 2 bitlen components + 24 for seed structure.
|
40
|
+
key_length = 64 + (2 * nbyte)
|
41
|
+
end
|
42
|
+
# RSA
|
43
|
+
else
|
44
|
+
if bits[0] == 0x06
|
45
|
+
# Expected length: 4 for 'e' + 'n'
|
46
|
+
key_length = 4 + nbyte
|
47
|
+
else
|
48
|
+
# Expected length: 4 for 'e' and 7 other components. 2 components are bitlen size, 5 are bitlen/2
|
49
|
+
key_length = 4 + (2 * nbyte) + (5 * hnbyte)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
key = buffer[last_offset, key_length + 16]
|
54
|
+
next unless key.length == (key_length+16)
|
55
|
+
|
56
|
+
report_hit(:type => "CAPI-#{key_type}", :data => key, :offset => source_offset + last_offset)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Memorandom
|
2
|
+
module Plugins
|
3
|
+
class CC < PluginTemplate
|
4
|
+
|
5
|
+
# Use the credit_card_validator gem for luhn checks and card verification
|
6
|
+
require 'credit_card_validator'
|
7
|
+
|
8
|
+
@@description = "This plugin looks for credit card numbers"
|
9
|
+
@@confidence = 0.10
|
10
|
+
|
11
|
+
# Scan takes a buffer and an offset of where this buffer starts in the source
|
12
|
+
def scan(buffer, source_offset)
|
13
|
+
|
14
|
+
buffer.scan(
|
15
|
+
# Look for credit card numbers in various formats
|
16
|
+
/(?:^|\D)([\d \-]{12,32})(?:$|\D)/
|
17
|
+
).each do |m|
|
18
|
+
matched = m.first
|
19
|
+
|
20
|
+
# This may hit an earlier identical match, but thats ok
|
21
|
+
last_offset = buffer.index(matched)
|
22
|
+
next unless last_offset
|
23
|
+
|
24
|
+
# Clean out any non-digits and validate the card
|
25
|
+
cleaned = matched.gsub(/[^\d]+/, '')
|
26
|
+
next unless CreditCardValidator::Validator.valid?(cleaned)
|
27
|
+
cc_type = CreditCardValidator::Validator.card_type(cleaned)
|
28
|
+
cc_type = cc_type.split('_').map{|x| x.capitalize }.join
|
29
|
+
|
30
|
+
report_hit(:type => "CreditCard(#{cc_type})", :data => cleaned, :offset => source_offset + last_offset)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Memorandom
|
2
|
+
module Plugins
|
3
|
+
class DER < PluginTemplate
|
4
|
+
|
5
|
+
require 'openssl'
|
6
|
+
|
7
|
+
@@description = "This plugin looks for DER-encoded encryption keys (RSA/DSA/EC)"
|
8
|
+
@@confidence = 0.90
|
9
|
+
|
10
|
+
# Scan takes a buffer and an offset of where this buffer starts in the source
|
11
|
+
def scan(buffer, source_offset)
|
12
|
+
|
13
|
+
buffer.scan(
|
14
|
+
# Look for a DER record start (0x30), a length value, and a version marker.
|
15
|
+
# This identifies RSA, DSA, and EC keys
|
16
|
+
/\x30.{1,5}\x02\x01(?:\x00\x02|\x01\x04)/m
|
17
|
+
).each do |m|
|
18
|
+
# This may hit an earlier identical match, but thats ok
|
19
|
+
last_offset = buffer.index(m)
|
20
|
+
next unless last_offset
|
21
|
+
|
22
|
+
# Attempt to parse the key at the specified offset
|
23
|
+
key_candidate = buffer[last_offset, 20000]
|
24
|
+
key_type = nil
|
25
|
+
key = nil
|
26
|
+
|
27
|
+
[:RSA, :DSA, :EC ].each do |ktype|
|
28
|
+
next unless OpenSSL::PKey.const_defined?(ktype)
|
29
|
+
key_type = ktype
|
30
|
+
key = OpenSSL::PKey.const_get(ktype).new(key_candidate) rescue nil
|
31
|
+
break if key
|
32
|
+
end
|
33
|
+
|
34
|
+
# Ignore this if OpenSSL could not parse out a valid key
|
35
|
+
next unless key
|
36
|
+
|
37
|
+
report_hit(:type => "#{key_type}", :data => key.to_pem, :offset => source_offset + last_offset)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Memorandom
|
2
|
+
module Plugins
|
3
|
+
class Hashes < PluginTemplate
|
4
|
+
|
5
|
+
@@description = "This plugin looks for common hash formats"
|
6
|
+
@@confidence = 0.10
|
7
|
+
|
8
|
+
# Scan takes a buffer and an offset of where this buffer starts in the source
|
9
|
+
def scan(buffer, source_offset)
|
10
|
+
|
11
|
+
# Unix password hash formats
|
12
|
+
buffer.scan(
|
13
|
+
/[a-z0-9_]+:\$\d+\$[$a-z0-9\.\/]+:\d+:\d+:\d+[a-z0-9 :]*/mi
|
14
|
+
).each do |m|
|
15
|
+
# This may hit an earlier identical match, but thats ok
|
16
|
+
last_offset = buffer.index(m)
|
17
|
+
report_hit(:type => 'UnixHash', :data => m, :offset => source_offset + last_offset)
|
18
|
+
last_offset += m.length
|
19
|
+
end
|
20
|
+
|
21
|
+
# Hexadecimal password hashes
|
22
|
+
buffer.scan(
|
23
|
+
/[a-f0-9]{16,128}/mi
|
24
|
+
).each do |m|
|
25
|
+
next unless m.length % 2 == 0
|
26
|
+
# This may hit an earlier identical match, but thats ok
|
27
|
+
last_offset = buffer.index(m)
|
28
|
+
report_hit(:type => 'CommonHash', :data => m, :offset => source_offset + last_offset)
|
29
|
+
last_offset += m.length
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Memorandom
|
2
|
+
module Plugins
|
3
|
+
class PEM < PluginTemplate
|
4
|
+
|
5
|
+
@@description = "This plugin looks for PEM-encoded data (keys, certificates, crls, etc)"
|
6
|
+
@@confidence = 0.90
|
7
|
+
|
8
|
+
# Scan takes a buffer and an offset of where this buffer starts in the source
|
9
|
+
def scan(buffer, source_offset)
|
10
|
+
buffer.scan(
|
11
|
+
/-----BEGIN\s*[^\-]+-----+\r?\n[^\-]*-----END\s*[^\-]+-----\r?\n?/m
|
12
|
+
).each do |m|
|
13
|
+
# This may hit an earlier identical match, but thats ok
|
14
|
+
last_offset = buffer.index(m)
|
15
|
+
report_hit(:plugin => self, :type => 'PEM', :data => m, :offset => source_offset + last_offset)
|
16
|
+
last_offset += m.length
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Memorandom
|
2
|
+
module Plugins
|
3
|
+
class RSA < PluginTemplate
|
4
|
+
|
5
|
+
require 'openssl'
|
6
|
+
|
7
|
+
@@description = "This plugin looks for RSA keys by finding Bignum-encoded p-values"
|
8
|
+
@@confidence = 0.90
|
9
|
+
|
10
|
+
# Scan takes a buffer and an offset of where this buffer starts in the source
|
11
|
+
def scan(buffer, source_offset)
|
12
|
+
|
13
|
+
key_lengths = [1024, 2048]
|
14
|
+
|
15
|
+
key_lengths.each do |bits|
|
16
|
+
p_size = bits / 16
|
17
|
+
n_size = bits / 8
|
18
|
+
|
19
|
+
found = []
|
20
|
+
0.upto(buffer.length - (p_size + n_size)) do |p_offset|
|
21
|
+
|
22
|
+
# Look for a prime of the right size (p or q)
|
23
|
+
found_p = OpenSSL::BN.new( buffer[p_offset, p_size].unpack("H*").first.to_i(16).to_s )
|
24
|
+
next unless found_p.prime?
|
25
|
+
next unless found_p > 0x1000000000000000000000
|
26
|
+
|
27
|
+
# Look for a modulus that matches the found p/q value
|
28
|
+
0.upto(buffer.length - (p_size + n_size) ) do |n_offset|
|
29
|
+
|
30
|
+
next if (n_offset < p_offset and (n_offset + n_size) > p_offset)
|
31
|
+
next if (p_offset < n_offset and (p_offset + p_size) > n_offset)
|
32
|
+
|
33
|
+
found_n = OpenSSL::BN.new(buffer[n_offset, n_size].unpack("H*").first.to_i(16).to_s )
|
34
|
+
|
35
|
+
next if found_n == 0
|
36
|
+
next if found_n == found_p
|
37
|
+
next unless found_n > 0x1000000000000000000000
|
38
|
+
next unless (found_n % found_p == 0)
|
39
|
+
|
40
|
+
found << [found_p, found_n, p_offset]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
found = found.uniq
|
45
|
+
|
46
|
+
next unless found.length > 0
|
47
|
+
|
48
|
+
mods = {}
|
49
|
+
|
50
|
+
# Track the last unique p/q value for a potential modulus
|
51
|
+
found.each do |info|
|
52
|
+
mods[ info[1] ] ||= {}
|
53
|
+
mods[ info[1] ][ info[0] ] = info[2]
|
54
|
+
end
|
55
|
+
p mods
|
56
|
+
|
57
|
+
next
|
58
|
+
|
59
|
+
mods.keys.each do |n|
|
60
|
+
uniq_pees = mods[n].keys.select do |k|
|
61
|
+
mods[n].keys.reject {|x| x == k}.select{|x| n == (x * k) }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Memorandom
|
2
|
+
class PluginTemplate
|
3
|
+
|
4
|
+
@@description = "This is an unconfigured plugin using the base template"
|
5
|
+
@@confidence = 0.80
|
6
|
+
|
7
|
+
attr_accessor :scanner, :hits
|
8
|
+
|
9
|
+
def initialize(scanner)
|
10
|
+
self.scanner = scanner
|
11
|
+
self.hits = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def report_hit(info = {})
|
15
|
+
unless info[:offset] and info[:type]
|
16
|
+
raise RuntimeError, "No offset or type supplied in the hit: #{info.inspect}"
|
17
|
+
end
|
18
|
+
|
19
|
+
scanner.report_hit(info.merge({:plugin => self }))
|
20
|
+
|
21
|
+
offset = info.delete(:offset)
|
22
|
+
self.hits[offset] = info
|
23
|
+
end
|
24
|
+
|
25
|
+
def reset
|
26
|
+
self.hits = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.description
|
30
|
+
@@description
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.confidence
|
34
|
+
@@confidence
|
35
|
+
end
|
36
|
+
|
37
|
+
def description
|
38
|
+
self.class.description
|
39
|
+
end
|
40
|
+
|
41
|
+
def confidence
|
42
|
+
self.class.confidence
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Memorandom
|
2
|
+
module Plugins
|
3
|
+
class URLParams < PluginTemplate
|
4
|
+
|
5
|
+
@@description = "This plugin looks for interesting URL parameters and POST data"
|
6
|
+
@@confidence = 0.50
|
7
|
+
|
8
|
+
# Scan takes a buffer and an offset of where this buffer starts in the source
|
9
|
+
def scan(buffer, source_offset)
|
10
|
+
buffer.scan(
|
11
|
+
/[%a-z0-9_\-=\&]*(?:sid|session|sess|user|usr|login|pass|secret|token)[%a-z0-9_\-=\&]*=[%a-z0-9_\-=&]+/mi
|
12
|
+
).each do |m|
|
13
|
+
# This may hit an earlier identical match, but thats ok
|
14
|
+
last_offset = buffer.index(m)
|
15
|
+
report_hit(:type => 'URLParams', :data => m, :offset => source_offset + last_offset)
|
16
|
+
last_offset += m.length
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
module Memorandom
|
2
|
+
class Scanner
|
3
|
+
|
4
|
+
require 'fileutils'
|
5
|
+
require 'openssl'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
attr_accessor :plugins, :source
|
9
|
+
attr_accessor :window, :overlap, :output
|
10
|
+
|
11
|
+
def initialize(opts={})
|
12
|
+
plugin_names = Memorandom::PluginManager.plugins.keys
|
13
|
+
|
14
|
+
# Allow only a subset of plugins to be selected
|
15
|
+
if opts[:plugins]
|
16
|
+
plugin_names = Memorandom::PluginManager.plugins.keys.select{ |name|
|
17
|
+
opts[:plugins].include?(name)
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
# Load the selected plugins
|
22
|
+
self.plugins = plugin_names.map { |name|
|
23
|
+
Memorandom::PluginManager.plugins[name].new(self)
|
24
|
+
}
|
25
|
+
|
26
|
+
self.window = opts[:window] || 1024*1024
|
27
|
+
self.overlap = opts[:overlap] || 1024*4
|
28
|
+
self.output = opts[:output]
|
29
|
+
|
30
|
+
FileUtils.mkdir_p(self.output) if self.output
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
def scan(target, source_name = nil)
|
35
|
+
fd = nil
|
36
|
+
|
37
|
+
if target.respond_to?(:read)
|
38
|
+
self.source = source_name || target.to_s
|
39
|
+
else
|
40
|
+
case target
|
41
|
+
when '-'
|
42
|
+
fd = $stdin
|
43
|
+
self.source = source_name || '<stdin>'
|
44
|
+
else
|
45
|
+
unless ( File.file?(target) or File.blockdev?(target) or File.chardev?(target) )
|
46
|
+
display("[-] Skipping #{target}: not a file")
|
47
|
+
return
|
48
|
+
end
|
49
|
+
begin
|
50
|
+
fd = File.open(target, "rb")
|
51
|
+
self.source = source_name ||"file:#{target.dup}"
|
52
|
+
rescue ::Interrupt
|
53
|
+
raise $!
|
54
|
+
rescue ::Exception
|
55
|
+
display("[-] Skipping #{target}: #{$!.class} #{$!}")
|
56
|
+
return
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Reset the plugin state between each target
|
62
|
+
self.plugins.each { |plugin| plugin.reset }
|
63
|
+
|
64
|
+
buffer = fd.read(self.window)
|
65
|
+
offset = 0
|
66
|
+
|
67
|
+
# Skip empty sources (an empty first read)
|
68
|
+
return unless buffer
|
69
|
+
|
70
|
+
while buffer.length > 0
|
71
|
+
|
72
|
+
self.plugins.each do |plugin|
|
73
|
+
# display("[*] Scanning #{buffer.length} bytes at offset #{offset}")
|
74
|
+
|
75
|
+
# Track a temporary per-plugin buffer and offset
|
76
|
+
scan_buffer = buffer
|
77
|
+
scan_offset = offset
|
78
|
+
|
79
|
+
# Adjust the buffer and offset if any hits have been found that are
|
80
|
+
# greater than offset. This is required because of overlap between
|
81
|
+
# search windows. It is rare, but should be handled here so that
|
82
|
+
# plugins don't have to worry about it.
|
83
|
+
|
84
|
+
adjust = plugin.hits.keys.select{|hit| hit > offset }.last
|
85
|
+
if adjust
|
86
|
+
start_index = ( adjust - offset + 1 )
|
87
|
+
scan_buffer = buffer[start_index, buffer.length-start_index]
|
88
|
+
scan_offset += start_index
|
89
|
+
end
|
90
|
+
|
91
|
+
# Scan the buffer with the plugin
|
92
|
+
plugin.scan(scan_buffer, scan_offset)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Calculate the next sliding window
|
96
|
+
seeked = self.window - self.overlap
|
97
|
+
offset += seeked
|
98
|
+
|
99
|
+
nbytes = fd.read(seeked)
|
100
|
+
break if not nbytes
|
101
|
+
|
102
|
+
# Append new data to the end of the buffer
|
103
|
+
buffer << nbytes
|
104
|
+
|
105
|
+
# Delete most of the previous data from the beginning
|
106
|
+
buffer[0, seeked] = ''
|
107
|
+
end
|
108
|
+
|
109
|
+
# Close the file descriptor if we opened it
|
110
|
+
fd.close if source =~ /^file:/
|
111
|
+
end
|
112
|
+
|
113
|
+
def report_hit(info)
|
114
|
+
unless self.output
|
115
|
+
display("[+] #{source} #{info[:type]}@#{info[:offset]} (#{info[:data][0,32].inspect}...)")
|
116
|
+
return
|
117
|
+
end
|
118
|
+
|
119
|
+
fname = source.split("/").last[0,128] + "_"
|
120
|
+
fname << info[:data][0,16].unpack("H*").first
|
121
|
+
fname << "_#{info[:offset]}.#{info[:type]}"
|
122
|
+
yname = fname + ".yml"
|
123
|
+
|
124
|
+
fname = clean_filename(fname)
|
125
|
+
yname = clean_filename(yname)
|
126
|
+
|
127
|
+
fpath = ::File.join(self.output, fname)
|
128
|
+
::File.open(fpath, "wb") { |fd| fd.write(info[:data]) }
|
129
|
+
|
130
|
+
display("[+] #{source} #{info[:type]}@#{info[:offset]} (#{info[:data][0,32].inspect}) stored in #{fpath}")
|
131
|
+
|
132
|
+
ypath = ::File.join(self.output, yname)
|
133
|
+
yhash = {
|
134
|
+
:source => source,
|
135
|
+
:type => info[:type],
|
136
|
+
:offset => info[:offset],
|
137
|
+
:timestamp => Time.now,
|
138
|
+
:length => info[:data].length
|
139
|
+
}
|
140
|
+
::File.open(ypath, "wb") { |fd| fd.write(yhash.to_yaml) }
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
def clean_filename(name)
|
145
|
+
name.gsub(/[^a-zA-Z0-9_\.\-]/, '_').gsub(/_+/, '_')
|
146
|
+
end
|
147
|
+
|
148
|
+
def display(str)
|
149
|
+
$stdout.puts(str)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
data/memorandom.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'memorandom/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'memorandom'
|
7
|
+
s.version = Memorandom::VERSION
|
8
|
+
s.authors = [
|
9
|
+
'Rapid7 Research'
|
10
|
+
]
|
11
|
+
s.email = [
|
12
|
+
'research@rapid7.com'
|
13
|
+
]
|
14
|
+
s.homepage = "https://www.github.com/rapid7/memorandom"
|
15
|
+
s.summary = %q{Utility and library for extracting secrets from binary blobs}
|
16
|
+
s.description = %q{
|
17
|
+
Memorandom provides a command-line utility and class library from extracting secrets
|
18
|
+
from binary files. Common use cases include extracting encryption keys from memory dumps
|
19
|
+
and identifying sensitive data stored in block devices.
|
20
|
+
}.gsub(/\s+/, ' ').strip
|
21
|
+
|
22
|
+
s.files = `git ls-files`.split("\n")
|
23
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
24
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
25
|
+
s.require_paths = ['lib']
|
26
|
+
|
27
|
+
# ---- Dependencies ----
|
28
|
+
|
29
|
+
s.add_development_dependency 'rspec'
|
30
|
+
s.add_development_dependency 'cucumber'
|
31
|
+
s.add_development_dependency 'aruba'
|
32
|
+
|
33
|
+
s.add_runtime_dependency 'credit_card_validator'
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: memorandom
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Rapid7 Research
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-04-23 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: cucumber
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: aruba
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: credit_card_validator
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: Memorandom provides a command-line utility and class library from extracting
|
79
|
+
secrets from binary files. Common use cases include extracting encryption keys from
|
80
|
+
memory dumps and identifying sensitive data stored in block devices.
|
81
|
+
email:
|
82
|
+
- research@rapid7.com
|
83
|
+
executables:
|
84
|
+
- memorandom.rb
|
85
|
+
extensions: []
|
86
|
+
extra_rdoc_files: []
|
87
|
+
files:
|
88
|
+
- Gemfile
|
89
|
+
- LICENSE
|
90
|
+
- README.md
|
91
|
+
- bin/memorandom.rb
|
92
|
+
- lib/memorandom.rb
|
93
|
+
- lib/memorandom/plugins.rb
|
94
|
+
- lib/memorandom/plugins/aes.rb
|
95
|
+
- lib/memorandom/plugins/capi.rb
|
96
|
+
- lib/memorandom/plugins/cc.rb
|
97
|
+
- lib/memorandom/plugins/der.rb
|
98
|
+
- lib/memorandom/plugins/hashes.rb
|
99
|
+
- lib/memorandom/plugins/pem.rb
|
100
|
+
- lib/memorandom/plugins/rsa.rb
|
101
|
+
- lib/memorandom/plugins/template.rb
|
102
|
+
- lib/memorandom/plugins/url_params.rb
|
103
|
+
- lib/memorandom/scanner.rb
|
104
|
+
- lib/memorandom/version.rb
|
105
|
+
- memorandom.gemspec
|
106
|
+
homepage: https://www.github.com/rapid7/memorandom
|
107
|
+
licenses: []
|
108
|
+
post_install_message:
|
109
|
+
rdoc_options: []
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
119
|
+
none: false
|
120
|
+
requirements:
|
121
|
+
- - ! '>='
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
requirements: []
|
125
|
+
rubyforge_project:
|
126
|
+
rubygems_version: 1.8.23
|
127
|
+
signing_key:
|
128
|
+
specification_version: 3
|
129
|
+
summary: Utility and library for extracting secrets from binary blobs
|
130
|
+
test_files: []
|