Create a dedicated db table to track sessions & session events.

* Add new db tables for session & session_events
* Migrate existing session data from events db table
* Modify session report methods to log to the new tables


git-svn-id: file:///home/svn/framework3/trunk@12273 4d416f70-5f16-0410-b530-b9f4589650da
unstable
Mike Smith 2011-04-07 21:59:32 +00:00
parent 892e241853
commit d5d9d56081
24 changed files with 2028 additions and 214 deletions

View File

@ -0,0 +1,110 @@
class AddSessionTable < ActiveRecord::Migration
class Event < ActiveRecord::Base
serialize :info
end
class SessionEvent < ActiveRecord::Base
belongs_to :session
end
class Session < ActiveRecord::Base
has_many :events, :class_name => 'AddSessionTable::SessionEvent'
serialize :datastore
end
def self.up
create_table :sessions do |t|
t.integer :host_id
t.string :stype # session type: meterpreter, shell, etc
t.string :via_exploit # module name
t.string :via_payload # payload name
t.string :desc # session description
t.integer :port
t.string :platform # platform type of the remote system
t.string :routes
t.text :datastore # module's datastore
t.timestamp :opened_at, :null => false
t.timestamp :closed_at
t.string :close_reason
end
create_table :session_events do |t|
t.integer :session_id
t.string :etype # event type: command, output, upload, download, filedelete
t.binary :command
t.binary :output
t.string :remote_path
t.string :local_path
t.timestamp :created_at
end
#
# Migrate session data from events table
#
close_events = Event.find_all_by_name("session_close")
open_events = Event.find_all_by_name("session_open")
command_events = Event.find_all_by_name("session_command")
output_events = Event.find_all_by_name("session_output")
upload_events = Event.find_all_by_name("session_upload")
download_events = Event.find_all_by_name("session_download")
open_events.each do |o|
c = close_events.find { |e| e.info[:session_uuid] == o.info[:session_uuid] }
s = Session.new(
:host_id => o.host_id,
:stype => o.info[:session_type],
:via_exploit => o.info[:via_exploit],
:via_payload => o.info[:via_payload],
:datastore => o.info[:datastore],
:opened_at => o.created_at
)
if c
s.closed_at = c.created_at
s.desc = c.info[:session_info]
else
# couldn't find the corresponding close event
s.closed_at = s.opened_at
s.desc = "?"
end
uuid = o.info[:session_uuid]
command_events.select { |e| e.info[:session_uuid] == uuid }.each do |e|
s.events.build(:created_at => e.created_at, :etype => "command", :command => e.info[:command] )
end
output_events.select { |e| e.info[:session_uuid] == uuid }.each do |e|
s.events.build(:created_at => e.created_at, :etype => "output", :output => e.info[:output] )
end
upload_events.select { |e| e.info[:session_uuid] == uuid }.each do |e|
s.events.build(:created_at => e.created_at, :etype => "upload", :local_path => e.info[:local_path], :remote_path => e.info[:remote_path] )
end
download_events.select { |e| e.info[:session_uuid] == uuid }.each do |e|
s.events.build(:created_at => e.created_at, :etype => "download", :local_path => e.info[:local_path], :remote_path => e.info[:remote_path] )
end
s.events.sort_by(&:created_at)
s.save!
end
end
def self.down
drop_table :sessions
drop_table :session_events
end
end

View File

@ -90,7 +90,7 @@ class CommandShell
# Keep reading data until no more data is available or the timeout is
# reached.
while (::Time.now.to_f < etime and ::IO.select([rstream], nil, nil, timeo))
while (::Time.now.to_f < etime and (self.respond_to?(:ring) or ::IO.select([rstream], nil, nil, timeo)))
res = shell_read(-1, 0.01)
buff << res if res
timeo = etime - ::Time.now.to_f
@ -103,6 +103,8 @@ class CommandShell
# Read from the command shell.
#
def shell_read(length=-1, timeout=1)
return shell_read_ring(length,timeout) if self.respond_to?(:ring)
begin
rv = rstream.get_once(length, timeout)
framework.events.on_session_output(self, rv) if rv
@ -114,6 +116,50 @@ class CommandShell
end
end
#
# Read from the command shell.
#
def shell_read_ring(length=-1, timeout=1)
self.ring_buff ||= ""
# Short-circuit bad length values
return "" if length == 0
# Return data from the stored buffer if available
if self.ring_buff.length >= length and length > 0
buff = self.ring_buff.slice!(0,length)
return buff
end
buff = self.ring_buff
self.ring_buff = ""
begin
::Timeout.timeout(timeout) do
while( (length > 0 and buff.length < length) or (length == -1 and buff.length == 0))
ring.select
nseq,data = ring.read_data(self.ring_seq)
if data
self.ring_seq = nseq
buff << data
end
end
end
rescue ::Timeout::Error
rescue ::Exception => e
shell_close
raise e
end
# Store any leftovers in the ring buffer backlog
if length > 0 and buff.length > length
self.ring_buff = buff[length, buff.length - length]
buff = buff[0,length]
end
buff
end
#
# Writes to the command shell.
#
@ -178,7 +224,14 @@ protected
# shell_write instead of operating on rstream directly.
def _interact
framework.events.on_session_interact(self)
if self.respond_to?(:ring)
_interact_ring
else
_interact_stream
end
end
def _interact_stream
fds = [rstream.fd, user_input.fd]
while self.interacting
sd = Rex::ThreadSafe.select(fds, nil, fds, 0.5)
@ -190,8 +243,51 @@ protected
if sd[0].include? user_input.fd
shell_write(user_input.gets)
end
Thread.pass
end
end
def _interact_ring
begin
rdr = Rex::ThreadFactory.spawn("RingMonitor", false) do
seq = nil
while self.interacting
# Look for any pending data from the remote ring
nseq,data = ring.read_data(seq)
# Update the sequence number if necessary
seq = nseq || seq
# Write output to the local stream if successful
user_output.print(data) if data
begin
# Wait for new data to arrive on this session
ring.wait(seq)
rescue EOFError => e
break
end
end
end
while self.interacting
# Look for any pending input or errors from the local stream
sd = Rex::ThreadSafe.select([ _local_fd ], nil, [_local_fd], 5.0)
# Write input to the ring's input mechanism
shell_write(user_input.gets) if sd
end
ensure
rdr.kill
end
end
attr_accessor :ring_seq # This tracks the last seen ring buffer sequence (for shell_read)
attr_accessor :ring_buff # This tracks left over read data to maintain a compatible API
end
class CommandShellWindows < CommandShell

View File

@ -281,8 +281,21 @@ class Meterpreter < Rex::Post::Meterpreter::Client
::Timeout.timeout(60) do
username = self.sys.config.getuid
sysinfo = self.sys.config.sysinfo
framework.db.report_note({
:type => "host.os.session_fingerprint",
:host => self,
:workspace => workspace,
:data => {
:name => sysinfo["Computer"],
:os => sysinfo["OS"],
:arch => sysinfo["Architecture"],
}
})
safe_info = "#{username} @ #{sysinfo['Computer']}"
safe_info.force_encoding("ASCII-8BIT") if safe_info.respond_to?(:force_encoding)
# Should probably be using Rex::Text.ascii_safe_hex but leave
# this as is for now since "\xNN" is arguably uglier than "_"
# showing up in various places in the UI.
safe_info.gsub!(/[\x00-\x08\x0b\x0c\x0e-\x19\x7f-\xff]+/n,"_")
self.info = safe_info
end

View File

