Class: Msf::Modules::External::Bridge

Inherits:
Object
  • Object
show all
Defined in:
lib/msf/core/modules/external/bridge.rb,
lib/msf/core/modules/external/bridge.rb

Direct Known Subclasses

GoBridge, PyBridge, RbBridge

Constant Summary collapse

LOADERS =
[
  Msf::Modules::External::PyBridge,
  Msf::Modules::External::RbBridge,
  Msf::Modules::External::GoBridge,
  Msf::Modules::External::Bridge
]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(module_path, framework: nil) ⇒ Bridge

Returns a new instance of Bridge.


44
45
46
47
48
49
50
51
52
# File 'lib/msf/core/modules/external/bridge.rb', line 44

def initialize(module_path, framework: nil)
  self.env = {}
  self.running = false
  self.path = module_path
  self.cmd = [[self.path, self.path]]
  self.messages = Queue.new
  self.buf = ''
  self.framework = framework
end

Instance Attribute Details

#bufObject (protected)

Returns the value of attribute buf


57
58
59
# File 'lib/msf/core/modules/external/bridge.rb', line 57

def buf
  @buf
end

#cmdObject (protected)

Returns the value of attribute cmd


57
58
59
# File 'lib/msf/core/modules/external/bridge.rb', line 57

def cmd
  @cmd
end

#envObject (protected)

Returns the value of attribute env


57
58
59
# File 'lib/msf/core/modules/external/bridge.rb', line 57

def env
  @env
end

#exit_statusObject

Returns the value of attribute exit_status


9
10
11
# File 'lib/msf/core/modules/external/bridge.rb', line 9

def exit_status
  @exit_status
end

#frameworkObject (protected)

Returns the value of attribute framework


57
58
59
# File 'lib/msf/core/modules/external/bridge.rb', line 57

def framework
  @framework
end

#iosObject (protected)

Returns the value of attribute ios


57
58
59
# File 'lib/msf/core/modules/external/bridge.rb', line 57

def ios
  @ios
end

#messagesObject

Returns the value of attribute messages


9
10
11
# File 'lib/msf/core/modules/external/bridge.rb', line 9

def messages
  @messages
end

#pathObject

Returns the value of attribute path


9
10
11
# File 'lib/msf/core/modules/external/bridge.rb', line 9

def path
  @path
end

#read_threadObject (protected)

Returns the value of attribute read_thread


57
58
59
# File 'lib/msf/core/modules/external/bridge.rb', line 57

def read_thread
  @read_thread
end

#runningObject

Returns the value of attribute running


9
10
11
# File 'lib/msf/core/modules/external/bridge.rb', line 9

def running
  @running
end

#wait_threadObject (protected)

Returns the value of attribute wait_thread


57
58
59
# File 'lib/msf/core/modules/external/bridge.rb', line 57

def wait_thread
  @wait_thread
end

Class Method Details

.applies?(module_name) ⇒ Boolean

Returns:

  • (Boolean)

11
12
13
# File 'lib/msf/core/modules/external/bridge.rb', line 11

def self.applies?(module_name)
  File::executable? module_name
end

.open(module_path, framework: nil) ⇒ Object


226
227
228
229
230
231
232
# File 'lib/msf/core/modules/external/bridge.rb', line 226

def self.open(module_path, framework: nil)
  LOADERS.each do |klass|
    return klass.new module_path, framework: framework if klass.applies? module_path
  end

  nil
end

Instance Method Details

#cleanupObject (protected)


150
151
152
153
154
155
# File 'lib/msf/core/modules/external/bridge.rb', line 150

def cleanup
  self.running = false
  self.messages.close
  harvest_process
  self.ios.each {|fd| fd.close rescue nil} # Yeah, yeah. I know.
end

#closeObject


33
34
35
36
37
38
# File 'lib/msf/core/modules/external/bridge.rb', line 33

def close
  self.running = false
  self.read_thread.join

  self
end

#exec(req) ⇒ Object


15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/msf/core/modules/external/bridge.rb', line 15

