Class: Memorandom::Scanner

Inherits:
Object
  • Object
show all
Defined in:
lib/memorandom/scanner.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Scanner

Returns a new instance of Scanner.



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/memorandom/scanner.rb', line 11

def initialize(opts={})
  plugin_names = Memorandom::PluginManager.plugins.keys

  # Allow only a subset of plugins to be selected
  if opts[:plugins]
    plugin_names = Memorandom::PluginManager.plugins.keys.select{ |name|  
      opts[:plugins].include?(name) 
    }
  end

  # Load the selected plugins
  self.plugins = plugin_names.map { |name|
    Memorandom::PluginManager.plugins[name].new(self)
  }

  self.window  = opts[:window]  || 1024*1024
  self.overlap = opts[:overlap] || 1024*4
  self.output  = opts[:output]

  FileUtils.mkdir_p(self.output) if self.output

end

Instance Attribute Details

#outputObject

Returns the value of attribute output.



9
10
11
# File 'lib/memorandom/scanner.rb', line 9

def output
  @output
end

#overlapObject

Returns the value of attribute overlap.



9
10
11
# File 'lib/memorandom/scanner.rb', line 9

def overlap
  @overlap
end

#pluginsObject

Returns the value of attribute plugins.



8
9
10
# File 'lib/memorandom/scanner.rb', line 8

def plugins
  @plugins
end

#sourceObject

Returns the value of attribute source.



8
9
10
# File 'lib/memorandom/scanner.rb', line 8

def source
  @source
end

#windowObject

Returns the value of attribute window.



9
10
11
# File 'lib/memorandom/scanner.rb', line 9

def window
  @window
end

Instance Method Details

#clean_filename(name) ⇒ Object



144
145
146
# File 'lib/memorandom/scanner.rb', line 144

def clean_filename(name)
  name.gsub(/[^a-zA-Z0-9_\.\-]/, '_').gsub(/_+/, '_')
end

#display(str) ⇒ Object



148
149
150
# File 'lib/memorandom/scanner.rb', line 148

def display(str)
  $stdout.puts(str)
end

#report_hit(info) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/memorandom/scanner.rb', line 113

def report_hit(info)
  unless self.output
    display("[+] #{source} #{info[:type]}@#{info[:offset]} (#{info[:data][0,32].inspect}...)")
    return
  end

  fname = source.split("/").last[0,128] + "_"
  fname << info[:data][0,16].unpack("H*").first
  fname << "_#{info[:offset]}.#{info[:type]}"
  yname = fname + ".yml"

  fname = clean_filename(fname)
  yname = clean_filename(yname)

  fpath = ::File.join(self.output, fname)
  ::File.open(fpath, "wb") { |fd| fd.write(info[:data]) }

  display("[+] #{source} #{info[:type]}@#{info[:offset]} (#{info[:data][0,32].inspect}) stored in #{fpath}")

  ypath = ::File.join(self.output, yname)
  yhash = { 
    :source    => source, 
    :type      => info[:type], 
    :offset    => info[:offset], 
    :timestamp => Time.now,
    :length    => info[:data].length
  }
  ::File.open(ypath, "wb") { |fd| fd.write(yhash.to_yaml) }

end

#scan(target, source_name = nil) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/memorandom/scanner.rb', line 34

def scan(target, source_name = nil)
  fd = nil

  if target.respond_to?(:read)
    self.source = source_name || target.to_s
  else
    case target
    when '-'
      fd = $stdin
      self.source = source_name || '<stdin>'
    else
      unless ( File.file?(target) or File.blockdev?(target) or File.chardev?(target) )
        display("[-] Skipping #{target}: not a file")
        return
      end
      begin
        fd = File.open(target, "rb")
        self.source = source_name ||"file:#{target.dup}"
      rescue ::Interrupt
        raise $!
      rescue ::Exception
        display("[-] Skipping #{target}: #{$!.class} #{$!}")
        return
      end
    end
  end

  # Reset the plugin state between each target
  self.plugins.each { |plugin| plugin.reset }

  buffer = fd.read(self.window)
  offset = 0

  # Skip empty sources (an empty first read)
  return unless buffer

  while buffer.length > 0

    self.plugins.each do |plugin|
      # display("[*] Scanning #{buffer.length} bytes at offset #{offset}")

      # Track a temporary per-plugin buffer and offset
      scan_buffer = buffer
      scan_offset = offset 

      # Adjust the buffer and offset if any hits have been found that are 
      # greater than offset. This is required because of overlap between 
      # search windows. It is rare, but should be handled here so that
      # plugins don't have to worry about it.

      adjust = plugin.hits.keys.select{|hit| hit > offset }.last
      if adjust
        start_index = ( adjust - offset + 1 )
        scan_buffer = buffer[start_index, buffer.length-start_index]
        scan_offset += start_index
      end

      # Scan the buffer with the plugin
      plugin.scan(scan_buffer, scan_offset)
    end

    # Calculate the next sliding window 
    seeked = self.window - self.overlap
    offset += seeked

    nbytes = fd.read(seeked)
    break if not nbytes

    # Append new data to the end of the buffer
    buffer << nbytes

    # Delete most of the previous data from the beginning
    buffer[0, seeked] = ''
  end

  # Close the file descriptor if we opened it
  fd.close if source =~ /^file:/
end