@ -420,6 +420,173 @@ class DBManager
wspace.services.all(:include => :host, :conditions => conditions, :order => "hosts.address, port")
end
# Returns a session based on opened_time, host address, and workspace
# (or returns nil)
def get_session(opts)
return if not active
wspace = opts[:workspace] || opts[:wspace] || workspace
addr = opts[:addr] || opts[:address] || opts[:host] || return
host = get_host(:workspace => wspace, :host => addr)
time = opts[:opened_at] || opts[:created_at] || opts[:time] || return
Msf::DBManager::Session.find_by_host_id_and_opened_at(host.id, time)
end
# Record a new session in the database
#
# opts must contain either
# :session - the Msf::Session object we are reporting
# :host - the Host object we are reporting a session on.
#
def report_session(opts)
return if not active
if opts[:session]
raise ArgumentError.new("Invalid :session, expected Msf::Session") unless opts[:session].kind_of? Msf::Session
session = opts[:session]
wspace = opts[:workspace] || find_workspace(session.workspace)
h_opts = { }
h_opts[:host] = normalize_host(session)
h_opts[:arch] = session.arch if session.arch
h_opts[:workspace] = wspace
host = find_or_create_host(h_opts)
sess_data = {
:host_id => host.id,
:stype => session.type,
:desc => session.info,
:platform => session.platform,
:via_payload => session.via_payload,
:via_exploit => session.via_exploit,
:routes => [],
:datastore => session.exploit_datastore.to_h,
:opened_at => Time.now
}
elsif opts[:host]
raise ArgumentError.new("Invalid :host, expected Host object") unless opts[:host].kind_of? Host
host = opts[:host]
sess_data = {
:host_id => host.id,
:stype => opts[:stype],
:desc => opts[:desc],
:platform => opts[:platform],
:via_payload => opts[:via_payload],
:via_exploit => opts[:via_exploit],
:routes => opts[:routes],
:datastore => opts[:datastore],
:opened_at => opts[:opened_at],
:closed_at => opts[:closed_at],
:close_reason => opts[:close_reason],
}
else
raise ArgumentError.new("Missing option :session or :host")
end
ret = {}
task = queue(Proc.new {
s = Msf::DBManager::Session.create(sess_data)
if opts[:session]
session.db_record = s
else
myret = s.save!
end
ret[:session] = s
})
# If this is a live session, we know the host is vulnerable to something.
# If the exploit used was multi/handler, though, we don't know what
# it's vulnerable to, so it isn't really useful to save it.
if opts[:session]
if session.via_exploit and session.via_exploit != "exploit/multi/handler"
return unless host
port = session.exploit_datastore["RPORT"]
service = (port ? host.services.find_by_port(port) : nil)
mod = framework.modules.create(session.via_exploit)
vuln_info = {
:host => host.address,
:name => session.via_exploit,
:refs => mod.references,
:workspace => wspace
}
framework.db.report_vuln(vuln_info)
# Exploit info is like vuln info, except it's /just/ for storing
# successful exploits in an unserialized way. Yes, there is
# duplication, but it makes exporting a score card about a
# million times easier. TODO: See if vuln/exploit can get fixed up
# to one useful table.
exploit_info = {
:name => session.via_exploit,
:payload => session.via_payload,
:workspace => wspace,
:host => host,
:service => service,
:session_uuid => session.uuid
}
framework.db.report_exploit(exploit_info)
end
end
# Always wait for the task so that session.db_record gets stored
# properly. This allows us to have session events immediately after
# reporting the new session without having to worry about race
# conditions.
return nil if task.wait() != :done
return ret[:session]
end
#
# Record a session event in the database
#
# opts must contain one of:
# :session -- the Msf::Session OR the Msf::DBManager::Session we are reporting
# :etype -- event type, enum: command, output, upload, download, filedelete
#
# opts may contain
# :output -- the data for an output event
# :command -- the data for an command event
# :remote_path -- path to the associated file for upload, download, and filedelete events
# :local_path -- path to the associated file for upload, and download
#
def report_session_event(opts)
return if not active
raise ArgumentError.new("Missing required option :session") if opts[:session].nil?
raise ArgumentError.new("Expected an :etype") unless opts[:etype]
if opts[:session].respond_to? :db_record
session = opts[:session].db_record
event_data = { :created_at => Time.now }
else
session = opts[:session]
event_data = { :created_at => opts[:created_at] }
end
unless session.kind_of? Msf::DBManager::Session
raise ArgumentError.new("Invalid :session, expected Session object got #{session.class}")
end
event_data[:session_id] = session.id
[:remote_path, :local_path, :output, :command, :etype].each do |attr|
event_data[attr] = opts[attr] if opts[attr]
end
task = queue(Proc.new {
s = Msf::DBManager::SessionEvent.create(event_data)
})
return task
end
def report_session_route(session, route)
return if not active
task = queue(Proc.new {
session.db_record.routes << route
session.db_record.save!
})
end
def report_session_route_remove(session, route)
return if not active
task = queue(Proc.new {
session.db_record.routes.delete(route)
session.db_record.save!
})
end
def get_client(opts)
wspace = opts.delete(:workspace) || workspace
@ -567,6 +734,9 @@ class DBManager
return if not active
wait = opts.delete(:wait)
wspace = opts.delete(:workspace) || workspace
if wspace.kind_of? String
wspace = find_workspace(wspace)
end
seen = opts.delete(:seen) || false
crit = opts.delete(:critical) || false
host = nil
@ -576,8 +746,8 @@ class DBManager
if opts[:host].kind_of? Host
host = opts[:host]
else
report_host({:workspace => wspace, :host => opts[:host]})
addr = normalize_host(opts[:host])
report_host({:workspace => wspace, :host => addr})
end
# Do the same for a service if that's also included.
if (opts[:port])
@ -634,10 +804,10 @@ class DBManager
conditions[:host_id] = host[:id] if host
conditions[:service_id] = service[:id] if service
notes = wspace.notes.find(:all, :conditions => conditions)
case mode
when :unique
notes = wspace.notes.find(:all, :conditions => conditions)
# Only one note of this type should exist, make a new one if it
# isn't there. If it is, grab it and overwrite its data.
if notes.empty?
@ -647,6 +817,8 @@ class DBManager
end
note.data = data
when :unique_data
notes = wspace.notes.find(:all, :conditions => conditions)
# Don't make a new Note with the same data as one that already
# exists for the given: type and (host or service)
notes.each do |n|
@ -2742,7 +2914,7 @@ class DBManager
end
}
host_address = host_data[:host].dup # Preserve after report_host() deletes
report_host(host_data)
report_host(host_data.merge(:wait => true))
host.elements.each('services/service') do |service|
service_data = {}
service_data[:workspace] = wspace
@ -2832,6 +3004,51 @@ class DBManager
end
report_cred(cred_data.merge(:wait => true))
end
host.elements.each('sessions/session') do |sess|
sess_id = nils_for_nulls(sess.elements["id"].text.to_s.strip.to_i)
sess_data = {}
sess_data[:host] = get_host(:workspace => wspace, :address => host_address)
%W{desc platform port stype}.each {|datum|
if sess.elements[datum].respond_to? :text
sess_data[datum.intern] = nils_for_nulls(sess.elements[datum].text.to_s.strip)
end
}
%W{opened-at close-reason closed-at via-exploit via-payload}.each {|datum|
if sess.elements[datum].respond_to? :text
sess_data[datum.gsub("-","_").intern] = nils_for_nulls(sess.elements[datum].text.to_s.strip)
end
}
sess_data[:datastore] = nils_for_nulls(unserialize_object(sess.elements["datastore"], allow_yaml))
sess_data[:routes] = nils_for_nulls(unserialize_object(sess.elements["routes"], allow_yaml))
if not sess_data[:closed_at] # Fake a close if we don't already have one
sess_data[:closed_at] = Time.now
sess_data[:close_reason] = "Imported at #{Time.now}"
end
existing_session = get_session(
:workspace => sess_data[:host].workspace,
:addr => sess_data[:host].address,
:time => sess_data[:opened_at]
)
this_session = existing_session || report_session(sess_data.merge(:wait => true))
next if existing_session
sess.elements.each('events/event') do |sess_event|
sess_event_data = {}
sess_event_data[:session] = this_session
%W{created-at etype local-path remote-path}.each {|datum|
if sess_event.elements[datum].respond_to? :text
sess_event_data[datum.gsub("-","_").intern] = nils_for_nulls(sess_event.elements[datum].text.to_s.strip)
end
}
%W{command output}.each {|datum|
if sess_event.elements[datum].respond_to? :text
sess_event_data[datum.gsub("-","_").intern] = nils_for_nulls(unserialize_object(sess_event.elements[datum], allow_yaml))
end
}
report_session_event(sess_event_data)
end
end
end
# Import web sites
@ -4468,17 +4685,56 @@ class DBManager
end
end
#
# Returns something suitable for the +:host+ parameter to the various report_* methods
#
# Takes a Host object, a Session object, an Msf::Session object or a String
# address
#
def normalize_host(host)
# If the host parameter is a Session, try to extract its address
if host.respond_to?('target_host')
thost = host.target_host
tpeer = host.tunnel_peer
if tpeer and (!thost or thost.empty?)
thost = tpeer.split(":")[0]
return host if host.kind_of? Host
norm_host = nil
if (host.kind_of? String)
# If it's an IPv4 addr with a host on the end, strip the port
if host =~ /((\d{1,3}\.){3}\d{1,3}):\d+/
norm_host = $1
else
norm_host = host
end
host = thost
elsif host.kind_of? Session
norm_host = host.host
elsif host.respond_to?(:target_host)
# Then it's an Msf::Session object with a target but target_host
# won't be set in some cases, so try tunnel_peer as well
thost = host.target_host
if host.tunnel_peer and (!thost or thost.empty?)
# tunnel_peer is of the form ip:port, so strip off the port to
# get the addr by itself
thost = host.tunnel_peer.split(":")[0]
end
norm_host = thost
end
host
# If we got here and don't have a norm_host yet, it could be a
# Msf::Session object with an empty or nil tunnel_host and tunnel_peer;
# see if it has a socket and use its peerhost if so.
if (
norm_host.nil? and
host.respond_to?(:sock) and
host.sock.respond_to?(:peerhost) and
host.sock.peerhost.to_s.length > 0
)
norm_host = session.sock.peerhost
end
# If We got here and still don't have a real host, there's nothing left
# to try, just log it and return what we were given
if not norm_host
dlog("Host could not be normalized: #{host.inspect}")
norm_host = host
end
norm_host
end
protected

View File

@ -75,12 +75,6 @@ class Export
return sz
end
# Converts binary and whitespace characters to a hex notation.
def ascii_safe_hex(str)
str.gsub!(/([\x00-\x20\x80-\xFF])/){ |x| "\\x%.2x" % x.unpack("C*")[0] }
return str
end
# Formats credentials according to their type, and writes it out to the
# supplied report file. Note for reimporting: Blank values are <BLANK>
def write_credentials(ptype,creds,report_file)
@ -112,8 +106,8 @@ class Export
end
else "text"
data.each do |c|
user = (c.user.nil? || c.user.empty?) ? "<BLANK>" : ascii_safe_hex(c.user)
pass = (c.pass.nil? || c.pass.empty?) ? "<BLANK>" : ascii_safe_hex(c.pass)
user = (c.user.nil? || c.user.empty?) ? "<BLANK>" : Rex::Text.ascii_safe_hex(c.user, true)
pass = (c.pass.nil? || c.pass.empty?) ? "<BLANK>" : Rex::Text.ascii_safe_hex(c.pass, true)
report_file.write "%s %s\n" % [user,pass]
end
end

View File

