Nmap Development mailing list archives
NSE script for OS identification / clarification using Netbios/SMB
From: Matthew Watchinski <mwatchinski () sourcefire com>
Date: Wed, 28 Nov 2007 14:30:19 -0500
Sounds like NSE is being used by a number of people so the Sourcefire VRT thought we should contributed some of the NSE scripts we've been working on. The attached script written by Judy Novak, utilizes Netbios requests and SMB AndX responses to help determine the OS and clarify the OS running on a host that has Netbios and SMB running. This can be helpful if OS identification returns multiple possible fingerprints for a given windows system. Hopefully people find it useful. Cheers, -matt ----------------------------------------------------------------------- -- This script probes a target for its operating system version sending -- traffic via UDP port 137 and TCP port 139/445. First, we need to -- elicit the NetBIOS share name associated with a workstation share. -- Once we have that, we need to encode the name into the "mangled" -- equivalent and send TCP 139/445 traffic to connect to the host and -- in an attempt to elicit the OS version name from an SMB Setup AndX -- response. -- -- Thanks to Michail Prokopyev and xSharez Scanner for required -- traffic to generate for OS version detection. -- -- Command line to run this script like following: -- -- sudo nmap -sU -sS --script osversion.nse -p U:137,T:139 10.4.12.224 -----------------------------------------------------------------------
----------------------------------------------------------------------- -- This script probes a target for its operating system version sending -- traffic via UDP port 137 and TCP port 139/445. First, we need to -- elicit the NetBIOS share name associated with a workstation share. -- Once we have that, we need to encode the name into the "mangled" -- equivalent and send TCP 139/445 traffic to connect to the host and -- in an attempt to elicit the OS version name from an SMB Setup AndX -- response. -- -- Thanks to Michail Prokopyev and xSharez Scanner for required -- traffic to generate for OS version detection. -- -- Command line to run this script like following: -- -- sudo nmap -sU -sS --script osversion.nse -p U:137,T:139 10.4.12.224 ----------------------------------------------------------------------- id = "Discover OS Version over NetBIOS and SMB" description = "Attempt to elicit OS version from host running NetBIOS/SMB" author = "Judy Novak" copyright = "Sourcefire Inc, (C) 2006-2007" license = "See NMAP COPYING file for license" categories = {"version"} hostrule = function(host) local port_u137 = nmap.get_port_state(host, {number=137, protocol="udp"}) local port_t139 = nmap.get_port_state(host, {number=139, protocol="tcp"}) local port_t445 = nmap.get_port_state(host, {number=445, protocol="tcp"}) if ( (port_u137 ~= nil and (port_u137.state == "open" or port_u137.state == "open|filtered")) and (port_t139 ~= nil and port_t139.state == "open") or (port_t445 ~= nil and port_t445.state == "open") ) then return true else return false end end action = function(host) local sharename, message, osversion, gen_msg, x osversion = "" gen_msg = "OS version cannot be determined.\n" sharename, message = udp_query(host) if (sharename ~= 0) then osversion, message = tcp_session(sharename, host) if (osversion ~= 0) then return(osversion) else return(gen_msg .. message) end else return(gen_msg .. message) end end ----------------------------------------------------------------------- -- A NetBIOS wildcard query is sent to a host in an attempt to discover -- any NetBIOS shares on the host. ----------------------------------------------------------------------- function udp_query(host) local l, sharename, message local WildCard = string.char(0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x43, 0x4b, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x21, 0x00, 0x00) local socket = nmap.new_socket() socket:connect(host.ip, 137, "udp") socket:send(WildCard) socket:set_timeout(100) local status, result = socket:receive_bytes(1); socket:close() if (result ~= nil) then l = string.len(result) sharename = extract_sharename(result) if (sharename ~= 0) then return sharename, 1 else message = "Failed to find NetBIOS share name in response to UDP NetBIOS wildcard query" return 0, message end end end ----------------------------------------------------------------------- -- This function extracts the name of a "workstation" share from the -- response to the UDP NetBIOS wildcard query. Typically, there are -- several share types returned, but only one with a "workstation" -- type/code can be queried later for the OS version. The workstation -- type/code is 0x44 0x00 for OS versions prior to Vista. The type/code -- for Vista is 0x04 0x00. ----------------------------------------------------------------------- function extract_sharename(resp) local lenpay, beg, eend, typebeg, typeend, temp, name, nametype, ntgeneric, ntvista, ename, myname, eename, ntunix beg = 58 eend = beg + 15 typebeg = eend + 1 lenpay = string.len(resp) while (eend <= lenpay) do myname = string_concatenate(resp, beg, eend - 1) nametype = string.byte(resp, typebeg) .. string.byte(resp, typebeg + 1) ntgeneric = string.find(nametype, 0x44,0x00) ntvista = string.find(nametype, 0x04, 0x00) ntunix = string.find(nametype, 0x64, 0x00) if (ntgeneric == 1) or (ntvista == 1) or (ntunix == 1) then ename = encode(myname) end if (ename ~= nil) then do ename = string.char(0x20) .. ename .. string.char(0x43, 0x41, 0x00) return(ename) end end beg = beg + 18 eend = beg + 15 typebeg = eend + 1 end return(0) end ----------------------------------------------------------------------- -- Extract multiple bytes from a string and return concatenated result ----------------------------------------------------------------------- function string_concatenate(mystring, start, stop) local x, temp, newname for x = start, stop, 1 do temp = string.byte(mystring,x) if (x > start) then newname = newname .. string.char(temp) else newname = string.char(temp) end end return(newname) end ----------------------------------------------------------------------- -- This function encodes the workstation share name returned from the -- UDP wildcard NetBIOS query. Each character from the NetBIOS share -- name is encoded/mangled using a special algorithm. Rather than -- implementing the algorithm, Microsoft offers a conversion table for -- any valid character found in a share name. I could not figure out -- how to use a Lua dictionary where the key value included a -- non-alphanumeric character. The static variable chars represents -- most of the characters that can be found in a share and the position -- in the string "chars" is the corresponding position in the trtable -- table. The character " had to be handled separately as it is used -- to delimit the value of chars. ----------------------------------------------------------------------- encode = function(name) local ln, y, nchar, newname, pos, temp, trtable local chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 !#$%&'()*+,-.=:;@^_{}~" local trtable = { string.char(0x45,0x42), string.char(0x45,0x43), string.char(0x45,0x44), string.char(0x45,0x45), string.char(0x45,0x46), string.char(0x45,0x47), string.char(0x45,0x48), string.char(0x45,0x49), string.char(0x45,0x4A), string.char(0x45,0x4B), string.char(0x45,0x4C), string.char(0x45,0x4D), string.char(0x45,0x4E), string.char(0x45,0x4F), string.char(0x45,0x50), string.char(0x46,0x41), string.char(0x46,0x42), string.char(0x46,0x43), string.char(0x46,0x44), string.char(0x46,0x45), string.char(0x46,0x46), string.char(0x46,0x47), string.char(0x46,0x48), string.char(0x46,0x49), string.char(0x46,0x4A), string.char(0x46,0x4B), string.char(0x44,0x41), string.char(0x44,0x42), string.char(0x44,0x43), string.char(0x44,0x44), string.char(0x44,0x45), string.char(0x44,0x46), string.char(0x44,0x47), string.char(0x44,0x48), string.char(0x44,0x49), string.char(0x44,0x4A), string.char(0x43,0x41), string.char(0x43,0x42), string.char(0x43,0x44), string.char(0x43,0x45), string.char(0x43,0x46), string.char(0x43,0x47), string.char(0x43,0x48), string.char(0x43,0x49), string.char(0x43,0x4A), string.char(0x43,0x4B), string.char(0x43,0x4C), string.char(0x43,0x4D), string.char(0x43,0x4E), string.char(0x43,0x4F), string.char(0x44,0x4E), string.char(0x44,0x4B), string.char(0x44,0x4C), string.char(0x45,0x41), string.char(0x46,0x4F), string.char(0x46,0x50), string.char(0x48,0x4C), string.char(0x48,0x4E), string.char(0x48,0x4F) } ln = string.len(name) y = 1 while (y <= ln) do temp = string.byte(name, y) if (temp == 0x00) then --Sharename must be followed by spaces not null's to be acceptable return(nil) elseif (temp == '"') then nchar = string.char(0x43,0x43) else do temp = string.char(temp) pos = string.find(chars, temp) nchar = trtable[pos] if (y > 1) then newname = newname .. nchar else newname = nchar end y = y + 1 end end end return(newname) end ----------------------------------------------------------------------- -- This function invokes the TCP traffic that is generated to get -- a response that yields the OS version information. The first -- payload is an SMB session initiation request followed by a -- negotiate payload, and followed by a Session Setup AndX request. -- The workstation share name extracted from the UDP wildcard NetBIOS -- response must be used in the SMB session initiation request(payload 1). -- Payload for the requests that follow is static. ----------------------------------------------------------------------- function tcp_session(ename, host) local catch = function() socket:close() end local rec1_payload, rec2_payload, rec3_payload, status, line1, line2, line3, osversion, winshare, pos, message message = 0 local win5 = "Windows 5.0" local win51 = "Windows 5.1" winshare = string.char(0x20, 0x46, 0x48, 0x45, 0x4A, 0x45, 0x4F, 0x45, 0x45, 0x45, 0x50, 0x46, 0x48, 0x46, 0x44, 0x43, 0x41, 0x43, 0x41, 0x43, 0x41, 0x43, 0x41, 0x43, 0x41, 0x43, 0x41, 0x43, 0x41, 0x43, 0x41, 0x43, 0x41, 0x00) rec1_payload = string.char(0x81, 0x00, 0x00, 0x44) .. ename .. winshare rec2_payload = string.char( 0x00, 0x00, 0x00, 0x85, 0xff, 0x53, 0x4d, 0x42, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc0 ) .. string.char( 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xfa ) .. string.char( 0x00, 0x00, 0x17, 0x62, 0x00, 0x61, 0x00, 0x02, 0x50, 0x43, 0x20, 0x4e, 0x45, 0x54, 0x57, 0x4f ) .. string.char( 0x52, 0x4b, 0x20, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x41, 0x4d, 0x20, 0x31, 0x2e, 0x30, 0x00, 0x02 ) .. string.char( 0x4c, 0x41, 0x4e, 0x4d, 0x41, 0x4e, 0x31, 0x2e, 0x30, 0x00, 0x02, 0x57, 0x69, 0x6e, 0x64, 0x6f ) .. string.char( 0x77, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x57, 0x6f, 0x72, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x70 ) .. string.char( 0x73, 0x20, 0x33, 0x2e, 0x31, 0x61, 0x00, 0x02, 0x4c, 0x4d, 0x31, 0x2e, 0x32, 0x58, 0x30, 0x30 ) .. string.char( 0x32, 0x00, 0x02, 0x4c, 0x41, 0x4e, 0x4d, 0x41, 0x4e, 0x32, 0x2e, 0x31, 0x00, 0x02, 0x4e, 0x54 ) .. string.char( 0x20, 0x4c, 0x4d, 0x20, 0x30, 0x2e, 0x31, 0x32, 0x00) rec3_payload = string.char( 0x00, 0x00, 0x00, 0xab, 0xff, 0x53, 0x4d, 0x42, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc0 ) .. string.char( 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xfa ) .. string.char( 0x00, 0x00, 0x17, 0x62, 0x0d, 0xff, 0x00, 0x00, 0x00, 0x04, 0x11, 0x0a, 0x00, 0x00, 0x00, 0x00 ) .. string.char( 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x6d ) .. string.char( 0x00, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00 ) .. string.char( 0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, 0x00, 0x00, 0x57, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x64, 0x00 ) .. string.char( 0x6f, 0x00, 0x77, 0x00, 0x73, 0x00, 0x20, 0x00, 0x39, 0x00, 0x35, 0x00, 0x2f, 0x00, 0x39, 0x00 ) .. string.char( 0x38, 0x00, 0x2f, 0x00, 0x4d, 0x00, 0x65, 0x00, 0x2f, 0x00, 0x4e, 0x00, 0x54, 0x00, 0x2f, 0x00 ) .. string.char( 0x32, 0x00, 0x6b, 0x00, 0x2f, 0x00, 0x58, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00 ) .. string.char( 0x53, 0x00, 0x68, 0x00, 0x61, 0x00, 0x72, 0x00, 0x65, 0x00, 0x7a, 0x00, 0x20, 0x00, 0x53, 0x00 ) .. string.char( 0x63, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x00) local socket = nmap.new_socket() local try = nmap.new_try(catch) try(socket:connect(host.ip,139,"tcp")) socket:set_timeout(100) try(socket:send(rec1_payload)) status, line1 = socket:receive_lines(1) if (not status) then socket:close() message = "Never received a response to SMB Session Request" return 0, message end socket:set_timeout(100) try(socket:send(rec2_payload)) status, line2 = socket:receive_lines(1) if (not status) then socket:close() message = "Never received a response to SMB Negotiate Protocol Request" return 0, message end socket:set_timeout(100) try(socket:send(rec3_payload)) status, line3 = socket:receive_lines(1) if (not status) then socket:close() message = "Never received a response to SMB Setup AndX Request" return 0, message end socket:close() osversion, message = extract_version(line3) if (osversion ~= 0) then pos = string.find(osversion, win5) if (pos ~= nil) then osversion = "Windows 2000" else pos = string.find(osversion, win51) if (pos ~= nil) then osversion = "Windows XP" end end end return osversion, message end ----------------------------------------------------------------------- -- Response from Session Setup AndX Request (TCP payload 3) -- Must be SMB response. Extract the OS version from it from a fixed -- offset in the payload. ----------------------------------------------------------------------- function extract_version(line) local temp, smb, ltemp, go, x, osversion, mychar, message smb = "SMB" .. string.char(0x73) temp = string_concatenate(line, 6, 9) message = 0 if (temp ~= smb) then message = "Didn't find correct SMB record as a response to the Session Setup AndX request" return 0, message end ltemp = string.len(line) temp = string_concatenate(line, 47, ltemp) x=1 while (x < ltemp) do mychar = string.byte(temp,x) if (mychar == 0) then return osversion, message else if (x == 1) then osversion = string.char(mychar) else osversion = osversion .. string.char(mychar) end end x = x + 2 end if (x >= ltemp) then message = "OS version not found in expected record Session Setup AndX response" return 0, message end end
_______________________________________________ Sent through the nmap-dev mailing list http://cgi.insecure.org/mailman/listinfo/nmap-dev Archived at http://SecLists.Org
Current thread:
- NSE script for OS identification / clarification using Netbios/SMB Matthew Watchinski (Nov 28)
- Re: [-SPAM-] NSE script for OS identification / clarification using Netbios/SMB jah (Nov 28)
- Re: NSE script for OS identification / clarification using Netbios/SMB Fyodor (Nov 28)
- Re: NSE script for OS identification / clarification using Netbios/SMB Brandon Enright (Nov 28)
- Re: NSE script for OS identification / clarification using Netbios/SMB Fyodor (Nov 29)
- Re: NSE script for OS identification / clarification using Netbios/SMB Brandon Enright (Nov 29)
- Re: NSE script for OS identification / clarification using Netbios/SMB Fyodor (Dec 04)
- Re: NSE script for OS identification / clarification using Netbios/SMB Brandon Enright (Nov 28)