local nmap = require "nmap" local shortport = require "shortport" local sip = require "sip" local stdnse = require "stdnse" local table = require "table" description = [[ Spoofs a call to a SIP phone and detects the action taken by the target (busy, declined, hung up, etc.) This works by sending a fake sip invite request to the target phone and checking the responses. A response with status code 180 means that the phone is ringing. The script waits for the next responses until timeout is reached or a special response is received. Special responses include: Busy (486), Decline (603), Timeout (408) or Hang up (200). ]] --- --@args sip-call-spoof.ua Source application's user agent. Defaults to -- Ekiga. -- --@args sip-call-spoof.from Caller user ID. Defaults to Home. -- --@args sip-call-spoof.extension SIP Extension to send request from. Defaults to -- 100. -- --@args sip-call-spoof.src Source address to spoof. -- --@args sip-call-spoof.timeout Time to wait for a response. Defaults to -- 5s -- -- @usage -- nmap --script=sip-call-spoof -sU -p 5060 -- nmap --script=sip-call-spoof -sU -p 5060 --script-args -- 'sip-call-spoof.ua=Nmap, sip-call-spoof.from=Boss' -- --@output -- 5060/udp open sip -- | sip-call-spoof: -- |_ Target hung up. (After 10.9 seconds) author = "Hani Benhabiles" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery", "intrusive"} portrule = shortport.port_or_service(5060, "sip", {"tcp", "udp"}) --- Function that sends an invite request with given parameters. -- @arg session SIP Session to use. -- @arg ua User Agent to use. -- @arg from SIP From field. -- @arg src Request source address to spoof. -- @arg extension Request SIP extension. -- @return status True if we got a response, false else. -- @return resp Response table if status is true, error string else. local sendinvite = function(session, ua, from, src, extension) local request = sip.Request:new(sip.Method.INVITE) request:setUri("sip:" .. session.sessdata:getServer()) request:setUA(ua) if src then session.sessdata:setDomain(src) end session.sessdata:setUsername(extension) session.sessdata:setName(from) request:setSessionData(session.sessdata) return session:exch(request) end --- Function that waits for certain responses for an amount of time. -- @arg session SIP Session to use. -- @arg timeout Max time to wait for responses other than ringing. -- @return ringing True if we got a ringing response, false else. -- @return responsecode Code for the latest meaningful response. -- could be 180, 200, 486, 408 or 603 local waitresponses = function(session,timeout) local response, status, data, responsecode, ringing, waittime local start = nmap.clock_ms() while (nmap.clock_ms() - start) < timeout do status, data = session.conn:recv() if status then response = sip.Response:new(data) responsecode = response:getErrorCode() waittime = nmap.clock_ms() - start if responsecode == sip.Error.RING then ringing = true elseif responsecode == sip.Error.BUSY then return ringing, sip.Error.BUSY elseif responsecode == sip.Error.DECLINE then return ringing, sip.Error.DECLINE, waittime elseif responsecode == sip.Error.OK then return ringing, sip.Error.OK, waittime elseif responsecode == sip.Error.TIMEOUT then return ringing, sip.Error.OK end end end if ringing then return ringing, sip.Error.RING end end --- Function that spoofs an invite request and listens for responses. -- @arg session SIP Session to use. -- @arg ua User Agent to use. -- @arg from SIP From field. -- @arg src Request source address to spoof. -- @arg extension Request SIP extension. -- @arg timeout Max time to wait for responses other than ringing. -- @return ringing True if we got a ringing response, false else. -- @return responsecode Code for the latest meaningful response. -- could be 180, 200, 486, 408 or 603 local invitespoof = function(session, ua, from, src, extension, timeout) local status, response = sendinvite(session, ua, from, src, extension) -- check if we got a 100 Trying response. if status and response:getErrorCode() == 100 then -- wait for responses return waitresponses(session, timeout) end end action = function(host, port) local status, session local ua = stdnse.get_script_args(SCRIPT_NAME .. ".ua") or "Ekiga" local from = stdnse.get_script_args(SCRIPT_NAME .. ".from") or "Home" local src = stdnse.get_script_args(SCRIPT_NAME .. ".src") local extension = stdnse.get_script_args(SCRIPT_NAME .. ".extension") or 100 local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) -- Default timeout value = 5 seconds. timeout = (timeout or 5) * 1000 session = sip.Session:new(host, port) status = session:connect() if not status then return "ERROR: Failed to connect to the SIP server." end local ringing, result, waittime = invitespoof(session, ua, from, src, extension, timeout) -- If we get a response, we set the port to open. if result then if nmap.get_port_state(host, port) ~= "open" then nmap.set_port_state(host, port, "open") end end -- We check for ringing to skip false positives. if ringing then if result == sip.Error.BUSY then return stdnse.format_output(true, "Target line is busy.") elseif result == sip.Error.DECLINE then return stdnse.format_output(true, ("Target declined the call. (After %.1f seconds)"):format(waittime / 1000)) elseif result == sip.Error.OK then return stdnse.format_output(true, ("Target hung up. (After %.1f seconds)"):format(waittime / 1000)) elseif result == sip.Error.TIMEOUT then return stdnse.format_output(true, "Ringing, no answer.") elseif result == sip.Error.RING then return stdnse.format_output(true, "Ringing, got no answer. (script timeout)") end else stdnse.print_debug(SCRIPT_NAME .. "Target phone didn't ring.") end end