@ -277,127 +277,60 @@ class FrameworkEventSubscriber
#report_event(:name => "ui_start", :info => info)
end
#
# Generic handler for session events
#
def session_event(name, session, opts={})
address = nil
if session.respond_to? :peerhost and session.peerhost.to_s.length > 0
address = session.peerhost
elsif session.respond_to? :tunnel_peer and session.tunnel_peer.to_s.length > 0
address = session.tunnel_peer[0, session.tunnel_peer.rindex(":") || session.tunnel_peer.length ]
elsif session.respond_to? :target_host and session.target_host.to_s.length > 0
address = session.target_host
else
elog("Session with no peerhost/tunnel_peer")
dlog("#{session.inspect}", LEV_3)
return
end
if framework.db.active
ws = framework.db.find_workspace(session.workspace)
event = {
:workspace => ws,
:username => session.username,
:name => name,
:host => address,
:info => {
:session_id => session.sid,
:session_info => session.info,
:session_uuid => session.uuid,
:session_type => session.type,
:username => session.username,
:target_host => address,
:via_exploit => session.via_exploit,
:via_payload => session.via_payload,
:tunnel_peer => session.tunnel_peer,
:exploit_uuid => session.exploit_uuid
}.merge(opts)
}
report_event(event)
end
end
require 'msf/core/session'
include ::Msf::SessionEvent
def on_session_open(session)
opts = { :datastore => session.exploit_datastore.to_h, :critical => true }
session_event('session_open', session, opts)
if framework.db.active
framework.db.sync
address = nil
if session.respond_to? :peerhost and session.peerhost.to_s.length > 0
address = session.peerhost
elsif session.respond_to? :tunnel_peer and session.tunnel_peer.to_s.length > 0
address = session.tunnel_peer[0, session.tunnel_peer.rindex(":") || session.tunnel_peer.length ]
elsif session.respond_to? :target_host and session.target_host.to_s.length > 0
address = session.target_host
else
elog("Session with no peerhost/tunnel_peer")
dlog("#{session.inspect}", LEV_3)
return
end
# Since we got a session, we know the host is vulnerable to something.
# If the exploit used was multi/handler, though, we don't know what
# it's vulnerable to, so it isn't really useful to save it.
if session.via_exploit and session.via_exploit != "exploit/multi/handler"
wspace = framework.db.find_workspace(session.workspace)
host = wspace.hosts.find_by_address(address)
return unless host
port = session.exploit_datastore["RPORT"]
service = (port ? host.services.find_by_port(port) : nil)
mod = framework.modules.create(session.via_exploit)
vuln_info = {
:host => host.address,
:name => session.via_exploit,
:refs => mod.references,
:workspace => wspace
}
framework.db.report_vuln(vuln_info)
# Exploit info is like vuln info, except it's /just/ for storing
# successful exploits in an unserialized way. Yes, there is
# duplication, but it makes exporting a score card about a
# million times easier. TODO: See if vuln/exploit can get fixed up
# to one useful table.
exploit_info = {
:name => session.via_exploit,
:payload => session.via_payload,
:workspace => wspace,
:host => host,
:service => service,
:session_uuid => session.uuid
}
ret = framework.db.report_exploit(exploit_info)
end
end
framework.db.report_session(:session => session)
end
def on_session_upload(session, lpath, rpath)
session_event('session_upload', session, :local_path => lpath, :remote_path => rpath)
framework.db.report_session_event({
:etype => 'upload',
:session => session,
:local_path => lpath,
:remote_path => rpath
})
end
def on_session_download(session, rpath, lpath)
session_event('session_download', session, :local_path => lpath, :remote_path => rpath)
#session_event('session_download', session, :local_path => lpath, :remote_path => rpath)
framework.db.report_session_event({
:etype => 'download',
:session => session,
:local_path => lpath,
:remote_path => rpath
})
end
def on_session_filedelete(session, path)
session_event('session_filedelete', session, :path => path)
#session_event('session_filedelete', session, :path => path)
framework.db.report_session_event({
:etype => 'filedelete',
:session => session,
:local_path => lpath,
:remote_path => rpath
})
end
def on_session_close(session, reason='')
session_event('session_close', session)
if session.db_record
# Don't bother saving here, the session's cleanup method will take
# care of that later.
session.db_record.close_reason = reason
end
end
def on_session_interact(session)
session_event('session_interact', session)
end
#def on_session_interact(session)
# $stdout.puts('session_interact', session.inspect)
#end
def on_session_command(session, command)
session_event('session_command', session, :command => command)
#session_event('session_command', session, :command => command)
framework.db.report_session_event({
:etype => 'command',
:session => session,
:command => command
})
end
def on_session_output(session, output)
@ -412,10 +345,23 @@ class FrameworkEventSubscriber
chunks << buff
end
chunks.each { |chunk|
session_event('session_output', session, :output => chunk)
#session_event('session_output', session, :output => chunk)
framework.db.report_session_event({
:etype => 'output',
:session => session,
:output => chunk
})
}
end
def on_session_route(session, route)
framework.db.report_session_route(session, route)
end
def on_session_route_remove(session, route)
framework.db.report_session_route_remove(session, route)
end
#
# This is covered by on_module_run and on_session_open, so don't bother

View File

@ -18,6 +18,8 @@ require 'msf/core/model/workspace'
require 'msf/core/model/vuln'
require 'msf/core/model/cred'
require 'msf/core/model/exploited_host'
require 'msf/core/model/session'
require 'msf/core/model/session_event'
require 'msf/core/model/wmap_target'
require 'msf/core/model/wmap_request'
@ -35,4 +37,5 @@ require 'msf/core/model/web_vuln'
require 'msf/core/model/imported_cred'
require 'msf/core/model/tag'
require 'msf/core/model/session_event'

View File

