From 8891c562114c6c8647c5ac37d823fa5abb011830 Mon Sep 17 00:00:00 2001 From: Ashley Donaldson Date: Mon, 11 Nov 2024 12:33:11 +1100 Subject: [PATCH 1/3] Give likely Windows versions for SMB v2-3 --- lib/msf/core/windows_version.rb | 97 +++++++---- modules/auxiliary/scanner/smb/smb_version.rb | 169 +++++++++++-------- spec/lib/msf/core/windows_version_spec.rb | 32 ++++ 3 files changed, 194 insertions(+), 104 deletions(-) diff --git a/lib/msf/core/windows_version.rb b/lib/msf/core/windows_version.rb index 009fa23b7b38..c1bc42c0157b 100644 --- a/lib/msf/core/windows_version.rb +++ b/lib/msf/core/windows_version.rb @@ -10,41 +10,46 @@ class WindowsVersion VER_NT_DOMAIN_CONTROLLER = 2 VER_NT_SERVER = 3 - Win2000 = Rex::Version.new('5.0.2195') - XP_SP0 = Rex::Version.new('5.1.2600.0') - XP_SP1 = Rex::Version.new('5.1.2600.1') - XP_SP2 = Rex::Version.new('5.1.2600.2') - XP_SP3 = Rex::Version.new('5.1.2600.3') - Server2003_SP0 = Rex::Version.new('5.2.3790.0') - Server2003_SP1 = Rex::Version.new('5.2.3790.1') - Server2003_SP2 = Rex::Version.new('5.2.3790.2') - Vista_SP0 = Server2008_SP0 = Rex::Version.new('6.0.6000.0') - Vista_SP1 = Server2008_SP1 = Rex::Version.new('6.0.6001.1') - Vista_SP2 = Server2008_SP2 = Rex::Version.new('6.0.6002.2') - Server2008_SP2_Update = Rex::Version.new('6.0.6003.2') # https://support.microsoft.com/en-us/topic/build-number-changing-to-6003-in-windows-server-2008-1335e4d4-c155-52eb-4a45-b85bd1909ca8 - Win7_SP0 = Server2008_R2_SP0 = Rex::Version.new('6.1.7600.0') - Win7_SP1 = Server2008_R2_SP1 = Rex::Version.new('6.1.7601.1') - Win8 = Server2012 = Rex::Version.new('6.2.9200.0') - Win81 = Server2012_R2 = Rex::Version.new('6.3.9600.0') - Win10_1507 = Win10_InitialRelease = Rex::Version.new('10.0.10240.0') - Win10_1511 = Rex::Version.new('10.0.10586.0') - Win10_1607 = Server2016 = Rex::Version.new('10.0.14393.0') - Win10_1703 = Rex::Version.new('10.0.15063.0') - Win10_1709 = Rex::Version.new('10.0.16299.0') - Win10_1803 = Rex::Version.new('10.0.17134.0') - Win10_1809 = Server2019 = Rex::Version.new('10.0.17763.0') - Win10_1903 = Rex::Version.new('10.0.18362.0') - Win10_1909 = Rex::Version.new('10.0.18363.0') - Win10_2004 = Rex::Version.new('10.0.19041.0') - Win10_20H2 = Rex::Version.new('10.0.19042.0') - Win10_21H1 = Rex::Version.new('10.0.19043.0') - Win10_21H2 = Rex::Version.new('10.0.19044.0') - Win10_22H2 = Rex::Version.new('10.0.19045.0') - Server2022 = Rex::Version.new('10.0.20348.0') - Win11_21H2 = Rex::Version.new('10.0.22000.0') - Win11_22H2 = Rex::Version.new('10.0.22621.0') - Win11_23H2 = Rex::Version.new('10.0.22631.0') - Server2022_23H2 = Rex::Version.new('10.0.25398.0') + module SpecificVersions + Win2000 = Rex::Version.new('5.0.2195') + XP_SP0 = Rex::Version.new('5.1.2600.0') + XP_SP1 = Rex::Version.new('5.1.2600.1') + XP_SP2 = Rex::Version.new('5.1.2600.2') + XP_SP3 = Rex::Version.new('5.1.2600.3') + Server2003_SP0 = Rex::Version.new('5.2.3790.0') + Server2003_SP1 = Rex::Version.new('5.2.3790.1') + Server2003_SP2 = Rex::Version.new('5.2.3790.2') + Vista_SP0 = Server2008_SP0 = Rex::Version.new('6.0.6000.0') + Vista_SP1 = Server2008_SP1 = Rex::Version.new('6.0.6001.1') + Vista_SP2 = Server2008_SP2 = Rex::Version.new('6.0.6002.2') + Server2008_SP2_Update = Rex::Version.new('6.0.6003.2') # https://support.microsoft.com/en-us/topic/build-number-changing-to-6003-in-windows-server-2008-1335e4d4-c155-52eb-4a45-b85bd1909ca8 + Win7_SP0 = Server2008_R2_SP0 = Rex::Version.new('6.1.7600.0') + Win7_SP1 = Server2008_R2_SP1 = Rex::Version.new('6.1.7601.1') + Win8 = Server2012 = Rex::Version.new('6.2.9200.0') + Win81 = Server2012_R2 = Rex::Version.new('6.3.9600.0') + Win10_1507 = Win10_InitialRelease = Rex::Version.new('10.0.10240.0') + Win10_1511 = Rex::Version.new('10.0.10586.0') + Win10_1607 = Server2016 = Rex::Version.new('10.0.14393.0') + Win10_1703 = Rex::Version.new('10.0.15063.0') + Win10_1709 = Rex::Version.new('10.0.16299.0') + Win10_1803 = Rex::Version.new('10.0.17134.0') + Win10_1809 = Server2019 = Rex::Version.new('10.0.17763.0') + Win10_1903 = Rex::Version.new('10.0.18362.0') + Win10_1909 = Rex::Version.new('10.0.18363.0') + Win10_2004 = Rex::Version.new('10.0.19041.0') + Win10_20H2 = Rex::Version.new('10.0.19042.0') + Win10_21H1 = Rex::Version.new('10.0.19043.0') + Win10_21H2 = Rex::Version.new('10.0.19044.0') + Win10_22H2 = Rex::Version.new('10.0.19045.0') + Server2022 = Rex::Version.new('10.0.20348.0') + Win11_21H2 = Rex::Version.new('10.0.22000.0') + Win11_22H2 = Rex::Version.new('10.0.22621.0') + Win11_23H2 = Rex::Version.new('10.0.22631.0') + Win11_24H2 = Rex::Version.new('10.0.26100.0') + Server2022_23H2 = Rex::Version.new('10.0.25398.0') + end + + include SpecificVersions module MajorRelease NT351 = 'Windows NT 3.51'.freeze @@ -60,7 +65,7 @@ module MajorRelease Server2008 = 'Windows Server 2008'.freeze Win7 = 'Windows 7'.freeze - Server2008R2 = 'Windows 2008 R2'.freeze + Server2008R2 = 'Windows Server 2008 R2'.freeze Win8 = 'Windows 8'.freeze Server2012 = 'Windows Server 2012'.freeze @@ -140,6 +145,24 @@ def xp_or_2003? build_number.between?(XP_SP0, Server2003_SP2) end + def self.from_ntlm_os_version(major, minor, build) + SpecificVersions.constants.each do |version_sym| + version = const_get(version_sym) + segments = version.segments + if segments[0..2] == [major, minor, build] + workstation = WindowsVersion.new(segments[0], segments[1], segments[2], segments[3], 0, VER_NT_WORKSTATION).product_name + server = WindowsVersion.new(segments[0], segments[1], segments[2], segments[3], 0, VER_NT_SERVER).product_name + if workstation == server + return workstation + else + return "#{workstation}/#{server}" + end + end + end + + nil + end + private attr_accessor :_major, :_minor, :_build, :_service_pack, :_revision, :product_type @@ -154,7 +177,7 @@ def major_release_name elsif _minor == 2 return MajorRelease::Server2003 if windows_server? - return MajorRelease::XP + return MajorRelease::XP # x64 Build end elsif _major == 6 if _minor == 0 diff --git a/modules/auxiliary/scanner/smb/smb_version.rb b/modules/auxiliary/scanner/smb/smb_version.rb index 2def6e697293..5edfe4e1df9e 100644 --- a/modules/auxiliary/scanner/smb/smb_version.rb +++ b/modules/auxiliary/scanner/smb/smb_version.rb @@ -129,8 +129,10 @@ def smb_proto_info simple.client.authenticate rescue RubySMB::Error::RubySMBError info[:auth_domain] = nil + info[:os_version] = nil else info[:auth_domain] = simple.client.default_domain + info[:os_version] = simple.client.os_version end end else @@ -145,7 +147,38 @@ def smb_proto_info info end - def smb_os_description(res, nd_smb_fingerprint) + def smb_description(info) + desc = "SMB Detected (versions:#{info[:versions].join(', ')}) (preferred dialect:#{info[:preferred_dialect]})" + info[:capabilities].each do |name, values| + desc << " (#{name} capabilities:#{values.join(', ')})" + end + + if info[:signing_required] + desc << ' (signatures:required)' + else + desc << ' (signatures:optional)' + end + desc << " (uptime:#{info[:uptime]})" if info[:uptime] + desc << " (guid:#{Rex::Text.to_guid(info[:server_guid])})" if info[:server_guid] + desc << " (authentication domain:#{info[:auth_domain]})" if info[:auth_domain] + + desc + end + + def convert_version_to_os_name(version) + rex_version = Rex::Version.new(version) + segments = rex_version.segments + if segments.length == 3 + windows_match = Msf::WindowsVersion::from_ntlm_os_version(segments[0], segments[1], segments[2]) + unless windows_match.nil? + return "Version #{version} (likely #{windows_match})" + end + end + + "Version #{version} (unknown OS)" + end + + def smb_os_description(res, info, nd_smb_fingerprint) # # Create the note hash for fingerprint.match # @@ -154,39 +187,41 @@ def smb_os_description(res, nd_smb_fingerprint) # # Create a descriptive string for service.info # - desc = res['os'].dup + words = [] + + if res['os'] == 'Unknown' && info[:os_version] + words << convert_version_to_os_name(info[:os_version]) + else + words << res['os'] + end if !res['edition'].to_s.empty? - desc << " #{res['edition']}" + words << " #{res['edition']}" nd_smb_fingerprint[:os_edition] = res['edition'] nd_fingerprint_match['os.edition'] = res['edition'] end if !res['sp'].to_s.empty? - desc << " #{res['sp'].downcase.gsub('service pack ', 'SP')}" + words << " #{res['sp'].downcase.gsub('service pack ', 'SP')}" nd_smb_fingerprint[:os_sp] = res['sp'] nd_fingerprint_match['os.version'] = res['sp'] end if !res['build'].to_s.empty? - desc << " (build:#{res['build']})" + words << " (build:#{res['build']})" nd_smb_fingerprint[:os_build] = res['build'] nd_fingerprint_match['os.build'] = res['build'] end if !res['lang'].to_s.empty? && res['lang'] != 'Unknown' - desc << " (language:#{res['lang']})" + words << " (language:#{res['lang']})" nd_smb_fingerprint[:os_lang] = res['lang'] nd_fingerprint_match['os.language'] = nd_smb_fingerprint[:os_lang] end - if simple.client.default_name - desc << " (name:#{simple.client.default_name})" - nd_smb_fingerprint[:SMBName] = simple.client.default_name - nd_fingerprint_match['host.name'] = nd_smb_fingerprint[:SMBName] - end + os_name = words.join(' ') - { text: desc, fingerprint_match: nd_fingerprint_match, smb_fingerprint: nd_smb_fingerprint } + { os_name: os_name, fingerprint_match: nd_fingerprint_match, smb_fingerprint: nd_smb_fingerprint } end # @@ -211,70 +246,70 @@ def run_host(ip) self.simple = nil begin - res = smb_fingerprint + smb1_fingerprint = smb_fingerprint info = smb_proto_info - desc = "SMB Detected (versions:#{info[:versions].join(', ')}) (preferred dialect:#{info[:preferred_dialect]})" - info[:capabilities].each do |name, values| - desc << " (#{name} capabilities:#{values.join(', ')})" - end - - if info[:signing_required] - desc << ' (signatures:required)' - else - desc << ' (signatures:optional)' - report_vuln({ - host: ip, - port: rport, - proto: 'tcp', - name: 'SMB Signing Is Not Required', - refs: [ - SiteReference.new('URL', 'https://support.microsoft.com/en-us/help/161372/how-to-enable-smb-signing-in-windows-nt'), - SiteReference.new('URL', 'https://support.microsoft.com/en-us/help/887429/overview-of-server-message-block-signing'), - ] - }) - end - desc << " (uptime:#{info[:uptime]})" if info[:uptime] - desc << " (guid:#{Rex::Text.to_guid(info[:server_guid])})" if info[:server_guid] - desc << " (authentication domain:#{info[:auth_domain]})" if info[:auth_domain] - lines << { type: :status, message: desc } # # Create the note hash for smb.fingerprint # nd_smb_fingerprint = { - native_os: res['native_os'], - native_lm: res['native_lm'] + native_os: smb1_fingerprint['native_os'], + native_lm: smb1_fingerprint['native_lm'] } - if res['os'] && res['os'] != 'Unknown' - description = smb_os_description(res, nd_smb_fingerprint) - desc << description[:text] - nd_fingerprint_match = description[:fingerprint_match] - nd_smb_fingerprint = description[:smb_fingerprint] - - if simple.client.default_domain - if simple.client.default_domain.encoding.name == 'UTF-8' - desc << " (domain:#{simple.client.default_domain})" - else - # Workgroup names are in ANSI, but may contain invalid characters - # Go through each char and convert/check - temp_workgroup = simple.client.default_domain.dup - desc << ' (workgroup:' - temp_workgroup.each_char do |i| - begin - desc << i.encode('UTF-8') - rescue ::Encoding::UndefinedConversionError # rubocop:disable Metrics/BlockNesting - desc << '?' - end + description = smb_os_description(smb1_fingerprint, info, nd_smb_fingerprint) + nd_fingerprint_match = description[:fingerprint_match] + nd_smb_fingerprint = description[:smb_fingerprint] + + info[:os_name] = description[:os_name] + + if simple.client.default_domain + if simple.client.default_domain.encoding.name == 'UTF-8' + info[:domain] = simple.client.default_domain + else + # Workgroup names are in ANSI, but may contain invalid characters + # Go through each char and convert/check + temp_workgroup = simple.client.default_domain.dup + workgroup = "" + temp_workgroup.each_char do |i| + begin + workgroup << i.encode('UTF-8') + rescue ::Encoding::UndefinedConversionError # rubocop:disable Metrics/BlockNesting + workgroup << '?' end - desc << ')' end - nd_smb_fingerprint[:SMBDomain] = simple.client.default_domain - nd_fingerprint_match['host.domain'] = nd_smb_fingerprint[:SMBDomain] + info[:workgroup] = workgroup end + nd_smb_fingerprint[:SMBDomain] = simple.client.default_domain + nd_fingerprint_match['host.domain'] = nd_smb_fingerprint[:SMBDomain] + end - lines << { type: :good, message: " Host is running #{desc}" } + if simple.client.default_name + nd_smb_fingerprint[:SMBName] = simple.client.default_name + nd_fingerprint_match['host.name'] = nd_smb_fingerprint[:SMBName] + info[:computer_name] = simple.client.default_name + end + + if info[:os_name] && info[:os_name] != 'Unknown' + smb_desc = smb_description(info) + os_desc = "Host is running #{info[:os_name]}" + + lines << { type: :status, message: smb_desc } + lines << { type: :good, message: " #{os_desc}" } + + unless info[:signing_required] + report_vuln({ + host: ip, + port: rport, + proto: 'tcp', + name: 'SMB Signing Is Not Required', + refs: [ + SiteReference.new('URL', 'https://support.microsoft.com/en-us/help/161372/how-to-enable-smb-signing-in-windows-nt'), + SiteReference.new('URL', 'https://support.microsoft.com/en-us/help/887429/overview-of-server-message-block-signing'), + ] + }) + end # Report the service with a friendly banner report_service( @@ -282,7 +317,7 @@ def run_host(ip) port: rport, proto: 'tcp', name: 'smb', - info: desc + info: "#{smb_desc}; #{os_desc}" ) # Report a fingerprint.match hash for name, domain, and language @@ -294,8 +329,8 @@ def run_host(ip) ntype: 'fingerprint.match', data: nd_fingerprint_match ) - elsif res['native_os'] || res['native_lm'] - desc = "#{res['native_os']} (#{res['native_lm']})" + elsif smb1_fingerprint['native_os'] || smb1_fingerprint['native_lm'] + desc = "#{smb1_fingerprint['native_os']} (#{smb1_fingerprint['native_lm']})" report_service(host: ip, port: rport, name: 'smb', info: desc) lines << { type: :status, message: " Host could not be identified: #{desc}" } else @@ -307,7 +342,7 @@ def run_host(ip) port: rport, proto: 'tcp', name: 'smb', - info: desc + info: "#{smb_desc}. #{os_desc}" ) diff --git a/spec/lib/msf/core/windows_version_spec.rb b/spec/lib/msf/core/windows_version_spec.rb index 951a3dd5f0f4..8f8ee9edcb6a 100644 --- a/spec/lib/msf/core/windows_version_spec.rb +++ b/spec/lib/msf/core/windows_version_spec.rb @@ -26,4 +26,36 @@ subject = described_class.new(1,2,3000,0, 0,Msf::WindowsVersion::VER_NT_WORKSTATION) expect(subject.to_s).to eq('Unknown Windows version: 1.2.3000') end + + it 'Reports correct SMB version for single match' do + major = 5 + minor = 1 + build = 2600 + version_string = described_class.from_ntlm_os_version(major, minor, build) + expect(version_string).to eq('Windows XP') + end + + it 'Reports correct SMB version for multiple matches' do + major = 6 + minor = 1 + build = 7601 + version_string = described_class.from_ntlm_os_version(major, minor, build) + expect(version_string).to eq('Windows 7 Service Pack 1/Windows Server 2008 R2 Service Pack 1') + end + + it 'Reports unknown SMB version for no identical old OS' do + major = 6 + minor = 1 + build = 7604 + version_string = described_class.from_ntlm_os_version(major, minor, build) + expect(version_string).to eq(nil) + end + + it 'Reports unknown SMB version for no identical Win10+' do + major = 10 + minor = 0 + build = 15064 + version_string = described_class.from_ntlm_os_version(major, minor, build) + expect(version_string).to eq(nil) + end end From 9bd27e431ddb890b0f67eaecee29fef7957c9dfc Mon Sep 17 00:00:00 2001 From: Ashley Donaldson Date: Fri, 15 Nov 2024 14:54:57 +1100 Subject: [PATCH 2/3] Give specific version details when the version matches perfectly --- lib/msf/core/windows_version.rb | 157 +++++++++++++++++----- spec/lib/msf/core/windows_version_spec.rb | 18 ++- 2 files changed, 141 insertions(+), 34 deletions(-) diff --git a/lib/msf/core/windows_version.rb b/lib/msf/core/windows_version.rb index c1bc42c0157b..4bfd85653617 100644 --- a/lib/msf/core/windows_version.rb +++ b/lib/msf/core/windows_version.rb @@ -10,30 +10,44 @@ class WindowsVersion VER_NT_DOMAIN_CONTROLLER = 2 VER_NT_SERVER = 3 - module SpecificVersions + module ServerSpecificVersions + Server2003_SP0 = Rex::Version.new('5.2.3790.0') + Server2003_SP1 = Rex::Version.new('5.2.3790.1') + Server2003_SP2 = Rex::Version.new('5.2.3790.2') + Server2008_SP0 = Rex::Version.new('6.0.6000.0') + Server2008_SP1 = Rex::Version.new('6.0.6001.1') + Server2008_SP2 = Rex::Version.new('6.0.6002.2') + Server2008_SP2_Update = Rex::Version.new('6.0.6003.2') # https://support.microsoft.com/en-us/topic/build-number-changing-to-6003-in-windows-server-2008-1335e4d4-c155-52eb-4a45-b85bd1909ca8 + Server2008_R2_SP0 = Rex::Version.new('6.1.7600.0') + Server2008_R2_SP1 = Rex::Version.new('6.1.7601.1') + Server2012 = Rex::Version.new('6.2.9200.0') + Server2012_R2 = Rex::Version.new('6.3.9600.0') + Server2016 = Rex::Version.new('10.0.14393.0') + Server2019 = Rex::Version.new('10.0.17763.0') + Server2022 = Rex::Version.new('10.0.20348.0') + Server2022_23H2 = Rex::Version.new('10.0.25398.0') + end + + module WorkstationSpecificVersions Win2000 = Rex::Version.new('5.0.2195') XP_SP0 = Rex::Version.new('5.1.2600.0') XP_SP1 = Rex::Version.new('5.1.2600.1') XP_SP2 = Rex::Version.new('5.1.2600.2') XP_SP3 = Rex::Version.new('5.1.2600.3') - Server2003_SP0 = Rex::Version.new('5.2.3790.0') - Server2003_SP1 = Rex::Version.new('5.2.3790.1') - Server2003_SP2 = Rex::Version.new('5.2.3790.2') - Vista_SP0 = Server2008_SP0 = Rex::Version.new('6.0.6000.0') - Vista_SP1 = Server2008_SP1 = Rex::Version.new('6.0.6001.1') - Vista_SP2 = Server2008_SP2 = Rex::Version.new('6.0.6002.2') - Server2008_SP2_Update = Rex::Version.new('6.0.6003.2') # https://support.microsoft.com/en-us/topic/build-number-changing-to-6003-in-windows-server-2008-1335e4d4-c155-52eb-4a45-b85bd1909ca8 - Win7_SP0 = Server2008_R2_SP0 = Rex::Version.new('6.1.7600.0') - Win7_SP1 = Server2008_R2_SP1 = Rex::Version.new('6.1.7601.1') - Win8 = Server2012 = Rex::Version.new('6.2.9200.0') - Win81 = Server2012_R2 = Rex::Version.new('6.3.9600.0') - Win10_1507 = Win10_InitialRelease = Rex::Version.new('10.0.10240.0') + Vista_SP0 = Rex::Version.new('6.0.6000.0') + Vista_SP1 = Rex::Version.new('6.0.6001.1') + Vista_SP2 = Rex::Version.new('6.0.6002.2') + Win7_SP0 = Rex::Version.new('6.1.7600.0') + Win7_SP1 = Rex::Version.new('6.1.7601.1') + Win8 = Rex::Version.new('6.2.9200.0') + Win81 = Rex::Version.new('6.3.9600.0') + Win10_1507 = Rex::Version.new('10.0.10240.0') Win10_1511 = Rex::Version.new('10.0.10586.0') - Win10_1607 = Server2016 = Rex::Version.new('10.0.14393.0') + Win10_1607 = Rex::Version.new('10.0.14393.0') Win10_1703 = Rex::Version.new('10.0.15063.0') Win10_1709 = Rex::Version.new('10.0.16299.0') Win10_1803 = Rex::Version.new('10.0.17134.0') - Win10_1809 = Server2019 = Rex::Version.new('10.0.17763.0') + Win10_1809 = Rex::Version.new('10.0.17763.0') Win10_1903 = Rex::Version.new('10.0.18362.0') Win10_1909 = Rex::Version.new('10.0.18363.0') Win10_2004 = Rex::Version.new('10.0.19041.0') @@ -41,15 +55,67 @@ module SpecificVersions Win10_21H1 = Rex::Version.new('10.0.19043.0') Win10_21H2 = Rex::Version.new('10.0.19044.0') Win10_22H2 = Rex::Version.new('10.0.19045.0') - Server2022 = Rex::Version.new('10.0.20348.0') Win11_21H2 = Rex::Version.new('10.0.22000.0') Win11_22H2 = Rex::Version.new('10.0.22621.0') Win11_23H2 = Rex::Version.new('10.0.22631.0') Win11_24H2 = Rex::Version.new('10.0.26100.0') - Server2022_23H2 = Rex::Version.new('10.0.25398.0') end - include SpecificVersions + include WorkstationSpecificVersions + include ServerSpecificVersions + + ServerNameMapping = { + :Server2003_SP0 => "Windows Server 2003", + :Server2003_SP1 => "Windows Server 2003 Service Pack 1", + :Server2003_SP2 => "Windows Server 2003 Service Pack 2", + :Server2008_SP0 => "Windows Server 2008", + :Server2008_SP1 => "Windows Server 2008 Service Pack 1", + :Server2008_SP2 => "Windows Server 2008 Service Pack 2", + :Server2008_SP2_Update => "Windows Server 2008 Service Pack 2 Update", + :Server2008_R2_SP0 => "Windows Server 2008 R2", + :Server2008_R2_SP1 => "Windows Server 2008 R2 Service Pack 1", + :Server2012 => "Windows Server 2012 R2", + :Server2012_R2 => "Windows Server 2012 R2", + :Server2016 => "Windows Server 2016", + :Server2019 => "Windows Server 2019", + :Server2022 => "Windows Server 2022", + :Server2022_23H2 => "Windows Server 2022 version 23H2" + } + + WorkstationNameMapping = { + :Win2000 => "Windows 2000", + :XP_SP0 => "Windows XP", + :XP_SP1 => "Windows XP Service Pack 1", + :XP_SP2 => "Windows XP Service Pack 2", + :XP_SP3 => "Windows XP Service Pack 3", + :Vista_SP0 => "Windows Vista", + :Vista_SP1 => "Windows Vista Service Pack 1", + :Vista_SP2 => "Windows Vista Service Pack 2", + :Win7_SP0 => "Windows 7", + :Win7_SP1 => "Windows 7 Service Pack 1", + :Win8 => "Windows 8", + :Win81 => "Windows 8.1", + :Win10_1507 => "Windows 10 version 1507", + :Win10_1511 => "Windows 10 version 1511", + :Win10_1607 => "Windows 10 version 1607", + :Win10_1703 => "Windows 10 version 1703", + :Win10_1709 => "Windows 10 version 1709", + :Win10_1803 => "Windows 10 version 1803", + :Win10_1809 => "Windows 10 version 1809", + :Win10_1903 => "Windows 10 version 1903", + :Win10_1909 => "Windows 10 version 1909", + :Win10_2004 => "Windows 10 version 2004", + :Win10_20H2 => "Windows 10 version 20H2", + :Win10_21H1 => "Windows 10 version 21H1", + :Win10_21H2 => "Windows 10 version 21H2", + :Win10_22H2 => "Windows 10 version 22H2", + :Win11_21H2 => "Windows 11 version 21H2", + :Win11_22H2 => "Windows 11 version 22H2", + :Win11_23H2 => "Windows 11 version 23H2", + :Win11_24H2 => "Windows 11 version 24H2" + } + + Win10_InitialRelease = Win10_1507 module MajorRelease NT351 = 'Windows NT 3.51'.freeze @@ -117,6 +183,15 @@ def domain_controller? # The name of the OS, as it is most commonly rendered. Includes Service Pack if present, or build number if Win10 or higher. def product_name + # First check if there's a specific, known version we have a string for + if windows_server? + known_version = self.class.version_string(_major, _minor, _build, ServerSpecificVersions, ServerNameMapping) + else + known_version = self.class.version_string(_major, _minor, _build, WorkstationSpecificVersions, WorkstationNameMapping) + end + return known_version unless known_version.nil? + + # Otherwise, build it up from version numbers, to the best of our ability result = "Unknown Windows version: #{_major}.#{_minor}.#{_build}" name = major_release_name result = name unless name.nil? @@ -145,22 +220,27 @@ def xp_or_2003? build_number.between?(XP_SP0, Server2003_SP2) end + # Get the string representation of the OS, given a major, minor and build number + # (as reported by an NTLM handshake). + # The NTLM structure makes no guarantee that it is actually Windows, so if we + # don't find a precise match, return nil + # @param major [Integer] The major build number reported in the NTLM handshake + # @param minor [Integer] The minor build number reported in the NTLM handshake + # @param build [Integer] The build build number reported in the NTLM handshake + # @return [String] The possible matching OS versions, or nil if no identical match can be found def self.from_ntlm_os_version(major, minor, build) - SpecificVersions.constants.each do |version_sym| - version = const_get(version_sym) - segments = version.segments - if segments[0..2] == [major, minor, build] - workstation = WindowsVersion.new(segments[0], segments[1], segments[2], segments[3], 0, VER_NT_WORKSTATION).product_name - server = WindowsVersion.new(segments[0], segments[1], segments[2], segments[3], 0, VER_NT_SERVER).product_name - if workstation == server - return workstation - else - return "#{workstation}/#{server}" - end - end - end + workstation_string = self.version_string(major, minor, build, WorkstationSpecificVersions, WorkstationNameMapping) + server_string = self.version_string(major, minor, build, ServerSpecificVersions, ServerNameMapping) - nil + version_strings = [] + version_strings.append(workstation_string) unless workstation_string.nil? + version_strings.append(server_string) unless server_string.nil? + + if version_strings.length > 0 + version_strings.join('/') + else + nil + end end private @@ -206,5 +286,18 @@ def major_release_name end return nil end + + # Get a Windows OS string from a version, given a set of version constants + def self.version_string(major, minor, build, version_module, mapping) + version_module.constants.each do |version_sym| + version = version_module.const_get(version_sym) + segments = version.segments + if segments[0..2] == [major, minor, build] + return mapping[version_sym] + end + end + + nil + end end end diff --git a/spec/lib/msf/core/windows_version_spec.rb b/spec/lib/msf/core/windows_version_spec.rb index 8f8ee9edcb6a..5cb7550b393a 100644 --- a/spec/lib/msf/core/windows_version_spec.rb +++ b/spec/lib/msf/core/windows_version_spec.rb @@ -13,12 +13,17 @@ end it 'Adds build suffix to Windows 10' do + subject = described_class.new(10,0,18360,0, 0,Msf::WindowsVersion::VER_NT_WORKSTATION) + expect(subject.to_s).to eq('Windows 10+ Build 18360') + end + + it 'Uses known Windows 10 version' do subject = described_class.new(10,0,18362,0, 0,Msf::WindowsVersion::VER_NT_WORKSTATION) - expect(subject.to_s).to eq('Windows 10+ Build 18362') + expect(subject.to_s).to eq('Windows 10 version 1903') end it 'Adds service pack suffix' do - subject = described_class.new(5,1,2600,2, 0,Msf::WindowsVersion::VER_NT_WORKSTATION) + subject = described_class.new(5,1,2602,2, 0,Msf::WindowsVersion::VER_NT_WORKSTATION) expect(subject.to_s).to eq('Windows XP Service Pack 2') end @@ -27,6 +32,15 @@ expect(subject.to_s).to eq('Unknown Windows version: 1.2.3000') end + it 'Has string name for each named version' do + described_class::ServerSpecificVersions.constants.each do |version_sym| + expect(described_class::ServerNameMapping).to include(version_sym) + end + described_class::WorkstationSpecificVersions.constants.each do |version_sym| + expect(described_class::WorkstationNameMapping).to include(version_sym) + end + end + it 'Reports correct SMB version for single match' do major = 5 minor = 1 From 6f4ab97c83ec910da6d8baf8e5168e25e2c8e8db Mon Sep 17 00:00:00 2001 From: Ashley Donaldson Date: Fri, 22 Nov 2024 13:06:58 +1100 Subject: [PATCH 3/3] Commenting changes --- lib/msf/core/windows_version.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/msf/core/windows_version.rb b/lib/msf/core/windows_version.rb index 4bfd85653617..3e96bb53bd9d 100644 --- a/lib/msf/core/windows_version.rb +++ b/lib/msf/core/windows_version.rb @@ -222,12 +222,13 @@ def xp_or_2003? # Get the string representation of the OS, given a major, minor and build number # (as reported by an NTLM handshake). - # The NTLM structure makes no guarantee that it is actually Windows, so if we - # don't find a precise match, return nil + # The NTLM structure makes no guarantee that the underlying OS of the server is + # actually Windows, so if we don't find a precise match, return nil + # # @param major [Integer] The major build number reported in the NTLM handshake # @param minor [Integer] The minor build number reported in the NTLM handshake # @param build [Integer] The build build number reported in the NTLM handshake - # @return [String] The possible matching OS versions, or nil if no identical match can be found + # @return [String] The possible matching OS versions, or nil if no corresponding match can be found def self.from_ntlm_os_version(major, minor, build) workstation_string = self.version_string(major, minor, build, WorkstationSpecificVersions, WorkstationNameMapping) server_string = self.version_string(major, minor, build, ServerSpecificVersions, ServerNameMapping) @@ -287,7 +288,7 @@ def major_release_name return nil end - # Get a Windows OS string from a version, given a set of version constants + # Get a Windows OS version string representation for a given major, minor and build number def self.version_string(major, minor, build, version_module, mapping) version_module.constants.each do |version_sym| version = version_module.const_get(version_sym)