summaryrefslogtreecommitdiffstats
path: root/scripts/p2p-conficker.nse
blob: 4e457832ebc7fe5327d997e84104f55c745a003c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
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