summaryrefslogtreecommitdiffstats
path: root/scripts/p2p-conficker.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/p2p-conficker.nse')
-rw-r--r--scripts/p2p-conficker.nse651
1 files changed, 651 insertions, 0 deletions
diff --git a/scripts/p2p-conficker.nse b/scripts/p2p-conficker.nse
new file mode 100644
index 0000000..4e45783
--- /dev/null
+++ b/scripts/p2p-conficker.nse
@@ -0,0 +1,651 @@
+local ipOps = require "ipOps"
+local math = require "math"
+local nmap = require "nmap"
+local os = require "os"
+local smb = require "smb"
+local stdnse = require "stdnse"
+local string = require "string"
+local table = require "table"
+
+description = [[
+Checks if a host is infected with Conficker.C or higher, based on
+Conficker's peer to peer communication.
+
+When Conficker.C or higher infects a system, it opens four ports: two TCP
+and two UDP. The ports are random, but are seeded with the current week and
+the IP of the infected host. By determining the algorithm, one can check if
+these four ports are open, and can probe them for more data.
+
+Once the open ports are found, communication can be initiated using
+Conficker's custom peer to peer protocol. If a valid response is received,
+then a valid Conficker infection has been found.
+
+This check won't work properly on a multihomed or NATed system because the
+open ports will be based on a nonpublic IP. The argument
+<code>checkall</code> tells Nmap to attempt communication with every open
+port (much like a version check) and the argument <code>realip</code> tells
+Nmap to base its port generation on the given IP address instead of the
+actual IP.
+
+By default, this will run against a system that has a standard Windows port
+open (445, 139, 137). The arguments <code>checkall</code> and
+<code>checkconficker</code> will both perform checks regardless of which
+port is open, see the args section for more information.
+
+Note: Ensure your clock is correct (within a week) before using this script!
+
+The majority of research for this script was done by Symantec Security
+Response, and some was taken from public sources (most notably the port
+blacklisting was found by David Fifield). A big thanks goes out to everybody
+who contributed!
+]]
+
+---
+-- @args checkall If set to <code>1</code> or <code>true</code>, attempt
+-- to communicate with every open port.
+-- @args checkconficker If set to <code>1</code> or <code>true</code>, the script will always run on active hosts,
+-- it doesn't matter if any open ports were detected.
+-- @args realip An IP address to use in place of the one known by Nmap.
+--
+-- @usage
+-- # Run the scripts against host(s) that appear to be Windows
+-- nmap --script p2p-conficker,smb-os-discovery,smb-check-vulns --script-args=safe=1 -T4 -vv -p445 <host>
+-- sudo nmap -sU -sS --script p2p-conficker,smb-os-discovery,smb-check-vulns --script-args=safe=1 -vv -T4 -p U:137,T:139 <host>
+--
+-- # Run the scripts against all active hosts (recommended)
+-- nmap -p139,445 -vv --script p2p-conficker,smb-os-discovery,smb-check-vulns --script-args=checkconficker=1,safe=1 -T4 <host>
+--
+-- # Run scripts against all 65535 ports (slow)
+-- nmap --script p2p-conficker,smb-os-discovery,smb-check-vulns -p- --script-args=checkall=1,safe=1 -vv -T4 <host>
+--
+-- # Base checks on a different ip address (NATed)
+-- nmap --script p2p-conficker,smb-os-discovery -p445 --script-args=realip=\"192.168.1.65\" -vv -T4 <host>
+--
+-- @output
+-- Clean machine (results printed only if extra verbosity ("-vv")is specified):
+-- Host script results:
+-- | p2p-conficker: Checking for Conficker.C or higher...
+-- | Check 1 (port 44329/tcp): CLEAN (Couldn't connect)
+-- | Check 2 (port 33824/tcp): CLEAN (Couldn't connect)
+-- | Check 3 (port 31380/udp): CLEAN (Failed to receive data)
+-- | Check 4 (port 52600/udp): CLEAN (Failed to receive data)
+-- |_ 0/4 checks: Host is CLEAN or ports are blocked
+--
+-- Infected machine (results always printed):
+-- Host script results:
+-- | p2p-conficker: Checking for Conficker.C or higher...
+-- | Check 1 (port 18707/tcp): INFECTED (Received valid data)
+-- | Check 2 (port 65273/tcp): INFECTED (Received valid data)
+-- | Check 3 (port 11722/udp): INFECTED (Received valid data)
+-- | Check 4 (port 12690/udp): INFECTED (Received valid data)
+-- |_ 4/4 checks: Host is likely INFECTED
+--
+-----------------------------------------------------------------------
+
+author = "Ron Bowes (with research from Symantec Security Response)"
+copyright = "Ron Bowes"
+license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
+categories = {"default","safe"}
+
+
+-- Max packet size
+local MAX_PACKET = 0x2000
+
+-- Flags
+local mode_flags =
+{
+ FLAG_MODE = 1 << 0,
+ FLAG_LOCAL_ACK = 1 << 1,
+ FLAG_IS_TCP = 1 << 2,
+ FLAG_IP_INCLUDED = 1 << 3,
+ FLAG_UNKNOWN0_INCLUDED = 1 << 4,
+ FLAG_UNKNOWN1_INCLUDED = 1 << 5,
+ FLAG_DATA_INCLUDED = 1 << 6,
+ FLAG_SYSINFO_INCLUDED = 1 << 7,
+ FLAG_ENCODED = 1 << 15,
+}
+
+---For a hostrule, simply use the 'smb' ports as an indicator, unless the user overrides it
+hostrule = function(host)
+ if ( nmap.address_family() ~= 'inet' ) then
+ return false
+ end
+ if(smb.get_port(host) ~= nil) then
+ return true
+ elseif(nmap.registry.args.checkall == "true" or nmap.registry.args.checkall == "1") then
+ return true
+ elseif(nmap.registry.args.checkconficker == "true" or nmap.registry.args.checkconficker == "1") then
+ return true
+ end
+
+ return false
+end
+
+-- Multiply two 32-bit integers and return a 64-bit product. The first return
+-- value is the low-order 32 bits of the product and the second return value is
+-- the high-order 32 bits.
+--
+--@param u First number (0 <= u <= 0xFFFFFFFF)
+--@param v Second number (0 <= v <= 0xFFFFFFFF)
+--@return 64-bit product of u*v, as a pair of 32-bit integers.
+local function mul64(u, v)
+ -- This is based on formula (2) from section 4.3.3 of The Art of
+ -- Computer Programming. We split u and v into upper and lower 16-bit
+ -- chunks, such that
+ -- u = 2**16 u1 + u0 and v = 2**16 v1 + v0
+ -- Then
+ -- u v = (2**16 u1 + u0) * (2**16 v1 + v0)
+ -- = 2**32 u1 v1 + 2**16 (u0 v1 + u1 v0) + u0 v0
+ assert(0 <= u and u <= 0xFFFFFFFF)
+ assert(0 <= v and v <= 0xFFFFFFFF)
+ local u0, u1 = (u & 0xFFFF), (u >> 16)
+ local v0, v1 = (v & 0xFFFF), (v >> 16)
+ -- t uses at most 49 bits, which is within the range of exact integer
+ -- precision of a Lua number.
+ local t = u0 * v0 + (u0 * v1 + u1 * v0) * 65536
+ return (t & 0xFFFFFFFF), u1 * v1 + (t >> 32)
+end
+
+---Rotates the 64-bit integer defined by h:l left by one bit.
+--
+--@param h The high-order 32 bits
+--@param l The low-order 32 bits
+--@return 64-bit rotated integer, as a pair of 32-bit integers.
+local function rot64(h, l)
+ local i
+
+ assert(0 <= h and h <= 0xFFFFFFFF)
+ assert(0 <= l and l <= 0xFFFFFFFF)
+
+ local tmp = h & 0x80000000
+ h = h << 1
+ h = h | (l >> 31)
+ l = l << 1
+ if tmp ~= 0 then
+ l = l | 1
+ end
+
+ h = h & 0xFFFFFFFF
+ l = l & 0xFFFFFFFF
+
+ return h, l
+end
+
+
+---Check if a port is Blacklisted. Thanks to David Fifield for determining the purpose of the "magic"
+-- array:
+-- <http://www.bamsoftware.com/wiki/Nmap/PortSetGraphics#conficker>
+--
+-- Basically, each bit in the blacklist array represents a group of 32 ports. If that bit is on, those ports
+-- are blacklisted and will never come up.
+--
+--@param port The port to check
+--@return true if the port is blacklisted, false otherwise
+local function is_blacklisted_port(port)
+ local r, l
+
+ local blacklist = { 0xFFFFFFFF, 0xFFFFFFFF, 0xF0F6BFBB, 0xBB5A5FF3,
+ 0xF3977011, 0xEB67BFBF, 0x5F9BFAC8, 0x34D88091, 0x1E2282DF, 0x573402C4,
+ 0xC0000084, 0x03000209, 0x01600002, 0x00005000, 0x801000C0, 0x00500040,
+ 0x000000A1, 0x01000000, 0x01000000, 0x00022A20, 0x00000080, 0x04000000,
+ 0x40020000, 0x88000000, 0x00000180, 0x00081000, 0x08801900, 0x00800B81,
+ 0x00000280, 0x080002C0, 0x00A80000, 0x00008000, 0x00100040, 0x00100000,
+ 0x00000000, 0x00000000, 0x10000008, 0x00000000, 0x00000000, 0x00000004,
+ 0x00000002, 0x00000000, 0x00040000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00410000, 0x82000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000008, 0x80000000,
+ }
+
+ r = port >> 5
+ l = 1 << (r & 0x1f)
+ r = r >> 5
+
+ return blacklist[r + 1] & l ~= 0
+end
+
+---Generates the four random ports that Conficker uses, based on the current time and the IP address.
+--
+--@param ip The IP address as a 32-bit little endian integer
+--@param seed The seed, based on the time (<code>floor((time - 345600) / 604800)</code>)
+--@return An array of four ports; the first and third are TCP, and the second and fourth are UDP.
+local function prng_generate_ports(ip, seed)
+ local ports = {0, 0, 0, 0}
+ local v1, v2
+ local port1, port2, shift1, shift2
+ local i
+ local magic = 0x015A4E35
+
+ stdnse.debug1("Conficker: Generating ports based on ip (0x%08x) and seed (%d)", ip, seed)
+
+ v1 = -(ip + 1)
+ repeat
+ -- Loop 10 times to generate the first pair of ports
+ for i = 0, 9, 1 do
+ v1, v2 = mul64(v1 & 0xFFFFFFFF, magic & 0xFFFFFFFF)
+
+ -- Add 1 to v1, handling overflows
+ if(v1 ~= 0xFFFFFFFF) then
+ v1 = v1 + 1
+ else
+ v1 = 0
+ v2 = v2 + 1
+ end
+
+ v2 = v2 >> i
+
+ ports[(i % 2) + 1] = (v2 & 0xFFFF) ~ ports[(i % 2) + 1]
+ end
+ until(is_blacklisted_port(ports[1]) == false and is_blacklisted_port(ports[2]) == false and ports[1] ~= ports[2])
+
+ -- Update the accumulator with the seed
+ v1 = v1 ~ seed
+
+ -- Loop 10 more times to generate the second pair of ports
+ repeat
+ for i = 0, 9, 1 do
+ v1, v2 = mul64(v1 & 0xFFFFFFFF, magic & 0xFFFFFFFF)
+
+ -- Add 1 to v1, handling overflows
+ if(v1 ~= 0xFFFFFFFF) then
+ v1 = v1 + 1
+ else
+ v1 = 0
+ v2 = v2 + 1
+ end
+
+ v2 = v2 >> i
+
+ ports[(i % 2) + 3] = (v2 & 0xFFFF) ~ ports[(i % 2) + 3]
+ end
+ until(is_blacklisted_port(ports[3]) == false and is_blacklisted_port(ports[4]) == false and ports[3] ~= ports[4])
+
+ return {ports[1], ports[2], ports[3], ports[4]}
+end
+
+---Calculate a checksum for the data. This checksum is appended to every Conficker packet before the random noise.
+-- The checksum includes the key and data, but not the noise and optional length.
+--
+--@param data The data to create a checksum for.
+--@return An integer representing the checksum.
+local function p2p_checksum(data)
+ local hash = #data
+
+ stdnse.debug2("Conficker: Calculating checksum for %d-byte buffer", #data)
+
+ data:gsub(".", function(i)
+ local h = hash ~ string.byte(i)
+ -- Incorporate the current character into the checksum
+ hash = (h + h) | (h >> 31)
+ hash = hash & 0xFFFFFFFF
+ end
+ )
+
+ return hash
+end
+
+---Encrypt/decrypt the buffer with a simple xor-based symmetric encryption. It uses a 64-bit key, represented
+-- by key1:key2, that is transmitted in plain text. Since sniffed packets can be decrypted, this is a
+-- simple obfuscation technique.
+--
+--@param packet The packet to encrypt (before the key and optional length are prepended).
+--@param key1 The low-order 32 bits in the key.
+--@param key2 The high-order 32 bits in the key.
+--@return The encrypted (or decrypted) data.
+local function p2p_cipher(packet, key1, key2)
+ local i
+ local buf = {}
+
+ for i = 1, #packet, 1 do
+ -- Do a 64-bit rotate on key1:key2
+ key2, key1 = rot64(key2, key1)
+
+ -- Generate the key (the right-most byte)
+ local k = key1 & 0x0FF
+
+ -- Xor the current character and add it to the encrypted buffer
+ buf[i] = string.char(string.byte(packet, i) ~ k)
+
+ -- Update the key with 'k'
+ key1 = key1 + k
+ if(key1 > 0xFFFFFFFF) then
+ -- Handle overflows
+ key2 = key2 + (key1 >> 32)
+ key2 = key2 & 0xFFFFFFFF
+ key1 = key1 & 0xFFFFFFFF
+ end
+ end
+
+ return table.concat(buf)
+end
+
+---Decrypt the packet, verify it, and parse it. This function will fail with an error if the packet can't be
+-- parsed properly (likely means the port is being used for something else), but will return successfully
+-- without checking the packet's checksum (although it does calculate the checksum). It's up to the calling
+-- function to decide if it cares about the checksum.
+--
+--@param packet The packet, without the optional length (if it's TCP).
+--@return (status, result) If status is true, result is a table (including 'hash' and 'real_hash'). If status
+-- is false, result is a string that indicates why the parse failed.
+function p2p_parse(packet)
+ local pos = 1
+ local data = {}
+
+ -- Get the key
+ if #packet < 8 then
+ return false, "Packet was too short [1]"
+ end
+ data['key1'], data['key2'], pos = string.unpack("<I4 I4", packet, pos)
+
+ -- Decrypt the second half of the packet using the key
+ packet = string.sub(packet, 1, pos - 1) .. p2p_cipher(string.sub(packet, pos), data['key1'], data['key2'])
+
+ -- Parse the flags
+ if #packet - pos + 1 < 2 then
+ return false, "Packet was too short [2]"
+ end
+ data['flags'], pos = string.unpack("<I2", packet, pos)
+
+ -- Get the IP, if it's present
+ if(data['flags'] & mode_flags.FLAG_IP_INCLUDED) ~= 0 then
+ if #packet - pos + 1 < 6 then
+ return false, "Packet was too short [3]"
+ end
+ data['ip'], data['port'], pos = string.unpack("<I4 I2", packet, pos)
+ end
+
+ -- Read the first unknown value, if present
+ if(data['flags'] & mode_flags.FLAG_UNKNOWN0_INCLUDED) ~= 0 then
+ if #packet - pos + 1 < 4 then
+ return false, "Packet was too short [3]"
+ end
+ data['unknown0'], pos = string.unpack("<I4", packet, pos)
+ end
+
+ -- Read the second unknown value, if present
+ if(data['flags'] & mode_flags.FLAG_UNKNOWN1_INCLUDED) ~= 0 then
+ if #packet - pos + 1 < 4 then
+ return false, "Packet was too short [4]"
+ end
+ data['unknown1'], pos = string.unpack("<I4", packet, pos)
+ end
+
+ -- Read the data, if present
+ if(data['flags'] & mode_flags.FLAG_DATA_INCLUDED) ~= 0 then
+ if #packet - pos + 1 < 3 then
+ return false, "Packet was too short [5]"
+ end
+ data['data_flags'], data['data_length'], pos = string.unpack("<B I2", packet, pos)
+ if #packet - pos + 1 < data.data_length then
+ return false, "Packet was too short [6]"
+ end
+ data['data'], pos = string.unpack(("c%d"):format(data['data_length']), packet, pos)
+ end
+
+ -- Read the sysinfo, if present
+ if(data['flags'] & mode_flags.FLAG_SYSINFO_INCLUDED) ~= 0 then
+ local sysinfo_format = "<I2 BBI2 BB I2 I4 I2I2I4I2I2"
+ if #packet - pos + 1 < string.packsize(sysinfo_format) then
+ return false, "Packet was too short [7]"
+ end
+
+ data['sysinfo_systemtestflags'],
+ data['sysinfo_os_major'],
+ data['sysinfo_os_minor'],
+ data['sysinfo_os_build'],
+ data['sysinfo_os_servicepack_major'],
+ data['sysinfo_os_servicepack_minor'],
+ data['sysinfo_ntdll_translation_file_information'],
+ data['sysinfo_prng_sample'],
+ data['sysinfo_unknown0'],
+ data['sysinfo_unknown1'],
+ data['sysinfo_unknown2'],
+ data['sysinfo_unknown3'],
+ data['sysinfo_unknown4'], pos = string.unpack(sysinfo_format, packet, pos)
+ end
+
+ -- Pull out the data that's used in the hash
+ data['hash_data'] = string.sub(packet, 1, pos - 1)
+
+ -- Read the hash
+ if #packet - pos + 1 < 4 then
+ return false, "Packet was too short [8]"
+ end
+ data['hash'], pos = string.unpack("<I4", packet, pos)
+
+ -- Record the noise
+ data['noise'] = string.sub(packet, pos)
+
+ -- Generate the actual hash (we're going to ignore it for now, but it can be checked higher up)
+ data['real_hash'] = p2p_checksum(data['hash_data'])
+
+ return true, data
+end
+
+---Create a peer to peer packet for the given protocol.
+--
+--@param protocol The protocol (either 'tcp' or 'udp' -- tcp packets have a length in front, and an extra
+-- flag)
+--@param do_encryption (optional) If set to false, packets aren't encrypted (the key '0' is used). Useful
+-- for testing. Default: true.
+local function p2p_create_packet(protocol, do_encryption)
+ assert(protocol == "tcp" or protocol == "udp")
+
+ local key1 = math.random(1, 0x7FFFFFFF)
+ local key2 = math.random(1, 0x7FFFFFFF)
+
+ -- A key of 0 disables the encryption
+ if(do_encryption == false) then
+ key1 = 0
+ key2 = 0
+ end
+
+ local flags = 0
+
+ -- Set a couple flags that we need (we don't send any optional data)
+ flags = flags | mode_flags.FLAG_MODE
+ flags = flags | mode_flags.FLAG_ENCODED
+ -- flags = flags | mode_flags.FLAG_LOCAL_ACK)
+ -- Set the special TCP flag
+ if(protocol == "tcp") then
+ flags = flags | mode_flags.FLAG_IS_TCP
+ end
+
+ -- Add the key and flags that are always present (and skip over the boring stuff)
+ local packet = string.pack("<I4 I4 I2", key1, key2, flags)
+
+ -- Generate the checksum for the packet
+ local hash = p2p_checksum(packet)
+ packet = packet .. string.pack("<I4", hash)
+
+ -- Encrypt the full packet, except for the key and optional length
+ packet = string.sub(packet, 1, 8) .. p2p_cipher(string.sub(packet, 9), key1, key2)
+
+ -- Add the length in front if it's TCP
+ if(protocol == "tcp") then
+ packet = string.pack("<s2", packet)
+ end
+
+ return true, packet
+end
+
+---Checks if conficker is present on the given port/protocol. The ports Conficker uses are fairly standard, so
+-- those should generally be used for this check. This can also be sent to any open port on the system.
+--
+--@param ip The ip address of the system to check
+--@param port The port to check (can be taken from <code>prng_generate_ports</code>, or from unidentified ports)
+--@return (status, reason, data) Status indicates whether or not Conficker is suspected to be present (<code>true</code) =
+-- Conficker, <code>false</code> = no Conficker). If status is true, data is the table of information returned by
+-- Conficker.
+local function conficker_check(ip, port, protocol)
+ local status, packet
+ local socket
+ local response
+
+ status, packet = p2p_create_packet(protocol)
+ if(status == false) then
+ return false, packet
+ end
+
+ -- Try to connect to the first socket
+ socket = nmap.new_socket()
+ socket:set_timeout(5000)
+ status, response = socket:connect(ip, port, protocol)
+ if(status == false) then
+ return false, "Couldn't establish connection (" .. response .. ")"
+ end
+
+ -- Send the packet
+ socket:send(packet)
+
+ -- Read a response (2 bytes minimum, because that's the TCP length)
+ status, response = socket:receive_bytes(2)
+ if(status == false) then
+ return false, "Couldn't receive bytes: " .. response
+ elseif(response == "ERROR") then
+ return false, "Failed to receive data"
+ elseif(response == "TIMEOUT") then
+ return false, "Timeout"
+ elseif(response == "EOF") then
+ return false, "Couldn't connect"
+ elseif #response < 2 then
+ return false, "Data too short"
+ end
+
+ -- If it's TCP, get the length and make sure we have the full packet
+ if(protocol == "tcp") then
+ local length = string.unpack("<I2", response)
+
+ -- Only try for 2 timeouts to get the whole packet
+ local tries = 2
+ while length > (#response - 2) and tries > 0 do
+ tries = tries - 1
+
+ local status, response2 = socket:receive_bytes(length - (#response - 2))
+ if(status == false) then
+ return false, "Couldn't receive bytes: " .. response2
+ elseif(response2 == "ERROR") then
+ return false, "Failed to receive data"
+ elseif(response2 == "TIMEOUT") then
+ return false, "Timeout"
+ elseif(response2 == "EOF") then
+ return false, "Couldn't connect"
+ end
+
+ response = response .. response2
+ end
+
+ -- Remove the 'length' bytes
+ response = string.sub(response, 3)
+ end
+
+ -- Close the socket
+ socket:close()
+
+ local status, result = p2p_parse(response)
+
+ if(status == false) then
+ return false, "Data received, but wasn't Conficker data: " .. result
+ end
+
+ if(result['hash'] ~= result['real_hash']) then
+ return false, "Data received, but checksum was invalid (possibly INFECTED)"
+ end
+
+ return true, "Received valid data", result
+end
+
+action = function(host)
+ local tcp_ports = {}
+ local udp_ports = {}
+ local response = {}
+ local i
+ local port, protocol
+ local count = 0
+ local checks = 0
+
+ -- Generate a complete list of valid ports
+ if(nmap.registry.args.checkall == "true" or nmap.registry.args.checkall == "1") then
+ for i = 1, 65535, 1 do
+ if(not(is_blacklisted_port(i))) then
+ local tcp = nmap.get_port_state(host, {number=i, protocol="tcp"})
+ if(tcp ~= nil and tcp.state == "open") then
+ tcp_ports[i] = true
+ end
+
+ local udp = nmap.get_port_state(host, {number=i, protocol="udp"})
+ if(udp ~= nil and (udp.state == "open" or udp.state == "open|filtered")) then
+ udp_ports[i] = true
+ end
+ end
+ end
+ end
+
+
+ -- Generate ports based on the ip and time
+ local seed = math.floor((os.time() - 345600) / 604800)
+ local ip = host.ip
+
+ -- Use the provided IP, if it exists
+ if(nmap.registry.args.realip ~= nil) then
+ ip = nmap.registry.args.realip
+ end
+
+ -- Reverse the IP's endianness
+ ip = ipOps.todword(ip)
+ ip = string.pack(">I4", ip)
+ ip = string.unpack("<I4", ip)
+
+ -- Generate the ports
+ local generated_ports = prng_generate_ports(ip, seed)
+ tcp_ports[generated_ports[1]] = true
+ tcp_ports[generated_ports[3]] = true
+ udp_ports[generated_ports[2]] = true
+ udp_ports[generated_ports[4]] = true
+
+ table.insert(response, "Checking for Conficker.C or higher...")
+
+ -- Check the TCP ports
+ for port in pairs(tcp_ports) do
+ local status, reason
+
+ status, reason = conficker_check(host.ip, port, "tcp")
+ checks = checks + 1
+
+ if(status == true) then
+ table.insert(response, string.format("Check %d (port %d/%s): INFECTED (%s)", checks, port, "tcp", reason))
+ count = count + 1
+ else
+ table.insert(response, string.format("Check %d (port %d/%s): CLEAN (%s)", checks, port, "tcp", reason))
+ end
+ end
+
+ -- Check the UDP ports
+ for port in pairs(udp_ports) do
+ local status, reason
+
+ status, reason = conficker_check(host.ip, port, "udp")
+ checks = checks + 1
+
+ if(status == true) then
+ table.insert(response, string.format("Check %d (port %d/%s): INFECTED (%s)", checks, port, "udp", reason))
+ count = count + 1
+ else
+ table.insert(response, string.format("Check %d (port %d/%s): CLEAN (%s)", checks, port, "udp", reason))
+ end
+ end
+
+ -- Check how many INFECTED hits we got
+ if(count == 0) then
+ if (nmap.verbosity() > 1) then
+ table.insert(response, string.format("%d/%d checks are positive: Host is CLEAN or ports are blocked", count, checks))
+ else
+ response = ''
+ end
+ else
+ table.insert(response, string.format("%d/%d checks are positive: Host is likely INFECTED", count, checks))
+ end
+
+ return stdnse.format_output(true, response)
+end
+