@ -25,6 +25,877 @@ class Host < ActiveRecord::Base
n && n.data[:locked]
end
# Determine if the fingerprint data is readable. If not, it nearly always
# means that there was a problem with the YAML or the Marshal'ed data,
# so let's log that for later investigation.
def validate_fingerprint_data(fp)
if fp.data.kind_of?(Hash) and !fp.data.empty?
return true
else
dlog("Could not validate fingerprint data: #{fp.inspect}")
return false
end
end
def normalize_os
host = self
wname = {} # os_name == Linux, Windows, Mac OS X, VxWorks
wtype = {} # purpose == server, client, device
wflav = {} # os_flavor == Ubuntu, Debian, 2003, 10.5, JetDirect
wvers = {} # os_sp == 9.10, SP2, 10.5.3, 3.05
warch = {} # arch == x86, PPC, SPARC, MIPS, ''
wlang = {} # os_lang == English, ''
whost = {} # hostname
# Normalize the operating system fingerprints provided by various
# scanners (nmap, nexpose, retina, nessus, etc). These are stored as
# notes (instead of directly in the os_* fields) specifically for this
# purpose. Note that we're already restricting to this host by using
# host.notes instead of Note, so don't need a host_id in the conditions.
fingers = host.notes.find(:all,
:conditions => [ "ntype like '%%fingerprint'" ]
)
fingers.each do |fp|
next if not validate_fingerprint_data(fp)
norm = normalize_scanner_fp(fp)
wvers[norm[:os_sp]] = wvers[norm[:os_sp]].to_i + (100 * norm[:certainty])
wname[norm[:os_name]] = wname[norm[:os_name]].to_i + (100 * norm[:certainty])
wflav[norm[:os_flavor]] = wflav[norm[:os_flavor]].to_i + (100 * norm[:certainty])
warch[norm[:arch]] = warch[norm[:arch]].to_i + (100 * norm[:certainty])
end
# Grab service information and assign scores. Some services are
# more trustworthy than others. If more services agree than not,
# than that should be considered as well.
# Each service has a starting number of points. Services that
# are more difficult to fake are awarded more points. The points
# represent a running total, not a fixed score.
# XXX: This needs to be refactored in a big way. Tie-breaking is
# pretty arbitrary, it would be nice to explicitly believe some
# services over others, but that means recording which service
# has an opinion and which doesn't. It would also be nice to
# identify "impossible" combinations of services and alert that
# something funny is going on.
host.services.find(:all).each do |s|
next if not s.info
points = 0
case s.name
when 'smb'
points = 210
case s.info
when /(ubuntu|debian|fedora|red ?hat|rhel)/i
wname['Linux'] = wname['Linux'].to_i + points
wflav[$1.capitalize] = wflav[$1.capitalize].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /^Windows/
win_sp = nil
win_flav = nil
win_lang = nil
ninfo = s.info
ninfo.gsub!('(R)', '')
ninfo.gsub!('(TM)', '')
ninfo.gsub!(/\s+/, ' ')
# Windows (R) Web Server 2008 6001 Service Pack 1 (language: Unknown) (name:PG-WIN2008WEB) (domain:WORKGROUP)
# Windows XP Service Pack 3 (language: English) (name:EGYPT-B3E55BF3C) (domain:EGYPT-B3E55BF3C)
# Windows 7 Ultimate (Build 7600) (language: Unknown) (name:WIN7) (domain:WORKGROUP)
#if ninfo =~ /^Windows ([^\s]+)(.*)(Service Pack |\(Build )([^\(]+)\(/
if ninfo =~ /^Windows (.*)(Service Pack [^\s]+|\(Build [^\)]+\))/
win_flav = $1.strip
win_sp = ($2).strip
win_sp.gsub!(/with.*/, '')
win_sp.gsub!('Service Pack', 'SP')
win_sp.gsub!('Build', 'b')
win_sp.gsub!(/\s+/, '')
win_sp.tr!("()", '')
else
if ninfo =~ /^Windows ([^\s+]+)([^\(]+)\(/
win_flav = $2.strip
end
end
if ninfo =~ /name: ([^\)]+)\)/
hostname = $1.strip
end
if ninfo =~ /language: ([^\)]+)\)/
win_lang = $1.strip
end
win_lang = nil if win_lang =~ /unknown/i
win_vers = win_sp
wname['Microsoft Windows'] = wname['Microsoft Windows'].to_i + points
wlang[win_lang] = wlang[win_lang].to_i + points if win_lang
wflav[win_flav] = wflav[win_flav].to_i + points if win_flav
wvers[win_vers] = wvers[win_vers].to_i + points if win_vers
whost[hostname] = whost[hostname].to_i + points if hostname
case win_flav
when /NT|2003|2008/
win_type = 'server'
else
win_type = 'client'
end
wtype[win_type] = wtype[win_type].to_i + points
end
when 'ssh'
points = 104
case s.info
when /honeypot/i # Never trust this
nil
when /ubuntu/i
# This needs to be above /debian/ becuase the ubuntu banner contains both, e.g.:
# SSH-2.0-OpenSSH_5.3p1 Debian-3ubuntu6
wname['Linux'] = wname['Linux'].to_i + points
wflav['Ubuntu'] = wflav['Ubuntu'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /debian/i
wname['Linux'] = wname['Linux'].to_i + points
wflav['Debian'] = wflav['Debian'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /FreeBSD/
wname['FreeBSD'] = wname['FreeBSD'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /sun_ssh/i
wname['Sun Solaris'] = wname['Sun Solaris'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /vshell|remotelyanywhere|freessh/i
wname['Microsoft Windows'] = wname['Microsoft Windows'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /radware/i
wname['RadWare'] = wname['RadWare'].to_i + points
wtype['device'] = wtype['device'].to_i + points
when /dropbear/i
wname['Linux'] = wname['Linux'].to_i + points
wtype['device'] = wtype['device'].to_i + points
when /netscreen/i
wname['NetScreen'] = wname['NetScreen'].to_i + points
wtype['device'] = wtype['device'].to_i + points
when /vpn3/
wname['Cisco VPN 3000'] = wname['Cisco VPN 3000'].to_i + points
wtype['device'] = wtype['device'].to_i + points
when /cisco/i
wname['Cisco IOS'] = wname['Cisco IOS'].to_i + points
wtype['device'] = wtype['device'].to_i + points
when /mpSSH/
wname['HP iLO'] = wname['HP iLO'].to_i + points
wtype['server'] = wtype['server'].to_i + points
end
when 'http'
points = 99
case s.info
when /iSeries/
wname['IBM iSeries'] = wname['IBM iSeries'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /Mandrake/i
wname['Linux'] = wname['Linux'].to_i + points
wflav['Mandrake'] = wflav['Mandrake'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /Mandriva/i
wname['Linux'] = wname['Linux'].to_i + points
wflav['Mandrake'] = wflav['Mandrake'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /Ubuntu/i
wname['Linux'] = wname['Linux'].to_i + points
wflav['Ubuntu'] = wflav['Ubuntu'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /Debian/i
wname['Linux'] = wname['Linux'].to_i + points
wflav['Debian'] = wflav['Debian'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /Fedora/i
wname['Linux'] = wname['Linux'].to_i + points
wflav['Fedora'] = wflav['Fedora'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /CentOS/i
wname['Linux'] = wname['Linux'].to_i + points
wflav['CentOS'] = wflav['CentOS'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /RHEL/i
wname['Linux'] = wname['Linux'].to_i + points
wflav['RHEL'] = wflav['RHEL'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /Red.?Hat/i
wname['Linux'] = wname['Linux'].to_i + points
wflav['Red Hat'] = wflav['Red Hat'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /SuSE/i
wname['Linux'] = wname['Linux'].to_i + points
wflav['SUSE'] = wflav['SUSE'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /TurboLinux/i
wname['Linux'] = wname['Linux'].to_i + points
wflav['TurboLinux'] = wflav['TurboLinux'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /Gentoo/i
wname['Linux'] = wname['Linux'].to_i + points
wflav['Gentoo'] = wflav['Gentoo'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /Conectiva/i
wname['Linux'] = wname['Linux'].to_i + points
wflav['Conectiva'] = wflav['Conectiva'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /Asianux/i
wname['Linux'] = wname['Linux'].to_i + points
wflav['Asianux'] = wflav['Asianux'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /Trustix/i
wname['Linux'] = wname['Linux'].to_i + points
wflav['Trustix'] = wflav['Trustix'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /White Box/
wname['Linux'] = wname['Linux'].to_i + points
wflav['White Box'] = wflav['White Box'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /UnitedLinux/
wname['Linux'] = wname['Linux'].to_i + points
wflav['UnitedLinux'] = wflav['UnitedLinux'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /PLD\/Linux/
wname['Linux'] = wname['Linux'].to_i + points
wflav['PLD/Linux'] = wflav['PLD/Linux'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /Vine\/Linux/
wname['Linux'] = wname['Linux'].to_i + points
wflav['Vine/Linux'] = wflav['Vine/Linux'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /rPath/
wname['Linux'] = wname['Linux'].to_i + points
wflav['rPath'] = wflav['rPath'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /StartCom/
wname['Linux'] = wname['Linux'].to_i + points
wflav['StartCom'] = wflav['StartCom'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /linux/i
wname['Linux'] = wname['Linux'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /PalmOS/
wname['PalmOS'] = wname['PalmOS'].to_i + points
wtype['device'] = wtype['device'].to_i + points
when /Microsoft[\x20\x2d]IIS\/[234]\.0/
wname['Microsoft Windows NT 4.0'] = wname['Microsoft Windows NT 4.0'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /Microsoft[\x20\x2d]IIS\/5\.0/
wname['Microsoft Windows 2000'] = wname['Microsoft Windows 2000'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /Microsoft[\x20\x2d]IIS\/5\.1/
wname['Microsoft Windows XP'] = wname['Microsoft Windows XP'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /Microsoft[\x20\x2d]IIS\/6\.0/
wname['Microsoft Windows 2003'] = wname['Microsoft Windows 2003'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /Microsoft[\x20\x2d]IIS\/7\.0/
wname['Microsoft Windows 2008'] = wname['Microsoft Windows 2008'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /Win32/i
wname['Microsoft Windows'] = wname['Microsoft Windows'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /DD\-WRT ([^\s]+) /i
wname['Linux'] = wname['Linux'].to_i + points
wflav['DD-WRT'] = wflav['DD-WRT'].to_i + points
wvers[$1.strip] = wvers[$1.strip].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /Darwin/
wname['Apple Mac OS X'] = wname['Apple Mac OS X'].to_i + points
when /FreeBSD/i
wname['FreeBSD'] = wname['FreeBSD'].to_i + points
when /OpenBSD/i
wname['OpenBSD'] = wname['OpenBSD'].to_i + points
when /NetBSD/i
wname['NetBSD'] = wname['NetBSD'].to_i + points
when /NetWare/i
wname['Novell NetWare'] = wname['Novell NetWare'].to_i + points
when /OpenVMS/i
wname['OpenVMS'] = wname['OpenVMS'].to_i + points
when /SunOS|Solaris/i
wname['Sun Solaris'] = wname['Sun Solaris'].to_i + points
when /HP.?UX/i
wname['HP-UX'] = wname['HP-UX'].to_i + points
end
when 'snmp'
points = 103
case s.info
when /^Sun SNMP Agent/
wname['Sun Solaris'] = wname['Sun Solaris'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /^SunOS ([^\s]+) ([^\s]+) /
# XXX 1/2 XXX what does this comment mean i wonder
wname['Sun Solaris'] = wname['Sun Solaris'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /^Linux ([^\s]+) ([^\s]+) /
whost[$1] = whost[$1].to_i + points
wname['Linux ' + $2] = wname['Linux ' + $2].to_i + points
wvers[$2] = wvers[$2].to_i + points
arch = get_arch_from_string(s.info)
warch[arch] = warch[arch].to_i + points if arch
wtype['server'] = wtype['server'].to_i + points
when /^Novell NetWare ([^\s]+)/
wname['Novell NetWare ' + $1] = wname['Novell NetWare ' + $1].to_i + points
wvers[$1] = wvers[$1].to_i + points
arch = "x86"
warch[arch] = warch[arch].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /^Novell UnixWare ([^\s]+)/
wname['Novell UnixWare ' + $1] = wname['Novell UnixWare ' + $1].to_i + points
wvers[$1] = wvers[$1].to_i + points
arch = "x86"
warch[arch] = warch[arch].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /^HP-UX ([^\s]+) ([^\s]+) /
# XXX
wname['HP-UX ' + $2] = wname['HP-UX ' + $2].to_i + points
wvers[$1] = wvers[$1].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /^IBM PowerPC.*Base Operating System Runtime AIX version: (\d+\.\d+)/
wname['IBM AIX ' + $1] = wname['IBM AIX ' + $1].to_i + points
wvers[$1] = wvers[$1].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /^SCO TCP\/IP Runtime Release ([^\s]+)/
wname['SCO UnixWare ' + $1] = wname['SCO UnixWare ' + $1].to_i + points
wvers[$1] = wvers[$1].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /.* IRIX version ([^\s]+)/
wname['SGI IRIX ' + $1] = wname['SGI IRIX ' + $1].to_i + points
wvers[$1] = wvers[$1].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /^Unisys ([^\s]+) version ([^\s]+) kernel/
wname['Unisys ' + $2] = wname['Unisys ' + $2].to_i + points
wvers[$2] = wvers[$2].to_i + points
whost[$1] = whost[$1].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /.*OpenVMS V([^\s]+) /
# XXX
wname['OpenVMS ' + $1] = wname['OpenVMS ' + $1].to_i + points
wvers[$1] = wvers[$1].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /^Hardware:.*Software: Windows NT Version ([^\s]+) /
wname['Microsoft Windows NT ' + $1] = wname['Microsoft Windows NT ' + $1].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /^Hardware:.*Software: Windows 2000 Version 5\.0/
wname['Microsoft Windows 2000'] = wname['Microsoft Windows 2000'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /^Hardware:.*Software: Windows 2000 Version 5\.1/
wname['Microsoft Windows XP'] = wname['Microsoft Windows XP'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /^Hardware:.*Software: Windows Version 5\.2/
wname['Microsoft Windows 2003'] = wname['Microsoft Windows 2003'].to_i + points
wtype['server'] = wtype['server'].to_i + points
# XXX: TODO 2008, Vista, Windows 7
when /^Microsoft Windows CE Version ([^\s]+)+/
wname['Microsoft Windows CE ' + $1] = wname['Microsoft Windows CE ' + $1].to_i + points
wtype['client'] = wtype['client'].to_i + points
when /^IPSO ([^\s]+) ([^\s]+) /
whost[$1] = whost[$1].to_i + points
wname['Nokia IPSO ' + $2] = wname['Nokia IPSO ' + $2].to_i + points
wvers[$2] = wvers[$2].to_i + points
arch = get_arch_from_string(s.info)
warch[arch] = warch[arch].to_s + points if arch
wtype['device'] = wtype['device'].to_i + points
when /^Sun StorEdge/
wname['Sun StorEdge'] = wname['Sun StorEdge'].to_i + points
wtype['device'] = wtype['device'].to_i + points
when /^HP StorageWorks/
wname['HP StorageWorks'] = wname['HP StorageWorks'].to_i + points
wtype['device'] = wtype['device'].to_i + points
when /^Network Storage/
# XXX
wname['Network Storage Router'] = wname['Network Storage Router'].to_i + points
wtype['device'] = wtype['device'].to_i + points
when /Cisco Internetwork Operating System.*Version ([^\s]+)/
vers = $1.split(/[,^\s]/)[0]
wname['Cisco IOS ' + vers] = wname['Cisco IOS ' + vers].to_i + points
wvers[vers] = wvers[vers].to_i + points
wtype['device'] = wtype['device'].to_i + points
when /Cisco Catalyst.*Version ([^\s]+)/
vers = $1.split(/[,^\s]/)[0]
wname['Cisco CatOS ' + vers] = wname['Cisco CatOS ' + vers].to_i + points
wvers[vers] = wvers[vers].to_i + points
wtype['device'] = wtype['device'].to_i + points
when /Cisco 761.*Version ([^\s]+)/
vers = $1.split(/[,^\s]/)[0]
wname['Cisco 761 ' + vers] = wname['Cisco 761 ' + vers].to_i + points
wvers[vers] = wvers[vers].to_i + points
wtype['device'] = wtype['device'].to_i + points
when /Network Analysis Module.*Version ([^\s]+)/
vers = $1.split(/[,^\s]/)[0]
wname['Cisco NAM ' + vers] = wname['Cisco NAM ' + vers].to_i + points
wvers[vers] = wvers[vers].to_i + points
wtype['device'] = wtype['device'].to_i + points
when /VPN 3000 Concentrator Series Version ([^\s]+)/
vers = $1.split(/[,^\s]/)[0]
wname['Cisco VPN 3000 ' + vers] = wname['Cisco VPN 3000 ' + vers].to_i + points
wvers[vers] = wvers[vers].to_i + points
wtype['device'] = wtype['device'].to_i + points
when /ProCurve.*Switch/
wname['3Com ProCurve Switch'] = wname['3Com ProCurve Switch'].to_i + points
wtype['device'] = wtype['device'].to_i + points
when /ProCurve.*Access Point/
wname['3Com Access Point'] = wname['3Com Access Point'].to_i + points
wtype['device'] = wtype['device'].to_i + points
when /3Com.*Access Point/i
wname['3Com Access Point'] = wname['3Com Access Point'].to_i + points
wtype['device'] = wtype['device'].to_i + points
when /ShoreGear/
wname['ShoreTel Appliance'] = wname['ShoreTel Appliance'].to_i + points
wtype['device'] = wtype['device'].to_i + points
when /firewall/i
wname['Unknown Firewall'] = wname['Unknown Firewall'].to_i + points
wtype['device'] = wtype['device'].to_i + points
when /phone/i
wname['Unknown Phone'] = wname['Unknown Phone'].to_i + points
wtype['device'] = wtype['device'].to_i + points
when /router/i
wname['Unknown Router'] = wname['Unknown Router'].to_i + points
wtype['device'] = wtype['device'].to_i + points
when /switch/i
wname['Unknown Switch'] = wname['Unknown Switch'].to_i + points
wtype['device'] = wtype['device'].to_i + points
#
# Printer Signatures
#
when /^HP ETHERNET MULTI-ENVIRONMENT/
wname['HP Printer'] = wname['HP Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /Canon/i
wname['Canon Printer'] = wname['Canon Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /Epson/i
wname['Epson Printer'] = wname['Epson Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /ExtendNet/i
wname['ExtendNet Printer'] = wname['ExtendNet Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /Fiery/i
wname['Fiery Printer'] = wname['Fiery Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /Konica/i
wname['Konica Printer'] = wname['Konica Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /Lanier/i
wname['Lanier Printer'] = wname['Lanier Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /Lantronix/i
wname['Lantronix Printer'] = wname['Lantronix Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /Lexmark/i
wname['Lexmark Printer'] = wname['Lexmark Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /Magicolor/i
wname['Magicolor Printer'] = wname['Magicolor Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /Minolta/i
wname['Minolta Printer'] = wname['Minolta Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /NetJET/i
wname['NetJET Printer'] = wname['NetJET Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /OKILAN/i
wname['OKILAN Printer'] = wname['OKILAN Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /Phaser/i
wname['Phaser Printer'] = wname['Phaser Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /PocketPro/i
wname['PocketPro Printer'] = wname['PocketPro Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /Ricoh/i
wname['Ricoh Printer'] = wname['Ricoh Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /Savin/i
wname['Savin Printer'] = wname['Savin Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /SHARP AR/i
wname['SHARP Printer'] = wname['SHARP Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /Star Micronix/i
wname['Star Micronix Printer'] = wname['Star Micronix Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /Source Tech/i
wname['Source Tech Printer'] = wname['Source Tech Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /Xerox/i
wname['Xerox Printer'] = wname['Xerox Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /^Brother/i
wname['Brother Printer'] = wname['Brother Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /^Axis.*Network Print/i
wname['Axis Printer'] = wname['Axis Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /^Prestige/i
wname['Prestige Printer'] = wname['Prestige Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /^ZebraNet/i
wname['ZebraNet Printer'] = wname['ZebraNet Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /e\-STUDIO/i
wname['eStudio Printer'] = wname['eStudio Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /^Gestetner/i
wname['Gestetner Printer'] = wname['Gestetner Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /IBM.*Print/i
wname['IBM Printer'] = wname['IBM Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /HP (Color|LaserJet|InkJet)/i
wname['HP Printer'] = wname['HP Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /Dell (Color|Laser|Ink)/i
wname['Dell Printer'] = wname['Dell Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
when /Print/i
wname['Unknown Printer'] = wname['Unknown Printer'].to_i + points
wtype['printer'] = wtype['printer'].to_i + points
end # End of s.info for SNMP
when 'telnet'
points = 105
case s.info
when /IRIX/
wname['SGI IRIX'] = wname['SGI IRIX'].to_i + points
when /AIX/
wname['IBM AIX'] = wname['IBM AIX'].to_i + points
when /(FreeBSD|OpenBSD|NetBSD)\/(.*) /
wname[$1] = wname[$1].to_i + points
arch = get_arch_from_string($2)
warch[arch] = warch[arch].to_i + points
when /Ubuntu (\d+(\.\d+)+)/
wname['Linux'] = wname['Linux'].to_i + points
wflav['Ubuntu'] = wflav['Ubuntu'].to_i + points
wvers[$1] = wvers[$1].to_i + points
when /User Access Verification/
wname['Cisco IOS'] = wname['Cisco IOS'].to_i + points
when /Microsoft/
wname['Microsoft Windows'] = wname['Microsoft Windows'].to_i + points
end # End of s.info for TELNET
wtype['server'] = wtype['server'].to_i + points
when 'smtp'
points = 103
case s.info
when /ESMTP.*SGI\.8/
wname['SGI IRIX'] = wname['SGI IRIX'].to_i + points
wtype['server'] = wtype['server'].to_i + points
end # End of s.info for SMTP
when 'netbios'
points = 201
case s.info
when /W2K3/i
wname['Microsoft Windows 2003'] = wname['Microsoft Windows 2003'].to_i + points
wtype['server'] = wtype['server'].to_i + points
when /W2K8/i
wname['Microsoft Windows 2008'] = wname['Microsoft Windows 2008'].to_i + points
wtype['server'] = wtype['server'].to_i + points
end # End of s.info for NETBIOS
when 'dns'
points = 101
case s.info
when 'Microsoft DNS'
wname['Microsoft Windows'] = wname['Microsoft Windows'].to_i + points
wtype['server'] = wtype['server'].to_i + points
end # End of s.info for DNS
end # End of s.name case
# End of Services
end
#
# Report the best match here
#
best_match = {}
best_match[:os_name] = wname.keys.sort{|a,b| wname[b] <=> wname[a]}[0]
best_match[:purpose] = wtype.keys.sort{|a,b| wtype[b] <=> wtype[a]}[0]
best_match[:os_flavor] = wflav.keys.sort{|a,b| wflav[b] <=> wflav[a]}[0]
best_match[:os_sp] = wvers.keys.sort{|a,b| wvers[b] <=> wvers[a]}[0]
best_match[:arch] = warch.keys.sort{|a,b| warch[b] <=> warch[a]}[0]
best_match[:name] = whost.keys.sort{|a,b| whost[b] <=> whost[a]}[0]
best_match[:os_lang] = wlang.keys.sort{|a,b| wlang[b] <=> wlang[a]}[0]
best_match[:os_flavor] ||= ""
if best_match[:os_name]
# Handle cases where the flavor contains the base name
best_match[:os_flavor].gsub!(best_match[:os_name], '')
end
best_match[:os_name] ||= 'Unknown'
best_match[:purpose] ||= 'device'
[:os_name, :purpose, :os_flavor, :os_sp, :arch, :name, :os_lang].each do |host_attr|
next if host.attribute_locked? host_attr
if best_match[host_attr]
host[host_attr] = Rex::Text.ascii_safe_hex(best_match[host_attr])
end
end
host.save!
p host
end
protected
#
# Convert a host.os.*_fingerprint Note into a hash containing the standard os_* fields
#
# Also includes a :certainty which is a float from 0 - 1.00 indicating the
# scanner's confidence in its fingerprint. If the particular scanner does
# not provide such information, defaults to 0.80.
#
def normalize_scanner_fp(fp)
return {} if not validate_fingerprint_data(fp)
ret = {}
data = fp.data
case fp.ntype
when 'host.os.session_fingerprint'
# These come from meterpreter sessions' client.sys.config.sysinfo
if data[:os] =~ /Windows/
ret.update(parse_windows_os_str(data[:os]))
else
ret[:os_name] = data[:os]
end
ret[:arch] = data[:arch] if data[:arch]
when 'host.os.nmap_fingerprint'
# :os_vendor=>"Microsoft" :os_family=>"Windows" :os_version=>"2000" :os_accuracy=>"94"
#
# :os_match=>"Microsoft Windows Vista SP0 or SP1, Server 2008, or Windows 7 Ultimate (build 7000)"
# :os_vendor=>"Microsoft" :os_family=>"Windows" :os_version=>"7" :os_accuracy=>"100"
ret[:certainty] = data[:os_accuracy].to_f / 100.0
if (data[:os_vendor] == data[:os_family])
ret[:os_name] = data[:os_family]
else
ret[:os_name] = data[:os_vendor] + " " + data[:os_family]
end
when 'host.os.nexpose_fingerprint'
# :family=>"Windows" :certainty=>"0.85" :vendor=>"Microsoft" :product=>"Windows 7 Ultimate Edition"
# :family=>"Linux" :certainty=>"0.64" :vendor=>"Linux" :product=>"Linux"
# :family=>"Linux" :certainty=>"0.80" :vendor=>"Ubuntu" :product=>"Linux"
# :family=>"IOS" :certainty=>"0.80" :vendor=>"Cisco" :product=>"IOS"
# :family=>"embedded" :certainty=>"0.61" :vendor=>"Linksys" :product=>"embedded"
ret[:certainty] = data[:certainty].to_f
case data[:family]
when /AIX|ESX|Mac OS X|OpenSolaris|Solaris|IOS|Linux/
if data[:vendor] == data[:family]
ret[:os_name] = data[:vendor]
else
# family often contains the vendor string, so rip it out to
# avoid useless duplication
ret[:os_name] = data[:vendor] + " " + data[:family].gsub(data[:vendor], '').strip
end
when "Windows"
ret[:os_name] = "Microsoft Windows"
ret[:os_flavor] = data[:product].gsub("Windows", '').strip if data[:product]
when "embedded"
ret[:os_name] = data[:vendor]
else
ret[:os_name] = data[:vendor]
end
ret[:arch] = get_arch_from_string(data[:arch]) if data[:arch]
ret[:arch] ||= get_arch_from_string(data[:desc]) if data[:desc]
when 'host.os.retina_fingerprint'
# :os=>"Windows Server 2003 (X64), Service Pack 2"
case data[:os]
when /Windows/
ret.merge(parse_windows_os_str(data[:os]))
else
# No idea what this looks like if it isn't windows. Just store
# the whole thing and hope for the best. XXX: Ghetto. =/
ret[:os_name] = data[:os]
end
when 'host.os.nessus_fingerprint'
# :os=>"Microsoft Windows 2000 Advanced Server (English)"
# :os=>"Microsoft Windows 2000\nMicrosoft Windows XP"
# :os=>"Linux Kernel 2.6"
# :os=>"Sun Solaris 8"
# :os=>"IRIX 6.5"
# Nessus sometimes jams multiple OS names together with a newline.
oses = data[:os].split(/\n/)
if oses.length > 1
# Multiple fingerprints means Nessus wasn't really sure, reduce
# the certainty accordingly
ret[:certainty] = 0.5
else
ret[:certainty] = 0.8
end
# Since there is no confidence associated with them, the best we
# can do is just take the first one.
case oses.first
when /Windows/
ret.merge(parse_windows_os_str(os))
when /(.*)?((\d+\.)+\d+)$/
# Then this fingerprint has some version information at the
# end, pull it off.
ret[:os_name] = $1.gsub("Kernel", '').strip
ret[:os_sp] = $2
else
ret[:os_name] = oses.first
end
when 'host.os.qualys_fingerprint'
# :os=>"Microsoft Windows 2000"
# :os=>"Windows 2003"
# :os=>"Microsoft Windows XP Professional SP3"
# :os=>"Ubuntu Linux"
# :os=>"Cisco IOS 12.0(3)T3"
case data[:os]
when /Windows/
ret.merge(parse_windows_os_str(data[:os]))
else
parts = data[:os].split(/\s+/, 3)
ret[:os_name] = parts[0] + " " + parts[1]
ret[:os_sp] = parts[2] if parts[2]
end
# XXX: We should really be using smb_version's stored fingerprints
# instead of parsing the service info manually. Disable for now so we
# don't count smb twice.
#when 'smb.fingerprint'
# # smb_version is kind enough to store everything we need directly
# ret.merge(fp.data)
# # If it's windows, this should be a pretty high-confidence
# # fingerprint. Otherwise, it's samba which doesn't give us much of
# # anything in most cases.
# ret[:certainty] = 1.0 if fp.data[:os_name] =~ /Windows/
end
ret[:certainty] ||= 0.8
ret
end
def parse_windows_os_str(str)
ret = {}
ret[:os_name] = "Microsoft Windows"
ret[:arch] = get_arch_from_string(str)
if str =~ /(Service Pack|SP) ?(\d+)/
ret[:os_sp] = "SP#{$2}"
end
# Flavor
case str
when /(XP|2000|2003|Vista|7 .* Edition|7)/
ret[:os_flavor] = $1
else
# If we couldn't pull out anything specific for the flavor, just cut
# off the stuff we know for sure isn't it and hope for the best
ret[:os_flavor] ||= str.gsub(/(Microsoft )?Windows|(Service Pack|SP) ?(\d+)/, '').strip
end
if str =~ /NT|2003|2008|SBS|Server/
ret[:type] = 'server'
else
ret[:type] = 'client'
end
ret
end
# A case switch to return a normalized arch based on a given string.
def get_arch_from_string(str)
case str
when /x64|amd64|x86_64/i
"x64"
when /x86|i[3456]86/i
"x86"
when /PowerPC|PPC|POWER|ppc/
"ppc"
when /SPARC/i
"sparc"
when /MIPS/i
"mips"
when /ARM/i
"arm"
else
nil
end
end
end
end

View File

@ -8,6 +8,13 @@ class Note < ActiveRecord::Base
belongs_to :host
belongs_to :service
serialize :data
def after_save
if data_changed? and ntype =~ /fingerprint/
host.normalize_os
end
end
end
end

View File

@ -15,6 +15,13 @@ class Service < ActiveRecord::Base
has_many :web_vulns, :through => :web_sites
serialize :info
def after_save
if info_changed?
host.normalize_os
end
end
end
end

View File

@ -0,0 +1,11 @@
module Msf
class DBManager
class Session < ActiveRecord::Base
has_one :host
serialize :datastore
serialize :routes
end
end
end

View File

@ -0,0 +1,11 @@
module Msf
class DBManager
# TODO: needs a belongs_to :session when that model gets committed.
class SessionEvent < ActiveRecord::Base
include DBSave
end
end
end

View File

@ -36,7 +36,7 @@ class Session < Base
authenticate(token)
s = @framework.sessions[sid.to_i]
if(not s)
raise ::XMLRPC::FaultException.new(404, "unknown session")
raise ::XMLRPC::FaultException.new(404, "unknown session while stopping")
end
s.kill
{ "result" => "success" }
@ -185,7 +185,7 @@ protected
authenticate(token)
s = @framework.sessions[sid.to_i]
if(not s)
raise ::XMLRPC::FaultException.new(404, "unknown session")
raise ::XMLRPC::FaultException.new(404, "unknown session while validating")
end
if(s.type != type)
raise ::XMLRPC::FaultException.new(403, "session is not "+type)

View File

@ -79,7 +79,8 @@ module Session
def initialize
self.alive = true
self.uuid = Rex::Text.rand_text_alphanumeric(8).downcase
self.routes = []
@routes = RouteArray.new(self)
#self.routes = []
end
# Direct descendents
@ -241,7 +242,16 @@ module Session
#
# Perform session-specific cleanup.
#
# NOTE: session classes overriding this method must call super!
# Also must tolerate being called multiple times.
#
def cleanup
if db_record and framework.db.active
db_record.closed_at = Time.now
# ignore exceptions
db_record.save
db_record = nil
end
end
#
@ -262,6 +272,7 @@ module Session
def dead?
(not self.alive)
end
def alive?
(self.alive)
end
@ -316,6 +327,10 @@ module Session
# An array of routes associated with this session
#
attr_accessor :routes
#
# This session's associated database record
#
attr_accessor :db_record
protected
attr_accessor :via # :nodoc:
@ -324,3 +339,21 @@ end
end
class RouteArray < Array # :nodoc: all
def initialize(sess)
self.session = sess
super()
end
def <<(val)
session.framework.events.on_session_route(session, val)
super
end
def delete(val)
session.framework.events.on_session_route_remove(session, val)
super
end
attr_accessor :session
end

View File

@ -34,7 +34,11 @@ protected
#
def _interact
framework.events.on_session_interact(self)
interact_stream(rstream)
if self.respond_to?(:ring)
interact_ring(ring)
else
interact_stream(rstream)
end
end
end

View File

@ -1,4 +1,5 @@
require 'rex/ui'
require 'rex/io/ring_buffer'
module Msf
module Session
@ -21,7 +22,11 @@ module Interactive
# Initializes the session.
#
def initialize(rstream, opts={})
self.rstream = rstream
# A nil is passed in the case of non-stream interactive sessions (Meterpreter)
if rstream
self.rstream = rstream
self.ring = Rex::IO::RingBuffer.new(rstream, {:size => opts[:ring_size] || 100 })
end
super()
end
@ -37,10 +42,11 @@ module Interactive
# Returns the local information.
#
def tunnel_local
return @local_info if @local_info
begin
rstream.localinfo
@local_info = rstream.localinfo
rescue ::Exception
'127.0.0.1'
@local_info = '127.0.0.1'
end
end
@ -48,10 +54,11 @@ module Interactive
# Returns the remote peer information.
#
def tunnel_peer
return @peer_info if @peer_info
begin
@peer_info = rstream.peerinfo
rescue ::Exception
@peer_info ||= '127.0.0.1'
@peer_info = '127.0.0.1'
end
end
@ -65,7 +72,6 @@ module Interactive
# Terminate the session
#
def kill
self.interacting = false if self.interactive?
self.reset_ui
self.cleanup
super()
@ -76,11 +82,13 @@ module Interactive
#
def cleanup
begin
self.interacting = false if self.interactive?
rstream.close if (rstream)
rescue ::Exception
end
rstream = nil
super
end
#
@ -88,6 +96,11 @@ module Interactive
#
attr_accessor :rstream
#
# The RingBuffer object used to allow concurrent access to this session
#
attr_accessor :ring
protected
#
@ -104,10 +117,7 @@ protected
begin
user_want_abort?
rescue Interrupt
# The user hit ctrl-c while we were handling a ctrl-c, send a
# literal ctrl-c to the shell. XXX Doesn't actually work.
#$stdout.puts("\n[*] interrupted interrupt, sending literal ctrl-c\n")
#$stdout.puts(run_cmd("\x03"))
# The user hit ctrl-c while we were handling a ctrl-c. Ignore
end
end

View File

@ -21,8 +21,47 @@ class SessionManager < Hash
self.framework = framework
self.sid_pool = 0
self.reaper_thread = framework.threads.spawn("SessionManager", true, self) do |manager|
begin
while true
::IO.select(nil, nil, nil, 0.5)
rings = values.select{|s| s.respond_to?(:ring) and s.ring and s.rstream }
ready = ::IO.select(rings.map{|s| s.rstream}, nil, nil, 0.5) || [[],[],[]]
ready[0].each do |fd|
s = rings.select{|s| s.rstream == fd}.first
next if not s
begin
buff = fd.get_once(-1)
if buff
# Store the data in the associated ring
s.ring.store_data(buff)
# Store the session event into the database.
# Rescue anything the event handlers raise so they
# don't break our session.
framework.events.on_session_output(s, buff) rescue nil
end
rescue ::Exception => e
wlog("Exception reading from Session #{s.sid}: #{e.class} #{e}")
unless e.kind_of? EOFError
# Don't bother with a call stack if it's just a
# normal EOF
dlog("Call Stack\n#{e.backtrace.join("\n")}", 'core', LEV_3)
end
# Flush any ring data in the queue
s.ring.clear_data rescue nil
# Shut down the socket itself
s.rstream.close rescue nil
# Deregister the session
manager.deregister(s, "Died from #{e.class}")
end
end
# Check for closed / dead / terminated sessions
manager.each_value do |s|
if not s.alive?
manager.deregister(s, "Died")
@ -31,6 +70,11 @@ class SessionManager < Hash
end
end
end
rescue ::Exception => e
wlog("Exception in reaper thread #{e.class} #{e}")
wlog("Call Stack\n#{e.backtrace.join("\n")}", 'core', LEV_3)
end
end
end
@ -61,7 +105,13 @@ class SessionManager < Hash
session.framework = framework
# Notify the framework that we have a new session opening up...
framework.events.on_session_open(session)
# Don't let errant event handlers kill our session
begin
framework.events.on_session_open(session)
rescue ::Exception => e
wlog("Exception in on_session_open event handler: #{e.class}: #{e}")
wlog("Call Stack\n#{e.backtrace.join("\n")}", 'core', LEV_3)
end
if session.respond_to?("console")
session.console.on_command_proc = Proc.new { |command, error| framework.events.on_session_command(session, command) }
@ -81,7 +131,7 @@ class SessionManager < Hash
end
# Tell the framework that we have a parting session
framework.events.on_session_close(session, reason)
framework.events.on_session_close(session, reason) rescue nil
# If this session implements the comm interface, remove any routes
# that have been created for it.
@ -89,10 +139,6 @@ class SessionManager < Hash
Rex::Socket::SwitchBoard.remove_by_comm(session)
end
if session.kind_of?(Msf::Session::Interactive)
session.interacting = false
end
# Remove it from the hash
self.delete(session.sid.to_i)

View File

@ -211,7 +211,7 @@ class TaskManager
elog("taskmanager: task triggered an exception: #{e.class} #{e}")
elog("taskmanager: task proc: #{task.proc.inspect} ")
elog("taskmanager: task Call stack: #{task.source.join("\n")} ")
elog("taskmanager: task Call stack: \n#{task.source.join("\n")} ")
dlog("Call stack:\n#{$@.join("\n")}")
task.status = :dropped

View File

@ -63,7 +63,8 @@ class ThreadManager < Array
begin
argv.shift.call(*argv)
rescue ::Exception => e
elog("thread exception: #{::Thread.current[:tm_name]} critical=#{::Thread.current[:tm_crit]} error:#{e.class} #{e} #{e.backtrace} source:#{::Thread.current[:tm_call].inspect}")
elog("thread exception: #{::Thread.current[:tm_name]} critical=#{::Thread.current[:tm_crit]} error:#{e.class} #{e} source:#{::Thread.current[:tm_call].inspect}")
elog("Call Stack\n#{e.backtrace.join("\n")}")
raise e
end
end

View File

@ -870,11 +870,12 @@ class Core
return false
end
case args.shift
arg = args.shift
case arg
when "add"
when "add", "remove"
if (args.length < 3)
print_error("Missing arguments to route add.")
print_error("Missing arguments to route #{arg}.")
return false
end
@ -912,56 +913,22 @@ class Core
return false
end
Rex::Socket::SwitchBoard.add_route(
args[0],
args[1],
gw)
when "remove"
if (args.length < 3)
print_error("Missing arguments to route remove.")
return false
end
# Satisfy check to see that formatting is correct
unless Rex::Socket::RangeWalker.new(args[0]).length == 1
print_error "Invalid IP Address"
return false
end
unless Rex::Socket::RangeWalker.new(args[1]).length == 1
print_error "Invalid Subnet mask"
return false
end
gw = nil
# Satisfy case problems
args[2] = "Local" if (args[2] =~ /local/i)
begin
# If the supplied gateway is a global Comm, use it.
if (Rex::Socket::Comm.const_defined?(args[2]))
gw = Rex::Socket::Comm.const_get(args[2])
if arg == "remove"
worked = Rex::Socket::SwitchBoard.remove_route(args[0], args[1], gw)
if worked
print_status("Route removed")
else
print_error("Route not found")
end
else
worked = Rex::Socket::SwitchBoard.add_route(args[0], args[1], gw)
if worked
print_status("Route added")
else
print_error("Route already exists")
end
rescue NameError
end
# If we still don't have a gateway, check if it's a session.
if ((gw == nil) and
(session = framework.sessions.get(args[2])) and
(session.kind_of?(Msf::Session::Comm)))
gw = session
elsif (gw == nil)
print_error("Invalid gateway specified.")
return false
end
Rex::Socket::SwitchBoard.remove_route(
args[0],
args[1],
gw)
when "get"
if (args.length == 0)
print_error("You must supply an IP address.")

364
lib/rex/io/ring_buffer.rb Normal file
View File

@ -0,0 +1,364 @@
#
# This class implements a ring buffer with "cursors" in the form of sequence numbers.
# To use this class, pass in a file descriptor and a ring size, the class will read
# data from the file descriptor and store it in the ring. If the ring becomes full,
# the oldest item will be overwritten. To emulate a stream interface, call read_data
# to grab the last sequence number and any buffered data, call read_data again,
# passing in the sequence number and all data newer than that sequence will be
# returned, along with a new sequence to read from.
#
require 'rex/socket'
module Rex
module IO
class RingBuffer
attr_accessor :queue # The data queue, essentially an array of two-element arrays, containing a sequence and data buffer
attr_accessor :seq # The next available sequence number
attr_accessor :fd # The associated socket or IO object for this ring buffer
attr_accessor :size # The number of available slots in the queue
attr_accessor :mutex # The mutex locking access to the queue
attr_accessor :beg # The index of the earliest data fragment in the ring
attr_accessor :cur # The sequence number of the earliest data fragment in the ring
attr_accessor :monitor # The thread handle of the built-in monitor when used
attr_accessor :monitor_thread_error # :nodoc: #
#
# Create a new ring buffer
#
def initialize(socket, opts={})
self.size = opts[:size] || (1024 * 4)
self.fd = socket
self.seq = 0
self.beg = 0
self.cur = 0
self.queue = Array.new( self.size )
self.mutex = Mutex.new
end
#
# Start the built-in monitor, not called when used in a larger framework
#
def start_monitor
self.monitor = monitor_thread if not self.monitor
end
#
# Stop the built-in monitor
#
def stop_monitor
self.monitor.kill if self.monitor
self.monitor = nil
end
#
# The built-in monitor thread
#
def monitor_thread
Thread.new do
begin
while self.fd
buff = self.fd.get_once(-1, 1.0)
next if not buff
store_data(buff)
end
rescue ::Exception => e
self.monitor_thread_error = e
end
end
end
#
# Push data back into the associated stream socket. Logging must occur
# elsewhere, this function is simply a passthrough.
#
def put(data)
self.fd.put(data)
end
#
# The clear_data method wipes the ring buffer
#
def clear_data
self.mutex.synchronize do
self.seq = 0
self.beg = 0
self.cur = 0
self.queue = Array.new( self.size )
end
end
#
# The store_data method is used to insert data into the ring buffer.
#
def store_data(data)
self.mutex.synchronize do
# self.cur points to the array index of queue containing the last item
# adding data will result in cur + 1 being used to store said data
# if cur is larger than size - 1, it will wrap back around. If cur
# is *smaller* beg, beg is increemnted to cur + 1 (and wrapped if
# necessary
loc = 0
if self.seq > 0
loc = ( self.cur + 1 ) % self.size
if loc <= self.beg
self.beg = (self.beg + 1) % self.size
end
end
self.queue[loc] = [self.seq += 1, data]
self.cur = loc
end
end
#
# The read_data method returns a two element array with the new reader cursor (a sequence number)
# and the returned data buffer (if any). A result of nil/nil indicates that no data is available
#
def read_data(ptr=nil)
self.mutex.synchronize do
# Verify that there is data in the queue
return [nil,nil] if not self.queue[self.beg]
# Configure the beginning read pointer (sequence number, not index)
ptr ||= self.queue[self.beg][0]
return [nil,nil] if not ptr
# If the pointer is below our baseline, we lost some data, so jump forward
if ptr < self.queue[self.beg][0]
ptr = self.queue[self.beg][0]
end
# Calculate how many blocks exist between the current sequence number
# and the requested pointer, this becomes the number of blocks we will
# need to read to satisfy the result. Due to the mutex block, we do
# not need to scan to find the sequence of the starting block or
# check the sequence of the ending block.
dis = self.seq - ptr
# If the requested sequnce number is less than our base pointer, it means
# that no new data is available and we should return empty.
return [nil,nil] if dis < 0
# Calculate the beginning block index and number of blocks to read
off = ptr - self.queue[self.beg][0]
set = (self.beg + off) % self.size
# Build the buffer by reading forward by the number of blocks needed
# and return the last read sequence number, plus one, as the new read
# pointer.
buff = ""
cnt = 0
lst = ptr
ptr.upto(self.seq) do |i|
block = self.queue[ (set + cnt) % self.size ]
lst,data = block[0],block[1]
buff += data
cnt += 1
end
return [lst + 1, buff]
end
end
#
# The base_sequence method returns the earliest sequence number in the queue. This is zero until
# all slots are filled and the ring rotates.
#
def base_sequence
self.mutex.synchronize do
return 0 if not self.queue[self.beg]
return self.queue[self.beg][0]
end
end
#
# The last_sequence method returns the "next" sequence number where new data will be
# available.
#
def last_sequence
self.seq
end
#
# The create_steam method assigns a IO::Socket compatible object to the ringer buffer
#
def create_stream
Stream.new(self)
end
#
# The select method returns when there is a chance of new data
# XXX: This is mostly useless and requires a rewrite to use a
# real select or notify mechanism
#
def select
::IO.select([ self.fd ], nil, [ self.fd ], 0.10)
end
#
# The wait method blocks until new data is available
#
def wait(seq)
nseq = nil
while not nseq
nseq,data = read_data(seq)
select
end
end
#
# The wait_for method blocks until new data is available or the timeout is reached
#
def wait_for(seq,timeout=1)
begin
::Timeout.timeout(timeout) do
wait(seq)
end
rescue ::Timeout::Error
end
end
#
# This class provides a backwards compatible "stream" socket that uses
# the parents ring buffer.
#
class Stream
attr_accessor :ring
attr_accessor :seq
attr_accessor :buff
def initialize(ring)
self.ring = ring
self.seq = ring.base_sequence
self.buff = ''
end
def read(len=nil)
if len and self.buff.length >= len
data = self.buff.slice!(0,len)
return data
end
while true
lseq, data = self.ring.read_data( self.seq )
return if not lseq
self.seq = lseq
self.buff << data
if len
if self.buff.length >= len
return self.buff.slice!(0,len)
else
IO.select(nil, nil, nil, 0.25)
next
end
end
data = self.buff
self.buff = ''
return data
# Not reached
break
end
end
def write(data)
self.ring.write(data)
end
end
end
end
end
=begin
server = Rex::Socket.create_tcp_server('LocalPort' => 0)
lport = server.getsockname[2]
client = Rex::Socket.create_tcp('PeerHost' => '127.0.0.1', 'PeerPort' => lport)
conn = server.accept
r = Rex::IO::RingBuffer.new(conn, {:size => 1024*1024})
client.put("1")
client.put("2")
client.put("3")
s,d = r.read_data
client.put("4")
client.put("5")
client.put("6")
s,d = r.read_data(s)
client.put("7")
client.put("8")
client.put("9")
s,d = r.read_data(s)
client.put("0")
s,d = r.read_data(s)
test_counter = 11
1.upto(100) do
client.put( "X" )
test_counter += 1
end
sleep(1)
s,d = r.read_data
p s
p d
fdata = ''
File.open("/bin/ls", "rb") do |fd|
fdata = fd.read(fd.stat.size)
fdata = fdata * 10
client.put(fdata)
end
sleep(1)
s,vdata = r.read_data(s)
if vdata != fdata
puts "DATA FAILED"
else
puts "DATA VERIFIED"
end
r.clear_data
a = r.create_stream
b = r.create_stream
client.put("ABC123")
sleep(1)
p a.read
p b.read
client.put("$$$$$$")
sleep(1)
p a.read
p b.read
c = r.create_stream
p c.read
=end

View File

@ -597,6 +597,20 @@ module Text
[ str.downcase.gsub(/'/,'').gsub(/\\?x([a-f0-9][a-f0-9])/, '\1') ].pack("H*")
end
#
# Turn non-printable chars into hex representations, leaving others alone
#
# If +whitespace+ is true, converts whitespace (0x20, 0x09, etc) to hex as
# well.
#
def self.ascii_safe_hex(str, whitespace=false)
if whitespace
str.gsub(/([\x00-\x20\x80-\xFF])/){ |x| "\\x%.2x" % x.unpack("C*")[0] }
else
str.gsub(/([\x00-\x08\x0b\x0c\x0e-\x1f\x80-\xFF])/n){ |x| "\\x%.2x" % x.unpack("C*")[0]}
end
end
#
# Wraps text at a given column using a supplied indention
#

View File

@ -199,6 +199,50 @@ protected
end
end
#
# Interacts between a local stream and a remote ring buffer. This has to use
# a secondary thread to prevent the select on the local stream from blocking
#
def interact_ring(ring)
begin
rdr = Rex::ThreadFactory.spawn("RingMonitor", false) do
seq = nil
while self.interacting
# Look for any pending data from the remote ring
nseq,data = ring.read_data(seq)
# Update the sequence number if necessary
seq = nseq || seq
# Write output to the local stream if successful
user_output.print(data) if data
# Wait for new data to arrive on this session
ring.wait(seq)
end
end
while self.interacting
# Look for any pending input from the local stream
sd = Rex::ThreadSafe.select([ _local_fd ], nil, [_local_fd], 5.0)
# Write input to the ring's input mechanism
if sd
data = user_input.gets
ring.put(data)
end
end
ensure
rdr.kill
end
end
#
# Installs a signal handler to monitor suspend signal notifications.
#

View File

@ -801,14 +801,20 @@ class Metasploit3 < Msf::Auxiliary
(os_name, os_flavor, os_sp, os_lang, arch, ua_name, ua_ver) = detected_version.split(':')
if framework.db.active
host_info = { :host => cli.peerhost }
host_info[:os_name] = os_name if os_name != "undefined"
host_info[:os_flavor] = os_flavor if os_flavor != "undefined"
host_info[:os_sp] = os_sp if os_sp != "undefined"
host_info[:os_lang] = os_lang if os_lang != "undefined"
host_info[:arch] = arch if arch != "undefined"
note_data = { }
note_data[:os_name] = os_name if os_name != "undefined"
note_data[:os_flavor] = os_flavor if os_flavor != "undefined"
note_data[:os_sp] = os_sp if os_sp != "undefined"
note_data[:os_lang] = os_lang if os_lang != "undefined"
note_data[:arch] = arch if arch != "undefined"
print_status("Reporting: #{note_data.inspect}")
report_host(host_info)
report_note({
:host => cli.peerhost,
:type => 'javascript_fingerprint',
:data => note_data,
:update => :unique_data,
})
client_info = ({
:host => cli.peerhost,
:ua_string => request['User-Agent'],