def exec(req)
  unless self.running
    self.running = true
    send(req)
    self.read_thread = threadme do
      begin
        while self.running && m = next_message
          self.messages.push m
        end
      ensure
        cleanup
      end
    end

    self
  end
end

#harvest_processObject (protected)


139
140
141
142
143
144
145
146
147
148
# File 'lib/msf/core/modules/external/bridge.rb', line 139

def harvest_process
  if self.wait_thread.join(10)
    self.exit_status = self.wait_thread.value
  elsif Process.kill('TERM', self.wait_thread.pid) && self.wait_thread.join(10)
    self.exit_status = self.wait_thread.value
  else
    Process.kill('KILL', self.wait_thread.pid)
    self.exit_status = self.wait_thread.value
  end
end

#next_message(timeout = 600) ⇒ Object (protected)


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
112
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
# File 'lib/msf/core/modules/external/bridge.rb', line 81

def next_message(timeout=600)
  _, out, err = self.ios
  message = ''

  # Multiple messages can come over the wire all at once, and since yajl
  # doesn't play nice with windows, we have to emulate a state machine to
  # read just enough off the wire to get one request at a time. Since
  # Windows cannot do a nonblocking read on a pipe, we are forced to do a
  # whole lot of `select` syscalls and keep a buffer ourselves :(
  begin
    loop do
      # This is so we don't end up calling JSON.parse on every char and
      # catch an exception. Windows can't do nonblock on pipes, so we
      # still have to do the select if we are not at the end of object
      # and don't have any buffer left
      parts = self.buf.split '}', 2
      if parts.length == 2 # [part, rest]
        message << parts[0] << '}'
        self.buf = parts[1]
        break
      elsif parts.length == 1 # [part]
        message << parts[0]
        self.buf = ''
      end

      # We would call Rex::Threadsafe directly, but that would require Rex for standalone use
      res = select([out, err], nil, nil, timeout)
      if res == nil
        # This is what we would have gotten without Rex and what `readpartial` can also raise
        raise EOFError.new
      else
        fds = res[0]
        # Preferentially drain and log stderr, EOF counts as activity, but
        # stdout might have some buffered data left, so carry on
        if fds.include?(err) && !err.eof?
          errbuf = err.readpartial(4096)
          if self.framework
            elog "Unexpected output running #{self.path}:\n#{errbuf}"
          else
            $stderr.puts errbuf
          end
        end
        if fds.include? out
          self.buf << out.readpartial(4096)
        end
      end
    end

    Message.from_module(JSON.parse(message))
  rescue JSON::ParserError
    # Probably an incomplete response, but no way to really tell. Keep trying
    # until EOF
    retry
  rescue EOFError => e
    self.running = false
  end
end

#send(message) ⇒ Object (protected)

XXX TODO non-blocking writes, check write lengths


61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/msf/core/modules/external/bridge.rb', line 61

def send(message)
  input, output, err, status = ::Open3.popen3(self.env, *self.cmd)
  self.ios = [input, output, err]
  self.wait_thread = status
  # We would call Rex::Threadsafe directly, but that would require rex for standalone use
  case select(nil, [input], nil, 0.1)
  when nil
    raise "Cannot run module #{self.path}"
  when [[], [input], []]
    m = message.to_json
    write_message(input, m)
  else
    raise "Error running module #{self.path}"
  end
end

#success?Boolean

Returns:

  • (Boolean)

40
41
42
# File 'lib/msf/core/modules/external/bridge.rb', line 40

def success?
  self.exit_status && self.exit_status.success?
end

#threadme(&block) ⇒ Object (protected)


157
158
159
160
161
162
163
164
# File 'lib/msf/core/modules/external/bridge.rb', line 157

def threadme(&block)
  if self.framework
    # Leak as few connections as possible
    self.framework.threads.spawn("External Module #{self.path}", false, &block)
  else
    ::Thread.new &block
  end
end

#write_message(fd, json) ⇒ Object (protected)


77
78
79
# File 'lib/msf/core/modules/external/bridge.rb', line 77

def write_message(fd, json)
  fd.write(json)
end