├── .gitignore ├── ArpPinger.cpp ├── ArpPinger.h ├── BannerProcessor.cpp ├── BannerProcessor.h ├── CMakeLists.txt ├── CPackConfig.cmake ├── CensysScanner.cpp ├── CensysScanner.h ├── ConcurrentQueue.cpp ├── ConcurrentQueue.h ├── CpeDictionaryMatcher.cpp ├── CpeDictionaryMatcher.h ├── DataReader.cpp ├── DataReader.h ├── DebianIdentifier.cpp ├── DebianIdentifier.h ├── DebianLookup.cpp ├── DebianLookup.h ├── Doxyfile ├── EnterpriseLinuxIdentifier.cpp ├── EnterpriseLinuxIdentifier.h ├── EnterpriseLinuxLookup.cpp ├── EnterpriseLinuxLookup.h ├── FedoraIdentifier.cpp ├── FedoraIdentifier.h ├── Format.cpp ├── Format.h ├── Host.cpp ├── Host.h ├── HostScanner.cpp ├── HostScanner.h ├── HostScannerFactory.cpp ├── HostScannerFactory.h ├── HttpTokenizer.cpp ├── HttpTokenizer.h ├── IcmpPinger.cpp ├── IcmpPinger.h ├── InternalScanner.cpp ├── InternalScanner.h ├── LICENSE.md ├── LooquerScanner.cpp ├── LooquerScanner.h ├── Main.cpp ├── NmapScanner.cpp ├── NmapScanner.h ├── OperatingSystemIdentifier.cpp ├── OperatingSystemIdentifier.h ├── PassiveScanner.cpp ├── PassiveScanner.h ├── ProtocolTokenizer.cpp ├── ProtocolTokenizer.h ├── README.md ├── Service.cpp ├── Service.h ├── ServiceRegexMatcher.cpp ├── ServiceRegexMatcher.h ├── ServiceScanner.cpp ├── ServiceScanner.h ├── ServiceScannerFactory.cpp ├── ServiceScannerFactory.h ├── ShodanScanner.cpp ├── ShodanScanner.h ├── Stdafx.h ├── TaskQueueRunner.cpp ├── TaskQueueRunner.h ├── TcpScanner.cpp ├── TcpScanner.h ├── Test.cpp ├── ThreeDigitTokenizer.cpp ├── ThreeDigitTokenizer.h ├── UbuntuIdentifier.cpp ├── UbuntuIdentifier.h ├── UbuntuLookup.cpp ├── UbuntuLookup.h ├── UdpScanner.cpp ├── UdpScanner.h ├── Utils.cpp ├── Utils.h ├── VendorLookupFactory.cpp ├── VendorLookupFactory.h ├── VendorPackageLookup.cpp ├── VendorPackageLookup.h ├── VulnerabilityLookup.cpp ├── VulnerabilityLookup.h ├── WindowsIdentifier.cpp ├── WindowsIdentifier.h ├── build └── .gitkeep ├── cmake └── FindSQLite.cmake ├── conanfile.txt ├── data ├── README.md ├── copy-build.sh └── download.sh ├── distrib ├── README.md ├── deb │ ├── Dockerfile │ ├── README.md │ ├── compile.sh │ └── upload.sh └── rpm │ ├── Dockerfile │ ├── README.md │ ├── compile.sh │ └── upload.sh └── doxyextra.css /.gitignore: -------------------------------------------------------------------------------- 1 | /data/*.dat 2 | /data/*.dat.gz 3 | /data/*.db3 4 | /data/*.db3.bz2 5 | 6 | ### Conan ### 7 | conanbuildinfo.* 8 | conaninfo.txt 9 | 10 | ### VisualStudio ### 11 | ## Ignore Visual Studio temporary files, build results, and 12 | ## files generated by popular Visual Studio add-ons. 13 | 14 | # User-specific files 15 | *.suo 16 | *.user 17 | *.userosscache 18 | *.sln.docstates 19 | *.GhostDoc.xml 20 | 21 | # User-specific files (MonoDevelop/Xamarin Studio) 22 | *.userprefs 23 | 24 | # Build results 25 | [Dd]ebug/ 26 | [Dd]ebugPublic/ 27 | [Rr]elease/ 28 | [Rr]eleases/ 29 | [Mm]inSizeRel/ 30 | Win32/ 31 | x64/ 32 | x86/ 33 | build/ 34 | bld/ 35 | [Bb]in/ 36 | [Oo]bj/ 37 | 38 | # Visual Studio 2015 cache/options directory 39 | .vs/ 40 | # Uncomment if you have tasks that create the project's static files in wwwroot 41 | #wwwroot/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUNIT 48 | *.VisualState.xml 49 | TestResult.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # DNX 57 | project.lock.json 58 | artifacts/ 59 | 60 | *_i.c 61 | *_p.c 62 | *_i.h 63 | *.ilk 64 | *.meta 65 | *.obj 66 | *.pch 67 | *.pdb 68 | *.pgc 69 | *.pgd 70 | *.rsp 71 | *.sbr 72 | *.tlb 73 | *.tli 74 | *.tlh 75 | *.tmp 76 | *.tmp_proj 77 | *.log 78 | *.vspscc 79 | *.vssscc 80 | .builds 81 | *.pidb 82 | *.svclog 83 | *.scc 84 | 85 | # Chutzpah Test files 86 | _Chutzpah* 87 | 88 | # Visual C++ cache files 89 | ipch/ 90 | *.aps 91 | *.ncb 92 | *.opensdf 93 | *.sdf 94 | *.cachefile 95 | 96 | # Visual Studio profiler 97 | *.psess 98 | *.vsp 99 | *.vspx 100 | 101 | # TFS 2012 Local Workspace 102 | $tf/ 103 | 104 | # Guidance Automation Toolkit 105 | *.gpState 106 | 107 | # ReSharper is a .NET coding add-in 108 | _ReSharper*/ 109 | *.[Rr]e[Ss]harper 110 | *.DotSettings.user 111 | 112 | # JustCode is a .NET coding add-in 113 | .JustCode 114 | 115 | # TeamCity is a build add-in 116 | _TeamCity* 117 | 118 | # DotCover is a Code Coverage Tool 119 | *.dotCover 120 | 121 | # NCrunch 122 | _NCrunch_* 123 | .*crunch*.local.xml 124 | nCrunchTemp_* 125 | 126 | # MightyMoose 127 | *.mm.* 128 | AutoTest.Net/ 129 | 130 | # Web workbench (sass) 131 | .sass-cache/ 132 | 133 | # Installshield output folder 134 | [Ee]xpress/ 135 | 136 | # DocProject is a documentation generator add-in 137 | DocProject/buildhelp/ 138 | DocProject/Help/*.HxT 139 | DocProject/Help/*.HxC 140 | DocProject/Help/*.hhc 141 | DocProject/Help/*.hhk 142 | DocProject/Help/*.hhp 143 | DocProject/Help/Html2 144 | DocProject/Help/html 145 | 146 | # Click-Once directory 147 | publish/ 148 | 149 | # Publish Web Output 150 | *.[Pp]ublish.xml 151 | *.azurePubxml 152 | # TODO: Comment the next line if you want to checkin your web deploy settings 153 | # but database connection strings (with potential passwords) will be unencrypted 154 | *.pubxml 155 | *.publishproj 156 | 157 | # NuGet Packages 158 | *.nupkg 159 | # The packages folder can be ignored because of Package Restore 160 | **/packages/* 161 | # except build/, which is used as an MSBuild target. 162 | !**/packages/build/ 163 | # Uncomment if necessary however generally it will be regenerated when needed 164 | #!**/packages/repositories.config 165 | 166 | # Windows Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Windows Store app package directory 171 | AppPackages/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | [Ss]tyle[Cc]op.* 182 | ~$* 183 | *~ 184 | *.dbmdl 185 | *.dbproj.schemaview 186 | *.pfx 187 | *.publishsettings 188 | node_modules/ 189 | orleans.codegen.cs 190 | 191 | # RIA/Silverlight projects 192 | Generated_Code/ 193 | 194 | # Backup & report files from converting an old project file 195 | # to a newer Visual Studio version. Backup files are not needed, 196 | # because we have git ;-) 197 | _UpgradeReport_Files/ 198 | Backup*/ 199 | UpgradeLog*.XML 200 | UpgradeLog*.htm 201 | 202 | # SQL Server files 203 | *.mdf 204 | *.ldf 205 | 206 | # Business Intelligence projects 207 | *.rdl.data 208 | *.bim.layout 209 | *.bim_*.settings 210 | 211 | # Microsoft Fakes 212 | FakesAssemblies/ 213 | 214 | # Node.js Tools for Visual Studio 215 | .ntvs_analysis.dat 216 | 217 | # Visual Studio 6 build log 218 | *.plg 219 | 220 | # Visual Studio 6 workspace options file 221 | *.opt 222 | 223 | # Visual Studio LightSwitch build output 224 | **/*.HTMLClient/GeneratedArtifacts 225 | **/*.DesktopClient/GeneratedArtifacts 226 | **/*.DesktopClient/ModelManifest.xml 227 | **/*.Server/GeneratedArtifacts 228 | **/*.Server/ModelManifest.xml 229 | _Pvt_Extensions 230 | 231 | ### Intellij ### 232 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 233 | 234 | *.iml 235 | 236 | ## Directory-based project format: 237 | .idea/ 238 | # if you remove the above rule, at least ignore the following: 239 | 240 | # User-specific stuff: 241 | # .idea/workspace.xml 242 | # .idea/tasks.xml 243 | # .idea/dictionaries 244 | 245 | # Sensitive or high-churn files: 246 | # .idea/dataSources.ids 247 | # .idea/dataSources.xml 248 | # .idea/sqlDataSources.xml 249 | # .idea/dynamic.xml 250 | # .idea/uiDesigner.xml 251 | 252 | # Gradle: 253 | # .idea/gradle.xml 254 | # .idea/libraries 255 | 256 | # Mongo Explorer plugin: 257 | # .idea/mongoSettings.xml 258 | 259 | ## File-based project format: 260 | *.ipr 261 | *.iws 262 | 263 | ## Plugin-specific files: 264 | 265 | # IntelliJ 266 | /out/ 267 | 268 | # mpeltonen/sbt-idea plugin 269 | .idea_modules/ 270 | 271 | # JIRA plugin 272 | atlassian-ide-plugin.xml 273 | 274 | # Crashlytics plugin (for Android Studio and IntelliJ) 275 | com_crashlytics_export_strings.xml 276 | crashlytics.properties 277 | crashlytics-build.properties 278 | 279 | 280 | ### Windows ### 281 | # Windows image file caches 282 | Thumbs.db 283 | ehthumbs.db 284 | 285 | # Folder config file 286 | Desktop.ini 287 | 288 | # Recycle Bin used on file shares 289 | $RECYCLE.BIN/ 290 | 291 | # Windows Installer files 292 | *.cab 293 | *.msi 294 | *.msm 295 | *.msp 296 | 297 | # Windows shortcuts 298 | *.lnk 299 | -------------------------------------------------------------------------------- /ArpPinger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Stdafx.h" 3 | #include "HostScanner.h" 4 | #include 5 | #include 6 | #include 7 | 8 | #define ARP_OP_REQUEST 1 9 | #define ARP_OP_REPLY 2 10 | 11 | /*! 12 | * Structure of an Ethernet packet. 13 | */ 14 | struct EthHeader 15 | { 16 | 17 | /*! 18 | * Destination MAC address. 19 | */ 20 | unsigned char dst[6]; 21 | 22 | /*! 23 | * Source MAC address. 24 | */ 25 | unsigned char src[6]; 26 | 27 | /*! 28 | * Protocol type. (e.g. IP) 29 | */ 30 | unsigned short typ; 31 | 32 | }; 33 | 34 | /*! 35 | * Structure of an ARP packet. 36 | */ 37 | struct ArpHeader 38 | { 39 | 40 | /*! 41 | * Hardware type. (e.g. Ethernet) 42 | */ 43 | unsigned short htype; 44 | 45 | /*! 46 | * Protocol type. (e.g. IP) 47 | */ 48 | unsigned short ptype; 49 | 50 | /*! 51 | * Hardware size. 52 | */ 53 | unsigned char hlen; 54 | 55 | /*! 56 | * Protocol size. 57 | */ 58 | unsigned char plen; 59 | 60 | /*! 61 | * Packet type. (request or reply) 62 | */ 63 | unsigned short opcode; 64 | 65 | /*! 66 | * Sender MAC address. 67 | */ 68 | unsigned char srcmac[6]; 69 | 70 | /*! 71 | * Sender IP address. 72 | */ 73 | unsigned char srcip[4]; 74 | 75 | /*! 76 | * Target MAC address. 77 | */ 78 | unsigned char dstmac[6]; 79 | 80 | /*! 81 | * Target IP address. 82 | */ 83 | unsigned char dstip[4]; 84 | 85 | }; 86 | 87 | /*! 88 | * Structure for holding interface information. 89 | */ 90 | struct Interface 91 | { 92 | 93 | /*! 94 | * Identifier of the interface. 95 | */ 96 | std::string adapter; 97 | 98 | #if Unix 99 | 100 | /*! 101 | * Interface number. 102 | */ 103 | int ifnum; 104 | 105 | #endif 106 | 107 | /*! 108 | * Human-friendly description of the interface. 109 | */ 110 | std::string description; 111 | 112 | /*! 113 | * MAC address of the adapter. 114 | */ 115 | unsigned char macaddr[6]; 116 | 117 | /*! 118 | * Registered IPv4 address. 119 | */ 120 | unsigned int ipaddr; 121 | 122 | /*! 123 | * IPv4 network mask. 124 | */ 125 | unsigned int ipmask; 126 | 127 | /*! 128 | * IPv4 gateway. 129 | */ 130 | unsigned int ipgate; 131 | 132 | }; 133 | 134 | /*! 135 | * Represents internal scan data for the ARP scanner. 136 | */ 137 | struct ArpScanData 138 | { 139 | 140 | /*! 141 | * IPv4 address in decimal format. 142 | */ 143 | unsigned int ipaddr; 144 | 145 | /*! 146 | * Interface information. 147 | */ 148 | struct Interface* iface; 149 | 150 | }; 151 | 152 | /*! 153 | * Implements a scanner which sends ARP pings using raw sockets. 154 | */ 155 | class ArpPinger : public HostScanner 156 | { 157 | public: 158 | 159 | /*! 160 | * Creates a new instance of this type. 161 | */ 162 | ArpPinger(); 163 | 164 | /*! 165 | * Gets the currently set value for the option key. 166 | * 167 | * \param option Option index, see `OPT_*` macros. 168 | * \param value Pointer to the value to set. 169 | * 170 | * \return true if it succeeds, false if it fails. 171 | */ 172 | bool GetOption(int option, void* value); 173 | 174 | /*! 175 | * Sets a specified value for the option key. 176 | * 177 | * \param option Option index, see `OPT_*` macros. 178 | * \param value Pointer to the value to set. 179 | * 180 | * \return true if it succeeds, false if it fails. 181 | */ 182 | bool SetOption(int option, void* value); 183 | 184 | /*! 185 | * Value indicating whether this instance is a passive scanner. 186 | * 187 | * A passive scanner does not actively send packets towards the 188 | * scanned target, it instead uses miscellaneous data sources to 189 | * gather information regarding the target. 190 | * 191 | * \return true if passive, false if not. 192 | */ 193 | bool IsPassive() override; 194 | 195 | /*! 196 | * Scans a host to determine aliveness. 197 | * 198 | * \param host Host. 199 | */ 200 | void Scan(Host* host) override; 201 | 202 | /*! 203 | * Scans a list of hosts to determine aliveness. 204 | * 205 | * \param hosts List of hosts. 206 | */ 207 | void Scan(Hosts* hosts) override; 208 | 209 | /*! 210 | * Frees up the resources allocated during the lifetime of this instance. 211 | */ 212 | ~ArpPinger() override; 213 | 214 | private: 215 | 216 | /*! 217 | * Number of milliseconds to wait for a reply packet. 218 | */ 219 | unsigned long timeout = 3000; 220 | 221 | /*! 222 | * List of available interfaces and their properties. 223 | */ 224 | std::vector interfaces; 225 | 226 | /*! 227 | * Makes the required preparations in order to determine whether this 228 | * host is eligible for this type of scanning or not. 229 | * 230 | * \param host Host. 231 | */ 232 | void prepareHost(Host* host); 233 | 234 | /*! 235 | * Sends an ARP Request packet to the specified service. 236 | * 237 | * \param host Host. 238 | */ 239 | void sendRequest(Host* host); 240 | 241 | /*! 242 | * Sniffs the specified interfaces for ARP reply packets. 243 | * 244 | * \param ifaces Interfaces to sniff. 245 | * \param hosts Hosts mapped to their IP addresses in decimal formats, 246 | * for faster look-ups during packet processing. 247 | */ 248 | void sniffReplies(std::unordered_set ifaces, std::unordered_map hosts); 249 | 250 | /*! 251 | * Populates the list of active interfaces on the current machine. 252 | */ 253 | void loadInterfaces(); 254 | 255 | /*! 256 | * Determines whether the specified IP address is on the specified interface. 257 | * 258 | * \param ip IP address to check. 259 | * \param inf Interface to check against. 260 | * 261 | * \return Value indicating whether in range. 262 | */ 263 | static bool isIpOnIface(unsigned int ip, Interface* inf); 264 | 265 | }; 266 | -------------------------------------------------------------------------------- /BannerProcessor.cpp: -------------------------------------------------------------------------------- 1 | #include "BannerProcessor.h" 2 | #include "ServiceRegexMatcher.h" 3 | #include "CpeDictionaryMatcher.h" 4 | #include "ProtocolTokenizer.h" 5 | 6 | using namespace std; 7 | 8 | void BannerProcessor::Scan(Service* service) 9 | { 10 | auto matches = Scan(service->banner); 11 | 12 | if (matches.size() > 0) 13 | { 14 | service->cpe.insert(service->cpe.end(), matches.begin(), matches.end()); 15 | } 16 | } 17 | 18 | vector BannerProcessor::AutoProcess(const string& banner, bool processVendor) 19 | { 20 | vector cpes; 21 | 22 | // the regular expression pattern matcher implementation generally matches 23 | // against full service banners and as a result it is much more accurate 24 | 25 | static ServiceRegexMatcher srm; 26 | 27 | auto rmlst = srm.Scan(banner, processVendor); 28 | 29 | if (rmlst.size() > 0) 30 | { 31 | cpes.insert(cpes.end(), rmlst.begin(), rmlst.end()); 32 | } 33 | 34 | // the CPE dictionary entry matcher requires tokenization in order to be 35 | // more precise 36 | 37 | static CpeDictionaryMatcher cdm; 38 | 39 | auto tokens = ProtocolTokenizer::AutoTokenize(banner); 40 | 41 | for (auto token : tokens) 42 | { 43 | auto cdlst = cdm.Scan(token, processVendor); 44 | 45 | if (cdlst.size() > 0) 46 | { 47 | cpes.insert(cpes.end(), cdlst.begin(), cdlst.end()); 48 | } 49 | } 50 | 51 | // if neither methods matched, re-try matching the full service banner 52 | // with the CPE dictionary matcher as a last resort 53 | 54 | if (cpes.size() == 0) 55 | { 56 | auto cdlst = cdm.Scan(banner, processVendor); 57 | 58 | if (cdlst.size() > 0) 59 | { 60 | cpes.insert(cpes.end(), cdlst.begin(), cdlst.end()); 61 | } 62 | } 63 | 64 | // remove duplicates 65 | 66 | if (cpes.size() > 1) 67 | { 68 | sort(cpes.begin(), cpes.end()); 69 | cpes.erase(unique(cpes.begin(), cpes.end()), cpes.end()); 70 | } 71 | 72 | return cpes; 73 | } 74 | 75 | BannerProcessor::~BannerProcessor() 76 | { 77 | } 78 | -------------------------------------------------------------------------------- /BannerProcessor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Stdafx.h" 3 | #include "Service.h" 4 | 5 | /*! 6 | * Represents a banner processor. 7 | */ 8 | class BannerProcessor 9 | { 10 | public: 11 | 12 | /*! 13 | * Processes the banner of a service. 14 | * 15 | * \param service Scanned service. 16 | */ 17 | void Scan(Service* service); 18 | 19 | /*! 20 | * Processes the specified service banner. 21 | * 22 | * \param banner Service banner. 23 | * \param processVendor Whether to process vendor level patches appended to the end of the version 24 | * number. This removes the patch level from the CPE version and appends it to 25 | * the end via a semicolon separator. 26 | * 27 | * \return Matching CPE entries. 28 | */ 29 | virtual std::vector Scan(const std::string& banner, bool processVendor = true) = 0; 30 | 31 | /*! 32 | * Tries to processes the specified service banner with all known implementations of this class. 33 | * 34 | * \param banner Service banner. 35 | * \param processVendor Whether to process vendor level patches appended to the end of the version 36 | * number. This removes the patch level from the CPE version and appends it to 37 | * the end via a semicolon separator. 38 | * 39 | * \return Matching CPE entries. 40 | */ 41 | static std::vector AutoProcess(const std::string& banner, bool processVendor = true); 42 | 43 | /*! 44 | * Frees up the resources allocated during the lifetime of this instance. 45 | */ 46 | virtual ~BannerProcessor(); 47 | 48 | }; 49 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0.0) 2 | project(HostScanner) 3 | 4 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 5 | 6 | set(APP_MAJOR_VERSION 0) 7 | set(APP_MINOR_VERSION 2) 8 | set(APP_PATCH_VERSION 0) 9 | set(APP_VERSION "${APP_MAJOR_VERSION}.${APP_MINOR_VERSION}.${APP_PATCH_VERSION}") 10 | 11 | if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git/refs/heads/master") 12 | file(READ "${CMAKE_CURRENT_SOURCE_DIR}/.git/refs/heads/master" APP_COMMIT) 13 | string(REGEX MATCH "[a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9]" APP_COMMIT "${APP_COMMIT}") 14 | set(APP_VERSION "${APP_MAJOR_VERSION}.${APP_MINOR_VERSION}.${APP_PATCH_VERSION}-git~${APP_COMMIT}") 15 | endif() 16 | 17 | add_definitions(-DVERSION_MAJOR=${APP_MAJOR_VERSION} -DVERSION_MINOR=${APP_MINOR_VERSION} -DVERSION_PATCH=${APP_PATCH_VERSION} -DVERSION_STRING="${APP_VERSION}") 18 | 19 | if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 20 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /MP") 21 | 22 | add_definitions(-DHAVE_WPCAP -DWPCAP -DHAVE_REMOTE -D_ENABLE_ATOMIC_ALIGNMENT_FIX) 23 | 24 | find_path(WINPCAP_INCLUDE_DIR "pcap.h" "${WINPCAP_DIR}/Include") 25 | find_library(WINPCAP_LIBRARY NAMES "wpcap" PATHS "${WINPCAP_DIR}/Lib") 26 | 27 | include_directories(SYSTEM ${WINPCAP_INCLUDE_DIR}) 28 | elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU") 29 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -pthread -Wall -Wextra -Wno-write-strings") 30 | elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 31 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -pthread -Weverything -Wno-c++98-compat-pedantic -Wno-writable-strings -Wno-conversion -Wno-undef -Wno-padded -Wno-switch-enum -Wno-exit-time-destructors -Wno-global-constructors -Wno-shadow -Wno-unused-macros -Wno-old-style-cast -Wno-undefined-reinterpret-cast -Wno-disabled-macro-expansion -Wno-missing-prototypes") 32 | else() 33 | message(STATUS "Unrecognized compiler; build may not succeed.") 34 | endif() 35 | 36 | if (${BUILD_STATIC}) 37 | if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") 38 | set(BUILD_SHARED_LIBRARIES OFF) 39 | set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") 40 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static -static-libgcc -static-libstdc++") 41 | else() 42 | message(FATAL_ERROR "Static builds not yet supported for this compiler.") 43 | endif() 44 | endif() 45 | 46 | if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/conanbuildinfo.cmake") 47 | include(conanbuildinfo.cmake) 48 | conan_basic_setup() 49 | endif() 50 | 51 | if (NOT DEFINED WITHOUT_ZLIB OR NOT ${WITHOUT_ZLIB} EQUAL 1) 52 | if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 53 | set(ADDITIONAL_BOOST_COMPONENTS "zlib") 54 | else() 55 | find_package(ZLIB REQUIRED) 56 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lz") 57 | endif() 58 | add_definitions(-DHAVE_ZLIB) 59 | else() 60 | message(STATUS "HostScanner will be compiled without zlib; compressed data files will not be loadable.") 61 | endif() 62 | 63 | set(Boost_USE_STATIC_LIBS ON) 64 | set(Boost_USE_MULTITHREADED ON) 65 | find_package(Boost 1.55.0 COMPONENTS system regex iostreams filesystem date_time program_options unit_test_framework ${ADDITIONAL_BOOST_COMPONENTS} REQUIRED) 66 | include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) 67 | 68 | if (NOT DEFINED WITHOUT_CURL OR NOT ${WITHOUT_CURL} EQUAL 1) 69 | find_package(CURL) 70 | if (${CURL_FOUND}) 71 | include_directories(${CURL_INCLUDE_DIR}) 72 | add_definitions(-DHAVE_CURL) 73 | else() 74 | message(STATUS "HostScanner will be compiled without curl; features relying on online APIs will not be available.") 75 | endif() 76 | endif() 77 | 78 | find_package(SQLite 3 REQUIRED) 79 | include_directories(SYSTEM ${SQLITE_INCLUDE_DIR}) 80 | 81 | file(GLOB HEADERS "*.h") 82 | file(GLOB SOURCES "*.cpp") 83 | 84 | list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/Test.cpp") 85 | list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/Main.cpp") 86 | 87 | add_executable(HostScanner ${HEADERS} ${SOURCES} Main.cpp) 88 | add_executable(TestScanner ${HEADERS} ${SOURCES} Test.cpp) 89 | 90 | file(GLOB MISC_SRCS "*.*") 91 | source_group("Miscellaneous" FILES ${MISC_SRCS}) 92 | 93 | file(GLOB IDENTIFIER_SRCS "*Identifier.*") 94 | source_group("Identifiers" FILES ${IDENTIFIER_SRCS}) 95 | 96 | file(GLOB TOKENIZER_SRCS "*Tokenizer.*") 97 | source_group("Tokenizers" FILES ${TOKENIZER_SRCS}) 98 | 99 | file(GLOB LOOKUP_SRCS "*Lookup.*") 100 | source_group("Lookups" FILES ${LOOKUP_SRCS}) 101 | 102 | file(GLOB MATCHER_SRCS "*Matcher.*" "BannerProcessor.*") 103 | source_group("Matchers" FILES ${MATCHER_SRCS}) 104 | 105 | file(GLOB ACTIVE_SRCS "*Scanner*" "*Pinger*") 106 | source_group("Scanners/Active" FILES ${ACTIVE_SRCS}) 107 | 108 | file(GLOB EXTERNAL_SRCS "Nmap*.*") 109 | source_group("Scanners/External" FILES ${EXTERNAL_SRCS}) 110 | 111 | file(GLOB PASSIVE_SRCS "Shodan*.*" "Censys*.*" "Looquer*.*" "Passive*.*") 112 | source_group("Scanners/Passive" FILES ${PASSIVE_SRCS}) 113 | 114 | file(GLOB FACTORY_SRCS "*Factory.*") 115 | source_group("Factories" FILES ${FACTORY_SRCS}) 116 | 117 | if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 118 | file(GLOB CURL_DLLS ${CURL_INCLUDE_DIR}/../bin/*.dll) 119 | 120 | foreach(CURL_DLL ${CURL_DLLS}) 121 | add_custom_command(TARGET HostScanner POST_BUILD 122 | COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CURL_DLL}" $) 123 | add_custom_command(TARGET TestScanner POST_BUILD 124 | COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CURL_DLL}" $) 125 | endforeach() 126 | endif() 127 | 128 | if (${BUILD_STATIC}) 129 | set_target_properties(HostScanner PROPERTIES LINK_SEARCH_START_STATIC 1) 130 | set_target_properties(HostScanner PROPERTIES LINK_SEARCH_END_STATIC 1) 131 | set_target_properties(TestScanner PROPERTIES LINK_SEARCH_START_STATIC 1) 132 | set_target_properties(TestScanner PROPERTIES LINK_SEARCH_END_STATIC 1) 133 | endif() 134 | 135 | target_link_libraries(HostScanner ${WINPCAP_LIBRARY} ${CURL_LIBRARY} ${SQLITE_LIBRARIES} ${Boost_LIBRARIES}) 136 | target_link_libraries(TestScanner ${WINPCAP_LIBRARY} ${CURL_LIBRARY} ${SQLITE_LIBRARIES} ${Boost_LIBRARIES}) 137 | 138 | install(TARGETS ${HostScanner} DESTINATION bin) 139 | 140 | enable_testing() 141 | add_test(ScannerTest TestScanner) -------------------------------------------------------------------------------- /CPackConfig.cmake: -------------------------------------------------------------------------------- 1 | set(CPACK_PACKAGE_NAME "Host Scanner") 2 | set(CPACK_PACKAGE_VENDOR "RoliSoft") 3 | set(CPACK_PACKAGE_VERSION_MAJOR "0") 4 | set(CPACK_PACKAGE_VERSION_MINOR "1") 5 | set(CPACK_PACKAGE_VERSION_PATCH "0") 6 | set(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") 7 | set(CPACK_PACKAGE_DESCRIPTION "Discover hosts on a network and gather information about them for later analysis.") 8 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Host discovery and port scanning.") 9 | set(CPACK_PACKAGE_FILE_NAME "HostScanner-${CPACK_PACKAGE_VERSION}-${CMAKE_SYSTEM_PROCESSOR}") 10 | set(CPACK_PACKAGE_CONTACT "RoliSoft ") 11 | 12 | set(CPACK_INSTALLED_DIRECTORIES "${CMAKE_CURRENT_LIST_DIR}/build" "/bin") 13 | set(CPACK_GENERATOR "DEB" "RPM") 14 | set(CPACK_RESOURCE_FILE_LICENSE "LICENSE.md") 15 | set(CPACK_RESOURCE_FILE_README "README.md") 16 | set(CPACK_STRIP_FILES 1) 17 | 18 | set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "${CMAKE_SYSTEM_PROCESSOR}") 19 | set(CPACK_RPM_PACKAGE_ARCHITECTURE "${CMAKE_SYSTEM_PROCESSOR}") 20 | 21 | set(CPACK_DEBIAN_PACKAGE_SECTION "network") 22 | set(CPACK_RPM_PACKAGE_GROUP "Applications/Internet") 23 | 24 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "libcurl3") 25 | set(CPACK_RPM_PACKAGE_REQUIRES "libcurl") 26 | 27 | set(CPACK_SOURCE_GENERATOR "DEB RPM") -------------------------------------------------------------------------------- /CensysScanner.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Stdafx.h" 3 | #include "HostScanner.h" 4 | #include 5 | #include 6 | 7 | /*! 8 | * Implements a passive scanner which returns Censys data. 9 | */ 10 | class CensysScanner : public HostScanner 11 | { 12 | public: 13 | 14 | /*! 15 | * Initializes a new instance of this class. 16 | */ 17 | CensysScanner() = default; 18 | 19 | /*! 20 | * Initializes a new instance of this class. 21 | * 22 | * \param auth API username and password to use for the requests. 23 | */ 24 | explicit CensysScanner(const std::string& auth); 25 | 26 | /*! 27 | * Sets the specified API key. 28 | * 29 | * \param key API key to set. 30 | */ 31 | void SetKey(const std::string& key); 32 | 33 | /*! 34 | * Value indicating whether an key was specified. 35 | * 36 | * \return true if key is present, otherwise false. 37 | */ 38 | bool HasKey(); 39 | 40 | /*! 41 | * Sets the specified API endpoint location. 42 | * 43 | * \param uri API location to set. 44 | */ 45 | void SetEndpoint(const std::string& uri); 46 | 47 | /*! 48 | * Value indicating whether this instance is a passive scanner. 49 | * 50 | * A passive scanner does not actively send packets towards the 51 | * scanned target, it instead uses miscellaneous data sources to 52 | * gather information regarding the target. 53 | * 54 | * \return true if passive, false if not. 55 | */ 56 | bool IsPassive() override; 57 | 58 | /*! 59 | * Scans a host to determine service availability. 60 | * 61 | * \param host Host. 62 | */ 63 | void Scan(Host* host) override; 64 | 65 | /*! 66 | * Scans a list of hosts to determine service availability. 67 | * 68 | * \param hosts List of hosts. 69 | */ 70 | void Scan(Hosts* hosts) override; 71 | 72 | /*! 73 | * Frees up the resources allocated during the lifetime of this instance. 74 | */ 75 | ~CensysScanner() override; 76 | 77 | private: 78 | 79 | /*! 80 | * API username and password to use for the requests. 81 | */ 82 | std::string auth; 83 | 84 | /*! 85 | * API endpoint location. 86 | */ 87 | std::string endpoint = "https://censys.io/api/v1"; 88 | 89 | /*! 90 | * Gets the information available on the API for the specified host. 91 | * 92 | * \param host Host. 93 | */ 94 | void getHostInfo(Host* host); 95 | 96 | /*! 97 | * Traverses the supplied property tree and tries to find a relevant service banner. 98 | * 99 | * \param pt Property tree to traverse. 100 | * 101 | * \return Service banner, if any. 102 | */ 103 | std::string findServiceBanner(boost::property_tree::ptree pt); 104 | 105 | }; 106 | -------------------------------------------------------------------------------- /ConcurrentQueue.cpp: -------------------------------------------------------------------------------- 1 | #include "ConcurrentQueue.h" 2 | -------------------------------------------------------------------------------- /ConcurrentQueue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /*! 8 | * Implements a queue which can be used concurrently. 9 | * 10 | * \tparam T Generic type parameter. 11 | */ 12 | template 13 | class ConcurrentQueue 14 | { 15 | public: 16 | 17 | /*! 18 | * Initializes a new instance of this class. 19 | */ 20 | ConcurrentQueue(); 21 | 22 | /*! 23 | * Removes the top item from the queue and returns it. This function is non-blocking. 24 | * 25 | * \return The object formerly at the top of the queue. 26 | */ 27 | boost::optional Pop(); 28 | 29 | /*! 30 | * Removes the top item from the queue and returns it. This function is blocking, and if the 31 | * queue is empty, it will wait until a new item is available. 32 | * 33 | * \return The object formerly at the top of the queue. 34 | */ 35 | T PopWait(); 36 | 37 | /*! 38 | * Removes the top item from the queue and returns it. This function is blocking, and if the 39 | * queue is empty, it will wait until a new item is available or the timeout period has expired. 40 | * 41 | * \param timeout The number of milliseconds to wait for a new item. 42 | * 43 | * \return The object formerly at the top of the queue. 44 | */ 45 | boost::optional PopWait(int timeout); 46 | 47 | /*! 48 | * Pushes an object onto the queue. 49 | * 50 | * \param item The item to push. 51 | */ 52 | void Push(T& item); 53 | 54 | /*! 55 | * Frees up the resources allocated during the lifetime of this instance. 56 | */ 57 | ~ConcurrentQueue(); 58 | 59 | private: 60 | 61 | /*! 62 | * The queue instance used internally. 63 | */ 64 | std::queue lst; 65 | 66 | /*! 67 | * The mutex instance used to protect accesses to the internal queue. 68 | */ 69 | std::mutex mtx; 70 | 71 | /*! 72 | * The conditional variable used to signal state changes. 73 | */ 74 | std::condition_variable cvar; 75 | 76 | }; 77 | 78 | template 79 | ConcurrentQueue::ConcurrentQueue() 80 | { 81 | } 82 | 83 | template 84 | boost::optional ConcurrentQueue::Pop() 85 | { 86 | using namespace std; 87 | 88 | unique_lock mlock(mtx); 89 | 90 | if (lst.empty()) 91 | { 92 | return boost::optional(); 93 | } 94 | 95 | auto item = lst.front(); 96 | lst.pop(); 97 | 98 | return boost::optional(item); 99 | } 100 | 101 | template 102 | T ConcurrentQueue::PopWait() 103 | { 104 | using namespace std; 105 | 106 | unique_lock mlock(mtx); 107 | 108 | while (lst.empty()) 109 | { 110 | cvar.wait(mlock); 111 | } 112 | 113 | auto item = lst.front(); 114 | lst.pop(); 115 | 116 | return item; 117 | } 118 | 119 | template 120 | boost::optional ConcurrentQueue::PopWait(int timeout) 121 | { 122 | using namespace std; 123 | 124 | unique_lock mlock(mtx); 125 | 126 | auto due = chrono::system_clock::now() + chrono::milliseconds(timeout); 127 | 128 | while (lst.empty()) 129 | { 130 | if (cvar.wait_until(mlock, due) == cv_status::timeout) 131 | { 132 | return boost::optional(); 133 | } 134 | } 135 | 136 | auto item = lst.front(); 137 | lst.pop(); 138 | 139 | return boost::optional(item); 140 | } 141 | 142 | template 143 | void ConcurrentQueue::Push(T& item) 144 | { 145 | using namespace std; 146 | 147 | unique_lock mlock(mtx); 148 | 149 | lst.push(item); 150 | 151 | mlock.unlock(); 152 | cvar.notify_one(); 153 | } 154 | 155 | template 156 | ConcurrentQueue::~ConcurrentQueue() 157 | { 158 | } 159 | -------------------------------------------------------------------------------- /CpeDictionaryMatcher.cpp: -------------------------------------------------------------------------------- 1 | #include "CpeDictionaryMatcher.h" 2 | #include "DataReader.h" 3 | #include "Utils.h" 4 | #include 5 | 6 | using namespace std; 7 | using namespace boost; 8 | 9 | vector CpeDictionaryMatcher::entries = vector(); 10 | unordered_map> CpeDictionaryMatcher::aliases = unordered_map>(); 11 | 12 | vector CpeDictionaryMatcher::Scan(const string& banner, bool processVendor) 13 | { 14 | if (entries.size() == 0) 15 | { 16 | loadEntries(); 17 | } 18 | 19 | vector matches; 20 | 21 | if (banner.length() == 0) 22 | { 23 | return matches; 24 | } 25 | 26 | for (auto& ent : entries) 27 | { 28 | vector namepos; 29 | 30 | // check if all the tokens from the name are in the input 31 | 32 | auto nametok = true; 33 | 34 | for (auto& token : ent.tokens) 35 | { 36 | smatch what; 37 | 38 | if (!regex_search(banner, what, token)) 39 | { 40 | nametok = false; 41 | break; 42 | } 43 | 44 | namepos.push_back(what.position()); 45 | } 46 | 47 | if (!nametok) 48 | { 49 | continue; 50 | } 51 | 52 | string bestcpe, bestver; 53 | 54 | auto bestdist = UINT_MAX; 55 | auto besttokens = 0u; 56 | 57 | // if so, check if any associated versions are also in the input 58 | 59 | for (auto& version : ent.versions) 60 | { 61 | auto verpos = banner.find(version.version); 62 | 63 | if (verpos == string::npos) 64 | { 65 | continue; 66 | } 67 | 68 | // if the version number was found, check if the tokens associated 69 | // to this version are also present 70 | 71 | auto dist = 0u; 72 | auto vertok = true; 73 | 74 | for (auto& token : version.tokens) 75 | { 76 | smatch what; 77 | 78 | if (!regex_search(banner, what, token)) 79 | { 80 | vertok = false; 81 | break; 82 | } 83 | 84 | dist += abs(int(what.position()) - int(verpos)); 85 | } 86 | 87 | if (!vertok) 88 | { 89 | continue; 90 | } 91 | 92 | // if so, calculate distance from version to the tokens in the name 93 | 94 | for (auto npos : namepos) 95 | { 96 | dist += abs(npos - int(verpos)); 97 | } 98 | 99 | // check against current best 100 | 101 | if (version.tokens.size() > besttokens || (version.tokens.size() == besttokens && dist <= bestdist)) 102 | { 103 | bestdist = dist; 104 | besttokens = version.tokens.size(); 105 | bestcpe = ent.cpe + ":" + version.cpe; 106 | bestver = version.version; 107 | } 108 | } 109 | 110 | if (bestcpe.length() == 0) 111 | { 112 | continue; 113 | } 114 | 115 | // find vendor patch level, if any and asked 116 | 117 | if (processVendor) 118 | { 119 | smatch what; 120 | regex verfind(escapeRegex(bestver) + "(?[-+~_])(?[^$;\\s\\)\\/]+)"); 121 | 122 | if (regex_search(banner, what, verfind)) 123 | { 124 | // append vendor patch level to the CPE into a separate field 125 | 126 | bestcpe += ";" + what["tag"].str(); 127 | } 128 | } 129 | 130 | // save matching CPE 131 | 132 | matches.push_back(bestcpe); 133 | } 134 | 135 | return matches; 136 | } 137 | 138 | vector CpeDictionaryMatcher::GetEntries() 139 | { 140 | if (entries.size() == 0) 141 | { 142 | loadEntries(); 143 | } 144 | 145 | return entries; 146 | } 147 | 148 | unordered_map> CpeDictionaryMatcher::GetAliases() 149 | { 150 | if (aliases.size() == 0) 151 | { 152 | loadEntries(); 153 | } 154 | 155 | return aliases; 156 | } 157 | 158 | void CpeDictionaryMatcher::loadEntries() 159 | { 160 | static mutex mtx; 161 | auto locked = mtx.try_lock(); 162 | if (!locked) 163 | { 164 | // wait until running parser finishes before returning 165 | lock_guard guard(mtx); 166 | return; 167 | } 168 | 169 | // open entries file 170 | 171 | DataReader dr; 172 | 173 | if (!dr.OpenEnv("cpe-list")) 174 | { 175 | log(WRN, "CPE database was not found!"); 176 | 177 | mtx.unlock(); 178 | return; 179 | } 180 | 181 | unsigned short ptype, pver; 182 | 183 | dr.Read(ptype); 184 | dr.Read(pver); 185 | 186 | if (ptype != 1) 187 | { 188 | log(WRN, "CPE database type is incorrect."); 189 | 190 | mtx.unlock(); 191 | return; 192 | } 193 | 194 | if (pver != 1) 195 | { 196 | log(WRN, "CPE database version is not supported."); 197 | 198 | mtx.unlock(); 199 | return; 200 | } 201 | 202 | unsigned int pnum; 203 | dr.Read(pnum); 204 | 205 | for (auto i = 0u; i < pnum; i++) 206 | { 207 | CpeEntry ent; 208 | 209 | ent.cpe = dr.ReadString(); 210 | 211 | unsigned char tnum; 212 | dr.Read(tnum); 213 | 214 | ent.tokens = vector(); 215 | 216 | for (auto j = 0u; j < tnum; j++) 217 | { 218 | ent.tokens.push_back(std::move(regex("\\b(" + dr.ReadString() + ")\\b", regex::icase))); 219 | } 220 | 221 | unsigned int vnum; 222 | dr.Read(vnum); 223 | 224 | ent.versions = vector(); 225 | 226 | for (auto j = 0u; j < vnum; j++) 227 | { 228 | CpeVersionEntry ver; 229 | 230 | ver.cpe = dr.ReadString(); 231 | ver.version = dr.ReadString(); 232 | 233 | unsigned char vtnum; 234 | dr.Read(vtnum); 235 | 236 | ver.tokens = vector(); 237 | 238 | for (auto k = 0u; k < vtnum; k++) 239 | { 240 | ver.tokens.push_back(std::move(regex("\\b(" + dr.ReadString() + ")\\b", regex::icase))); 241 | } 242 | 243 | ent.versions.push_back(ver); 244 | } 245 | 246 | entries.push_back(ent); 247 | } 248 | 249 | // open aliases file 250 | 251 | if (!dr.OpenEnv("cpe-aliases")) 252 | { 253 | log(WRN, "CPE aliases database was not found!"); 254 | 255 | mtx.unlock(); 256 | return; 257 | } 258 | 259 | dr.Read(ptype); 260 | dr.Read(pver); 261 | 262 | if (ptype != 2) 263 | { 264 | log(WRN, "CPE aliases database type is incorrect."); 265 | 266 | mtx.unlock(); 267 | return; 268 | } 269 | 270 | if (pver != 1) 271 | { 272 | log(WRN, "CPE aliases database version is not supported."); 273 | 274 | mtx.unlock(); 275 | return; 276 | } 277 | 278 | dr.Read(pnum); 279 | 280 | for (auto i = 0u; i < pnum; i++) 281 | { 282 | unsigned short anum; 283 | dr.Read(anum); 284 | 285 | vector list; 286 | 287 | for (auto j = 0u; j < anum; j++) 288 | { 289 | auto alias = dr.ReadString(); 290 | 291 | list.push_back(alias); 292 | 293 | aliases.emplace(alias, list); 294 | } 295 | } 296 | 297 | // clean up 298 | 299 | mtx.unlock(); 300 | } 301 | 302 | CpeDictionaryMatcher::~CpeDictionaryMatcher() 303 | { 304 | } 305 | -------------------------------------------------------------------------------- /CpeDictionaryMatcher.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Stdafx.h" 3 | #include "BannerProcessor.h" 4 | #include 5 | #include 6 | #include 7 | 8 | /*! 9 | * Represents a sub-entry in the CPE dictionary. 10 | */ 11 | struct CpeVersionEntry 12 | { 13 | 14 | /*! 15 | * Version part of the CPE name. 16 | */ 17 | std::string cpe; 18 | 19 | /*! 20 | * Version number token. 21 | */ 22 | std::string version; 23 | 24 | /*! 25 | * Version-specific tokens of the entry. 26 | */ 27 | std::vector tokens; 28 | 29 | }; 30 | 31 | /*! 32 | * Represents a CPE dictionary entry. 33 | */ 34 | struct CpeEntry 35 | { 36 | 37 | /*! 38 | * CPE name of the entry. 39 | */ 40 | std::string cpe; 41 | 42 | /*! 43 | * Common tokens of the entry. 44 | */ 45 | std::vector tokens; 46 | 47 | /*! 48 | * Known versions of the entry. 49 | */ 50 | std::vector versions; 51 | 52 | }; 53 | 54 | /*! 55 | * Implements fuzzy matching of CPE names to service banners. 56 | */ 57 | class CpeDictionaryMatcher : public BannerProcessor 58 | { 59 | public: 60 | 61 | /*! 62 | * Processes the specified service banner. 63 | * 64 | * \param banner Service banner. 65 | * \param processVendor Whether to process vendor level patches appended to the end of the version 66 | * number. This removes the patch level from the CPE version and appends it to 67 | * the end via a semicolon separator. 68 | * 69 | * \return Matching CPE entries. 70 | */ 71 | std::vector Scan(const std::string& banner, bool processVendor = true) override; 72 | 73 | /*! 74 | * Gets the CPE entries. 75 | * 76 | * \return List of CPE entries. 77 | */ 78 | static std::vector GetEntries(); 79 | 80 | /*! 81 | * Gets the CPE aliases. 82 | * 83 | * \return List of CPE aliases. 84 | */ 85 | static std::unordered_map> GetAliases(); 86 | 87 | /*! 88 | * Frees up the resources allocated during the lifetime of this instance. 89 | */ 90 | ~CpeDictionaryMatcher() override; 91 | 92 | private: 93 | 94 | /*! 95 | * List of CPE dictionary entries with their associated product info. 96 | */ 97 | static std::vector entries; 98 | 99 | /*! 100 | * List of CPE name aliases. 101 | */ 102 | static std::unordered_map> aliases; 103 | 104 | /*! 105 | * Loads the entries and aliases database from external file. 106 | */ 107 | static void loadEntries(); 108 | 109 | }; 110 | -------------------------------------------------------------------------------- /DataReader.cpp: -------------------------------------------------------------------------------- 1 | #include "DataReader.h" 2 | #include "Utils.h" 3 | #include 4 | #include 5 | 6 | #if HAVE_ZLIB 7 | #include 8 | #endif 9 | 10 | using namespace std; 11 | namespace io = boost::iostreams; 12 | namespace fs = boost::filesystem; 13 | 14 | DataReader::DataReader() 15 | : fs(nullptr), bs(nullptr) 16 | { 17 | } 18 | 19 | bool DataReader::OpenFile(const string& filename) 20 | { 21 | Close(); 22 | 23 | fs = new ifstream(); 24 | fs->open(filename, ifstream::binary); 25 | 26 | if (!fs->good()) 27 | { 28 | Close(); 29 | return false; 30 | } 31 | 32 | bs = new io::filtering_istreambuf(); 33 | 34 | if (fs::path(filename).extension() == ".gz") 35 | { 36 | #if HAVE_ZLIB 37 | try 38 | { 39 | bs->push(io::gzip_decompressor()); 40 | } 41 | catch (boost::exception&) 42 | { 43 | Close(); 44 | return false; 45 | } 46 | #else 47 | Close(); 48 | return false; 49 | #endif 50 | } 51 | 52 | bs->push(*fs); 53 | 54 | return true; 55 | } 56 | 57 | bool DataReader::OpenEnv(const string& name) 58 | { 59 | vector paths = { 60 | #if Windows 61 | get<0>(splitPath(getAppPath())) + "\\data\\" + name + ".dat", 62 | #if HAVE_ZLIB 63 | get<0>(splitPath(getAppPath())) + "\\data\\" + name + ".dat.gz", 64 | #endif 65 | getEnvVar("APPDATA") + "\\RoliSoft\\Host Scanner\\data\\" + name + ".dat", 66 | #if HAVE_ZLIB 67 | getEnvVar("APPDATA") + "\\RoliSoft\\Host Scanner\\data\\" + name + ".dat.gz" 68 | #endif 69 | #else 70 | get<0>(splitPath(getAppPath())) + "/data/" + name + ".dat", 71 | #if HAVE_ZLIB 72 | get<0>(splitPath(getAppPath())) + "/data/" + name + ".dat.gz", 73 | #endif 74 | "/var/lib/HostScanner/data/" + name + ".dat", 75 | #if HAVE_ZLIB 76 | "/var/lib/HostScanner/data/" + name + ".dat.gz" 77 | #endif 78 | #endif 79 | }; 80 | 81 | for (auto path : paths) 82 | { 83 | fs::path fp(path); 84 | 85 | if (!fs::exists(fp) || !fs::is_regular_file(fp)) 86 | { 87 | continue; 88 | } 89 | 90 | if (OpenFile(path)) 91 | { 92 | return true; 93 | } 94 | } 95 | 96 | return false; 97 | } 98 | 99 | void DataReader::Close() 100 | { 101 | if (bs != nullptr) 102 | { 103 | delete bs; 104 | 105 | bs = nullptr; 106 | } 107 | 108 | if (fs != nullptr) 109 | { 110 | if (fs->is_open()) 111 | { 112 | fs->close(); 113 | } 114 | 115 | delete fs; 116 | 117 | fs = nullptr; 118 | } 119 | } 120 | 121 | tuple DataReader::ReadData() 122 | { 123 | unsigned short len; 124 | Read(len); 125 | 126 | if (len == 0) 127 | { 128 | return make_tuple(len, nullptr); 129 | } 130 | 131 | auto data = new char[len]; 132 | bs->sgetn(data, len); 133 | 134 | return make_tuple(len, data); 135 | } 136 | 137 | string DataReader::ReadString() 138 | { 139 | auto data = ReadData(); 140 | 141 | if (get<0>(data) == 0) 142 | { 143 | return ""; 144 | } 145 | 146 | auto text = string(get<1>(data), get<0>(data)); 147 | 148 | delete[] get<1>(data); 149 | 150 | return text; 151 | } 152 | 153 | DataReader::~DataReader() 154 | { 155 | Close(); 156 | } 157 | -------------------------------------------------------------------------------- /DataReader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Stdafx.h" 3 | #include 4 | #include 5 | #include 6 | 7 | #if BOOST_VERSION >= 105800 8 | #include 9 | #endif 10 | 11 | /*! 12 | * Implements a file stream wrapper for reading binary data files. 13 | */ 14 | class DataReader 15 | { 16 | public: 17 | 18 | /*! 19 | * Creates a new instance of this type. 20 | */ 21 | DataReader(); 22 | 23 | /*! 24 | * Opens the specified file for reading. 25 | * 26 | * \param filename Path to the data file. 27 | * 28 | * \return Value indicating whether the file was opened successfully. 29 | */ 30 | bool OpenFile(const std::string& filename); 31 | 32 | /*! 33 | * Finds the data file with the specified name and opens it for reading. 34 | * 35 | * \param name Name of the data file. 36 | * 37 | * \return Value indicating whether the file was opened successfully. 38 | */ 39 | bool OpenEnv(const std::string& name); 40 | 41 | /*! 42 | * Closes the currently open file, if any. 43 | */ 44 | void Close(); 45 | 46 | /*! 47 | * Reads the next field as a numeric of type `T` from the file. 48 | * 49 | * \param value Reference to variable where to store numeric of type `T`. 50 | */ 51 | template 52 | void Read(T& value) 53 | { 54 | bs->sgetn(reinterpret_cast(&value), sizeof(T)); 55 | #if BOOST_VERSION >= 105800 56 | boost::endian::little_to_native_inplace(value); 57 | #endif 58 | } 59 | 60 | /*! 61 | * Reads the next field as a variable-length binary data from the file. 62 | * 63 | * \return Field as variable-length binary data. 64 | */ 65 | std::tuple ReadData(); 66 | 67 | /*! 68 | * Reads the next field as string from the file. 69 | * 70 | * \return Field as string value. 71 | */ 72 | std::string ReadString(); 73 | 74 | /*! 75 | * Frees up the resources allocated during the lifetime of this instance. 76 | */ 77 | ~DataReader(); 78 | 79 | private: 80 | 81 | /*! 82 | * Pointer to the currently open data file. 83 | */ 84 | std::ifstream *fs; 85 | 86 | /*! 87 | * Pointer to the filtering stream buffer the currently open file is wrapped in. 88 | */ 89 | boost::iostreams::filtering_istreambuf* bs; 90 | 91 | }; 92 | -------------------------------------------------------------------------------- /DebianIdentifier.cpp: -------------------------------------------------------------------------------- 1 | #include "DebianIdentifier.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | using namespace boost; 9 | 10 | // https://www.debian.org/releases/ 11 | const unordered_map DebianIdentifier::VersionNames = unordered_map { 12 | { "buster", 10 }, 13 | { "stretch", 9 }, 14 | { "jessie", 8 }, 15 | { "wheezy", 7 }, 16 | { "squeeze", 6 }, 17 | { "lenny", 5 }, 18 | { "etch", 4 }, 19 | { "sarge", 3 }, // technically 3.1 20 | { "woody", 3 }, 21 | }; 22 | 23 | // compiled by browsing changelogs at https://packages.debian.org/ and https://archive.debian.net/ 24 | const unordered_map DebianIdentifier::BundledVersions = unordered_map { 25 | { "7.2p2", 9 }, 26 | { "6.7p1", 8 }, 27 | { "6.6p1", 7 }, // backport 28 | { "6.1p1", 7 }, // backport 29 | { "6.0p1", 7 }, 30 | { "5.5p1", 6 }, 31 | { "5.1p1", 5 }, 32 | { "4.3p2", 4 }, 33 | { "3.8.1p1", 3 }, // technically 3.1 34 | { "3.4p1", 3 }, 35 | }; 36 | 37 | bool DebianIdentifier::Scan(Host* host) 38 | { 39 | auto isDeb = false; 40 | string sshVer; 41 | optional debVer, secUpd; 42 | 43 | // check if any SSH services are available 44 | 45 | // ver -> OpenSSH version 46 | // deb -> "Debian" tag, if not present, below groups will not match 47 | // ver2 -> patch number 48 | // debrel -> name of the Debian version, mostly for 'squeeze' and older 49 | // debver -> version number of Debian, mostly for 'wheezy' and newer 50 | // debsec -> if debver also matched, the installed security patch 51 | static regex sshbnr("OpenSSH_(?[^\\s$]+)(?:\\s*(?Debian)\\-(?\\d+)(?:[\\.\\+~](?:(?buster|stretch|jessie|wheezy|squeeze|lenny|etch|sarge|woody)|deb(?\\d+)u?(?\\d+)?))?)?", regex::icase); 52 | 53 | // match any "debian" tag 54 | static regex debtag("\\bdebian\\b", regex::icase); 55 | 56 | // replace "7.2p1" to "7.2:p1" in the CPE 57 | static regex cpever("(\\d)p(\\d+)", regex::icase); 58 | static string cpepatch = "\\1:p\\2"; 59 | 60 | for (auto service : *host->services) 61 | { 62 | if (service->banner.empty()) 63 | { 64 | continue; 65 | } 66 | 67 | smatch sm; 68 | 69 | // if the service is not SSH, just check if there is a "Debian" tag in it anywhere 70 | 71 | if (!starts_with(service->banner, "SSH-2.0-OpenSSH_")) 72 | { 73 | if (regex_search(service->banner, sm, debtag)) 74 | { 75 | isDeb = true; 76 | } 77 | 78 | continue; 79 | } 80 | 81 | // if the service is SSH, special handling follows 82 | 83 | if (!regex_search(service->banner, sm, sshbnr)) 84 | { 85 | continue; 86 | } 87 | 88 | // while we're at it, append the CPE name 89 | 90 | sshVer = sm["ver"].str(); 91 | 92 | service->cpe.push_back("a:openbsd:openssh:" + regex_replace(sshVer, cpever, cpepatch)); 93 | 94 | // if there is a "Debian" tag in the banner, this host runs Debian for sure 95 | 96 | if (sm["deb"].matched) 97 | { 98 | isDeb = true; 99 | } 100 | 101 | // check if release name is present 102 | 103 | if (sm["debrel"].matched) 104 | { 105 | auto rel = sm["debrel"].str(); 106 | 107 | trim(rel); 108 | to_lower(rel); 109 | 110 | auto rnum = VersionNames.find(rel); 111 | 112 | if (rnum != VersionNames.end()) 113 | { 114 | debVer = (*rnum).second; 115 | } 116 | } 117 | 118 | // check if release version is present 119 | 120 | if (sm["debver"].matched) 121 | { 122 | debVer = stoi(sm["debver"].str()); 123 | 124 | if (sm["debsec"].matched) 125 | { 126 | secUpd = stoi(sm["debsec"].str()); 127 | } 128 | } 129 | 130 | // try to deduce Debian distribution based on the OpenSSH version 131 | 132 | if (!sshVer.empty() && !debVer.is_initialized()) 133 | { 134 | auto ver = sshVer; 135 | 136 | trim(ver); 137 | to_lower(ver); 138 | 139 | auto bver = BundledVersions.find(ver); 140 | 141 | if (bver != BundledVersions.end()) 142 | { 143 | debVer = (*bver).second; 144 | } 145 | } 146 | } 147 | 148 | // save information 149 | 150 | if (isDeb) 151 | { 152 | string cpe = "o:debian:debian_linux"; 153 | 154 | host->opSys = OpSys::Debian; 155 | 156 | if (debVer.is_initialized()) 157 | { 158 | cpe += ":" + to_string(debVer.get()); 159 | 160 | host->osVer = debVer.get(); 161 | } 162 | 163 | host->cpe.push_back(cpe); 164 | } 165 | 166 | return isDeb; 167 | } 168 | 169 | DebianIdentifier::~DebianIdentifier() 170 | { 171 | } 172 | -------------------------------------------------------------------------------- /DebianIdentifier.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Stdafx.h" 3 | #include "OperatingSystemIdentifier.h" 4 | #include 5 | 6 | /*! 7 | * Implements functionality for identifying Debian based on service banners. 8 | */ 9 | class DebianIdentifier : public OperatingSystemIdentifier 10 | { 11 | public: 12 | 13 | /*! 14 | * Debian distribution names mapped to their version numbers. 15 | */ 16 | static const std::unordered_map VersionNames; 17 | 18 | /*! 19 | * OpenSSH version numbers mapped to the Debian version they came with. 20 | */ 21 | static const std::unordered_map BundledVersions; 22 | 23 | /*! 24 | * Processes the specified host. 25 | * 26 | * \param host Scanned host. 27 | * 28 | * \return true if the operating system was identified, 29 | * otherwise false. 30 | */ 31 | bool Scan(Host* host) override; 32 | 33 | /*! 34 | * Frees up the resources allocated during the lifetime of this instance. 35 | */ 36 | ~DebianIdentifier() override; 37 | 38 | }; 39 | -------------------------------------------------------------------------------- /DebianLookup.cpp: -------------------------------------------------------------------------------- 1 | #include "DebianLookup.h" 2 | #include "DebianIdentifier.h" 3 | #include "Utils.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | using namespace boost; 11 | 12 | unordered_map DebianLookup::FindVulnerability(const string& cve, OpSys distrib, double ver) 13 | { 14 | unordered_map vuln; 15 | 16 | if (!ValidateCVE(cve)) 17 | { 18 | log(ERR, "Specified CVE identifier '" + cve + "' is not syntactically valid."); 19 | return vuln; 20 | } 21 | 22 | if (distrib != Debian) 23 | { 24 | log(ERR, "Specified distribution is not supported by this instance."); 25 | return vuln; 26 | } 27 | 28 | auto resp = getURL("https://security-tracker.debian.org/tracker/" + cve); 29 | 30 | if (get<2>(resp) != 200) 31 | { 32 | if (get<2>(resp) == -1) 33 | { 34 | log(ERR, "Failed to send HTTP request: " + get<1>(resp)); 35 | } 36 | else 37 | { 38 | log(ERR, "Failed to get reply: HTTP response code was " + to_string(get<2>(resp)) + "."); 39 | } 40 | 41 | return vuln; 42 | } 43 | 44 | auto html = get<0>(resp); 45 | 46 | // pkg -> name of the package 47 | // dist -> distribution (stable, unstable, etc) 48 | // ver -> version which fixes the vulnerability 49 | static regex tblrgx("href=\"\\/tracker\\/source-package\\/[^\"]+\">(?[^<]+)<\\/a><\\/td>[^<]*<\\/td>\\(?(?[^<\\)]+)\\)?<\\/td>(?:\\d:)?(?[^<]+)<\\/td>", regex::icase); 50 | 51 | sregex_iterator srit(html.begin(), html.end(), tblrgx); 52 | sregex_iterator end; 53 | 54 | string pkgfx; 55 | 56 | for (; srit != end; ++srit) 57 | { 58 | auto m = *srit; 59 | 60 | auto pkg = m["pkg"].str(); 61 | auto dist = m["dist"].str(); 62 | auto vers = m["ver"].str(); 63 | 64 | auto dver = DebianIdentifier::VersionNames.find(dist); 65 | 66 | if (ver == 0 || (dver != DebianIdentifier::VersionNames.end() && (*dver).second == ver)) 67 | { 68 | vuln.emplace(pkg, vers); 69 | } 70 | 71 | pkgfx = pkg; 72 | } 73 | 74 | if (!pkgfx.empty() && vuln.empty()) 75 | { 76 | // vulnerability is known, but no resolution for distribution 77 | 78 | vuln.emplace(pkgfx, ""); 79 | } 80 | 81 | return vuln; 82 | } 83 | 84 | vector> DebianLookup::GetChangelog(const string& pkg, OpSys distrib, double ver) 85 | { 86 | vector> updates; 87 | 88 | if (distrib != Debian) 89 | { 90 | log(ERR, "Specified distribution is not supported by this instance."); 91 | return updates; 92 | } 93 | 94 | string dver = "stable"; 95 | 96 | if (ver != 0) 97 | { 98 | for (auto& name : DebianIdentifier::VersionNames) 99 | { 100 | if (name.second == ver) 101 | { 102 | dver = name.first; 103 | break; 104 | } 105 | } 106 | } 107 | 108 | auto dpkg = pkg; 109 | 110 | if (dpkg == "openssh") 111 | { 112 | dpkg = "openssh-server"; 113 | } 114 | 115 | auto resp = getURL("https://packages.debian.org/" + dver + "/" + dpkg); 116 | 117 | if (get<2>(resp) != 200) 118 | { 119 | if (get<2>(resp) == -1) 120 | { 121 | log(ERR, "Failed to send HTTP request: " + get<1>(resp)); 122 | } 123 | else 124 | { 125 | log(ERR, "Failed to get reply: HTTP response code was " + to_string(get<2>(resp)) + "."); 126 | } 127 | 128 | return updates; 129 | } 130 | 131 | // url -> location of the latest package changelog 132 | static regex chlrgx("href=\"(?[^\"]*_changelog)\">Debian Changelog<\\/a>", regex::icase); 133 | 134 | smatch chlurl; 135 | 136 | if (!regex_search(get<0>(resp), chlurl, chlrgx)) 137 | { 138 | log(ERR, "Failed get changelog location for the package " + dpkg + "."); 139 | return updates; 140 | } 141 | 142 | resp = getURL(chlurl["url"].str()); 143 | 144 | if (get<2>(resp) != 200) 145 | { 146 | if (get<2>(resp) == -1) 147 | { 148 | log(ERR, "Failed to send HTTP request: " + get<1>(resp)); 149 | } 150 | else 151 | { 152 | log(ERR, "Failed to get reply: HTTP response code was " + to_string(get<2>(resp)) + "."); 153 | } 154 | 155 | return updates; 156 | } 157 | 158 | // pkg -> name of the package 159 | // ver -> version number of the package 160 | static regex verrgx("^(?[a-z0-9\\-\\._]+)\\s+\\((?:\\d:)?(?[^\\)]+)\\)", regex::icase); 161 | 162 | // date -> publication date of the package 163 | static regex datrgx("^ -- .*?(?(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun),\\s+\\d+.+)", regex::icase); 164 | 165 | string pver; 166 | istringstream iss(get<0>(resp)); 167 | 168 | for (string line; getline(iss, line); ) 169 | { 170 | smatch verm, datm; 171 | 172 | if (regex_search(line, verm, verrgx)) 173 | { 174 | pver = verm["ver"].str(); 175 | } 176 | else if (regex_search(line, datm, datrgx) && !pver.empty()) 177 | { 178 | updates.push_back(make_pair(pver, dateToUnix(datm["date"].str()))); 179 | } 180 | } 181 | 182 | return updates; 183 | } 184 | 185 | string DebianLookup::GetUpgradeCommand(const unordered_set& pkgs, OpSys distrib, double ver) 186 | { 187 | ignore_unused(distrib); 188 | ignore_unused(ver); 189 | 190 | if (pkgs.empty()) 191 | { 192 | return ""; 193 | } 194 | 195 | string cmd = "sudo apt-get install --only-upgrade"; 196 | 197 | for (auto& pkg : pkgs) 198 | { 199 | cmd += " " + pkg; 200 | } 201 | 202 | return cmd; 203 | } 204 | 205 | DebianLookup::~DebianLookup() 206 | { 207 | } 208 | -------------------------------------------------------------------------------- /DebianLookup.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Stdafx.h" 3 | #include "VendorPackageLookup.h" 4 | 5 | /*! 6 | * Implements the functionality to search Debian packages. 7 | */ 8 | class DebianLookup : public VendorPackageLookup 9 | { 10 | public: 11 | 12 | /*! 13 | * Looks up the status of a vulnerability in the vendor's repository. 14 | * 15 | * \param cve Identifier of the vulnerability. 16 | * \param distrib Operating system distribution. 17 | * \param ver Version number of distribution. 18 | * 19 | * \return If not affected an empty map, otherwise a map of vulnerable 20 | * packages associated to the version number that patches it, 21 | * or empty string if package is not yet fixed. 22 | */ 23 | std::unordered_map FindVulnerability(const std::string& cve, OpSys distrib = OpSys::Debian, double ver = 0.0) override; 24 | 25 | /*! 26 | * Gets the changelog of a package from the vendor's repository. 27 | * 28 | * \param pkg Name of the package. 29 | * \param distrib Operating system distribution. 30 | * \param ver Version number of distribution. 31 | * 32 | * \return List of version numbers and their release dates. 33 | */ 34 | std::vector> GetChangelog(const std::string& pkg, OpSys distrib = OpSys::Debian, double ver = 0.0) override; 35 | 36 | /*! 37 | * Generates a command which upgrades the specified vulnerable packages 38 | * on the host system. 39 | * 40 | * \param pkgs Vulnerable packages to upgrade. 41 | * \param distrib Operating system distribution. 42 | * \param ver Version number of distribution. 43 | * 44 | * \return Upgrade command. 45 | */ 46 | std::string GetUpgradeCommand(const std::unordered_set& pkgs, OpSys distrib = OpSys::Debian, double ver = 0.0) override; 47 | 48 | /*! 49 | * Frees up the resources allocated during the lifetime of this instance. 50 | */ 51 | ~DebianLookup() override; 52 | 53 | }; 54 | -------------------------------------------------------------------------------- /EnterpriseLinuxIdentifier.cpp: -------------------------------------------------------------------------------- 1 | #include "EnterpriseLinuxIdentifier.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | using namespace boost; 9 | 10 | // compiled from various sources, may be incomplete 11 | const unordered_map EnterpriseLinuxIdentifier::BundledVersions = unordered_map { 12 | { "6.6.1p1", 7 }, 13 | { "6.6p1", 6 }, 14 | { "5.3", 6 }, 15 | { "5.2p1", 6 }, 16 | { "4.3", 5 }, 17 | { "3.9", 4 }, 18 | }; 19 | 20 | bool EnterpriseLinuxIdentifier::Scan(Host* host) 21 | { 22 | auto isEL = false, isCO = false; 23 | string sshVer; 24 | optional elVer, secUpd; 25 | 26 | // check if any SSH services are available 27 | 28 | // ver -> OpenSSH version 29 | // tag -> any EL tag, if not present, below groups will not match 30 | // elver -> version number of RHEL/CentOS 31 | // secver -> if elver also matched, the installed security patch 32 | static regex sshbnr("OpenSSH_(?[^\\s\\-_$]+)(?:[\\s\\-_](?CentOS|RHEL|Red.?Hat)[\\s\\-_]?(?\\d+)(?:.*\\-(?\\d+)))?", regex::icase); 33 | 34 | // match any EL tag 35 | static regex enttag("\\b(centos|red.?hat|rhel)\\b", regex::icase); 36 | 37 | // replace "7.2p1" to "7.2:p1" in the CPE 38 | static regex cpever("(\\d)p(\\d+)", regex::icase); 39 | static string cpepatch = "\\1:p\\2"; 40 | 41 | for (auto service : *host->services) 42 | { 43 | if (service->banner.empty()) 44 | { 45 | continue; 46 | } 47 | 48 | smatch sm; 49 | 50 | // if the service is not SSH, just check if there is an EL tag in it anywhere 51 | 52 | if (!starts_with(service->banner, "SSH-2.0-OpenSSH_")) 53 | { 54 | if (regex_search(service->banner, sm, enttag)) 55 | { 56 | isEL = true; 57 | 58 | auto c = sm[1].str()[0]; 59 | isCO = c == 'C' || c == 'c'; 60 | } 61 | 62 | continue; 63 | } 64 | 65 | // if the service is SSH, special handling follows 66 | 67 | if (!regex_search(service->banner, sm, sshbnr)) 68 | { 69 | continue; 70 | } 71 | 72 | // while we're at it, append the CPE name 73 | 74 | sshVer = sm["ver"].str(); 75 | 76 | service->cpe.push_back("a:openbsd:openssh:" + regex_replace(sshVer, cpever, cpepatch)); 77 | 78 | // if there is an EL tag in the banner, this host runs EL for sure 79 | 80 | if (sm["tag"].matched) 81 | { 82 | isEL = true; 83 | 84 | auto c = sm["tag"].str()[0]; 85 | isCO = c == 'C' || c == 'c'; 86 | } 87 | 88 | // check if release version is present 89 | 90 | if (sm["elver"].matched) 91 | { 92 | elVer = stoi(sm["elver"].str()); 93 | 94 | if (sm["secver"].matched) 95 | { 96 | secUpd = stoi(sm["secver"].str()); 97 | } 98 | } 99 | } 100 | 101 | // if we are certain the host is running EL, we have OpenSSH version, 102 | // but no EL distribution version, try to deduce it 103 | 104 | if (isEL && !sshVer.empty() && !elVer.is_initialized()) 105 | { 106 | auto ver = sshVer; 107 | 108 | trim(ver); 109 | to_lower(ver); 110 | 111 | auto bver = BundledVersions.find(ver); 112 | 113 | if (bver != BundledVersions.end()) 114 | { 115 | elVer = (*bver).second; 116 | } 117 | } 118 | 119 | // save information 120 | 121 | if (isEL) 122 | { 123 | auto cpe = "o:" + string(isCO ? "centos:centos" : "redhat:enterprise_linux"); 124 | 125 | host->opSys = OpSys::EnterpriseLinux; 126 | 127 | if (elVer.is_initialized()) 128 | { 129 | cpe += ":" + to_string(elVer.get()); 130 | 131 | host->osVer = elVer.get(); 132 | } 133 | 134 | host->cpe.push_back(cpe); 135 | } 136 | 137 | return isEL; 138 | } 139 | 140 | EnterpriseLinuxIdentifier::~EnterpriseLinuxIdentifier() 141 | { 142 | } 143 | -------------------------------------------------------------------------------- /EnterpriseLinuxIdentifier.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Stdafx.h" 3 | #include "OperatingSystemIdentifier.h" 4 | #include 5 | 6 | /*! 7 | * Implements functionality for identifying Red Hat and CentOS based on service banners. 8 | */ 9 | class EnterpriseLinuxIdentifier : public OperatingSystemIdentifier 10 | { 11 | public: 12 | 13 | /*! 14 | * OpenSSH version numbers mapped to the RHEL/CentOS version they came with. 15 | */ 16 | static const std::unordered_map BundledVersions; 17 | 18 | /*! 19 | * Processes the specified host. 20 | * 21 | * \param host Scanned host. 22 | * 23 | * \return true if the operating system was identified, 24 | * otherwise false. 25 | */ 26 | bool Scan(Host* host) override; 27 | 28 | /*! 29 | * Frees up the resources allocated during the lifetime of this instance. 30 | */ 31 | ~EnterpriseLinuxIdentifier() override; 32 | 33 | 34 | }; 35 | -------------------------------------------------------------------------------- /EnterpriseLinuxLookup.cpp: -------------------------------------------------------------------------------- 1 | #include "EnterpriseLinuxLookup.h" 2 | #include "Utils.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | using namespace boost; 10 | 11 | unordered_map EnterpriseLinuxLookup::FindVulnerability(const string& cve, OpSys distrib, double ver) 12 | { 13 | unordered_map vuln; 14 | 15 | if (!ValidateCVE(cve)) 16 | { 17 | log(ERR, "Specified CVE identifier '" + cve + "' is not syntactically valid."); 18 | return vuln; 19 | } 20 | 21 | if (distrib != EnterpriseLinux && distrib != Fedora) 22 | { 23 | log(ERR, "Specified distribution is not supported by this instance."); 24 | return vuln; 25 | } 26 | 27 | auto resp = getURL("https://bugzilla.redhat.com/show_bug.cgi?ctype=xml&id=" + cve); 28 | 29 | if (get<2>(resp) != 200) 30 | { 31 | if (get<2>(resp) == -1) 32 | { 33 | log(ERR, "Failed to send HTTP request: " + get<1>(resp)); 34 | } 35 | else 36 | { 37 | log(ERR, "Failed to get reply: HTTP response code was " + to_string(get<2>(resp)) + "."); 38 | } 39 | 40 | return vuln; 41 | } 42 | 43 | auto html = get<0>(resp); 44 | 45 | // pkg -> name of the package 46 | // dist -> distribution (rhel-7, rhscl-1, fedora-all, etc) 47 | // status -> vulnerability status (affected, notaffected) 48 | static regex tblrgx("(?(?:rhel|rhscl|fedora)-(?:\\d+|all))\\/(?[^=]+)=(?[^,]+)", regex::icase); 49 | 50 | // res -> resolution (NOTABUG, ERRATA) 51 | static regex stsrgx("(?[^<]+)<\\/resolution>", regex::icase); 52 | 53 | // lst -> if res is ERRATA, comma-separated list of version numbers which fix the vulnerability 54 | static regex cfirgx("(?[^<]+)<\\/cf_fixed_in>", regex::icase); 55 | 56 | sregex_iterator srit(html.begin(), html.end(), tblrgx); 57 | sregex_iterator end; 58 | 59 | auto tdist = string(distrib == EnterpriseLinux ? "rhel" : "fedora"); 60 | auto tdall = tdist + "-all"; 61 | auto tdver = ver != 0 ? tdist + "-" + to_string(ver) : tdall; 62 | 63 | for (; srit != end; ++srit) 64 | { 65 | auto m = *srit; 66 | 67 | auto pkg = m["pkg"].str(); 68 | auto dist = m["dist"].str(); 69 | auto sts = m["status"].str(); 70 | 71 | if (dist != tdall && dist != tdver) 72 | { 73 | continue; 74 | } 75 | 76 | if (sts != "notaffected") 77 | { 78 | // placeholder for now 79 | 80 | vuln.emplace(pkg, ""); 81 | } 82 | } 83 | 84 | smatch cfism; 85 | 86 | if (regex_search(html, cfism, cfirgx)) 87 | { 88 | auto lst = cfism["lst"].str(); 89 | 90 | vector strs; 91 | split(strs, lst, is_any_of(",")); 92 | 93 | for (auto& str : strs) 94 | { 95 | trim(str); 96 | 97 | auto spc = str.find_last_of(" "); 98 | 99 | if (spc == string::npos) 100 | { 101 | continue; 102 | } 103 | 104 | auto spkg = str.substr(0, spc); 105 | auto sver = str.substr(spc + 1); 106 | 107 | for (auto& fix : vuln) 108 | { 109 | if (fix.second != "" || fix.first != spkg) 110 | { 111 | continue; 112 | } 113 | 114 | fix.second = sver; 115 | } 116 | } 117 | } 118 | 119 | return vuln; 120 | } 121 | 122 | vector> EnterpriseLinuxLookup::GetChangelog(const string& pkg, OpSys distrib, double ver) 123 | { 124 | vector> updates; 125 | 126 | if (distrib != EnterpriseLinux && distrib != Fedora) 127 | { 128 | log(ERR, "Specified distribution is not supported by this instance."); 129 | return updates; 130 | } 131 | 132 | auto resp = getURL("https://www.rpmfind.net/linux/rpm2html/search.php?query=" + pkg + "&system=" + (distrib == Fedora ? "fedora" : "centos") + "&arch=x86_64"); 133 | 134 | if (get<2>(resp) != 200) 135 | { 136 | if (get<2>(resp) == -1) 137 | { 138 | log(ERR, "Failed to send HTTP request: " + get<1>(resp)); 139 | } 140 | else 141 | { 142 | log(ERR, "Failed to get reply: HTTP response code was " + to_string(get<2>(resp)) + "."); 143 | } 144 | 145 | return updates; 146 | } 147 | 148 | string vertag = distrib == Fedora ? "fc" : "el"; 149 | 150 | if (ver == 0) 151 | { 152 | vertag += distrib == Fedora ? "24" : "7"; 153 | } 154 | else 155 | { 156 | vertag += to_string(int(floor(ver))); 157 | } 158 | 159 | // pkg -> name of the package 160 | // url -> location of the latest package changelog 161 | regex chlrgx("(?[^<]*)\\.html<\\/a>", regex::icase); 162 | 163 | smatch chlurl; 164 | 165 | if (!regex_search(get<0>(resp), chlurl, chlrgx)) 166 | { 167 | log(ERR, "Failed get changelog location for the package " + pkg + "."); 168 | return updates; 169 | } 170 | 171 | resp = getURL("https://www.rpmfind.net" + chlurl["url"].str()); 172 | 173 | if (get<2>(resp) != 200) 174 | { 175 | if (get<2>(resp) == -1) 176 | { 177 | log(ERR, "Failed to send HTTP request: " + get<1>(resp)); 178 | } 179 | else 180 | { 181 | log(ERR, "Failed to get reply: HTTP response code was " + to_string(get<2>(resp)) + "."); 182 | } 183 | 184 | return updates; 185 | } 186 | 187 | // ver -> version number of the package 188 | // date -> publication date of the package 189 | static regex verrgx("^(?:
)?\\* (?(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun).*?\\d{4}).*?>(?: \\-)? (?[^$ ]+)", regex::icase);
190 | 
191 | 	istringstream iss(get<0>(resp));
192 | 
193 | 	for (string line; getline(iss, line); )
194 | 	{
195 | 		smatch verm;
196 | 
197 | 		if (regex_search(line, verm, verrgx))
198 | 		{
199 | 			updates.push_back(make_pair(verm["ver"].str(), dateToUnix(verm["date"].str(), "%a %b %d %Y")));
200 | 		}
201 | 	}
202 | 
203 | 	return updates;
204 | }
205 | 
206 | string EnterpriseLinuxLookup::GetUpgradeCommand(const unordered_set& pkgs, OpSys distrib, double ver)
207 | {
208 | 	if (pkgs.empty())
209 | 	{
210 | 		return "";
211 | 	}
212 | 
213 | 	string cmd;
214 | 
215 | 	if (distrib == OpSys::Fedora && ver >= 22)
216 | 	{
217 | 		cmd = "sudo dnf update";
218 | 	}
219 | 	else
220 | 	{
221 | 		cmd = "sudo yum update";
222 | 	}
223 | 
224 | 	for (auto& pkg : pkgs)
225 | 	{
226 | 		cmd += " " + pkg;
227 | 	}
228 | 
229 | 	return cmd;
230 | }
231 | 
232 | EnterpriseLinuxLookup::~EnterpriseLinuxLookup()
233 | {
234 | }
235 | 


--------------------------------------------------------------------------------
/EnterpriseLinuxLookup.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include "Stdafx.h"
 3 | #include "VendorPackageLookup.h"
 4 | 
 5 | /*!
 6 |  * Implements the functionality to search Red Hat/CentOS/Fedora packages.
 7 |  */
 8 | class EnterpriseLinuxLookup : public VendorPackageLookup
 9 | {
10 | public:
11 | 	
12 | 	/*!
13 | 	 * Looks up the status of a vulnerability in the vendor's repository.
14 | 	 *
15 | 	 * \param cve Identifier of the vulnerability.
16 | 	 * \param distrib Operating system distribution.
17 | 	 * \param ver Version number of distribution.
18 | 	 *
19 | 	 * \return If not affected an empty map, otherwise a map of vulnerable
20 | 	 *         packages associated to the version number that patches it,
21 | 	 *         or empty string if package is not yet fixed.
22 | 	 */
23 | 	std::unordered_map FindVulnerability(const std::string& cve, OpSys distrib = OpSys::EnterpriseLinux, double ver = 0.0) override;
24 | 	
25 | 	/*!
26 | 	 * Gets the changelog of a package from the vendor's repository.
27 | 	 *
28 | 	 * \param pkg Name of the package.
29 | 	 * \param distrib Operating system distribution.
30 | 	 * \param ver Version number of distribution.
31 | 	 * 
32 | 	 * \return List of version numbers and their release dates.
33 | 	 */
34 | 	std::vector> GetChangelog(const std::string& pkg, OpSys distrib = OpSys::EnterpriseLinux, double ver = 0.0) override;
35 | 	
36 | 	/*!
37 | 	 * Generates a command which upgrades the specified vulnerable packages
38 | 	 * on the host system.
39 | 	 *
40 | 	 * \param pkgs Vulnerable packages to upgrade.
41 | 	 * \param distrib Operating system distribution.
42 | 	 * \param ver Version number of distribution.
43 | 	 *
44 | 	 * \return Upgrade command.
45 | 	 */
46 | 	std::string GetUpgradeCommand(const std::unordered_set& pkgs, OpSys distrib = OpSys::EnterpriseLinux, double ver = 0.0) override;
47 | 
48 | 	/*!
49 | 	 * Frees up the resources allocated during the lifetime of this instance.
50 | 	 */
51 | 	~EnterpriseLinuxLookup() override;
52 | 
53 | };
54 | 


--------------------------------------------------------------------------------
/FedoraIdentifier.cpp:
--------------------------------------------------------------------------------
  1 | #include "FedoraIdentifier.h"
  2 | #include 
  3 | #include 
  4 | #include 
  5 | #include 
  6 | 
  7 | using namespace std;
  8 | using namespace boost;
  9 | 
 10 | // compiled by going through build logs at https://admin.fedoraproject.org/pkgdb/package/rpms/openssh/
 11 | const unordered_map FedoraIdentifier::BundledVersions = unordered_map {
 12 | 	{ "7.2p2",   25 },
 13 | 	{ "7.2p1",   25 },
 14 | 	{ "7.1p2",   25 },
 15 | 	{ "7.1p1",   24 },
 16 | 	{ "7.0p1",   24 },
 17 | 	{ "6.9p1",   24 },
 18 | 	{ "6.8p1",   23 },
 19 | 	{ "6.7p1",   23 },
 20 | 	{ "6.6.1p1", 22 },
 21 | 	{ "6.4p1",   21 },
 22 | 	{ "6.3p1",   21 },
 23 | 	{ "6.2p2",   21 },
 24 | 	{ "6.2p1",   20 },
 25 | 	{ "6.1p1",   19 },
 26 | 	{ "6.0p1",   18 },
 27 | 	{ "5.9p1",   18 },
 28 | 	{ "5.8p2",   17 },
 29 | 	{ "5.8p1",   16 },
 30 | 	{ "5.7p1",   15 },
 31 | 	{ "5.6p1",   15 },
 32 | 	{ "5.5p1",   14 },
 33 | 	{ "5.4p1",   14 },
 34 | 	{ "5.3p1",   13 },
 35 | 	{ "5.2p1",   13 },
 36 | };
 37 | 
 38 | bool FedoraIdentifier::Scan(Host* host)
 39 | {
 40 | 	auto isFed = false;
 41 | 	string sshVer;
 42 | 	optional fedVer, secUpd;
 43 | 
 44 | 	// check if any SSH services are available
 45 | 
 46 | 	// ver -> OpenSSH version
 47 | 	static regex sshbnr("OpenSSH_(?[^\\s\\-_$]+)", regex::icase);
 48 | 	
 49 | 	// match any "fedora" tag
 50 | 	static regex fedtag("\\bfedora\\b", regex::icase);
 51 | 
 52 | 	// replace "7.2p1" to "7.2:p1" in the CPE
 53 | 	static regex cpever("(\\d)p(\\d+)", regex::icase);
 54 | 	static string cpepatch = "\\1:p\\2";
 55 | 
 56 | 	for (auto service : *host->services)
 57 | 	{
 58 | 		if (service->banner.empty())
 59 | 		{
 60 | 			continue;
 61 | 		}
 62 | 
 63 | 		smatch sm;
 64 | 
 65 | 		// if the service is not SSH, just check if there is a "Fedora" tag in it anywhere
 66 | 
 67 | 		if (!starts_with(service->banner, "SSH-2.0-OpenSSH_"))
 68 | 		{
 69 | 			if (regex_search(service->banner, sm, fedtag))
 70 | 			{
 71 | 				isFed = true;
 72 | 			}
 73 | 
 74 | 			continue;
 75 | 		}
 76 | 
 77 | 		// if the service is SSH, special handling follows
 78 | 
 79 | 		if (!regex_search(service->banner, sm, sshbnr))
 80 | 		{
 81 | 			continue;
 82 | 		}
 83 | 
 84 | 		// while we're at it, append the CPE name
 85 | 
 86 | 		sshVer = sm["ver"].str();
 87 | 
 88 | 		service->cpe.push_back("a:openbsd:openssh:" + regex_replace(sshVer, cpever, cpepatch));
 89 | 	}
 90 | 
 91 | 	// if we are certain the host is running Fedora, we have OpenSSH version,
 92 | 	// but no Fedora distribution version, try to deduce it
 93 | 	
 94 | 	if (isFed && !sshVer.empty() && !fedVer.is_initialized())
 95 | 	{
 96 | 		auto ver = sshVer;
 97 | 
 98 | 		trim(ver);
 99 | 		to_lower(ver);
100 | 
101 | 		auto bver = BundledVersions.find(ver);
102 | 
103 | 		if (bver != BundledVersions.end())
104 | 		{
105 | 			fedVer = (*bver).second;
106 | 		}
107 | 	}
108 | 
109 | 	// save information
110 | 
111 | 	if (isFed)
112 | 	{
113 | 		string cpe = "o:redhat:fedora";
114 | 
115 | 		host->opSys = OpSys::Fedora;
116 | 
117 | 		if (fedVer.is_initialized())
118 | 		{
119 | 			cpe += ":" + to_string(fedVer.get());
120 | 
121 | 			host->osVer = fedVer.get();
122 | 		}
123 | 
124 | 		host->cpe.push_back(cpe);
125 | 	}
126 | 
127 | 	return isFed;
128 | }
129 | 
130 | FedoraIdentifier::~FedoraIdentifier()
131 | {
132 | }
133 | 


--------------------------------------------------------------------------------
/FedoraIdentifier.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include "Stdafx.h"
 3 | #include "OperatingSystemIdentifier.h"
 4 | #include 
 5 | 
 6 | /*!
 7 |  * Implements functionality for identifying Fedora based on service banners.
 8 |  */
 9 | class FedoraIdentifier : public OperatingSystemIdentifier
10 | {
11 | public:
12 | 
13 | 	/*!
14 | 	* OpenSSH version numbers mapped to the Fedora version they came with.
15 | 	*/
16 | 	static const std::unordered_map BundledVersions;
17 | 	
18 | 	/*!
19 | 	 * Processes the specified host.
20 | 	 * 
21 | 	 * \param host Scanned host.
22 | 	 * 
23 | 	 * \return true if the operating system was identified,
24 | 	 * 		   otherwise false.
25 | 	 */
26 | 	bool Scan(Host* host) override;
27 | 
28 | 	/*!
29 | 	 * Frees up the resources allocated during the lifetime of this instance.
30 | 	 */
31 | 	~FedoraIdentifier() override;
32 | 
33 | };
34 | 


--------------------------------------------------------------------------------
/Format.cpp:
--------------------------------------------------------------------------------
 1 | #include "Format.h"
 2 | 
 3 | using namespace std;
 4 | 
 5 | bool Format::Data::istty = false;
 6 | 
 7 | #ifdef Windows
 8 | HANDLE Format::Data::stdHwd;
 9 | CONSOLE_SCREEN_BUFFER_INFO Format::Data::bufInf;
10 | WORD Format::Data::defColor;
11 | WORD Format::Data::curColor;
12 | WORD Format::Data::curStyle;
13 | #endif
14 | 
15 | void Format::Init()
16 | {
17 | 	Data::istty = isatty(fileno(stdout)) != 0;
18 | 
19 | 	if (!Data::istty)
20 | 	{
21 | 		return;
22 | 	}
23 | 
24 | #ifdef Windows
25 | 
26 | 	Data::stdHwd = GetStdHandle(STD_OUTPUT_HANDLE);
27 | 	GetConsoleScreenBufferInfo(Data::stdHwd, &Data::bufInf);
28 | 	Data::defColor = Data::bufInf.wAttributes;
29 | 
30 | #endif
31 | }
32 | 
33 | ostream& Format::operator<<(ostream& os, ColorCode code)
34 | {
35 | 	if (!Data::istty)
36 | 	{
37 | 		return os;
38 | 	}
39 | 
40 | #ifdef Windows
41 | 
42 | 	if (code == ColorCode::Default)
43 | 	{
44 | 		Data::curColor = Data::defColor;
45 | 	}
46 | 	else
47 | 	{
48 | 		Data::curColor = WORD(code);
49 | 	}
50 | 
51 | 	SetConsoleTextAttribute(Data::stdHwd, Data::curColor | Data::curStyle);
52 | 
53 | #elif Unix
54 | 
55 | 	os << "\033[" << static_cast(code) << "m";
56 | 
57 | #endif
58 | 
59 | 	return os;
60 | }
61 | 
62 | ostream& Format::operator<<(ostream& os, StyleCode code)
63 | {
64 | 	if (!Data::istty)
65 | 	{
66 | 		return os;
67 | 	}
68 | 
69 | #ifdef Windows
70 | 
71 | 	if (code == StyleCode::Normal)
72 | 	{
73 | 		Data::curStyle = 0;
74 | 	}
75 | 	else
76 | 	{
77 | 		Data::curStyle |= WORD(code);
78 | 	}
79 | 
80 | 	SetConsoleTextAttribute(Data::stdHwd, Data::curColor | Data::curStyle);
81 | 
82 | #elif Unix
83 | 
84 | 	os << "\033[" << static_cast(code) << "m";
85 | 
86 | #endif
87 | 
88 | 	return os;
89 | }
90 | 


--------------------------------------------------------------------------------
/Format.h:
--------------------------------------------------------------------------------
  1 | #pragma once
  2 | #include "Stdafx.h"
  3 | #include 
  4 | 
  5 | #ifdef Windows
  6 | 	#include 
  7 | 
  8 | 	#define isatty(f) _isatty(f)
  9 | 	#define fileno(s) _fileno(s)
 10 | #elif Unix
 11 | 	#include 
 12 | #endif
 13 | 
 14 | /*!
 15 |  * Methods to change the color of the output.
 16 |  */
 17 | namespace Format
 18 | {
 19 | 	using namespace std;
 20 | 
 21 | 	/*!
 22 | 	 * Available colors.
 23 | 	 */
 24 | 	enum ColorCode
 25 | 	{
 26 | #ifdef Windows
 27 | 
 28 | 		Red     = FOREGROUND_RED,
 29 | 		Green   = FOREGROUND_GREEN,
 30 | 		Blue    = FOREGROUND_BLUE,
 31 | 		Yellow  = FOREGROUND_RED  | FOREGROUND_GREEN,
 32 | 		Magenta = FOREGROUND_RED  | FOREGROUND_BLUE,
 33 | 		Cyan    = FOREGROUND_BLUE | FOREGROUND_GREEN,
 34 | 		White   = FOREGROUND_RED  | FOREGROUND_GREEN | FOREGROUND_BLUE,
 35 | 		Default = INT_MAX
 36 | 
 37 | #elif Unix
 38 | 
 39 | 		Red     = 31,
 40 | 		Green   = 32,
 41 | 		Blue    = 34,
 42 | 		Yellow  = 33,
 43 | 		Magenta = 35,
 44 | 		Cyan    = 36,
 45 | 		White   = 37,
 46 | 		Default = 39
 47 | 
 48 | #endif
 49 | 	};
 50 | 
 51 | 	/*!
 52 | 	 * Available font styles.
 53 | 	 */
 54 | 	enum StyleCode
 55 | 	{
 56 | #if Windows
 57 | 
 58 | 		Bold	  = FOREGROUND_INTENSITY,
 59 | 		Underline = COMMON_LVB_UNDERSCORE,
 60 | 		Blink     = 0, // N/A
 61 | 		Reverse   = COMMON_LVB_REVERSE_VIDEO,
 62 | 		Hidden    = 0, // N/A
 63 | 		Normal    = INT_MAX
 64 | 
 65 | #elif Unix
 66 | 
 67 | 		Bold       = 1,
 68 | 		Underline  = 4,
 69 | 		Blink      = 5,
 70 | 		Reverse    = 7,
 71 | 		Hidden     = 8,
 72 | 		Normal     = 0
 73 | 
 74 | #endif
 75 | 	};
 76 | 
 77 | 	/*!
 78 | 	 * An ephemeral class to hold console color data.
 79 | 	 */
 80 | 	class Data
 81 | 	{
 82 | 	public:
 83 | 
 84 | 		/*!
 85 | 		 * Whether the current terminal is interactive.
 86 | 		 */
 87 | 		static bool istty;
 88 | 
 89 | #ifdef Windows
 90 | 		static HANDLE stdHwd;
 91 | 		static CONSOLE_SCREEN_BUFFER_INFO bufInf;
 92 | 		static WORD defColor;
 93 | 		static WORD curColor;
 94 | 		static WORD curStyle;
 95 | #endif
 96 | 
 97 | 	};
 98 | 
 99 | 	/*!
100 | 	 * Initializes the coloring support.
101 | 	 */
102 | 	void Init();
103 | 
104 | 	/*!
105 | 	 * Implements printing for the `Color::ColorCode` type.
106 | 	 */
107 | 	ostream& operator<< (ostream& os, ColorCode code);
108 | 
109 | 	/*!
110 | 	 * Implements printing for the `Color::StyleCode` type.
111 | 	 */
112 | 	ostream& operator<< (ostream& os, StyleCode code);
113 | 
114 | }
115 | 


--------------------------------------------------------------------------------
/Host.cpp:
--------------------------------------------------------------------------------
  1 | #include "Host.h"
  2 | 
  3 | using namespace std;
  4 | 
  5 | Host::Host(const Host& host)
  6 | 	: address(host.address), alive(host.alive), reason(host.reason), cpe(host.cpe),
  7 | 	  services(new Services()), opSys(host.opSys), osVer(host.osVer), date(host.date), data(host.data)
  8 | {
  9 | 	for (auto service : *host.services)
 10 | 	{
 11 | 		services->push_back(new Service(*service));
 12 | 	}
 13 | }
 14 | 
 15 | Host::Host(const string& address)
 16 | 	: address(address), cpe(), services(new Services()), opSys(OpSys::Unidentified), osVer(0), date(), data(nullptr)
 17 | {
 18 | }
 19 | 
 20 | Host::Host(const string& address, const set& tcps, const set& udps)
 21 | 	: address(address), cpe(), services(new Services()), opSys(OpSys::Unidentified), osVer(0), date(), data(nullptr)
 22 | {
 23 | 	if (tcps.size() > 0)
 24 | 	{
 25 | 		for (auto tcp : tcps)
 26 | 		{
 27 | 			auto service = new Service(address, tcp, IPPROTO_TCP);
 28 | 
 29 | 			service->host = this;
 30 | 
 31 | 			services->push_back(service);
 32 | 		}
 33 | 	}
 34 | 
 35 | 	if (udps.size() > 0)
 36 | 	{
 37 | 		for (auto udp : udps)
 38 | 		{
 39 | 			auto service = new Service(address, udp, IPPROTO_UDP);
 40 | 
 41 | 			service->host = this;
 42 | 
 43 | 			services->push_back(service);
 44 | 		}
 45 | 	}
 46 | }
 47 | 
 48 | Service* Host::AddService(Service* service)
 49 | {
 50 | 	if (address == service->address)
 51 | 	{
 52 | 		service->host = this;
 53 | 
 54 | 		services->push_back(service);
 55 | 
 56 | 		return service;
 57 | 	}
 58 | 
 59 | 	return nullptr;
 60 | }
 61 | 
 62 | Service* Host::AddService(unsigned short port, IPPROTO protocol)
 63 | {
 64 | 	auto service = new Service(address, port, protocol);
 65 | 
 66 | 	service->host = this;
 67 | 
 68 | 	services->push_back(service);
 69 | 
 70 | 	return service;
 71 | }
 72 | 
 73 | int Host::AddServices(const Services& servlist)
 74 | {
 75 | 	auto count = 0;
 76 | 
 77 | 	for (auto service : servlist)
 78 | 	{
 79 | 		if (address == service->address)
 80 | 		{
 81 | 			service->host = this;
 82 | 
 83 | 			services->push_back(service);
 84 | 
 85 | 			count++;
 86 | 		}
 87 | 	}
 88 | 
 89 | 	return count;
 90 | }
 91 | 
 92 | int Host::AddServices(const set& ports, IPPROTO protocol)
 93 | {
 94 | 	auto count = 0;
 95 | 
 96 | 	for (auto port : ports)
 97 | 	{
 98 | 		auto service = new Service(address, port, protocol);
 99 | 
100 | 		service->host = this;
101 | 
102 | 		services->push_back(service);
103 | 
104 | 		count++;
105 | 	}
106 | 
107 | 	return count;
108 | }
109 | 
110 | Host::~Host()
111 | {
112 | 	for (auto& service : *services)
113 | 	{
114 | 		delete service;
115 | 	}
116 | 
117 | 	services->clear();
118 | 
119 | 	delete services;
120 | }
121 | 
122 | void freeHosts(Hosts& hosts)
123 | {
124 | 	for (auto& host : hosts)
125 | 	{
126 | 		delete host;
127 | 	}
128 | 
129 | 	hosts.clear();
130 | }
131 | 


--------------------------------------------------------------------------------
/Host.h:
--------------------------------------------------------------------------------
  1 | #pragma once
  2 | #include 
  3 | #include 
  4 | #include 
  5 | #include "Stdafx.h"
  6 | #include "Service.h"
  7 | 
  8 | /*!
  9 |  * List of operating systems.
 10 |  */
 11 | typedef enum
 12 | {
 13 | 
 14 | 	/*!
 15 | 	 * The operating system was not identified.
 16 | 	 */
 17 | 	Unidentified = -1,
 18 | 	
 19 | 	/*!
 20 | 	 * Debian Linux
 21 | 	 */
 22 | 	Debian = 1,
 23 | 	
 24 | 	/*!
 25 | 	 * Ubuntu Linux
 26 | 	 */
 27 | 	Ubuntu = 2,
 28 | 	
 29 | 	/*!
 30 | 	 * Red Hat/CentOS Linux
 31 | 	 */
 32 | 	EnterpriseLinux = 3,
 33 | 	
 34 | 	/*!
 35 | 	 * Fedora Linux
 36 | 	 */
 37 | 	Fedora = 4,
 38 | 
 39 | 	/*!
 40 | 	 * Windows
 41 | 	 */
 42 | 	WindowsNT = 10,
 43 | 
 44 | } OpSys;
 45 | 
 46 | /*!
 47 |  * Represents a host which hosts a collection of services.
 48 |  */
 49 | class Host
 50 | {
 51 | public:
 52 | 
 53 | 	/*!
 54 | 	 * Remote address.
 55 | 	 */
 56 | 	std::string address;
 57 | 	
 58 | 	/*!
 59 | 	 * Whether the service is alive at this host.
 60 | 	 */
 61 | 	bool alive = false;
 62 | 	
 63 | 	/*!
 64 | 	 * Reason for the value specified in `alive`.
 65 | 	 * Negative values are errors, positive values are scanner-dependent reasons.
 66 | 	 */
 67 | 	AliveReason reason = AR_NotScanned;
 68 | 
 69 | 	/*!
 70 | 	 * CPE names of the host.
 71 | 	 */
 72 | 	std::vector cpe;
 73 | 
 74 | 	/*!
 75 | 	 * List of services on this host.
 76 | 	 */
 77 | 	Services* services;
 78 | 
 79 | 	/*!
 80 | 	 * The operating system of the host.
 81 | 	 */
 82 | 	OpSys opSys;
 83 | 
 84 | 	/*!
 85 | 	 * The version of the operating system.
 86 | 	 */
 87 | 	double osVer;
 88 | 
 89 | 	/*!
 90 | 	 * Time of last packet sent to this host.
 91 | 	 */
 92 | 	std::chrono::time_point date;
 93 | 
 94 | 	/*!
 95 | 	 * Object store reserved for the scanner.
 96 | 	 */
 97 | 	void* data;
 98 | 
 99 | 	/*!
100 | 	 * Copies the specified instance.
101 | 	 *
102 | 	 * \param host Instance to copy.
103 | 	 */
104 | 	Host(const Host& host);
105 | 
106 | 	/*!
107 | 	 * Creates a new instance of this type.
108 | 	 * 
109 | 	 * \param address Remote address.
110 | 	 */
111 | 	explicit Host(const std::string& address);
112 | 
113 | 	/*!
114 | 	 * Creates a new instance of this type.
115 | 	 *
116 | 	 * \param address Remote address.
117 | 	 * \param tcps List of TCP ports to attach as a service.
118 | 	 * \param udps List of UDP ports to attach as a service.
119 | 	 */
120 | 	Host(const std::string& address, const std::set& tcps, const std::set& udps = { });
121 | 
122 | 	/*!
123 | 	 * Adds a service to the host.
124 | 	 * 
125 | 	 * \remarks If the address of the host and service object don't
126 | 	 * 			match, this function call will be silently rejected.
127 | 	 * 
128 | 	 * \param service The service object to add.
129 | 	 * 
130 | 	 * \return Service object added to list, or `nullptr` on failure.
131 | 	 */
132 | 	Service* AddService(Service* service);
133 | 
134 | 	/*!
135 | 	 * Adds a service to the host.
136 | 	 *
137 | 	 * \param port Remote port.
138 | 	 * \param protocol Remote protocol, otherwise TCP.
139 | 	 * 
140 | 	 * \return Service object added to list, or `nullptr` on failure.
141 | 	 */
142 | 	Service* AddService(unsigned short port, IPPROTO protocol = IPPROTO_TCP);
143 | 	
144 | 	/*!
145 | 	 * Adds the specified list of services to the host.
146 | 	 *
147 | 	 * \remarks If the address of the host and service object don't
148 | 	 * 			match, this function call will be silently rejected.
149 | 	 *
150 | 	 * \param servlist The service objects to add.
151 | 	 *
152 | 	 * \return Number of added service objects.
153 | 	 */
154 | 	int AddServices(const Services& servlist);
155 | 
156 | 	/*!
157 | 	 * Adds the specified list of services to the host.
158 | 	 *
159 | 	 * \param ports Remote ports.
160 | 	 * \param protocol Remote protocol, otherwise TCP.
161 | 	 *
162 | 	 * \return Number of added service objects.
163 | 	 */
164 | 	int AddServices(const std::set& ports, IPPROTO protocol = IPPROTO_TCP);
165 | 
166 | 	/*!
167 | 	 * Frees up the resources allocated during the lifetime of this instance.
168 | 	 */
169 | 	~Host();
170 | 
171 | };
172 | 
173 | /*!
174 |  * Represents a list of hosts.
175 |  */
176 | typedef std::vector Hosts;
177 | 
178 | /*!
179 |  * Frees up the structures allocated within this array.
180 |  *
181 |  * \param hosts List of hosts.
182 |  */
183 | void freeHosts(Hosts& hosts);
184 | 


--------------------------------------------------------------------------------
/HostScanner.cpp:
--------------------------------------------------------------------------------
  1 | #include "HostScanner.h"
  2 | #include "ServiceScanner.h"
  3 | #include 
  4 | #include 
  5 | 
  6 | using namespace std;
  7 | 
  8 | Hosts* HostScanner::GenerateCidr(const char* address, int cidr, Hosts* hosts)
  9 | {
 10 | 	// get lowest and highest IP for supplied CIDR
 11 | 
 12 | 	unsigned int ip, bitmask, gateway, broadcast;
 13 | 
 14 | 	inet_pton(AF_INET, address, &ip);
 15 | 	ip = ntohl(ip);
 16 | 
 17 | 	bitmask = createBitmask(cidr);
 18 | 
 19 | 	gateway   = ip &  bitmask;
 20 | 	broadcast = ip | ~bitmask;
 21 | 
 22 | 	// generate list of hosts for range
 23 | 
 24 | 	if (hosts == nullptr)
 25 | 	{
 26 | 		hosts = new Hosts();
 27 | 	}
 28 | 
 29 | 	for (ip = gateway; ip <= broadcast; ip++)
 30 | 	{
 31 | 		hosts->push_back(new Host(uintToIp(ip)));
 32 | 	}
 33 | 
 34 | 	return hosts;
 35 | }
 36 | 
 37 | Hosts* HostScanner::GenerateRange(const char* start, const char* finish, Hosts* hosts)
 38 | {
 39 | 	// parse supplied addresses
 40 | 
 41 | 	unsigned int ip, low, high;
 42 | 
 43 | 	inet_pton(AF_INET, start,  &low);
 44 | 	inet_pton(AF_INET, finish, &high);
 45 | 
 46 | 	low  = ntohl(low);
 47 | 	high = ntohl(high);
 48 | 
 49 | 	if (high < low)
 50 | 	{
 51 | 		swap(low, high);
 52 | 	}
 53 | 
 54 | 	// generate list of hosts for range
 55 | 
 56 | 	if (hosts == nullptr)
 57 | 	{
 58 | 		hosts = new Hosts();
 59 | 	}
 60 | 
 61 | 	for (ip = low; ip <= high; ip++)
 62 | 	{
 63 | 		hosts->push_back(new Host(uintToIp(ip)));
 64 | 	}
 65 | 
 66 | 	return hosts;
 67 | }
 68 | 
 69 | void HostScanner::DumpResults(Hosts* hosts)
 70 | {
 71 | 	for (auto host : *hosts)
 72 | 	{
 73 | 		ServiceScanner::DumpResults(host->services);
 74 | 	}
 75 | }
 76 | 
 77 | HostScanner::~HostScanner()
 78 | {
 79 | }
 80 | 
 81 | unsigned int HostScanner::createBitmask(int cidr)
 82 | {
 83 | 	cidr = max(0, min(cidr, 32));
 84 | 
 85 | 	unsigned int bitmask = UINT_MAX;
 86 | 
 87 | 	for (int i = 0; i < 33 - cidr - 1; i++)
 88 | 	{
 89 | 		bitmask <<= 1;
 90 | 	}
 91 | 
 92 | 	return bitmask;
 93 | }
 94 | 
 95 | const char* HostScanner::uintToIp(unsigned int ip)
 96 | {
 97 | 	auto addr = new char[16];
 98 | 	snprintf(addr, 16, "%u.%u.%u.%u", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF);
 99 | 	return addr;
100 | }
101 | 


--------------------------------------------------------------------------------
/HostScanner.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include "Stdafx.h"
 3 | #include "Host.h"
 4 | 
 5 | /*!
 6 |  * Represents a host scanner.
 7 |  */
 8 | class HostScanner
 9 | {
10 | public:
11 | 
12 | 	/*!
13 | 	 * Value indicating whether this instance is a passive scanner.
14 | 	 * 
15 | 	 * A passive scanner does not actively send packets towards the
16 | 	 * scanned target, it instead uses miscellaneous data sources to
17 | 	 * gather information regarding the target.
18 | 	 * 
19 | 	 * \return true if passive, false if not.
20 | 	 */
21 | 	virtual bool IsPassive() = 0;
22 | 
23 | 	/*!
24 | 	 * Scans a host to determine service availability.
25 | 	 * 
26 | 	 * \param host Host.
27 | 	 */
28 | 	virtual void Scan(Host* host) = 0;
29 | 
30 | 	/*!
31 | 	 * Scans a list of hosts to determine service availability.
32 | 	 * 
33 | 	 * \param hosts List of hosts.
34 | 	 */
35 | 	virtual void Scan(Hosts* hosts) = 0;
36 | 
37 | 	/*!
38 | 	 * Generates a host list for a network range.
39 | 	 *
40 | 	 * \param address IP address.
41 | 	 * \param cidr CIDR value.
42 | 	 * \param hosts Existing list to fill, if any.
43 | 	 *
44 | 	 * \return List of hosts.
45 | 	 */
46 | 	static Hosts* GenerateCidr(const char* address, int cidr, Hosts* hosts = nullptr);
47 | 
48 | 	/*!
49 | 	 * Generates a host list for a network range.
50 | 	 *
51 | 	 * \param start IP address to start with.
52 | 	 * \param finish IP address to end with.
53 | 	 * \param hosts Existing list to fill, if any.
54 | 	 *
55 | 	 * \return List of hosts.
56 | 	 */
57 | 	static Hosts* GenerateRange(const char* start, const char* finish, Hosts* hosts = nullptr);
58 | 
59 | 	/*!
60 | 	 * Dumps the scan results into the standard output.
61 | 	 *
62 | 	 * \param hosts List of hosts.
63 | 	 */
64 | 	static void DumpResults(Hosts* hosts);
65 | 
66 | 	/*!
67 | 	 * Frees up the resources allocated during the lifetime of this instance.
68 | 	 */
69 | 	virtual ~HostScanner();
70 | 
71 | private:
72 | 
73 | 	/*!
74 | 	 * Creates a bitmask from the specified value which will be used to generate
75 | 	 * an IP address list with a starting address and this being the CIDR value.
76 | 	 *
77 | 	 * \param cidr CIDR value to create bitmask for.
78 | 	 */
79 | 	static unsigned createBitmask(int cidr);
80 | 
81 | 	/*!
82 | 	 * Transforms the specified IP address from unsigned integer form to its string notation.
83 | 	 *
84 | 	 * \param ip Numerical form of the IP address.
85 | 	 */
86 | 	static const char* uintToIp(unsigned ip);
87 | 
88 | };
89 | 


--------------------------------------------------------------------------------
/HostScannerFactory.cpp:
--------------------------------------------------------------------------------
 1 | #include "Stdafx.h"
 2 | #include "HostScannerFactory.h"
 3 | #include "ShodanScanner.h"
 4 | #include "NmapScanner.h"
 5 | #include "InternalScanner.h"
 6 | 
 7 | HostScanner* HostScannerFactory::Get(bool passive, bool external)
 8 | {
 9 | 	if (passive)
10 | 	{
11 | 		return new ShodanScanner();
12 | 	}
13 | 
14 | 	if (external)
15 | 	{
16 | 		return new NmapScanner();
17 | 	}
18 | 
19 | 	return new InternalScanner();
20 | }
21 | 


--------------------------------------------------------------------------------
/HostScannerFactory.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include "Stdafx.h"
 3 | #include "HostScanner.h"
 4 | 
 5 | /*
 6 |  * Implements the factory pattern for retrieving host scanner instances.
 7 |  */
 8 | class HostScannerFactory
 9 | {
10 | public:
11 | 
12 | 	/*!
13 | 	 * Gets a scanner instance which supports the specified criteria.
14 | 	 *
15 | 	 * \param passive Whether to fetch existing data via 3rd-party service.
16 | 	 * \param external Whether to use an external scanner.
17 | 	 * 
18 | 	 * \return Instance to be used for scanning.
19 | 	 */
20 | 	static HostScanner* Get(bool passive = false, bool external = false);
21 | 
22 | };
23 | 


--------------------------------------------------------------------------------
/HttpTokenizer.cpp:
--------------------------------------------------------------------------------
 1 | #include "HttpTokenizer.h"
 2 | #include 
 3 | 
 4 | using namespace std;
 5 | using namespace boost;
 6 | 
 7 | bool HttpTokenizer::CanTokenize(const string& banner)
 8 | {
 9 | 	static regex rgx("^HTTP\\/1\\.[01]\\s(?:\\d{3})(?:\\.\\d+)?\\s", regex::perl);
10 | 
11 | 	try
12 | 	{
13 | 		return regex_search(banner, rgx);
14 | 	}
15 | 	catch (boost::exception&)
16 | 	{
17 | 		return false;
18 | 	}
19 | }
20 | 
21 | vector HttpTokenizer::Tokenize(const string& banner)
22 | {
23 | 	vector lines;
24 | 
25 | 	static regex flrgx("^(Server|X-(?:Powered-By|AspNet(?:Mvc)?-Version|Page-Speed)):\\s+([^\\r\\n]+)$", regex::perl);
26 | 
27 | 	sregex_token_iterator flit(banner.begin(), banner.end(), flrgx, { 1, 2 });
28 | 	sregex_token_iterator end;
29 | 
30 | 	for (; flit != end; ++flit)
31 | 	{
32 | 		auto name  = (*flit).str();
33 | 		auto value = (*++flit).str();
34 | 
35 | 		if (name != "Server" && name != "X-Powered-By")
36 | 		{
37 | 			value = name.substr(2) + " " + value;
38 | 		}
39 | 
40 | 		lines.push_back(value);
41 | 	}
42 | 
43 | 	if (lines.size() == 0)
44 | 	{
45 | 		return vector { banner };
46 | 	}
47 | 
48 | 	vector tokens;
49 | 
50 | 	for (auto& line : lines)
51 | 	{
52 | 		static regex scrgx("([A-Za-z0-9\\-_\\.]+)(?:[\\/ ](\\d[^\\s]*))?", regex::perl);
53 | 
54 | 		sregex_token_iterator scit(line.begin(), line.end(), scrgx, { 1, 2 });
55 | 
56 | 		for (; scit != end; ++scit)
57 | 		{
58 | 			auto product = (*scit).str();
59 | 			auto version = (*++scit).str();
60 | 
61 | 			if (version.length() != 0)
62 | 			{
63 | 				product += "/" + version;
64 | 			}
65 | 
66 | 			tokens.push_back(product);
67 | 		}
68 | 	}
69 | 
70 | 	if (tokens.size() == 0)
71 | 	{
72 | 		return vector { banner };
73 | 	}
74 | 
75 | 	return tokens;
76 | }
77 | 
78 | HttpTokenizer::~HttpTokenizer()
79 | {
80 | }
81 | 


--------------------------------------------------------------------------------
/HttpTokenizer.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include "Stdafx.h"
 3 | #include "ProtocolTokenizer.h"
 4 | 
 5 | /*!
 6 |  * Implements a lightweight HTTP protocol parser to be used for extraction of server names and version numbers.
 7 |  */
 8 | class HttpTokenizer : public ProtocolTokenizer
 9 | {
10 | public:
11 | 	
12 | 	/*!
13 | 	 * Determines whether the specified service banner can be tokenized using this
14 | 	 * instance of the protocol parser.
15 | 	 * 
16 | 	 * \param banner Service banner.
17 | 	 * 
18 | 	 * \return Value indicating ability to process.
19 | 	 */
20 | 	bool CanTokenize(const std::string& banner) override;
21 | 
22 | 	/*!
23 | 	 * Processes the specified service banner.
24 | 	 * 
25 | 	 * \param banner Service banner.
26 | 	 * 
27 | 	 * \return Extracted tokens.
28 | 	 */
29 | 	std::vector Tokenize(const std::string& banner) override;
30 | 
31 | 	/*!
32 | 	 * Frees up the resources allocated during the lifetime of this instance.
33 | 	 */
34 | 	~HttpTokenizer() override;
35 | 
36 | };
37 | 


--------------------------------------------------------------------------------
/IcmpPinger.h:
--------------------------------------------------------------------------------
  1 | #pragma once
  2 | #include 
  3 | #include "Stdafx.h"
  4 | #include "ServiceScanner.h"
  5 | 
  6 | #define ICMP_ECHO_REQUEST 8
  7 | #define ICMP_ECHO_REPLY   0
  8 | #define ICMP_DEST_UNREACH 3
  9 | 
 10 | #define ICMP6_ECHO_REQUEST 128
 11 | #define ICMP6_ECHO_REPLY   129
 12 | #define ICMP6_DEST_UNREACH 1
 13 | 
 14 | /*!
 15 |  * Structure of an ICMP packet.
 16 |  */
 17 | struct IcmpHeader
 18 | {
 19 | 
 20 | 	/*!
 21 | 	 * Type of the packet.
 22 | 	 */
 23 | 	unsigned char type;
 24 | 
 25 | 	/*!
 26 | 	 * Code of the packet.
 27 | 	 */
 28 | 	unsigned char code;
 29 | 	
 30 | 	/*!
 31 | 	 * Checksum of the packet, excluding the IP header.
 32 | 	 */
 33 | 	unsigned short checksum;
 34 | 
 35 | };
 36 | 
 37 | /*!
 38 |  * Structure of an ICMP packet for echo request/reply.
 39 |  */
 40 | struct IcmpEcho : IcmpHeader
 41 | {
 42 | 
 43 | 	/*!
 44 | 	 * ID of the echo request.
 45 | 	 */
 46 | 	unsigned short id;
 47 | 	
 48 | 	/*!
 49 | 	 * Sequence number of the echo request.
 50 | 	 */
 51 | 	unsigned short seq;
 52 | 
 53 | 	/*!
 54 | 	 * Payload to be echoed.
 55 | 	 */
 56 | 	char data[32];
 57 | 
 58 | };
 59 | 
 60 | /*!
 61 |  * Represents scan data for the ICMP scanner.
 62 |  */
 63 | struct IcmpScanData
 64 | {
 65 | 
 66 | 	/*!
 67 | 	 * Connected socket.
 68 | 	 */
 69 | 	SOCKET socket;
 70 | 
 71 | 	/*!
 72 | 	 * Expiration time of the current operation.
 73 | 	 */
 74 | 	std::chrono::time_point timeout;
 75 | 
 76 | };
 77 | 
 78 | /*!
 79 |  * Implements a scanner which sends ICMP pings using raw sockets.
 80 |  */
 81 | class IcmpPinger : public ServiceScanner
 82 | {
 83 | public:
 84 | 	
 85 | 	/*!
 86 | 	 * Gets the currently set value for the option key.
 87 | 	 *
 88 | 	 * \param option Option index, see `OPT_*` macros.
 89 | 	 * \param value Pointer to the value to set.
 90 | 	 *
 91 | 	 * \return true if it succeeds, false if it fails.
 92 | 	 */
 93 | 	bool GetOption(int option, void* value) override;
 94 | 
 95 | 	/*!
 96 | 	 * Sets a specified value for the option key.
 97 | 	 *
 98 | 	 * \param option Option index, see `OPT_*` macros.
 99 | 	 * \param value Pointer to the value to set.
100 | 	 *
101 | 	 * \return true if it succeeds, false if it fails.
102 | 	 */
103 | 	bool SetOption(int option, void* value) override;
104 | 	
105 | 	/*!
106 | 	 * Get a task which scans a service to determine its aliveness.
107 | 	 *
108 | 	 * \param service Service to scan.
109 | 	 * 
110 | 	 * \return Task to scan the specified service.
111 | 	 */
112 | 	void* GetTask(Service* service) override;
113 | 
114 | 	/*!
115 | 	 * Frees up the resources allocated during the lifetime of this instance.
116 | 	 */
117 | 	~IcmpPinger() override;
118 | 
119 | private:
120 | 	
121 | 	/*!
122 | 	 * Number of milliseconds to wait for a reply packet.
123 | 	 */
124 | 	unsigned long timeout = 3000;
125 | 
126 | 	/*!
127 | 	 * Global sequence counter for the ping packets.
128 | 	 */
129 | 	static unsigned short sequence;
130 | 
131 | 	/*!
132 | 	 * Sends an ICMP Echo Request to the specified service.
133 | 	 *
134 | 	 * \param service Service.
135 | 	 *
136 | 	 * \return Next task, or `nullptr` if failed to initialize socket.
137 | 	 */
138 | 	void* initSocket(Service* service);
139 | 
140 | 	/*!
141 | 	 * Receives the response.
142 | 	 *
143 | 	 * \param service Service.
144 | 	 *
145 | 	 * \return Same task if no data received yet, or `nullptr` if succeeded in
146 | 	 * 		   reading the response or socket disconnected while trying to do so.
147 | 	 */
148 | 	void* pollSocket(Service* service);
149 | 	
150 | 	/*!
151 | 	 * Calculates the checksum for the specified packet, which is the 16-bit one's complement
152 | 	 * of the one's complement sum of the ICMP message starting with the `type` field.
153 | 	 *
154 | 	 * \param buf Packet to checksum.
155 | 	 * \param len Size of packet.
156 | 	 *
157 | 	 * \return Corresponding checksum.
158 | 	 */
159 | 	static unsigned short checksum(unsigned short* buf, int len);
160 | 
161 | };
162 | 


--------------------------------------------------------------------------------
/InternalScanner.cpp:
--------------------------------------------------------------------------------
  1 | #include "Stdafx.h"
  2 | #include "InternalScanner.h"
  3 | #include "TaskQueueRunner.h"
  4 | #include 
  5 | #include "ServiceScannerFactory.h"
  6 | 
  7 | using namespace std;
  8 | 
  9 | bool InternalScanner::GetOption(int option, void* value)
 10 | {
 11 | 	switch (option)
 12 | 	{
 13 | 	case OPT_TIMEOUT:
 14 | 		*reinterpret_cast(value) = timeout;
 15 | 		return true;
 16 | 
 17 | 	case OPT_DELAY:
 18 | 		*reinterpret_cast(value) = delay;
 19 | 		return true;
 20 | 
 21 | 	default:
 22 | 		return false;
 23 | 	}
 24 | }
 25 | 
 26 | bool InternalScanner::SetOption(int option, void* value)
 27 | {
 28 | 	switch (option)
 29 | 	{
 30 | 	case OPT_TIMEOUT:
 31 | 		timeout = *reinterpret_cast(value);
 32 | 		return true;
 33 | 
 34 | 	case OPT_DELAY:
 35 | 		delay = *reinterpret_cast(value);
 36 | 		return true;
 37 | 
 38 | 	default:
 39 | 		return false;
 40 | 	}
 41 | }
 42 | 
 43 | bool InternalScanner::IsPassive()
 44 | {
 45 | 	return false;
 46 | }
 47 | 
 48 | void InternalScanner::Scan(Host* host)
 49 | {
 50 | 	Hosts hosts = { host };
 51 | 	Scan(&hosts);
 52 | }
 53 | 
 54 | void InternalScanner::Scan(Hosts* hosts)
 55 | {
 56 | 	unordered_map scanners;
 57 | 
 58 | 	TaskQueueRunner tqr(hosts->size() * hosts->front()->services->size(), 65535);
 59 | 
 60 | 	// traverse hosts and create list of services
 61 | 
 62 | 	Services servs;
 63 | 
 64 | 	for (auto host : *hosts)
 65 | 	{
 66 | 		for (auto service : *host->services)
 67 | 		{
 68 | 			servs.push_back(service);
 69 | 		}
 70 | 	}
 71 | 
 72 | 	// the reason why stable_sort is used here is because otherwise the queue
 73 | 	// would otherwise have the first IP with all of its ports, then the second,
 74 | 	// and so on. this would put a huge strain on the IPs sequentially.
 75 | 	// through sorting the list by port number, the queue will go through the
 76 | 	// ports sequentially, iterating through the list of IPs for each port.
 77 | 	// while this results in the same amount of load for the scanner, it will
 78 | 	// be much more gentle on the scanned targets. as for why stable sort is
 79 | 	// used, it's because the the "normal" sort is not guaranteed to preserve
 80 | 	// the order between equal elements, which would make the IP addresses list
 81 | 	// rather randomized. by using stable sort, the IP addresses will keep their
 82 | 	// sequential order by port, and as such the load will be kept to a minimum.
 83 | 
 84 | 	stable_sort(servs.begin(), servs.end(), [](Service* a, Service* b)
 85 | 	{
 86 | 		return a->port < b->port;
 87 | 	});
 88 | 
 89 | 	// create task for each service and add it to the queue
 90 | 
 91 | 	for (auto service : servs)
 92 | 	{
 93 | 		auto scanner = scanners[service->protocol];
 94 | 
 95 | 		if (scanner == nullptr)
 96 | 		{
 97 | 			scanner = ServiceScannerFactory::Get(service->protocol);
 98 | 
 99 | 			if (scanner == nullptr)
100 | 			{
101 | 				continue;
102 | 			}
103 | 
104 | 			scanner->SetOption(OPT_TIMEOUT, &timeout);
105 | 			scanner->SetOption(OPT_DELAY,   &delay);
106 | 
107 | 			scanners[service->protocol] = scanner;
108 | 		}
109 | 
110 | 		tqr.Enqueue(scanner->GetTask(service));
111 | 	}
112 | 
113 | 	servs.clear();
114 | 
115 | 	// run the queue
116 | 
117 | 	tqr.Run();
118 | 
119 | 	// clean-up
120 | 
121 | 	for (auto scanner : scanners)
122 | 	{
123 | 		delete scanner.second;
124 | 	}
125 | }
126 | 
127 | InternalScanner::~InternalScanner()
128 | {
129 | }
130 | 


--------------------------------------------------------------------------------
/InternalScanner.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include "Stdafx.h"
 3 | #include "HostScanner.h"
 4 | 
 5 | /*!
 6 |  * Implements a host scanner.
 7 |  * 
 8 |  * The purpose of this scanner is to scan the specified list of addresses
 9 |  * and determine which one is alive.
10 |  */
11 | class InternalScanner : public HostScanner
12 | {
13 | public:
14 | 
15 | 	/*!
16 | 	 * Gets the currently set value for the option key.
17 | 	 *
18 | 	 * \param option Option index, see `OPT_*` macros.
19 | 	 * \param value Pointer to the value to set.
20 | 	 *
21 | 	 * \return true if it succeeds, false if it fails.
22 | 	 */
23 | 	bool GetOption(int option, void* value);
24 | 
25 | 	/*!
26 | 	 * Sets a specified value for the option key.
27 | 	 *
28 | 	 * \param option Option index, see `OPT_*` macros.
29 | 	 * \param value Pointer to the value to set.
30 | 	 *
31 | 	 * \return true if it succeeds, false if it fails.
32 | 	 */
33 | 	bool SetOption(int option, void* value);
34 | 
35 | 	/*!
36 | 	 * Value indicating whether this instance is a passive scanner.
37 | 	 * 
38 | 	 * A passive scanner does not actively send packets towards the
39 | 	 * scanned target, it instead uses miscellaneous data sources to
40 | 	 * gather information regarding the target.
41 | 	 * 
42 | 	 * \return true if passive, false if not.
43 | 	 */
44 | 	bool IsPassive() override;
45 | 
46 | 	/*!
47 | 	 * Scans a host to determine service availability.
48 | 	 * 
49 | 	 * \param host Host.
50 | 	 */
51 | 	void Scan(Host* host) override;
52 | 
53 | 	/*!
54 | 	 * Scans a list of hosts to determine service availability.
55 | 	 * 
56 | 	 * \param hosts List of hosts.
57 | 	 */
58 | 	void Scan(Hosts* hosts) override;
59 | 
60 | 	/*!
61 | 	 * Frees up the resources allocated during the lifetime of this instance.
62 | 	 */
63 | 	~InternalScanner() override;
64 | 
65 | private:
66 | 	
67 | 	/*!
68 | 	 * Number of milliseconds to wait for connections to finish.
69 | 	 * Since this scanner uses multiple sub-scanners, this will be the
70 | 	 * applied timeout to each individual scanner, multiplying the final timeout.
71 | 	 */
72 | 	unsigned long timeout = 3000;
73 | 	
74 | 	/*!
75 | 	 * Number of milliseconds to wait between packets sent to the same host.
76 | 	 */
77 | 	unsigned long delay = 100;
78 | 
79 | };
80 | 


--------------------------------------------------------------------------------
/LooquerScanner.cpp:
--------------------------------------------------------------------------------
  1 | #include "LooquerScanner.h"
  2 | #include "Utils.h"
  3 | #include 
  4 | #include 
  5 | #include 
  6 | #include 
  7 | #include 
  8 | #include 
  9 | #include 
 10 | 
 11 | using namespace std;
 12 | using namespace boost;
 13 | 
 14 | namespace fs = boost::filesystem;
 15 | 
 16 | LooquerScanner::LooquerScanner(const string& key)
 17 | 	: key(key)
 18 | {
 19 | }
 20 | 
 21 | void LooquerScanner::SetKey(const string& key)
 22 | {
 23 | 	this->key = key;
 24 | }
 25 | 
 26 | bool LooquerScanner::HasKey()
 27 | {
 28 | 	return !key.empty();
 29 | }
 30 | 
 31 | void LooquerScanner::SetEndpoint(const string& uri)
 32 | {
 33 | 	endpoint = uri;
 34 | }
 35 | 
 36 | bool LooquerScanner::IsPassive()
 37 | {
 38 | 	return true;
 39 | }
 40 | 
 41 | void LooquerScanner::Scan(Host* host)
 42 | {
 43 | 	getHostInfo(host);
 44 | }
 45 | 
 46 | void LooquerScanner::Scan(Hosts* hosts)
 47 | {
 48 | 	for (auto host : *hosts)
 49 | 	{
 50 | 		getHostInfo(host);
 51 | 	}
 52 | }
 53 | 
 54 | void LooquerScanner::getHostInfo(Host* host)
 55 | {
 56 | 	using property_tree::write_json;
 57 | 	using property_tree::ptree;
 58 | 
 59 | 	if (key.length() == 0)
 60 | 	{
 61 | 		return;
 62 | 	}
 63 | 
 64 | 	string json;
 65 | 
 66 | 	if (starts_with(endpoint, "file://"))
 67 | 	{
 68 | 		auto path = fs::path(endpoint.substr(7)) / fs::path(host->address);
 69 | 
 70 | 		log(VRB, "Reading " + path.string() + "...");
 71 | 
 72 | 		ifstream fs(path.string());
 73 | 
 74 | 		if (!fs.good())
 75 | 		{
 76 | 			log(ERR, "Failed to open JSON file for reading: " + path.string());
 77 | 			return;
 78 | 		}
 79 | 
 80 | 		stringstream buf;
 81 | 		buf << fs.rdbuf();
 82 | 
 83 | 		json = buf.str();
 84 | 	}
 85 | 	else
 86 | 	{
 87 | 		auto url = endpoint + "/search?token=" + key + "&q=ip" + (host->address.find(':') == string::npos ? "v4" : "") + ":%22" + host->address + "%22";
 88 | 
 89 | 		log(VRB, "Downloading " + url + "...");
 90 | 
 91 | 		auto req = getURL(url);
 92 | 
 93 | 		if (get<2>(req) != 200)
 94 | 		{
 95 | 			if (get<2>(req) == -1)
 96 | 			{
 97 | 				log(ERR, "Failed to send HTTP request to Mr Looquer for " + host->address + ": " + get<1>(req));
 98 | 			}
 99 | 			else
100 | 			{
101 | 				log(ERR, "Failed to get JSON reply from Mr Looquer for " + host->address + ": HTTP response code was " + to_string(get<2>(req)) + ".");
102 | 			}
103 | 
104 | 			return;
105 | 		}
106 | 
107 | 		json = get<0>(req);
108 | 	}
109 | 
110 | 	// parse the JSON response from Looquer
111 | 
112 | 	istringstream jstr(json);
113 | 	ptree pt;
114 | 
115 | 	try
116 | 	{
117 | 		read_json(jstr, pt);
118 | 	}
119 | 	catch (boost::exception const& ex)
120 | 	{
121 | 		string reason;
122 | 
123 | 		auto exst = dynamic_cast(&ex);
124 | 		if (NULL != exst)
125 | 		{
126 | 			reason = exst->what();
127 | 		}
128 | 		else
129 | 		{
130 | 			reason = diagnostic_information(ex);
131 | 		}
132 | 
133 | 		log(ERR, "Failed to parse JSON response: " + reason);
134 | 
135 | 		return;
136 | 	}
137 | 
138 | 	try
139 | 	{
140 | 		// enumerate results
141 | 
142 | 		for (auto& ptrun : pt.get_child("hits"))
143 | 		{
144 | 			// get basic info of port
145 | 
146 | 			auto jproto = ptrun.second.get("protocol", "");
147 | 			auto jport  = ptrun.second.get("port", "");
148 | 
149 | 			if (jproto.length() == 0)
150 | 			{
151 | 				continue;
152 | 			}
153 | 
154 | 			unsigned short port = static_cast(stoi(jport));
155 | 			IPPROTO proto = IPPROTO_NONE;
156 | 
157 | 			if (jproto == string("tcp"))
158 | 			{
159 | 				proto = IPPROTO_TCP;
160 | 			}
161 | 			else if (jproto == string("udp"))
162 | 			{
163 | 				proto = IPPROTO_UDP;
164 | 			}
165 | 
166 | 			// see if service already exists
167 | 
168 | 			Service* service = nullptr;
169 | 
170 | 			for (auto& serv : *host->services)
171 | 			{
172 | 				if (serv->port == port && serv->protocol == proto)
173 | 				{
174 | 					service = serv;
175 | 					break;
176 | 				}
177 | 			}
178 | 
179 | 			if (service == nullptr)
180 | 			{
181 | 				service = new Service(host->address, port, proto);
182 | 				host->services->push_back(service);
183 | 
184 | 				service->alive = host->alive = true;
185 | 				service->reason = host->reason = AR_ReplyReceived;
186 | 			}
187 | 
188 | 			// get service banner, if any
189 | 
190 | 			auto jdata = ptrun.second.get("banner", "");
191 | 
192 | 			if (jdata.length() != 0 && service->banner.length() < jdata.length())
193 | 			{
194 | 				service->banner = jdata;
195 | 			}
196 | 
197 | 			// get CPEs, if any
198 | 
199 | 			auto jcpe = ptrun.second.get("cpe", "");
200 | 
201 | 			if (jcpe.length() != 0)
202 | 			{
203 | 				service->cpe.push_back(jcpe.substr(5));
204 | 			}
205 | 
206 | 			// save extended port data
207 | 
208 | 			if (service->data == nullptr)
209 | 			{
210 | 				stringstream ss;
211 | 				write_json(ss, ptrun.second, false);
212 | 				service->data = reinterpret_cast(new string(ss.str()));
213 | 			}
214 | 		}
215 | 	}
216 | 	catch (boost::exception const& ex)
217 | 	{
218 | 		string reason;
219 | 
220 | 		auto exst = dynamic_cast(&ex);
221 | 		if (NULL != exst)
222 | 		{
223 | 			reason = exst->what();
224 | 		}
225 | 		else
226 | 		{
227 | 			reason = diagnostic_information(ex);
228 | 		}
229 | 
230 | 		log(ERR, "Failed to process JSON response: " + reason);
231 | 
232 | 		return;
233 | 	}
234 | }
235 | 
236 | LooquerScanner::~LooquerScanner()
237 | {
238 | }
239 | 


--------------------------------------------------------------------------------
/LooquerScanner.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include "Stdafx.h"
 3 | #include "HostScanner.h"
 4 | #include 
 5 | 
 6 | /*!
 7 |  * Implements a passive scanner which returns Mr. Looquer data.
 8 |  */
 9 | class LooquerScanner : public HostScanner
10 | {
11 | public:
12 | 
13 | 	/*!
14 | 	 * Initializes a new instance of this class.
15 | 	 */
16 | 	LooquerScanner() = default;
17 | 
18 | 	/*!
19 | 	 * Initializes a new instance of this class.
20 | 	 *
21 | 	 * \param key API key to use for the requests.
22 | 	 */
23 | 	explicit LooquerScanner(const std::string& key);
24 | 
25 | 	/*!
26 | 	 * Sets the specified API key.
27 | 	 *
28 | 	 * \param key API key to set.
29 | 	 */
30 | 	void SetKey(const std::string& key);
31 | 
32 | 	/*!
33 | 	* Value indicating whether an API key was specified.
34 | 	*
35 | 	* \return true if key is present, otherwise false.
36 | 	*/
37 | 	bool HasKey();
38 | 
39 | 	/*!
40 | 	 * Sets the specified API endpoint location.
41 | 	 *
42 | 	 * \param uri API location to set.
43 | 	 */
44 | 	void SetEndpoint(const std::string& uri);
45 | 
46 | 	/*!
47 | 	 * Value indicating whether this instance is a passive scanner.
48 | 	 * 
49 | 	 * A passive scanner does not actively send packets towards the
50 | 	 * scanned target, it instead uses miscellaneous data sources to
51 | 	 * gather information regarding the target.
52 | 	 * 
53 | 	 * \return true if passive, false if not.
54 | 	 */
55 | 	bool IsPassive() override;
56 | 
57 | 	/*!
58 | 	 * Scans a host to determine service availability.
59 | 	 * 
60 | 	 * \param host Host.
61 | 	 */
62 | 	void Scan(Host* host) override;
63 | 
64 | 	/*!
65 | 	 * Scans a list of hosts to determine service availability.
66 | 	 * 
67 | 	 * \param hosts List of hosts.
68 | 	 */
69 | 	void Scan(Hosts* hosts) override;
70 | 
71 | 	/*!
72 | 	 * Frees up the resources allocated during the lifetime of this instance.
73 | 	 */
74 | 	~LooquerScanner() override;
75 | 
76 | private:
77 | 	
78 | 	/*!
79 | 	 * API key to use for the requests.
80 | 	 */
81 | 	std::string key;
82 | 
83 | 	/*!
84 | 	 * API endpoint location.
85 | 	 */
86 | 	std::string endpoint = "https://mrlooquer.com/api/v1";
87 | 
88 | 	/*!
89 | 	 * Gets the information available on the API for the specified host.
90 | 	 *
91 | 	 * \param host Host.
92 | 	 */
93 | 	void getHostInfo(Host* host);
94 | 
95 | };
96 | 


--------------------------------------------------------------------------------
/NmapScanner.h:
--------------------------------------------------------------------------------
  1 | #pragma once
  2 | #include 
  3 | #include "Stdafx.h"
  4 | #include "HostScanner.h"
  5 | 
  6 | /*!
  7 |  * Provides interoperability with Nmap.
  8 |  */
  9 | class NmapScanner : public HostScanner
 10 | {
 11 | public:
 12 | 
 13 | 	/*!
 14 | 	 * Gets the currently set value for the option key.
 15 | 	 *
 16 | 	 * \param option Option index, see `OPT_*` macros.
 17 | 	 * \param value Pointer to the value to set.
 18 | 	 *
 19 | 	 * \return true if it succeeds, false if it fails.
 20 | 	 */
 21 | 	bool GetOption(int option, void* value);
 22 | 
 23 | 	/*!
 24 | 	 * Sets a specified value for the option key.
 25 | 	 *
 26 | 	 * \param option Option index, see `OPT_*` macros.
 27 | 	 * \param value Pointer to the value to set.
 28 | 	 *
 29 | 	 * \return true if it succeeds, false if it fails.
 30 | 	 */
 31 | 	bool SetOption(int option, void* value);
 32 | 
 33 | 	/*!
 34 | 	 * Value indicating whether this instance is a passive scanner.
 35 | 	 * 
 36 | 	 * A passive scanner does not actively send packets towards the
 37 | 	 * scanned target, it instead uses miscellaneous data sources to
 38 | 	 * gather information regarding the target.
 39 | 	 * 
 40 | 	 * \return true if passive, false if not.
 41 | 	 */
 42 | 	bool IsPassive() override;
 43 | 
 44 | 	/*!
 45 | 	 * Scans a host to determine aliveness.
 46 | 	 *
 47 | 	 * \param host Host.
 48 | 	 */
 49 | 	void Scan(Host* host) override;
 50 | 
 51 | 	/*!
 52 | 	 * Scans a list of hosts to determine aliveness.
 53 | 	 *
 54 | 	 * \param hosts List of hosts.
 55 | 	 */
 56 | 	void Scan(Hosts* hosts) override;
 57 | 
 58 | 	/*!
 59 | 	 * Processes the specified XML output from nmap.
 60 | 	 *
 61 | 	 * \return Reconstructed list of hosts.
 62 | 	 */
 63 | 	Hosts* Process(const std::string& xml);
 64 | 
 65 | 	/*!
 66 | 	 * Gets the version number of the installed nmap executable.
 67 | 	 *
 68 | 	 * \return Version number of nmap.
 69 | 	 */
 70 | 	std::string GetVersion();
 71 | 
 72 | 	/*!
 73 | 	 * Frees up the resources allocated during the lifetime of this instance.
 74 | 	 */
 75 | 	~NmapScanner() override;
 76 | 
 77 | private:
 78 | 
 79 | 	/*!
 80 | 	 * The `-T` option of nmap. Value between 0..5, which maps
 81 | 	 * to the same timeouts as the other scanners within this
 82 | 	 * application. Level 6 will be 5, since 6 is not available.
 83 | 	 */
 84 | 	int delay = 3;
 85 | 
 86 | 	/*!
 87 | 	 * Runs Nmap on the specified hosts.
 88 | 	 *
 89 | 	 * \param hosts List of hosts.
 90 | 	 * \param v6 Whether to turn on IPv6 support.
 91 | 	 *
 92 | 	 * \remarks Turning on IPv6 support means that IPv4 will be turned off.
 93 | 	 *
 94 | 	 * \return XML response from Nmap.
 95 | 	 */
 96 | 	std::string runNmap(Hosts* hosts, bool v6 = false);
 97 | 
 98 | 	/*!
 99 | 	 * Parses the specified XML output and updates matching hosts and services.
100 | 	 *
101 | 	 * \param xml XML response from Nmap.
102 | 	 * \param hosts List of hosts.
103 | 	 * \param append Whether to manipulate the host list or append to it.
104 | 	 */
105 | 	void parseXml(const std::string& xml, Hosts* hosts, bool append = false);
106 | 
107 | };
108 | 


--------------------------------------------------------------------------------
/OperatingSystemIdentifier.cpp:
--------------------------------------------------------------------------------
 1 | #include "OperatingSystemIdentifier.h"
 2 | #include "UbuntuIdentifier.h"
 3 | #include "DebianIdentifier.h"
 4 | #include "EnterpriseLinuxIdentifier.h"
 5 | #include "FedoraIdentifier.h"
 6 | #include "WindowsIdentifier.h"
 7 | 
 8 | using namespace std;
 9 | 
10 | bool OperatingSystemIdentifier::AutoProcess(Host* host)
11 | {
12 | 	// Ubuntu is tried first, since some distributions tag them as
13 | 	// "Debian-Ubuntu", which might trigger a false-positive with
14 | 	// DebianIdentifier, if not ruled out by UbuntuIdentifier first
15 | 
16 | 	static UbuntuIdentifier ubuntu;
17 | 
18 | 	if (ubuntu.Scan(host))
19 | 	{
20 | 		return true;
21 | 	}
22 | 
23 | 	// as OpenSSH in Debian has a DebianBanner feature added, which is
24 | 	// on by default, Debian is the easiest to map based on SSH version
25 | 
26 | 	static DebianIdentifier debian;
27 | 
28 | 	if (debian.Scan(host))
29 | 	{
30 | 		return true;
31 | 	}
32 | 
33 | 	// if everything failed so far, try RHEL/CentOS, however these
34 | 	// cannot be mapped with 100% certainty by the SSH version alone
35 | 
36 | 	static EnterpriseLinuxIdentifier rhel;
37 | 
38 | 	if (rhel.Scan(host))
39 | 	{
40 | 		return true;
41 | 	}
42 | 
43 | 	// lastly try Fedora, however since most packages overlap in
44 | 	// Fedora, the identified distribution may not be completely
45 | 	// accurate, depending on the update/upgrade habits of the admin
46 | 
47 | 	static FedoraIdentifier fedora;
48 | 
49 | 	if (fedora.Scan(host))
50 | 	{
51 | 		return true;
52 | 	}
53 | 
54 | 	// try checking if any Windows-exclusive services are running
55 | 
56 | 	static WindowsIdentifier windows;
57 | 
58 | 	if (windows.Scan(host))
59 | 	{
60 | 		return true;
61 | 	}
62 | 
63 | 	return false;
64 | }
65 | 
66 | string OperatingSystemIdentifier::OpSysString(OpSys opsys)
67 | {
68 | 	static unordered_map opsyses = {
69 | 		{ Unidentified,    "Unidentified" },
70 | 		{ Debian,          "Debian" },
71 | 		{ Ubuntu,          "Ubuntu" },
72 | 		{ EnterpriseLinux, "Red Hat/CentOS" },
73 | 		{ Fedora,          "Fedora" },
74 | 		{ WindowsNT,       "Windows" },
75 | 	};
76 | 
77 | 	auto iter = opsyses.find(opsys);
78 | 
79 | 	return iter != opsyses.end() ? iter->second : "Unkown";
80 | }
81 | 
82 | OperatingSystemIdentifier::~OperatingSystemIdentifier()
83 | {
84 | }
85 | 


--------------------------------------------------------------------------------
/OperatingSystemIdentifier.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include "Stdafx.h"
 3 | #include "Host.h"
 4 | 
 5 | /*!
 6 |  * Represents an operating system identifier.
 7 |  */
 8 | class OperatingSystemIdentifier
 9 | {
10 | public:
11 | 
12 | 	/*!
13 | 	 * Processes the specified host.
14 | 	 * 
15 | 	 * \param host Scanned host.
16 | 	 * 
17 | 	 * \return true if the operating system was identified,
18 | 	 * 		   otherwise false.
19 | 	 */
20 | 	virtual bool Scan(Host* host) = 0;
21 | 
22 | 	/*!
23 | 	 * Tries to processes the specified host with all known implementations of this class.
24 | 	 *
25 | 	 * \param host Scanned host.
26 | 	 * 
27 | 	 * \return true if the operating system was identified,
28 | 	 * 		   otherwise false.
29 | 	 */
30 | 	static bool AutoProcess(Host* host);
31 | 
32 | 	/*!
33 | 	 * Resolves the value of the enum `OpSys` to its textual representation.
34 | 	 *
35 | 	 * \param opsys Enum value.
36 | 	 *
37 | 	 * \return Textual representation.
38 | 	 */
39 | 	static std::string OpSysString(OpSys opsys);
40 | 
41 | 	/*!
42 | 	 * Frees up the resources allocated during the lifetime of this instance.
43 | 	 */
44 | 	virtual ~OperatingSystemIdentifier();
45 | 	
46 | };
47 | 


--------------------------------------------------------------------------------
/PassiveScanner.cpp:
--------------------------------------------------------------------------------
  1 | #include "PassiveScanner.h"
  2 | #include "ShodanScanner.h"
  3 | #include "CensysScanner.h"
  4 | #include "LooquerScanner.h"
  5 | #include 
  6 | 
  7 | using namespace std;
  8 | 
  9 | PassiveScanner::PassiveScanner(const string& shodan_key, const string& censys_auth, const string& looquer_key)
 10 | 	: shodan_key(shodan_key), censys_auth(censys_auth), looquer_key(looquer_key)
 11 | {
 12 | }
 13 | 
 14 | void PassiveScanner::SetShodanKey(const string& key)
 15 | {
 16 | 	shodan_key = key;
 17 | }
 18 | 
 19 | bool PassiveScanner::HasShodanKey()
 20 | {
 21 | 	return !shodan_key.empty();
 22 | }
 23 | 
 24 | void PassiveScanner::SetShodanEndpoint(const string& uri)
 25 | {
 26 | 	shodan_uri = uri;
 27 | }
 28 | 
 29 | void PassiveScanner::SetCensysKey(const string& key)
 30 | {
 31 | 	censys_auth = key;
 32 | }
 33 | 
 34 | bool PassiveScanner::HasCensysKey()
 35 | {
 36 | 	return !censys_auth.empty();
 37 | }
 38 | 
 39 | void PassiveScanner::SetCensysEndpoint(const string& uri)
 40 | {
 41 | 	censys_uri = uri;
 42 | }
 43 | 
 44 | void PassiveScanner::SetLooquerKey(const string& key)
 45 | {
 46 | 	looquer_key = key;
 47 | }
 48 | 
 49 | bool PassiveScanner::HasLooquerKey()
 50 | {
 51 | 	return !looquer_key.empty();
 52 | }
 53 | 
 54 | void PassiveScanner::SetLooquerEndpoint(const string& uri)
 55 | {
 56 | 	looquer_uri = uri;
 57 | }
 58 | 
 59 | bool PassiveScanner::IsPassive()
 60 | {
 61 | 	return true;
 62 | }
 63 | 
 64 | void PassiveScanner::Scan(Host* host)
 65 | {
 66 | 	static ShodanScanner ss(shodan_key);
 67 | 	static CensysScanner cs(censys_auth);
 68 | 	static LooquerScanner ls(looquer_key);
 69 | 
 70 | 	if (!shodan_uri.empty())
 71 | 	{
 72 | 		ss.SetEndpoint(shodan_uri);
 73 | 	}
 74 | 
 75 | 	if (!censys_uri.empty())
 76 | 	{
 77 | 		cs.SetEndpoint(censys_uri);
 78 | 	}
 79 | 
 80 | 	if (!looquer_uri.empty())
 81 | 	{
 82 | 		ls.SetEndpoint(looquer_uri);
 83 | 	}
 84 | 
 85 | 	auto shost = new Host(*host);
 86 | 	auto chost = new Host(*host);
 87 | 	auto lhost = new Host(*host);
 88 | 
 89 | 	auto sf = async(launch::async, [shost]() { ss.Scan(shost); });
 90 | 	auto sc = async(launch::async, [chost]() { cs.Scan(chost); });
 91 | 	auto sl = async(launch::async, [lhost]() { ls.Scan(lhost); });
 92 | 
 93 | 	sf.wait();
 94 | 	sc.wait();
 95 | 	sl.wait();
 96 | 
 97 | 	mergeHosts(shost, host);
 98 | 	mergeHosts(chost, host);
 99 | 	mergeHosts(lhost, host);
100 | 
101 | 	delete shost;
102 | 	delete chost;
103 | 	delete lhost;
104 | }
105 | 
106 | void PassiveScanner::Scan(Hosts* hosts)
107 | {
108 | 	for (auto host : *hosts)
109 | 	{
110 | 		Scan(host);
111 | 	}
112 | }
113 | 
114 | void PassiveScanner::mergeHosts(Host* src, Host* dst)
115 | {
116 | 	if (src->alive && !dst->alive)
117 | 	{
118 | 		dst->alive = src->alive;
119 | 	}
120 | 
121 | 	if (dst->reason == AR_NotScanned || dst->reason == AR_ScanFailed)
122 | 	{
123 | 		dst->reason = src->reason;
124 | 	}
125 | 
126 | 	for (auto srcsrv : *src->services)
127 | 	{
128 | 		Service* dstsrv = nullptr;
129 | 
130 | 		for (auto tmpsrv : *dst->services)
131 | 		{
132 | 			if (tmpsrv->port == srcsrv->port && tmpsrv->protocol == srcsrv->protocol)
133 | 			{
134 | 				dstsrv = tmpsrv;
135 | 				break;
136 | 			}
137 | 		}
138 | 
139 | 		if (dstsrv != nullptr)
140 | 		{
141 | 			mergeServices(srcsrv, dstsrv);
142 | 		}
143 | 		else
144 | 		{
145 | 			dst->services->push_back(new Service(*srcsrv));
146 | 		}
147 | 	}
148 | }
149 | 
150 | void PassiveScanner::mergeServices(Service* src, Service* dst)
151 | {
152 | 	if (src->alive && !dst->alive)
153 | 	{
154 | 		dst->alive = src->alive;
155 | 	}
156 | 
157 | 	if (dst->reason == AR_NotScanned || dst->reason == AR_ScanFailed)
158 | 	{
159 | 		dst->reason = src->reason;
160 | 	}
161 | 
162 | 	if (dst->banner.length() < src->banner.length())
163 | 	{
164 | 		dst->banner = src->banner;
165 | 	}
166 | 
167 | 	if (src->cpe.size() != 0)
168 | 	{
169 | 		dst->cpe.insert(dst->cpe.end(), src->cpe.begin(), src->cpe.end());
170 | 	}
171 | }
172 | 
173 | PassiveScanner::~PassiveScanner()
174 | {
175 | }
176 | 


--------------------------------------------------------------------------------
/PassiveScanner.h:
--------------------------------------------------------------------------------
  1 | #pragma once
  2 | #include "Stdafx.h"
  3 | #include "HostScanner.h"
  4 | #include 
  5 | 
  6 | /*!
  7 |  * Implements a virtual scanner which returns data from all available passive scanners.
  8 |  */
  9 | class PassiveScanner : public HostScanner
 10 | {
 11 | public:
 12 | 
 13 | 	/*!
 14 | 	 * Initializes a new instance of this class.
 15 | 	 */
 16 | 	PassiveScanner() = default;
 17 | 
 18 | 	/*!
 19 | 	 * Initializes a new instance of this class.
 20 | 	 *
 21 | 	 * \param shodan_key Shodan API key to use for the requests.
 22 | 	 * \param censys_auth Censys API username and password to use for the requests.
 23 | 	 * \param looquer_key Mr Looquer API key to use for the requests.
 24 | 	 */
 25 | 	PassiveScanner(const std::string& shodan_key, const std::string& censys_auth, const std::string& looquer_key);
 26 | 
 27 | 	/*!
 28 | 	 * Sets the specified API key for the Shodan scanner.
 29 | 	 *
 30 | 	 * \param key API key to set.
 31 | 	 */
 32 | 	void SetShodanKey(const std::string& key);
 33 | 
 34 | 	/*!
 35 | 	 * Value indicating whether a Shodan API key was specified.
 36 | 	 *
 37 | 	 * \return true if key is present, otherwise false.
 38 | 	 */
 39 | 	bool HasShodanKey();
 40 | 
 41 | 	/*!
 42 | 	 * Sets the specified API endpoint location for the Shodan scanner.
 43 | 	 *
 44 | 	 * \param uri API location to set.
 45 | 	 */
 46 | 	void SetShodanEndpoint(const std::string& uri);
 47 | 
 48 | 	/*!
 49 | 	 * Sets the specified API key for the Censys scanner.
 50 | 	 *
 51 | 	 * \param key API key to set.
 52 | 	 */
 53 | 	void SetCensysKey(const std::string& key);
 54 | 
 55 | 	/*!
 56 | 	 * Value indicating whether a Censys API key was specified.
 57 | 	 *
 58 | 	 * \return true if key is present, otherwise false.
 59 | 	 */
 60 | 	bool HasCensysKey();
 61 | 
 62 | 	/*!
 63 | 	 * Sets the specified API endpoint location for the Censys scanner.
 64 | 	 *
 65 | 	 * \param uri API location to set.
 66 | 	 */
 67 | 	void SetCensysEndpoint(const std::string& uri);
 68 | 
 69 | 	/*!
 70 | 	 * Sets the specified API key for the Looquer scanner.
 71 | 	 *
 72 | 	 * \param key API key to set.
 73 | 	 */
 74 | 	void SetLooquerKey(const std::string& key);
 75 | 
 76 | 	/*!
 77 | 	* Value indicating whether a Looquer API key was specified.
 78 | 	*
 79 | 	* \return true if key is present, otherwise false.
 80 | 	*/
 81 | 	bool HasLooquerKey();
 82 | 
 83 | 	/*!
 84 | 	 * Sets the specified API endpoint location for the Looquer scanner.
 85 | 	 *
 86 | 	 * \param uri API location to set.
 87 | 	 */
 88 | 	void SetLooquerEndpoint(const std::string& uri);
 89 | 
 90 | 	/*!
 91 | 	 * Value indicating whether this instance is a passive scanner.
 92 | 	 * 
 93 | 	 * A passive scanner does not actively send packets towards the
 94 | 	 * scanned target, it instead uses miscellaneous data sources to
 95 | 	 * gather information regarding the target.
 96 | 	 * 
 97 | 	 * \return true if passive, false if not.
 98 | 	 */
 99 | 	bool IsPassive() override;
100 | 
101 | 	/*!
102 | 	 * Scans a host to determine service availability.
103 | 	 * 
104 | 	 * \param host Host.
105 | 	 */
106 | 	void Scan(Host* host) override;
107 | 
108 | 	/*!
109 | 	 * Scans a list of hosts to determine service availability.
110 | 	 * 
111 | 	 * \param hosts List of hosts.
112 | 	 */
113 | 	void Scan(Hosts* hosts) override;
114 | 
115 | 	/*!
116 | 	 * Frees up the resources allocated during the lifetime of this instance.
117 | 	 */
118 | 	~PassiveScanner() override;
119 | 
120 | private:
121 | 	
122 | 	/*!
123 | 	 * Shodan API key to use for the requests.
124 | 	 */
125 | 	std::string shodan_key;
126 | 
127 | 	/*!
128 | 	 * API endpoint location of Shodan.
129 | 	 */
130 | 	std::string shodan_uri;
131 | 
132 | 	/*!
133 | 	 * Censys API username and password to use for the requests.
134 | 	 */
135 | 	std::string censys_auth;
136 | 
137 | 	/*!
138 | 	 * API endpoint location of Censys.
139 | 	 */
140 | 	std::string censys_uri;
141 | 	
142 | 	/*!
143 | 	 * Mr Looquer API key to use for the requests.
144 | 	 */
145 | 	std::string looquer_key;
146 | 
147 | 	/*!
148 | 	 * API endpoint location of Mr Looquer.
149 | 	 */
150 | 	std::string looquer_uri;
151 | 
152 | 	/*!
153 | 	 * Merges two host results.
154 | 	 *
155 | 	 * \param src Scan result to merge.
156 | 	 * \param dst Destination for the merger.
157 | 	 */
158 | 	static void mergeHosts(Host* src, Host* dst);
159 | 
160 | 	/*!
161 | 	 * Merges two service results.
162 | 	 *
163 | 	 * \param src Service to merge.
164 | 	 * \param dst Destination for the merger.
165 | 	 */
166 | 	static void mergeServices(Service* src, Service* dst);
167 | 
168 | };
169 | 


--------------------------------------------------------------------------------
/ProtocolTokenizer.cpp:
--------------------------------------------------------------------------------
 1 | #include "ProtocolTokenizer.h"
 2 | #include "HttpTokenizer.h"
 3 | #include "ThreeDigitTokenizer.h"
 4 | 
 5 | using namespace std;
 6 | 
 7 | vector ProtocolTokenizer::AutoTokenize(const string& banner)
 8 | {
 9 | 	// primitive implementation for now, later perhaps register implementations
10 | 	// into an ordered_map by protocol popularity and call CanTokenize() on each
11 | 
12 | 	static HttpTokenizer http;
13 | 
14 | 	if (http.CanTokenize(banner))
15 | 	{
16 | 		return http.Tokenize(banner);
17 | 	}
18 | 
19 | 	static ThreeDigitTokenizer tdt;
20 | 
21 | 	if (tdt.CanTokenize(banner))
22 | 	{
23 | 		return tdt.Tokenize(banner);
24 | 	}
25 | 
26 | 	// if no protocol-specific tokenizer is available,
27 | 	// return the whole string as a token
28 | 
29 | 	return vector { banner };
30 | }
31 | 
32 | ProtocolTokenizer::~ProtocolTokenizer()
33 | {
34 | }
35 | 


--------------------------------------------------------------------------------
/ProtocolTokenizer.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include "Stdafx.h"
 3 | #include 
 4 | 
 5 | /*!
 6 |  * Represents a lightweight protocol parser to be used for tokenizing service banners.
 7 |  */
 8 | class ProtocolTokenizer
 9 | {
10 | public:
11 | 	
12 | 	/*!
13 | 	 * Determines whether the specified service banner can be tokenized using this
14 | 	 * instance of the protocol parser.
15 | 	 * 
16 | 	 * \param banner Service banner.
17 | 	 * 
18 | 	 * \return Value indicating ability to process.
19 | 	 */
20 | 	virtual bool CanTokenize(const std::string& banner) = 0;
21 | 
22 | 	/*!
23 | 	 * Processes the specified service banner.
24 | 	 * 
25 | 	 * \param banner Service banner.
26 | 	 * 
27 | 	 * \return Extracted tokens.
28 | 	 */
29 | 	virtual std::vector Tokenize(const std::string& banner) = 0;
30 | 
31 | 	/*!
32 | 	 * Tries to processes the specified service banner with all known implementations
33 | 	 * of this class, in decreasing order of protocol popularity.
34 | 	 *
35 | 	 * \param banner Service banner.
36 | 	 * 
37 | 	 * \return Extracted tokens.
38 | 	 */
39 | 	static std::vector AutoTokenize(const std::string& banner);
40 | 
41 | 	/*!
42 | 	 * Frees up the resources allocated during the lifetime of this instance.
43 | 	 */
44 | 	virtual ~ProtocolTokenizer();
45 | 	
46 | };
47 | 


--------------------------------------------------------------------------------
/Service.cpp:
--------------------------------------------------------------------------------
 1 | #include "Service.h"
 2 | #include 
 3 | 
 4 | using namespace std;
 5 | 
 6 | Service::Service(const Service& service)
 7 | 	: address(service.address), port(service.port), protocol(service.protocol),
 8 | 	  alive(service.alive), reason(service.reason), banner(service.banner),
 9 | 	  cpe(service.cpe), date(service.date), host(service.host), data(service.data)
10 | {
11 | }
12 | 
13 | Service::Service(const string& address, unsigned short port, IPPROTO protocol)
14 | 	: address(address), port(port), protocol(protocol),
15 | 	  alive(false), reason(AR_NotScanned), banner(""), cpe(), date(), host(nullptr), data(nullptr)
16 | {
17 | }
18 | 
19 | string Service::ReasonString(AliveReason reason)
20 | {
21 | 	static unordered_map reasons = {
22 | 		{ AR_ScanFailed,        "ScanFailed" },
23 | 		{ AR_NotScanned,        "NotScanned" },
24 | 		{ AR_InProgress,        "InProgress" },
25 | 		{ AR_InProgress_Extra,  "InProgressExtra" },
26 | 		{ AR_TimedOut,          "TimedOut" },
27 | 		{ AR_IcmpUnreachable,   "IcmpUnreachable" },
28 | 		{ AR_ReplyReceived,     "ReplyReceived" }
29 | 	};
30 | 
31 | 	auto iter = reasons.find(reason);
32 | 
33 | 	return iter != reasons.end() ? iter->second : "Unkown";
34 | }
35 | 
36 | Service::~Service()
37 | {
38 | }
39 | 
40 | void freeServices(Services& services)
41 | {
42 | 	for (auto& service : services)
43 | 	{
44 | 		delete service;
45 | 	}
46 | 
47 | 	services.clear();
48 | }
49 | 


--------------------------------------------------------------------------------
/Service.h:
--------------------------------------------------------------------------------
  1 | #pragma once
  2 | #include 
  3 | #include 
  4 | #include "Stdafx.h"
  5 | 
  6 | /*!
  7 |  * List of reasons which caused the host to be determined as it is.
  8 |  */
  9 | typedef enum
 10 | {
 11 | 
 12 | 	/*!
 13 | 	 * Error occurred during scan.
 14 | 	 */
 15 | 	AR_ScanFailed = -1,
 16 | 
 17 | 	/*!
 18 | 	 * Service hasn't yet been scanned.
 19 | 	 */
 20 | 	AR_NotScanned = 0,
 21 | 
 22 | 	/*!
 23 | 	 * Service is being scanned.
 24 | 	 */
 25 | 	AR_InProgress = 1,
 26 | 
 27 | 	/*!
 28 | 	 * Service is alive, but still being scanned.
 29 | 	 */
 30 | 	AR_InProgress_Extra = 2,
 31 | 
 32 | 	/*!
 33 | 	 * Service didn't reply within specified timeframe.
 34 | 	 */
 35 | 	AR_TimedOut = 3,
 36 | 
 37 | 	/*!
 38 | 	 * ICMP Destination Unreachable received.
 39 | 	 */
 40 | 	AR_IcmpUnreachable = 4,
 41 | 
 42 | 	/*!
 43 | 	 * Service replied within specified timeframe.
 44 | 	 */
 45 | 	AR_ReplyReceived = 5
 46 | 
 47 | } AliveReason;
 48 | 
 49 | /*!
 50 |  * Represents a service in the form of an IP/port.
 51 |  */
 52 | class Service
 53 | {
 54 | public:
 55 | 
 56 | 	/*!
 57 | 	 * Remote address.
 58 | 	 */
 59 | 	std::string address;
 60 | 
 61 | 	/*!
 62 | 	 * Remote port.
 63 | 	 */
 64 | 	unsigned short port;
 65 | 
 66 | 	/*!
 67 | 	 * Remote protocol.
 68 | 	 */
 69 | 	IPPROTO protocol;
 70 | 	
 71 | 	/*!
 72 | 	 * Whether the service is alive at this host.
 73 | 	 */
 74 | 	bool alive;
 75 | 	
 76 | 	/*!
 77 | 	 * Reason for the value specified in `alive`.
 78 | 	 * Negative values are errors, positive values are scanner-dependent reasons.
 79 | 	 */
 80 | 	AliveReason reason;
 81 | 
 82 | 	/*!
 83 | 	 * Service banner, if any.
 84 | 	 */
 85 | 	std::string banner;
 86 | 
 87 | 	/*!
 88 | 	 * CPE names of the service.
 89 | 	 */
 90 | 	std::vector cpe;
 91 | 
 92 | 	/*!
 93 | 	 * Time of last packet sent to this service.
 94 | 	 */
 95 | 	std::chrono::time_point date;
 96 | 
 97 | 	/*!
 98 | 	 * Parent host of this service.
 99 | 	 */
100 | 	class Host* host;
101 | 
102 | 	/*!
103 | 	 * Object store reserved for the scanner.
104 | 	 */
105 | 	void* data;
106 | 
107 | 	/*!
108 | 	 * Copies the specified instance.
109 | 	 *
110 | 	 * \param service Instance to copy.
111 | 	 */
112 | 	Service(const Service& service);
113 | 
114 | 	/*!
115 | 	 * Creates a new instance of this type.
116 | 	 * 
117 | 	 * \param address Remote address.
118 | 	 * \param port Remote port.
119 | 	 * \param protocol Remote protocol, otherwise TCP.
120 | 	 */
121 | 	Service(const std::string& address, unsigned short port, IPPROTO protocol = IPPROTO_TCP);
122 | 
123 | 	/*!
124 | 	 * Resolves the value of the enum `AliveReason` to its textual representation.
125 | 	 *
126 | 	 * \param reason Enum value.
127 | 	 *
128 | 	 * \return Textual representation.
129 | 	 */
130 | 	static std::string ReasonString(AliveReason reason);
131 | 
132 | 	/*!
133 | 	 * Frees up the resources allocated during the lifetime of this instance.
134 | 	 */
135 | 	~Service();
136 | 
137 | };
138 | 
139 | /*!
140 |  * Represents a list of services.
141 |  */
142 | typedef std::vector Services;
143 | 
144 | /*!
145 |  * Frees up the structures allocated within this array.
146 |  *
147 |  * \param services List of services.
148 |  */
149 | void freeServices(Services& services);
150 | 


--------------------------------------------------------------------------------
/ServiceRegexMatcher.cpp:
--------------------------------------------------------------------------------
  1 | #include "ServiceRegexMatcher.h"
  2 | #include "DataReader.h"
  3 | #include 
  4 | #include 
  5 | 
  6 | using namespace std;
  7 | using namespace boost;
  8 | 
  9 | vector ServiceRegexMatcher::regexes = vector();
 10 | 
 11 | vector ServiceRegexMatcher::Scan(const string& banner, bool processVendor)
 12 | {
 13 | 	if (regexes.size() == 0)
 14 | 	{
 15 | 		loadRegexes();
 16 | 	}
 17 | 
 18 | 	vector matches;
 19 | 
 20 | 	if (banner.length() == 0)
 21 | 	{
 22 | 		return matches;
 23 | 	}
 24 | 
 25 | 	static regex bsrgx("\\$(\\d+)", regex::perl);
 26 | 	static regex vtrgx("^v(?:er(?:sion)?)? *(?=\\d)", regex::perl | regex::icase);
 27 | 
 28 | 	for (auto& rgx : regexes)
 29 | 	{
 30 | 		match_results match;
 31 | 
 32 | 		auto found = false;
 33 | 
 34 | 		try
 35 | 		{
 36 | 			found = regex_search(banner, match, rgx.regex, match_single_line);
 37 | 		}
 38 | 		catch (boost::exception const&)
 39 | 		{
 40 | 			continue;
 41 | 		}
 42 | 
 43 | 		if (!found)
 44 | 		{
 45 | 			continue;
 46 | 		}
 47 | 
 48 | 		auto cpe = rgx.cpe;
 49 | 
 50 | 		auto cpeHasRgx = cpe.find('$') != string::npos;
 51 | 		auto verHasRgx = rgx.version.length() > 0 && rgx.version.find('$') != string::npos;
 52 | 
 53 | 		if (verHasRgx && !cpeHasRgx)
 54 | 		{
 55 | 			cpe += ":" + rgx.version;
 56 | 			cpeHasRgx = true;
 57 | 		}
 58 | 
 59 | 		// replace regular expression groups to their captured values in the version field
 60 | 
 61 | 		if (cpeHasRgx)
 62 | 		{
 63 | 			sregex_iterator bsit(cpe.begin(), cpe.end(), bsrgx);
 64 | 			sregex_iterator end;
 65 | 
 66 | 			string cpe2(cpe);
 67 | 
 68 | 			for (; bsit != end; ++bsit)
 69 | 			{
 70 | 				auto nums = (*bsit)[1].str();
 71 | 				auto numi = stoi(nums);
 72 | 				auto vals = regex_replace(match[numi].str(), vtrgx, "");
 73 | 
 74 | 				replace_first(cpe2, "$" + nums, vals);
 75 | 			}
 76 | 
 77 | 			cpe = cpe2;
 78 | 		}
 79 | 
 80 | 		// find vendor patch level, if any and asked
 81 | 
 82 | 		string patch;
 83 | 
 84 | 		if (processVendor)
 85 | 		{
 86 | 			smatch what;
 87 | 			regex verfind("^(?:[^:]+:){3}.*?(?[-+~_])(?[^:$;\\s\\)\\/]+)");
 88 | 
 89 | 			if (regex_search(cpe, what, verfind))
 90 | 			{
 91 | 				// remove vendor patch level from CPE version
 92 | 
 93 | 				patch = what["tag"].str();
 94 | 				cpe   = cpe.substr(0, std::distance(cpe.cbegin(), what["sep"].first));
 95 | 			}
 96 | 		}
 97 | 
 98 | 		// strip any irrelevant data
 99 | 
100 | 		auto fs = cpe.find(' ');
101 | 
102 | 		if (fs != string::npos)
103 | 		{
104 | 			cpe = cpe.substr(0, fs);
105 | 		}
106 | 
107 | 		// append vendor patch level separately, if any
108 | 
109 | 		if (processVendor && !patch.empty())
110 | 		{
111 | 			cpe += ";" + patch;
112 | 		}
113 | 
114 | 		matches.push_back(cpe);
115 | 	}
116 | 
117 | 	return matches;
118 | }
119 | 
120 | vector ServiceRegexMatcher::GetRegexes()
121 | {
122 | 	if (regexes.size() == 0)
123 | 	{
124 | 		loadRegexes();
125 | 	}
126 | 
127 | 	return regexes;
128 | }
129 | 
130 | void ServiceRegexMatcher::loadRegexes()
131 | {
132 | 	static mutex mtx;
133 | 	auto locked = mtx.try_lock();
134 | 	if (!locked)
135 | 	{
136 | 		// wait until running parser finishes before returning
137 | 		lock_guard guard(mtx);
138 | 		return;
139 | 	}
140 | 
141 | 	// open regexes file
142 | 
143 | 	DataReader dr;
144 | 
145 | 	if (!dr.OpenEnv("cpe-regex"))
146 | 	{
147 | 		log(WRN, "Regexes database was not found!");
148 | 
149 | 		mtx.unlock();
150 | 		return;
151 | 	}
152 | 
153 | 	unsigned short ptype, pver;
154 | 
155 | 	dr.Read(ptype);
156 | 	dr.Read(pver);
157 | 
158 | 	if (ptype != 15)
159 | 	{
160 | 		log(WRN, "Regexes database type is incorrect.");
161 | 
162 | 		mtx.unlock();
163 | 		return;
164 | 	}
165 | 
166 | 	if (pver != 1)
167 | 	{
168 | 		log(WRN, "Regexes database version is not supported.");
169 | 
170 | 		mtx.unlock();
171 | 		return;
172 | 	}
173 | 
174 | 	unsigned int pnum;
175 | 	dr.Read(pnum);
176 | 
177 | 	for (auto i = 0u; i < pnum; i++)
178 | 	{
179 | 		ServiceRegex rgx;
180 | 
181 | 		auto rgex   = dr.ReadString();
182 | 		rgx.cpe     = dr.ReadString();
183 | 		rgx.product = dr.ReadString();
184 | 		rgx.version = dr.ReadString();
185 | 
186 | 		if (rgx.cpe.length() == 0)
187 | 		{
188 | 			continue;
189 | 		}
190 | 
191 | 		try
192 | 		{
193 | 			rgx.regex = move(regex(rgex, regex::perl));
194 | 		}
195 | 		catch (runtime_error&)
196 | 		{
197 | 			continue;
198 | 		}
199 | 
200 | 		regexes.push_back(rgx);
201 | 	}
202 | 
203 | 	// clean up
204 | 
205 | 	mtx.unlock();
206 | }
207 | 
208 | ServiceRegexMatcher::~ServiceRegexMatcher()
209 | {
210 | }
211 | 


--------------------------------------------------------------------------------
/ServiceRegexMatcher.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include "Stdafx.h"
 3 | #include "BannerProcessor.h"
 4 | #include 
 5 | #include 
 6 | 
 7 | /*!
 8 |  * Represents a service identifier entry.
 9 |  */
10 | struct ServiceRegex
11 | {
12 | 
13 | 	/*!
14 | 	 * Regular expression to match against service banner.
15 | 	 */
16 | 	boost::regex regex;
17 | 
18 | 	/*!
19 | 	 * CPE name of the matched service.
20 | 	 */
21 | 	std::string cpe;
22 | 
23 | 	/*!
24 | 	 * Product name of the matched service.
25 | 	 */
26 | 	std::string product;
27 | 
28 | 	/*!
29 | 	 * Version number of the matched service.
30 | 	 */
31 | 	std::string version;
32 | 
33 | };
34 | 
35 | /*!
36 |  * Implements a bulk regular expression matcher against service banners.
37 |  */
38 | class ServiceRegexMatcher : public BannerProcessor
39 | {
40 | public:
41 | 	
42 | 	/*!
43 | 	 * Processes the specified service banner.
44 | 	 * 
45 | 	 * \param banner Service banner.
46 | 	 * \param processVendor Whether to process vendor level patches appended to the end of the version
47 | 	 *                      number. This removes the patch level from the CPE version and appends it to
48 | 	 *                      the end via a semicolon separator.
49 | 	 * 
50 | 	 * \return Matching CPE entries.
51 | 	 */
52 | 	std::vector Scan(const std::string& banner, bool processVendor = true) override;
53 | 
54 | 	/*!
55 | 	 * Gets the regular expressions.
56 | 	 *
57 | 	 * \return List of regular expressions.
58 | 	 */
59 | 	static std::vector GetRegexes();
60 | 
61 | 	/*!
62 | 	 * Frees up the resources allocated during the lifetime of this instance.
63 | 	 */
64 | 	~ServiceRegexMatcher() override;
65 | 
66 | private:
67 | 
68 | 	/*!
69 | 	 * List of regular expressions with their associated product info.
70 | 	 */
71 | 	static std::vector regexes;
72 | 
73 | 	/*!
74 | 	 * Loads the regex database from external file.
75 | 	 */
76 | 	static void loadRegexes();
77 | 
78 | };
79 | 


--------------------------------------------------------------------------------
/ServiceScanner.cpp:
--------------------------------------------------------------------------------
 1 | #include "ServiceScanner.h"
 2 | #include 
 3 | 
 4 | using namespace std;
 5 | 
 6 | void ServiceScanner::DumpResults(Services* services)
 7 | {
 8 | 	for (auto service : *services)
 9 | 	{
10 | 		if (!service->alive && (service->reason == AR_TimedOut || service->reason == AR_NotScanned || service->reason == AR_IcmpUnreachable))
11 | 		{
12 | 			continue;
13 | 		}
14 | 
15 | 		log(service->alive ? MSG : DBG, service->address + ":" + to_string(service->port) + " is " + (service->alive ? "open" : "closed") + " (" + Service::ReasonString(service->reason) + ")");
16 | 
17 | 		if (service->banner.length() > 0)
18 | 		{
19 | 			stringstream ss;
20 | 			ss << " -> ";
21 | 
22 | 			for (auto i = 0u; i < service->banner.length(); i++)
23 | 			{
24 | 				if (service->banner[i] == '\r') continue;
25 | 
26 | 				if (service->banner[i] == '\n')
27 | 				{
28 | 					if ((service->banner.length() - i) > 3)
29 | 					{
30 | 						ss << endl << " -> ";
31 | 					}
32 | 					else
33 | 					{
34 | 						ss << " ";
35 | 					}
36 | 				}
37 | 				else if (service->banner[i] >= ' ' && service->banner[i] <= '~')
38 | 				{
39 | 					ss << service->banner[i];
40 | 				}
41 | 				else
42 | 				{
43 | 					ss << ".";
44 | 				}
45 | 			}
46 | 
47 | 			log(VRB, ss.str(), false);
48 | 		}
49 | 	}
50 | }
51 | 
52 | ServiceScanner::~ServiceScanner()
53 | {
54 | }
55 | 


--------------------------------------------------------------------------------
/ServiceScanner.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include "Stdafx.h"
 3 | #include "Service.h"
 4 | 
 5 | /*!
 6 |  * Timeout option for the individual scans in milliseconds.
 7 |  * Default value is 3000ms for most scanners.
 8 |  */
 9 | #define OPT_TIMEOUT 1
10 | 
11 | /*!
12 |  * Number of milliseconds to wait between sending packets to the same host.
13 |  * Default value is 100ms for most scanners.
14 |  */
15 | #define OPT_DELAY 2
16 | 
17 | /*!
18 |  * Boolean value indicating whether to wait for and grab the service banner.
19 |  * Default value is true for scanners that support it.
20 |  */
21 | #define OPT_BANNER 5
22 | 
23 | /*!
24 |  * Represents a port scanner.
25 |  */
26 | class ServiceScanner
27 | {
28 | public:
29 | 
30 | 	/*!
31 | 	 * Get a task which scans a service to determine its aliveness.
32 | 	 *
33 | 	 * \param service Service to scan.
34 | 	 * 
35 | 	 * \return Task to scan the specified service.
36 | 	 */
37 | 	virtual void* GetTask(Service* service) = 0;
38 | 
39 | 	/*!
40 | 	 * Gets the currently set value for the option key.
41 | 	 *
42 | 	 * \param option Option index, see `OPT_*` macros.
43 | 	 * \param value Pointer to the value to set.
44 | 	 *
45 | 	 * \return true if it succeeds, false if it fails.
46 | 	 */
47 | 	virtual bool GetOption(int option, void* value) = 0;
48 | 
49 | 	/*!
50 | 	 * Sets a specified value for the option key.
51 | 	 *
52 | 	 * \param option Option index, see `OPT_*` macros.
53 | 	 * \param value Pointer to the value to set.
54 | 	 *
55 | 	 * \return true if it succeeds, false if it fails.
56 | 	 */
57 | 	virtual bool SetOption(int option, void* value) = 0;
58 | 
59 | 	/*!
60 | 	 * Dumps the scan results into the standard output.
61 | 	 *
62 | 	 * \param services List of services.
63 | 	 */
64 | 	static void DumpResults(Services* services);
65 | 
66 | 	/*!
67 | 	 * Frees up the resources allocated during the lifetime of this instance.
68 | 	 */
69 | 	virtual ~ServiceScanner();
70 | 	
71 | };
72 | 


--------------------------------------------------------------------------------
/ServiceScannerFactory.cpp:
--------------------------------------------------------------------------------
 1 | #include "Stdafx.h"
 2 | #include "ServiceScannerFactory.h"
 3 | #include "TcpScanner.h"
 4 | #include "UdpScanner.h"
 5 | #include "IcmpPinger.h"
 6 | 
 7 | ServiceScanner* ServiceScannerFactory::Get(IPPROTO protocol)
 8 | {
 9 | 	switch (protocol)
10 | 	{
11 | 	case IPPROTO_TCP:
12 | 		return new TcpScanner();
13 | 
14 | 	case IPPROTO_UDP:
15 | 		return new UdpScanner();
16 | 
17 | 	case IPPROTO_ICMP:
18 | 	case IPPROTO_ICMPV6:
19 | 		return new IcmpPinger();
20 | 
21 | 	default:
22 | 		return nullptr;
23 | 	}
24 | }
25 | 


--------------------------------------------------------------------------------
/ServiceScannerFactory.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include "Stdafx.h"
 3 | #include "ServiceScanner.h"
 4 | 
 5 | /*
 6 |  * Implements the factory pattern for retrieving port scanner instances.
 7 |  */
 8 | class ServiceScannerFactory
 9 | {
10 | public:
11 | 
12 | 	/*!
13 | 	 * Gets a scanner instance which supports the specified criteria.
14 | 	 *
15 | 	 * \param protocol IP protocol.
16 | 	 *
17 | 	 * \return Instance to be used for scanning, or nullptr if the
18 | 	 * 		   specified protocol is not supported.
19 | 	 */
20 | 	static ServiceScanner* Get(IPPROTO protocol);
21 | 
22 | };
23 | 


--------------------------------------------------------------------------------
/ShodanScanner.cpp:
--------------------------------------------------------------------------------
  1 | #include "ShodanScanner.h"
  2 | #include "Utils.h"
  3 | #include 
  4 | #include 
  5 | #include 
  6 | #include 
  7 | #include 
  8 | #include 
  9 | #include 
 10 | 
 11 | using namespace std;
 12 | using namespace boost;
 13 | 
 14 | namespace fs = boost::filesystem;
 15 | 
 16 | ShodanScanner::ShodanScanner(const string& key)
 17 | 	: key(key)
 18 | {
 19 | }
 20 | 
 21 | void ShodanScanner::SetKey(const string& key)
 22 | {
 23 | 	this->key = key;
 24 | }
 25 | 
 26 | bool ShodanScanner::HasKey()
 27 | {
 28 | 	return !key.empty();
 29 | }
 30 | 
 31 | void ShodanScanner::SetEndpoint(const string& uri)
 32 | {
 33 | 	endpoint = uri;
 34 | }
 35 | 
 36 | bool ShodanScanner::IsPassive()
 37 | {
 38 | 	return true;
 39 | }
 40 | 
 41 | void ShodanScanner::Scan(Host* host)
 42 | {
 43 | 	getHostInfo(host);
 44 | }
 45 | 
 46 | void ShodanScanner::Scan(Hosts* hosts)
 47 | {
 48 | 	for (auto host : *hosts)
 49 | 	{
 50 | 		getHostInfo(host);
 51 | 	}
 52 | }
 53 | 
 54 | void ShodanScanner::getHostInfo(Host* host)
 55 | {
 56 | 	using property_tree::write_json;
 57 | 	using property_tree::ptree;
 58 | 
 59 | 	if (key.length() == 0)
 60 | 	{
 61 | 		return;
 62 | 	}
 63 | 
 64 | 	string json;
 65 | 
 66 | 	if (starts_with(endpoint, "file://"))
 67 | 	{
 68 | 		auto path = fs::path(endpoint.substr(7)) / fs::path(host->address);
 69 | 
 70 | 		log(VRB, "Reading " + path.string() + "...");
 71 | 
 72 | 		ifstream fs(path.string());
 73 | 
 74 | 		if (!fs.good())
 75 | 		{
 76 | 			log(ERR, "Failed to open JSON file for reading: " + path.string());
 77 | 			return;
 78 | 		}
 79 | 
 80 | 		stringstream buf;
 81 | 		buf << fs.rdbuf();
 82 | 
 83 | 		json = buf.str();
 84 | 	}
 85 | 	else
 86 | 	{
 87 | 		auto url = endpoint + "/host/" + host->address + "?key=" + key;
 88 | 
 89 | 		log(VRB, "Downloading " + url + "...");
 90 | 
 91 | 		auto req = getURL(url);
 92 | 
 93 | 		if (get<2>(req) != 200)
 94 | 		{
 95 | 			if (get<2>(req) == -1)
 96 | 			{
 97 | 				log(ERR, "Failed to send HTTP request to Shodan for " + host->address + ": " + get<1>(req));
 98 | 			}
 99 | 			else
100 | 			{
101 | 				log(ERR, "Failed to get JSON reply from Shodan for " + host->address + ": HTTP response code was " + to_string(get<2>(req)) + ".");
102 | 			}
103 | 
104 | 			return;
105 | 		}
106 | 
107 | 		json = get<0>(req);
108 | 	}
109 | 
110 | 	// parse the JSON response from Shodan
111 | 
112 | 	istringstream jstr(json);
113 | 	ptree pt;
114 | 
115 | 	try
116 | 	{
117 | 		read_json(jstr, pt);
118 | 	}
119 | 	catch (boost::exception const& ex)
120 | 	{
121 | 		string reason;
122 | 
123 | 		auto exst = dynamic_cast(&ex);
124 | 		if (NULL != exst)
125 | 		{
126 | 			reason = exst->what();
127 | 		}
128 | 		else
129 | 		{
130 | 			reason = diagnostic_information(ex);
131 | 		}
132 | 
133 | 		log(ERR, "Failed to parse JSON response: " + reason);
134 | 
135 | 		return;
136 | 	}
137 | 
138 | 	try
139 | 	{
140 | 		// enumerate ports
141 | 
142 | 		for (auto& ptrun : pt.get_child("data"))
143 | 		{
144 | 			// get basic info of port
145 | 
146 | 			auto jproto = ptrun.second.get("transport", "");
147 | 			auto jport  = ptrun.second.get("port", "");
148 | 
149 | 			if (jproto.length() == 0)
150 | 			{
151 | 				continue;
152 | 			}
153 | 
154 | 			unsigned short port = static_cast(stoi(jport));
155 | 			IPPROTO proto = IPPROTO_NONE;
156 | 
157 | 			if (jproto == string("tcp"))
158 | 			{
159 | 				proto = IPPROTO_TCP;
160 | 			}
161 | 			else if (jproto == string("udp"))
162 | 			{
163 | 				proto = IPPROTO_UDP;
164 | 			}
165 | 
166 | 			auto service = new Service(host->address, port, proto);
167 | 			host->services->push_back(service);
168 | 
169 | 			service->alive  = host->alive  = true;
170 | 			service->reason = host->reason = AR_ReplyReceived;
171 | 
172 | 			// get service banner, if any
173 | 
174 | 			auto jdata = ptrun.second.get("data", "");
175 | 			
176 | 			if (jdata.length() != 0)
177 | 			{
178 | 				service->banner = jdata;
179 | 			}
180 | 
181 | 			// get HTML body, if any
182 | 
183 | 			auto jhtml = ptrun.second.get("html", "");
184 | 
185 | 			if (jhtml.length() != 0)
186 | 			{
187 | 				service->banner += jhtml;
188 | 			}
189 | 
190 | 			// get CPEs, if any
191 | 
192 | 			auto jcpes = ptrun.second.get_child_optional("cpe");
193 | 
194 | 			if (jcpes.is_initialized() && jcpes->size() != 0)
195 | 			{
196 | 				for (auto& jcpe : *jcpes)
197 | 				{
198 | 					auto cpe = jcpe.second.get_value();
199 | 
200 | 					if (cpe.length() != 0)
201 | 					{
202 | 						service->cpe.push_back(cpe.substr(5));
203 | 					}
204 | 				}
205 | 			}
206 | 
207 | 			// save extended port data
208 | 
209 | 			stringstream ss;
210 | 			write_json(ss, ptrun.second, false);
211 | 			service->data = reinterpret_cast(new string(ss.str()));
212 | 		}
213 | 	}
214 | 	catch (boost::exception const& ex)
215 | 	{
216 | 		string reason;
217 | 
218 | 		auto exst = dynamic_cast(&ex);
219 | 		if (NULL != exst)
220 | 		{
221 | 			reason = exst->what();
222 | 		}
223 | 		else
224 | 		{
225 | 			reason = diagnostic_information(ex);
226 | 		}
227 | 
228 | 		log(ERR, "Failed to process JSON response: " + reason);
229 | 
230 | 		return;
231 | 	}
232 | }
233 | 
234 | ShodanScanner::~ShodanScanner()
235 | {
236 | }
237 | 


--------------------------------------------------------------------------------
/ShodanScanner.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include "Stdafx.h"
 3 | #include "HostScanner.h"
 4 | #include 
 5 | 
 6 | /*!
 7 |  * Implements a passive scanner which returns Shodan data.
 8 |  */
 9 | class ShodanScanner : public HostScanner
10 | {
11 | public:
12 | 
13 | 	/*!
14 | 	 * Initializes a new instance of this class.
15 | 	 */
16 | 	ShodanScanner() = default;
17 | 
18 | 	/*!
19 | 	 * Initializes a new instance of this class.
20 | 	 *
21 | 	 * \param key API key to use for the requests.
22 | 	 */
23 | 	explicit ShodanScanner(const std::string& key);
24 | 
25 | 	/*!
26 | 	 * Sets the specified API key.
27 | 	 *
28 | 	 * \param key API key to set.
29 | 	 */
30 | 	void SetKey(const std::string& key);
31 | 
32 | 	/*!
33 | 	 * Value indicating whether an API key was specified.
34 | 	 *
35 | 	 * \return true if key is present, otherwise false.
36 | 	 */
37 | 	bool HasKey();
38 | 
39 | 	/*!
40 | 	 * Sets the specified API endpoint location.
41 | 	 *
42 | 	 * \param uri API location to set.
43 | 	 */
44 | 	void SetEndpoint(const std::string& uri);
45 | 
46 | 	/*!
47 | 	 * Value indicating whether this instance is a passive scanner.
48 | 	 * 
49 | 	 * A passive scanner does not actively send packets towards the
50 | 	 * scanned target, it instead uses miscellaneous data sources to
51 | 	 * gather information regarding the target.
52 | 	 * 
53 | 	 * \return true if passive, false if not.
54 | 	 */
55 | 	bool IsPassive() override;
56 | 
57 | 	/*!
58 | 	 * Scans a host to determine service availability.
59 | 	 * 
60 | 	 * \param host Host.
61 | 	 */
62 | 	void Scan(Host* host) override;
63 | 
64 | 	/*!
65 | 	 * Scans a list of hosts to determine service availability.
66 | 	 * 
67 | 	 * \param hosts List of hosts.
68 | 	 */
69 | 	void Scan(Hosts* hosts) override;
70 | 
71 | 	/*!
72 | 	 * Frees up the resources allocated during the lifetime of this instance.
73 | 	 */
74 | 	~ShodanScanner() override;
75 | 
76 | private:
77 | 	
78 | 	/*!
79 | 	 * API key to use for the requests.
80 | 	 */
81 | 	std::string key;
82 | 
83 | 	/*!
84 | 	 * API endpoint location.
85 | 	 */
86 | 	std::string endpoint = "https://api.shodan.io/shodan";
87 | 
88 | 	/*!
89 | 	 * Gets the information available on the API for the specified host.
90 | 	 *
91 | 	 * \param host Host.
92 | 	 */
93 | 	void getHostInfo(Host* host);
94 | 
95 | };
96 | 


--------------------------------------------------------------------------------
/Stdafx.h:
--------------------------------------------------------------------------------
  1 | #pragma once
  2 | 
  3 | #if _WIN32
  4 | 
  5 | 	#define Windows 1
  6 | 
  7 | #elif __unix__ || __APPLE__ || __MACH__
  8 | 
  9 | 	#define Unix 1
 10 | 
 11 | 	#if __linux__
 12 | 		#define Linux 1
 13 | 	#else
 14 | 		#define BSD 1
 15 | 	#endif
 16 | 
 17 | #endif
 18 | 
 19 | #if Windows
 20 | 
 21 | 	#include 
 22 | 	#include 
 23 | 	#include 
 24 | 
 25 | 	#define sleep(t) Sleep(t)
 26 | 	#define popen(c,m) _popen(c,m)
 27 | 	#define pclose(s) _pclose(s)
 28 | 
 29 | 	#define PATH_SEPARATOR "\\"
 30 | 
 31 | 	#pragma comment(lib, "ws2_32.lib")
 32 | 	#pragma comment(lib, "iphlpapi.lib")
 33 | 
 34 | #elif Unix
 35 | 
 36 | 	#include 
 37 | 	#include 
 38 | 	#include 
 39 | 	#include 
 40 | 	#include 
 41 | 	#include 
 42 | 
 43 | 	#define SD_RECEIVE SHUT_RD
 44 | 	#define SD_SEND SHUT_WR
 45 | 	#define SD_BOTH SHUT_RDWR
 46 | 
 47 | 	#define ioctlsocket(s,c,a) ioctl(s,c,a)
 48 | 	#define sleep(t) usleep(t*1000)
 49 | 	#define closesocket(s) close(s)
 50 | 
 51 | 	#define PATH_SEPARATOR "/"
 52 | 
 53 | 	typedef int SOCKET;
 54 | 	typedef int IPPROTO;
 55 | 	typedef struct timeval TIMEVAL;
 56 | 
 57 | #endif
 58 | 
 59 | #include 
 60 | 
 61 | /*!
 62 |  * Represents an error message.
 63 |  */
 64 | #define ERR 5
 65 | 
 66 | /*!
 67 |  * Represents a warning message.
 68 |  */
 69 | #define WRN 4
 70 | 
 71 | /*!
 72 |  * Represents a message with default severity.
 73 |  */
 74 | #define MSG 3
 75 | 
 76 | /*!
 77 |  * Represents a verbose message.
 78 |  * This is only visible with the --verbose switch.
 79 |  */
 80 | #define VRB 2
 81 | 
 82 | /*!
 83 |  * Represents a debug message.
 84 |  * This is only visible with the --debug switch.
 85 |  */
 86 | #define DBG 1
 87 | 
 88 | /*!
 89 |  * Represents an internal debug message.
 90 |  * This is only visible with the --internal switch.
 91 |  */
 92 | #define INT 0
 93 | 
 94 | /*!
 95 |  * Logs a message through the implemented provider.
 96 |  *
 97 |  * \param level Message severity level.
 98 |  * \param msg Message to log.
 99 |  * \param format Value indicating whether to enable formatting.
100 |  */
101 | void log(int level, const std::string& msg, bool format = true);
102 | 
103 | /*!
104 |  * Logs a message with default severity level.
105 |  *
106 |  * \param msg Message to log.
107 |  * \param format Value indicating whether to enable formatting.
108 |  */
109 | inline void log(const std::string& msg, bool format = true)
110 | {
111 | 	log(MSG, msg, format);
112 | }
113 | 


--------------------------------------------------------------------------------
/TaskQueueRunner.cpp:
--------------------------------------------------------------------------------
 1 | #include "TaskQueueRunner.h"
 2 | 
 3 | using namespace std;
 4 | using namespace boost;
 5 | 
 6 | TaskQueueRunner::TaskQueueRunner(int capacity, int batch)
 7 | 	: batch(batch), running(batch), pending(capacity)
 8 | {
 9 | }
10 | 
11 | void TaskQueueRunner::Enqueue(void* task)
12 | {
13 | 	pending.push(task);
14 | }
15 | 
16 | void TaskQueueRunner::Run()
17 | {
18 | 	// add the requested number of tasks from `pending` to `running`
19 | 
20 | 	for (auto i = 0; i < batch; i++)
21 | 	{
22 | 		void* task;
23 | 
24 | 		if (pending.pop(task))
25 | 		{
26 | 			running.push(task);
27 | 		}
28 | 		else
29 | 		{
30 | 			break;
31 | 		}
32 | 	}
33 | 
34 | 	// loop until both queues are empty
35 | 
36 | 	while (!pending.empty() || !running.empty())
37 | 	{
38 | 		void* task;
39 | 
40 | 		if (!running.pop(task))
41 | 		{
42 | 			break;
43 | 		}
44 | 
45 | 		// cast the task back to its original type and evaluate it
46 | 
47 | 		auto func = PTR_TO_MFN(task);
48 | 		auto eval = (*func)();
49 | 
50 | 		// since these pointers were created with `new std::function()`
51 | 		// we are the ones responsible to delete it after use
52 | 
53 | 		delete func;
54 | 
55 | 		// if the function evaluation returned a new function pointer,
56 | 		// put that one back into the queue. otherwise pop a new one
57 | 		// from the `pending` queue.
58 | 
59 | 		if (eval != nullptr)
60 | 		{
61 | 			running.push(eval);
62 | 		}
63 | 		else
64 | 		{
65 | 			void* next;
66 | 
67 | 			if (pending.pop(next))
68 | 			{
69 | 				running.push(next);
70 | 			}
71 | 		}
72 | 	}
73 | }
74 | 
75 | void TaskQueueRunner::QuickScan(ServiceScanner& scanner, Services& services)
76 | {
77 | 	TaskQueueRunner tqr(services.size(), 65535);
78 | 
79 | 	for (auto service : services)
80 | 	{
81 | 		tqr.Enqueue(scanner.GetTask(service));
82 | 	}
83 | 
84 | 	tqr.Run();
85 | }
86 | 
87 | TaskQueueRunner::~TaskQueueRunner()
88 | {
89 | }
90 | 


--------------------------------------------------------------------------------
/TaskQueueRunner.h:
--------------------------------------------------------------------------------
  1 | #pragma once
  2 | #include 
  3 | #include 
  4 | #include "Service.h"
  5 | #include "ServiceScanner.h"
  6 | 
  7 | /*!
  8 |  * A macro that defines a pointer to a member function with arguments bound.
  9 |  * 
 10 |  * The function can be called by re-casting to std::function via `PTR_TO_MFN`.
 11 |  * 
 12 |  * Since the function instance is allocated on the heap, you are responsible
 13 |  * for deleting the pointer (re-casted with `PTR_TO_MFN`!) after use in order
 14 |  * to avoid memory leaks.
 15 |  *
 16 |  * \param fn The member function to call.
 17 |  * \param inst The instance on which to invoke the call.
 18 |  * \param ... Variable arguments bound to the function.
 19 |  * 
 20 |  * \return `void*` pointing to callable member function.
 21 |  */
 22 | #define MFN_TO_PTR(fn, inst, ...) reinterpret_cast(new std::function(std::bind(&fn, inst, __VA_ARGS__)))
 23 | 
 24 | /*!
 25 |  * A macro that defines pointer to the underlying std::function of the `MFN_TO_PTR` call.
 26 |  * 
 27 |  * This pointer should be deleted after use, since the deletion of `void*` pointers
 28 |  * is undefined behaviour and most compilers will complain about it.
 29 |  * 
 30 |  * \param ptr The pointer created with `MFN_TO_PTR`.
 31 |  * 
 32 |  * \return `std::function` pointer.
 33 |  */
 34 | #define PTR_TO_MFN(ptr) reinterpret_cast*>(ptr)
 35 | 
 36 | /*!
 37 |  * Implements a task runner which uses two queues in order to differentiate between
 38 |  * running and pending tasks. Since network scan tasks mostly consist of waiting
 39 |  * for I/O operation results, it makes much more sense to multiplex the tasks in
 40 |  * this way.
 41 |  * 
 42 |  * A task gets into the `pending` queue. From there, the task runner will pick N
 43 |  * tasks and place them to the `running` queue. The `running` queue is looped,
 44 |  * the tasks are executed until a task is finished. When a task has finished,
 45 |  * it gets removed from the `running` queue and a new task will take its place
 46 |  * from the `pending` queue. The task runner stops when both queues are empty,
 47 |  * as it was inteded to receive all tasks before execution, as such it will not
 48 |  * block to wait for new tasks.
 49 |  */
 50 | class TaskQueueRunner
 51 | {
 52 | public:
 53 | 
 54 | 	/*!
 55 | 	 * Initializes a new instance of this class.
 56 | 	 *
 57 | 	 * \param capacity The total number of tasks to allocate space for.
 58 | 	 * \param batch The number of tasks to execute in one batch.
 59 | 	 */
 60 | 	TaskQueueRunner(int capacity, int batch);
 61 | 
 62 | 	/*!
 63 | 	 * Enqueues a task for execution.
 64 | 	 *
 65 | 	 * \param task The task to be enqueued.
 66 | 	 */
 67 | 	void Enqueue(void* task);
 68 | 
 69 | 	/*!
 70 | 	 * Executes the tasks in the queue.
 71 | 	 * 
 72 | 	 * This call is blocking and will return when the task queue is exhausted.
 73 | 	 */
 74 | 	void Run();
 75 | 
 76 | 	/*!
 77 | 	 * Helper function to easily scan a list of services with a specified scanner.
 78 | 	 *
 79 | 	 * \param scanner The scanner instance to invoke on the services.
 80 | 	 * \param services The list of services to scan.
 81 | 	 */
 82 | 	static void QuickScan(ServiceScanner& scanner, Services& services);
 83 | 
 84 | 	/*!
 85 | 	 * Frees up the resources allocated during the lifetime of this instance.
 86 | 	 */
 87 | 	~TaskQueueRunner();
 88 | 
 89 | private:
 90 | 
 91 | 	/*!
 92 | 	 * The number of tasks to execute in one batch.
 93 | 	 */
 94 | 	int batch;
 95 | 
 96 | 	/*!
 97 | 	 * The queue of tasks currently being executed and are waiting for the result
 98 | 	 * of an I/O operation.
 99 | 	 */
100 | 	boost::lockfree::queue running;
101 | 
102 | 	/*!
103 | 	 * The queue of tasks which are pending execution.
104 | 	 */
105 | 	boost::lockfree::queue pending;
106 | 
107 | };
108 | 


--------------------------------------------------------------------------------
/TcpScanner.h:
--------------------------------------------------------------------------------
  1 | #pragma once
  2 | #include 
  3 | #include "Stdafx.h"
  4 | #include "ServiceScanner.h"
  5 | 
  6 | /*!
  7 |  * Represents internal scan data for the TCP scanner.
  8 |  */
  9 | struct TcpScanData
 10 | {
 11 | 
 12 | 	/*!
 13 | 	 * Active non-blocking socket.
 14 | 	 */
 15 | 	SOCKET socket;
 16 | 
 17 | 	/*!
 18 | 	 * File descriptor set for writability.
 19 | 	 */
 20 | 	fd_set* fdset;
 21 | 
 22 | 	/*!
 23 | 	 * Expiration time of the current operation.
 24 | 	 */
 25 | 	std::chrono::time_point timeout;
 26 | 
 27 | 	/*!
 28 | 	 * Number of probes sent to the service.
 29 | 	 */
 30 | 	int probes;
 31 | 
 32 | };
 33 | 
 34 | /*!
 35 |  * Implements an active TCP port scanner.
 36 |  * 
 37 |  * This will try to initiate the three-way handshake with all the requested services.
 38 |  * It is not a stealthy method, and does not include any trickery to bypass firewalls.
 39 |  */
 40 | class TcpScanner : public ServiceScanner
 41 | {
 42 | public:
 43 | 
 44 | 	/*!
 45 | 	 * Gets the currently set value for the option key.
 46 | 	 *
 47 | 	 * \param option Option index, see `OPT_*` macros.
 48 | 	 * \param value Pointer to the value to set.
 49 | 	 *
 50 | 	 * \return true if it succeeds, false if it fails.
 51 | 	 */
 52 | 	bool GetOption(int option, void* value) override;
 53 | 
 54 | 	/*!
 55 | 	 * Sets a specified value for the option key.
 56 | 	 *
 57 | 	 * \param option Option index, see `OPT_*` macros.
 58 | 	 * \param value Pointer to the value to set.
 59 | 	 *
 60 | 	 * \return true if it succeeds, false if it fails.
 61 | 	 */
 62 | 	bool SetOption(int option, void* value) override;
 63 | 	
 64 | 	/*!
 65 | 	 * Get a task which scans a service to determine its aliveness.
 66 | 	 *
 67 | 	 * \param service Service to scan.
 68 | 	 * 
 69 | 	 * \return Task to scan the specified service.
 70 | 	 */
 71 | 	void* GetTask(Service* service) override;
 72 | 	
 73 | 	/*!
 74 | 	 * Frees up the resources allocated during the lifetime of this instance.
 75 | 	 */
 76 | 	~TcpScanner() override;
 77 | 
 78 | private:
 79 | 	
 80 | 	/*!
 81 | 	 * Number of milliseconds to wait for connections to finish.
 82 | 	 */
 83 | 	unsigned long timeout = 3000;
 84 | 	
 85 | 	/*!
 86 | 	 * Number of milliseconds to wait between packets sent to the same host.
 87 | 	 */
 88 | 	unsigned long delay = 100;
 89 | 
 90 | 	/*!
 91 | 	 * Indicates whether to wait for and grab service banners.
 92 | 	 */
 93 | 	bool grabBanner = true;
 94 | 
 95 | 	/*!
 96 | 	 * Initializes the socket and starts the non-blocking connection.
 97 | 	 *
 98 | 	 * \param service Service.
 99 | 	 * 
100 | 	 * \return Next task, or `nullptr` if failed to initialize socket.
101 | 	 */
102 | 	void* initSocket(Service* service);
103 | 
104 | 	/*!
105 | 	 * Collects the result of the socket connection.
106 | 	 *
107 | 	 * \param service Service.
108 | 	 *
109 | 	 * \return Same task if no data received yet, otherwise next task to
110 | 	 * 		   read banner, or `nullptr` if failed to read from socket.
111 | 	 */
112 | 	void* pollSocket(Service* service);
113 | 
114 | 	/*!
115 | 	 * Reads the banner from the specified service.
116 | 	 * This requires that the service have a connected socket.
117 | 	 *
118 | 	 * \param service Service.
119 | 	 *
120 | 	 * \return Same task if no data received yet, or `nullptr` if succeeded in
121 | 	 * 		   reading the banner or socket disconnected while trying to do so.
122 | 	 */
123 | 	void* readBanner(Service* service);
124 | 
125 | 	/*!
126 | 	 * Sends a protocol probe to the specified service.
127 | 	 * This requires that the service have a connected socket.
128 | 	 *
129 | 	 * \param service Service.
130 | 	 *
131 | 	 * \return Previous task to re-try reading the service banner, or `nullptr`
132 | 	 * 		   if socket disconnected while trying to send packet.
133 | 	 */
134 | 	void* sendProbe(Service* service);
135 | 
136 | };
137 | 


--------------------------------------------------------------------------------
/ThreeDigitTokenizer.cpp:
--------------------------------------------------------------------------------
  1 | #include "ThreeDigitTokenizer.h"
  2 | #include 
  3 | 
  4 | using namespace std;
  5 | using namespace boost;
  6 | 
  7 | bool ThreeDigitTokenizer::CanTokenize(const string& banner)
  8 | {
  9 | 	// the three-digit protocol parser is not specific to a single protocol, and
 10 | 	// can handle SMTP, NNTP and FTP amongst others. since there is no exact
 11 | 	// specification on how to provide the server information (e.g. Server header
 12 | 	// in HTTP) for these protocols, this tokenizer will just try to filter
 13 | 	// "non-success" (outside of 200-299) messages and clean them up some.
 14 | 	// current implementation "can tokenize" if the service banner starts with
 15 | 	// a response code between 200-599 followed by space or dash plus more text.
 16 | 
 17 | 	static regex rgx("^[2-5]\\d{2}[- ](?!$)", regex::perl);
 18 | 
 19 | 	try
 20 | 	{
 21 | 		return regex_search(banner, rgx);
 22 | 	}
 23 | 	catch (boost::exception&)
 24 | 	{
 25 | 		return false;
 26 | 	}
 27 | }
 28 | 
 29 | vector ThreeDigitTokenizer::Tokenize(const string& banner)
 30 | {
 31 | 	vector lines;
 32 | 
 33 | 	// try extracting tokens around the "ESMTP" word in messages with the status 220
 34 | 
 35 | 	static regex s1rgx("^2[02]0[- ][A-Za-z0-9\\.\\-_:]+([^\\r\\n]*?E?SMTP[^\\r\\n]*?)(?: *\\(|\\b(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun|ready)\\b|$)", regex::perl | regex::icase);
 36 | 
 37 | 	sregex_token_iterator s1it(banner.begin(), banner.end(), s1rgx, 1);
 38 | 	sregex_token_iterator end;
 39 | 
 40 | 	for (; s1it != end; ++s1it)
 41 | 	{
 42 | 		lines.push_back((*s1it).str());
 43 | 	}
 44 | 
 45 | 	if (lines.size() != 0)
 46 | 	{
 47 | 		return lines;
 48 | 	}
 49 | 
 50 | 	// loosen previous regex by removing the hostname removal part
 51 | 
 52 | 	static regex s2rgx("^2[02]0[- ][^\\r\\n]*?((?:Microsoft *)?E?SMTP[^\\r\\n]*?)(?: *\\(|\\b(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun|ready)\\b|$)", regex::perl | regex::icase);
 53 | 
 54 | 	sregex_token_iterator s2it(banner.begin(), banner.end(), s2rgx, 1);
 55 | 
 56 | 	for (; s2it != end; ++s2it)
 57 | 	{
 58 | 		lines.push_back((*s2it).str());
 59 | 	}
 60 | 
 61 | 	if (lines.size() != 0)
 62 | 	{
 63 | 		return lines;
 64 | 	}
 65 | 
 66 | 	// loosen it further and add additional services
 67 | 
 68 | 	static regex s3rgx("^2[02]0[- ]([^\\r\\n]*(?:E?SMTP|SNPP|NNTP|FTP)[^\\r\\n]*)$", regex::perl | regex::icase);
 69 | 
 70 | 	sregex_token_iterator s3it(banner.begin(), banner.end(), s3rgx, 1);
 71 | 
 72 | 	for (; s3it != end; ++s3it)
 73 | 	{
 74 | 		lines.push_back((*s3it).str());
 75 | 	}
 76 | 
 77 | 	if (lines.size() != 0)
 78 | 	{
 79 | 		return lines;
 80 | 	}
 81 | 
 82 | 	// if all else fails, return all informational/success lines
 83 | 
 84 | 	static regex s4rgx("^2[02]0[- ]([^\\r\\n]*)$", regex::perl);
 85 | 
 86 | 	sregex_token_iterator s4it(banner.begin(), banner.end(), s4rgx, 1);
 87 | 
 88 | 	for (; s4it != end; ++s4it)
 89 | 	{
 90 | 		lines.push_back((*s4it).str());
 91 | 	}
 92 | 
 93 | 	if (lines.size() != 0)
 94 | 	{
 95 | 		return lines;
 96 | 	}
 97 | 
 98 | 	// if that fails as well, it means the service banner only had error messages
 99 | 	// or the protocol was erroneously identified as being compatible with this
100 | 	// tokenizer; in this case, just return the original service banner.
101 | 
102 | 	return vector { banner };
103 | }
104 | 
105 | ThreeDigitTokenizer::~ThreeDigitTokenizer()
106 | {
107 | }
108 | 


--------------------------------------------------------------------------------
/ThreeDigitTokenizer.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include "Stdafx.h"
 3 | #include "ProtocolTokenizer.h"
 4 | 
 5 | /*!
 6 |  * Implements a lightweight parser for protocols which use three-digit response codes, such as SMTP, NNTP and FTP.
 7 |  */
 8 | class ThreeDigitTokenizer : public ProtocolTokenizer
 9 | {
10 | public:
11 | 	
12 | 	/*!
13 | 	 * Determines whether the specified service banner can be tokenized using this
14 | 	 * instance of the protocol parser.
15 | 	 * 
16 | 	 * \param banner Service banner.
17 | 	 * 
18 | 	 * \return Value indicating ability to process.
19 | 	 */
20 | 	bool CanTokenize(const std::string& banner) override;
21 | 
22 | 	/*!
23 | 	 * Processes the specified service banner.
24 | 	 * 
25 | 	 * \param banner Service banner.
26 | 	 * 
27 | 	 * \return Extracted tokens.
28 | 	 */
29 | 	std::vector Tokenize(const std::string& banner) override;
30 | 
31 | 	/*!
32 | 	 * Frees up the resources allocated during the lifetime of this instance.
33 | 	 */
34 | 	~ThreeDigitTokenizer() override;
35 | 
36 | };
37 | 


--------------------------------------------------------------------------------
/UbuntuIdentifier.cpp:
--------------------------------------------------------------------------------
  1 | #include "UbuntuIdentifier.h"
  2 | #include 
  3 | #include 
  4 | #include 
  5 | #include 
  6 | 
  7 | using namespace std;
  8 | using namespace boost;
  9 | 
 10 | // https://wiki.ubuntu.com/Releases
 11 | const unordered_map UbuntuIdentifier::VersionNames = unordered_map {
 12 | 	{ "xenial",   16.04 }, // lts
 13 | 	{ "wily",     15.10 },
 14 | 	{ "vivid",    15.04 },
 15 | 	{ "utpic",    14.10 },
 16 | 	{ "trusty",   14.04 }, // lts
 17 | 	{ "saucy",    13.10 },
 18 | 	{ "raring",   13.04 },
 19 | 	{ "quantal",  12.10 },
 20 | 	{ "precise",  12.04 }, // lts
 21 | 	{ "oneiric",  11.10 },
 22 | 	{ "natty",    11.04 },
 23 | 	{ "maverick", 10.10 },
 24 | 	{ "lucid",    10.04 }, // lts
 25 | 	{ "karmik",   9.10 },
 26 | 	{ "jaunty",   9.04 },
 27 | 	{ "intrepid", 8.10 },
 28 | 	{ "hardy",    8.04 }, // lts
 29 | 	{ "dapper",   6.06 }, // lts
 30 | };
 31 | 
 32 | // compiled by browsing changelogs at https://launchpad.net/ubuntu/+source/openssh
 33 | const unordered_map UbuntuIdentifier::BundledVersions = unordered_map {
 34 | 	{ "7.2p2",   16.04 },
 35 | 	{ "6.9p1",   15.10 },
 36 | 	{ "6.7p1",   15.04 },
 37 | 	{ "6.6p1",   14.10 },
 38 | 	{ "6.6.1p1", 14.04 }, // .4
 39 | 	{ "5.9p1",   14.04 },
 40 | 	{ "5.3p1",   10.04 },
 41 | 	{ "4.7p1",   8.04 },
 42 | 	{ "4.2p1",   6.06 },
 43 | };
 44 | 
 45 | const unordered_set UbuntuIdentifier::LtsVersions = unordered_set{
 46 | 	16.04, 14.04, 12.04, 10.04, 8.04, 6.06
 47 | };
 48 | 
 49 | bool UbuntuIdentifier::Scan(Host* host)
 50 | {
 51 | 	auto isDeb = false;
 52 | 	string sshVer;
 53 | 	optional debVer, secUpd;
 54 | 
 55 | 	// check if any SSH services are available
 56 | 
 57 | 	// ver -> OpenSSH version
 58 | 	// deb -> "Debian/Ubuntu" tag, if not present, below groups will not match
 59 | 	// ver2 -> patch number
 60 | 	// tag -> "ubuntu" tag in the version number
 61 | 	// debsec -> if tag also matched, the installed security patch
 62 | 	// tag2 -> "ubuntu" tag when the verbosity is turned off
 63 | 	static regex sshbnr("OpenSSH_(?[^\\s$]+)(?:\\s*(?:Debian|Ubuntu)\\-(?\\d+)(?ubuntu)(?\\d+(?:\\.\\d+)?)|\\s*(?Ubuntu))?", regex::icase);
 64 | 	
 65 | 	// match any "ubuntu" tag
 66 | 	static regex debtag("\\bubuntu\\b", regex::icase);
 67 | 
 68 | 	// replace "7.2p1" to "7.2:p1" in the CPE
 69 | 	static regex cpever("(\\d)p(\\d+)", regex::icase);
 70 | 	static string cpepatch = "\\1:p\\2";
 71 | 
 72 | 	for (auto service : *host->services)
 73 | 	{
 74 | 		if (service->banner.empty())
 75 | 		{
 76 | 			continue;
 77 | 		}
 78 | 
 79 | 		smatch sm;
 80 | 
 81 | 		// if the service is not SSH, just check if there is an "Ubuntu" tag in it anywhere
 82 | 
 83 | 		if (!starts_with(service->banner, "SSH-2.0-OpenSSH_"))
 84 | 		{
 85 | 			if (regex_search(service->banner, sm, debtag))
 86 | 			{
 87 | 				isDeb = true;
 88 | 			}
 89 | 
 90 | 			continue;
 91 | 		}
 92 | 
 93 | 		// if the service is SSH, special handling follows
 94 | 
 95 | 		if (!regex_search(service->banner, sm, sshbnr))
 96 | 		{
 97 | 			continue;
 98 | 		}
 99 | 
100 | 		// while we're at it, append the CPE name
101 | 
102 | 		sshVer = sm["ver"].str();
103 | 
104 | 		service->cpe.push_back("a:openbsd:openssh:" + regex_replace(sshVer, cpever, cpepatch));
105 | 
106 | 		// if there is an "Ubuntu" tag in the banner, this host runs Ubuntu for sure
107 | 		
108 | 		if (sm["tag"].matched || sm["tag2"].matched)
109 | 		{
110 | 			isDeb = true;
111 | 		}
112 | 
113 | 		// check if security version is present
114 | 
115 | 		if (sm["debsec"].matched)
116 | 		{
117 | 			secUpd = stod(sm["debsec"].str());
118 | 		}
119 | 
120 | 		// try to deduce Ubuntu distribution based on the OpenSSH version
121 | 
122 | 		if (!sshVer.empty() && !debVer.is_initialized())
123 | 		{
124 | 			auto ver = sshVer;
125 | 
126 | 			trim(ver);
127 | 			to_lower(ver);
128 | 
129 | 			auto bver = BundledVersions.find(ver);
130 | 
131 | 			if (bver != BundledVersions.end())
132 | 			{
133 | 				debVer = (*bver).second;
134 | 			}
135 | 		}
136 | 	}
137 | 
138 | 	// save information
139 | 	
140 | 	if (isDeb)
141 | 	{
142 | 		string cpe = "o:canonical:ubuntu_linux";
143 | 
144 | 		host->opSys = OpSys::Ubuntu;
145 | 		
146 | 		if (debVer.is_initialized())
147 | 		{
148 | 			cpe += ":" + to_string(debVer.get());
149 | 
150 | 			host->osVer = debVer.get();
151 | 
152 | 			if (LtsVersions.find(debVer.get()) != LtsVersions.end())
153 | 			{
154 | 				cpe += "-:lts";
155 | 			}
156 | 		}
157 | 
158 | 		host->cpe.push_back(cpe);
159 | 	}
160 | 
161 | 	return isDeb;
162 | }
163 | 
164 | UbuntuIdentifier::~UbuntuIdentifier()
165 | {
166 | }
167 | 


--------------------------------------------------------------------------------
/UbuntuIdentifier.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include "Stdafx.h"
 3 | #include "OperatingSystemIdentifier.h"
 4 | #include 
 5 | #include 
 6 | 
 7 | /*!
 8 |  * Implements functionality for identifying Ubuntu based on service banners.
 9 |  */
10 | class UbuntuIdentifier : public OperatingSystemIdentifier
11 | {
12 | public:
13 | 
14 | 	/*!
15 | 	 * Ubuntu distribution names mapped to their version numbers.
16 | 	 */
17 | 	static const std::unordered_map VersionNames;
18 | 
19 | 	/*!
20 | 	 * OpenSSH version numbers mapped to the Ubuntu version they came with.
21 | 	 */
22 | 	static const std::unordered_map BundledVersions;
23 | 
24 | 	/*!
25 | 	 * List of Long Term Support versions.
26 | 	 */
27 | 	static const std::unordered_set LtsVersions;
28 | 	
29 | 	/*!
30 | 	 * Processes the specified host.
31 | 	 * 
32 | 	 * \param host Scanned host.
33 | 	 * 
34 | 	 * \return true if the operating system was identified,
35 | 	 * 		   otherwise false.
36 | 	 */
37 | 	bool Scan(Host* host) override;
38 | 
39 | 	/*!
40 | 	 * Frees up the resources allocated during the lifetime of this instance.
41 | 	 */
42 | 	~UbuntuIdentifier() override;
43 | 
44 | };
45 | 


--------------------------------------------------------------------------------
/UbuntuLookup.cpp:
--------------------------------------------------------------------------------
  1 | #include "UbuntuLookup.h"
  2 | #include "UbuntuIdentifier.h"
  3 | #include "Utils.h"
  4 | #include 
  5 | #include 
  6 | #include 
  7 | #include 
  8 | 
  9 | using namespace std;
 10 | using namespace boost;
 11 | 
 12 | unordered_map UbuntuLookup::FindVulnerability(const string& cve, OpSys distrib, double ver)
 13 | {
 14 | 	unordered_map vuln;
 15 | 
 16 | 	if (!ValidateCVE(cve))
 17 | 	{
 18 | 		log(ERR, "Specified CVE identifier '" + cve + "' is not syntactically valid.");
 19 | 		return vuln;
 20 | 	}
 21 | 
 22 | 	if (distrib != Ubuntu)
 23 | 	{
 24 | 		log(ERR, "Specified distribution is not supported by this instance.");
 25 | 		return vuln;
 26 | 	}
 27 | 
 28 | 	auto resp = getURL("https://people.canonical.com/~ubuntu-security/cve/" + cve.substr(4, 4) + "/" + cve + ".html");
 29 | 
 30 | 	if (get<2>(resp) != 200)
 31 | 	{
 32 | 		if (get<2>(resp) == -1)
 33 | 		{
 34 | 			log(ERR, "Failed to send HTTP request: " + get<1>(resp));
 35 | 		}
 36 | 		else
 37 | 		{
 38 | 			log(ERR, "Failed to get reply: HTTP response code was " + to_string(get<2>(resp)) + ".");
 39 | 		}
 40 | 
 41 | 		return vuln;
 42 | 	}
 43 | 
 44 | 	auto html = get<0>(resp);
 45 | 
 46 | 	// pkg -> name of the package
 47 | 	// dist -> distribution (xenial, trusty, etc)
 48 | 	// status -> vulnerability status (safe, vuln)
 49 | 	// ver -> if fixed, version which fixes the vulnerability
 50 | 	static regex tblrgx("href=\"[^l]+launchpad\\.net\\/ubuntu\\/(?[^\\/]+)\\/[^\\/]+\\/(?[^\"]+)\">[^<]+<\\/a>[^<]*<\\/td>[^\"]+)\">[^<]+<\\/span>(?:\\s*\\((?:\\d:)?(?[^\\)]+))?", regex::icase);
 51 | 
 52 | 	sregex_iterator srit(html.begin(), html.end(), tblrgx);
 53 | 	sregex_iterator end;
 54 | 
 55 | 	for (; srit != end; ++srit)
 56 | 	{
 57 | 		auto m = *srit;
 58 | 
 59 | 		auto pkg  = m["pkg"].str();
 60 | 		auto dist = m["dist"].str();
 61 | 		auto sts  = m["status"].str();
 62 | 		auto vers = m["ver"].str();
 63 | 
 64 | 		auto dver = UbuntuIdentifier::VersionNames.find(dist);
 65 | 
 66 | 		if (ver == 0 || (dver != UbuntuIdentifier::VersionNames.end() && (*dver).second == ver))
 67 | 		{
 68 | 			vuln.emplace(pkg, sts == "safe" ? vers : "");
 69 | 		}
 70 | 	}
 71 | 
 72 | 	return vuln;
 73 | }
 74 | 
 75 | vector> UbuntuLookup::GetChangelog(const string& pkg, OpSys distrib, double ver)
 76 | {
 77 | 	vector> updates;
 78 | 
 79 | 	if (distrib != Ubuntu)
 80 | 	{
 81 | 		log(ERR, "Specified distribution is not supported by this instance.");
 82 | 		return updates;
 83 | 	}
 84 | 
 85 | 	string dver = "xenial";
 86 | 
 87 | 	if (ver != 0)
 88 | 	{
 89 | 		for (auto& name : UbuntuIdentifier::VersionNames)
 90 | 		{
 91 | 			if (name.second == ver)
 92 | 			{
 93 | 				dver = name.first;
 94 | 				break;
 95 | 			}
 96 | 		}
 97 | 	}
 98 | 
 99 | 	auto dpkg = pkg;
100 | 
101 | 	if (dpkg == "openssh")
102 | 	{
103 | 		dpkg = "openssh-server";
104 | 	}
105 | 
106 | 	auto resp = getURL("http://packages.ubuntu.com/" + dver + "/" + dpkg);
107 | 
108 | 	if (get<2>(resp) != 200)
109 | 	{
110 | 		if (get<2>(resp) == -1)
111 | 		{
112 | 			log(ERR, "Failed to send HTTP request: " + get<1>(resp));
113 | 		}
114 | 		else
115 | 		{
116 | 			log(ERR, "Failed to get reply: HTTP response code was " + to_string(get<2>(resp)) + ".");
117 | 		}
118 | 
119 | 		return updates;
120 | 	}
121 | 
122 | 	// url -> location of the latest package changelog
123 | 	static regex chlrgx("href=\"(?[^\"]*\\/changelog)\">Ubuntu Changelog<\\/a>", regex::icase);
124 | 
125 | 	smatch chlurl;
126 | 
127 | 	if (!regex_search(get<0>(resp), chlurl, chlrgx))
128 | 	{
129 | 		log(ERR, "Failed get changelog location for the package " + dpkg + ".");
130 | 		return updates;
131 | 	}
132 | 
133 | 	resp = getURL(chlurl["url"].str());
134 | 
135 | 	if (get<2>(resp) != 200)
136 | 	{
137 | 		if (get<2>(resp) == -1)
138 | 		{
139 | 			log(ERR, "Failed to send HTTP request: " + get<1>(resp));
140 | 		}
141 | 		else
142 | 		{
143 | 			log(ERR, "Failed to get reply: HTTP response code was " + to_string(get<2>(resp)) + ".");
144 | 		}
145 | 
146 | 		return updates;
147 | 	}
148 | 
149 | 	// pkg -> name of the package
150 | 	// ver -> version number of the package
151 | 	static regex verrgx("^(?[a-z0-9\\-\\._]+)\\s+\\((?:\\d:)?(?[^\\)]+)\\)", regex::icase);
152 | 
153 | 	// date -> publication date of the package
154 | 	static regex datrgx("^ -- .*?(?(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun),\\s+\\d+.+)", regex::icase);
155 | 
156 | 	string pver;
157 | 	istringstream iss(get<0>(resp));
158 | 
159 | 	for (string line; getline(iss, line); )
160 | 	{
161 | 		smatch verm, datm;
162 | 
163 | 		if (regex_search(line, verm, verrgx))
164 | 		{
165 | 			pver = verm["ver"].str();
166 | 		}
167 | 		else if (regex_search(line, datm, datrgx) && !pver.empty())
168 | 		{
169 | 			updates.push_back(make_pair(pver, dateToUnix(datm["date"].str())));
170 | 		}
171 | 	}
172 | 
173 | 	return updates;
174 | }
175 | 
176 | string UbuntuLookup::GetUpgradeCommand(const unordered_set& pkgs, OpSys distrib, double ver)
177 | {
178 | 	ignore_unused(distrib);
179 | 	ignore_unused(ver);
180 | 
181 | 	if (pkgs.empty())
182 | 	{
183 | 		return "";
184 | 	}
185 | 
186 | 	string cmd = "sudo apt-get install --only-upgrade";
187 | 
188 | 	for (auto& pkg : pkgs)
189 | 	{
190 | 		cmd += " " + pkg;
191 | 	}
192 | 
193 | 	return cmd;
194 | }
195 | 
196 | UbuntuLookup::~UbuntuLookup()
197 | {
198 | }
199 | 


--------------------------------------------------------------------------------
/UbuntuLookup.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include "Stdafx.h"
 3 | #include "VendorPackageLookup.h"
 4 | 
 5 | /*!
 6 |  * Implements the functionality to search Ubuntu packages.
 7 |  */
 8 | class UbuntuLookup : public VendorPackageLookup
 9 | {
10 | public:
11 | 	
12 | 	/*!
13 | 	 * Looks up the status of a vulnerability in the vendor's repository.
14 | 	 *
15 | 	 * \param cve Identifier of the vulnerability.
16 | 	 * \param distrib Operating system distribution.
17 | 	 * \param ver Version number of distribution.
18 | 	 *
19 | 	 * \return If not affected an empty map, otherwise a map of vulnerable
20 | 	 *         packages associated to the version number that patches it,
21 | 	 *         or empty string if package is not yet fixed.
22 | 	 */
23 | 	std::unordered_map FindVulnerability(const std::string& cve, OpSys distrib = OpSys::Ubuntu, double ver = 0.0) override;
24 | 	
25 | 	/*!
26 | 	 * Gets the changelog of a package from the vendor's repository.
27 | 	 *
28 | 	 * \param pkg Name of the package.
29 | 	 * \param distrib Operating system distribution.
30 | 	 * \param ver Version number of distribution.
31 | 	 * 
32 | 	 * \return List of version numbers and their release dates.
33 | 	 */
34 | 	std::vector> GetChangelog(const std::string& pkg, OpSys distrib = OpSys::Ubuntu, double ver = 0.0) override;
35 | 	
36 | 	/*!
37 | 	 * Generates a command which upgrades the specified vulnerable packages
38 | 	 * on the host system.
39 | 	 *
40 | 	 * \param pkgs Vulnerable packages to upgrade.
41 | 	 * \param distrib Operating system distribution.
42 | 	 * \param ver Version number of distribution.
43 | 	 *
44 | 	 * \return Upgrade command.
45 | 	 */
46 | 	std::string GetUpgradeCommand(const std::unordered_set& pkgs, OpSys distrib = OpSys::Ubuntu, double ver = 0.0) override;
47 | 
48 | 	/*!
49 | 	 * Frees up the resources allocated during the lifetime of this instance.
50 | 	 */
51 | 	~UbuntuLookup() override;
52 | 
53 | };
54 | 


--------------------------------------------------------------------------------
/UdpScanner.h:
--------------------------------------------------------------------------------
  1 | #pragma once
  2 | #include 
  3 | #include 
  4 | #include "Stdafx.h"
  5 | #include "ServiceScanner.h"
  6 | 
  7 | /*!
  8 |  * Represents internal scan data for the UDP scanner.
  9 |  */
 10 | struct UdpScanData
 11 | {
 12 | 
 13 | 	/*!
 14 | 	 * Connected socket.
 15 | 	 */
 16 | 	SOCKET socket;
 17 | 
 18 | 	/*!
 19 | 	 * Expiration time of the current operation.
 20 | 	 */
 21 | 	std::chrono::time_point timeout;
 22 | 
 23 | };
 24 | 
 25 | /*!
 26 |  * Implements an UDP port scanner.
 27 |  * 
 28 |  * This will try to initiate the three-way handshake with all the requested services.
 29 |  * It is not a stealthy method, and does not include any trickery to bypass firewalls.
 30 |  */
 31 | class UdpScanner : public ServiceScanner
 32 | {
 33 | public:
 34 | 	
 35 | 	/*!
 36 | 	 * Gets the currently set value for the option key.
 37 | 	 *
 38 | 	 * \param option Option index, see `OPT_*` macros.
 39 | 	 * \param value Pointer to the value to set.
 40 | 	 *
 41 | 	 * \return true if it succeeds, false if it fails.
 42 | 	 */
 43 | 	bool GetOption(int option, void* value) override;
 44 | 
 45 | 	/*!
 46 | 	 * Sets a specified value for the option key.
 47 | 	 *
 48 | 	 * \param option Option index, see `OPT_*` macros.
 49 | 	 * \param value Pointer to the value to set.
 50 | 	 *
 51 | 	 * \return true if it succeeds, false if it fails.
 52 | 	 */
 53 | 	bool SetOption(int option, void* value) override;
 54 | 	
 55 | 	/*!
 56 | 	 * Get a task which scans a service to determine its aliveness.
 57 | 	 *
 58 | 	 * \param service Service to scan.
 59 | 	 * 
 60 | 	 * \return Task to scan the specified service.
 61 | 	 */
 62 | 	void* GetTask(Service* service) override;
 63 | 
 64 | 	/*!
 65 | 	 * Gets the port-mapped protocol payloads.
 66 | 	 *
 67 | 	 * \return List of payloads.
 68 | 	 */
 69 | 	static std::unordered_map GetPayloads();
 70 | 
 71 | 	/*!
 72 | 	 * Frees up the resources allocated during the lifetime of this instance.
 73 | 	 */
 74 | 	~UdpScanner() override;
 75 | 
 76 | private:
 77 | 	
 78 | 	/*!
 79 | 	 * Number of milliseconds to wait for response.
 80 | 	 */
 81 | 	unsigned long timeout = 3000;
 82 | 	
 83 | 	/*!
 84 | 	 * Number of milliseconds to wait between packets sent to the same host.
 85 | 	 */
 86 | 	unsigned long delay = 100;
 87 | 
 88 | 	/*!
 89 | 	 * Indicates whether to wait for and grab service banners.
 90 | 	 */
 91 | 	bool grabBanner = true;
 92 | 
 93 | 	/*!
 94 | 	 * Map of well-known ports and their example payload.
 95 | 	 */
 96 | 	static std::unordered_map payloads;
 97 | 
 98 | 	/*!
 99 | 	 * Sends a datagram to the requested service, with crafted packet, when available.
100 | 	 *
101 | 	 * \param service Service.
102 | 	 *
103 | 	 * \return Next task, or `nullptr` if failed to initialize socket.
104 | 	 */
105 | 	void* initSocket(Service* service);
106 | 
107 | 	/*!
108 | 	 * Receives the response.
109 | 	 *
110 | 	 * \param service Service.
111 | 	 *
112 | 	 * \return Same task if no data received yet, otherwise next task to
113 | 	 * 		   read banner, or `nullptr` if failed to read from socket.
114 | 	 */
115 | 	void* pollSocket(Service* service);
116 | 
117 | 	/*!
118 | 	 * Loads the payload database from external file.
119 | 	 */
120 | 	static void loadPayloads();
121 | 
122 | };
123 | 


--------------------------------------------------------------------------------
/Utils.cpp:
--------------------------------------------------------------------------------
  1 | #include "Utils.h"
  2 | #include 
  3 | #include 
  4 | #include 
  5 | #include 
  6 | #include 
  7 | 
  8 | #if Unix
  9 | 	#include 
 10 | 	#include 
 11 | 	#include 
 12 | #endif
 13 | 
 14 | using namespace std;
 15 | using namespace boost;
 16 | 
 17 | string execute(const char* cmd)
 18 | {
 19 | 	// run the process
 20 | 
 21 | 	auto pipe = popen(cmd, "r");
 22 | 
 23 | 	if (!pipe)
 24 | 	{
 25 | 		log(ERR, "Failed to execute command: `" + string(cmd) + "`");
 26 | 		return "";
 27 | 	}
 28 | 
 29 | 	// read what it writes to the standard output during its lifetime
 30 | 
 31 | 	string result;
 32 | 
 33 | 	char buffer[512];
 34 | 	while (!feof(pipe))
 35 | 	{
 36 | 		if (fgets(buffer, 512, pipe) != NULL)
 37 | 		{
 38 | 			result += buffer;
 39 | 		}
 40 | 	}
 41 | 
 42 | 	// clean up and return
 43 | 
 44 | 	pclose(pipe);
 45 | 
 46 | 	return result;
 47 | }
 48 | 
 49 | string getAppPath()
 50 | {
 51 | #if Windows
 52 | 	char result[MAX_PATH];
 53 | 	auto size = GetModuleFileName(NULL, result, MAX_PATH);
 54 | #elif Unix
 55 | 	char result[PATH_MAX];
 56 | 	auto size = readlink("/proc/self/exe", result, PATH_MAX);
 57 | #endif
 58 | 	return string(result, (size > 0) ? size : 0);
 59 | }
 60 | 
 61 | string getWorkDir()
 62 | {
 63 | #if Windows
 64 | 	char result[MAX_PATH];
 65 | 	GetCurrentDirectory(MAX_PATH, result);
 66 | #elif Unix
 67 | 	char result[PATH_MAX];
 68 | 	getcwd(result, PATH_MAX);
 69 | #endif
 70 | 	return string(result);
 71 | }
 72 | 
 73 | string getEnvVar(const string& env)
 74 | {
 75 | #if Windows
 76 | 	char *result;
 77 | 	size_t size;
 78 | 	auto retval = _dupenv_s(&result, &size, env.c_str()) == 0 ? string(result) : "";
 79 | 	delete result;
 80 | 	return retval;
 81 | #elif Unix
 82 | 	return getenv(env.c_str());
 83 | #endif
 84 | }
 85 | 
 86 | tuple splitPath(const string& path)
 87 | {
 88 | 	auto idx = path.find_last_of(PATH_SEPARATOR);
 89 | 
 90 | 	if (idx == string::npos)
 91 | 	{
 92 | 		return make_tuple(path, "");
 93 | 	}
 94 | 
 95 | 	return make_tuple(path.substr(0, idx), path.substr(idx + 1));
 96 | }
 97 | 
 98 | string pluralize(int quantity, const string& unit, bool addIs, bool past)
 99 | {
100 | 	return to_string(quantity) + " " + unit + (quantity != 1 ? "s" : "") + (addIs ? (quantity != 1 ? (past ? " were" : " are") : (past ? " was" : " is")) : "");
101 | }
102 | 
103 | tuple getURL(const string& url, const std::function& opts)
104 | {
105 | #if HAVE_CURL
106 | 
107 | 	CURL *curl;
108 | 	CURLcode res;
109 | 
110 | 	curl = curl_easy_init();
111 | 
112 | 	if (!curl)
113 | 	{
114 | 		return make_tuple("", "failed to initialize curl", -1);
115 | 	}
116 | 
117 | 	curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
118 | 	curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
119 | 	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true);
120 | 
121 | 	string buffer, error;
122 | 
123 | 	curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
124 | 	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, static_cast([](char *ptr, size_t size, size_t nmemb, void *userdata)
125 | 		{
126 | 			auto blocks = size * nmemb;
127 | 			auto buffer = reinterpret_cast(userdata);
128 | 			buffer->append(ptr, blocks);
129 | 			return blocks;
130 | 		}));
131 | 
132 | 	if (opts != nullptr)
133 | 	{
134 | 		opts(curl);
135 | 	}
136 | 
137 | 	res = curl_easy_perform(curl);
138 | 
139 | 	long code;
140 | 	curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
141 | 
142 | 	if (res != CURLE_OK)
143 | 	{
144 | 		error = curl_easy_strerror(res);
145 | 	}
146 | 
147 | 	curl_easy_cleanup(curl);
148 | 
149 | 	return make_tuple(buffer, error, code);
150 | 
151 | #else
152 | 
153 | #if __GNUC__ || __clang__
154 | 	ignore_unused(url);
155 | 	ignore_unused(opts);
156 | #endif
157 | 
158 | 	return make_tuple("", "not compiled with curl support", -1);
159 | 
160 | #endif
161 | }
162 | 
163 | string getNetErrStr(optional code)
164 | {
165 | #if Windows
166 | 	LPSTR errlp = nullptr;
167 | 	FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0, code.is_initialized() ? code.get() : WSAGetLastError(), MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), LPSTR(&errlp), 0, 0);
168 | 	string err(errlp);
169 | 	LocalFree(errlp);
170 | 	return err;
171 | #else
172 | 	char* err = strerror(code.is_initialized() ? code.get() : errno);
173 | 	return err ? err : "";
174 | #endif
175 | }
176 | 
177 | long dateToUnix(const string& datetime, const string& format)
178 | {
179 | 	using namespace gregorian;
180 | 	using namespace local_time;
181 | 	using namespace posix_time;
182 | 
183 | 	ptime epoch(date(1970, 1, 1));
184 | 
185 | 	auto dt(local_sec_clock::local_time(time_zone_ptr()));
186 | 	auto lf(new local_time_input_facet(format));
187 | 
188 | 	stringstream ss(datetime);
189 | 	ss.imbue(locale(locale::classic(), lf));
190 | 
191 | 	ss >> dt;
192 | 
193 | 	return (dt.utc_time() - epoch).total_seconds();
194 | }
195 | 
196 | string unixToDate(long datetime, const string& format)
197 | {
198 | 	using namespace gregorian;
199 | 	using namespace local_time;
200 | 	using namespace posix_time;
201 | 
202 | 	ptime epoch(date(1970, 1, 1));
203 | 	auto lf(new date_facet(format.c_str()));
204 | 
205 | 	stringstream ss;
206 | 	ss.imbue(locale(locale::classic(), lf));
207 | 
208 | 	ss << (epoch + seconds(datetime)).date();
209 | 
210 | 	return ss.str();
211 | }
212 | 
213 | string escapeRegex(const string& input)
214 | {
215 | 	static const regex  escape("[.^$|()\\[\\]{}*+?\\\\]");
216 | 	static const string replace = "\\\\&";
217 | 
218 | 	return regex_replace(input, escape, replace, match_default | format_sed);
219 | }
220 | 
221 | int compareDates(const string& a, const string& b)
222 | {
223 | 	auto al = dateToUnix(a);
224 | 	auto bl = dateToUnix(b);
225 | 
226 | 	if (al < bl)
227 | 	{
228 | 		return -1;
229 | 	}
230 | 
231 | 	if (al > bl)
232 | 	{
233 | 		return 1;
234 | 	}
235 | 
236 | 	return 0;
237 | }
238 | 
239 | int compareVersions(const string& a, const string& b)
240 | {
241 | 	static const regex intrgx("(\\d+)");
242 | 
243 | 	sregex_iterator ait(a.begin(), a.end(), intrgx);
244 | 	sregex_iterator bit(b.begin(), b.end(), intrgx);
245 | 	sregex_iterator end;
246 | 
247 | 	while (true)
248 | 	{
249 | 		auto am = *ait;
250 | 		auto bm = *bit;
251 | 
252 | 		auto ai = stoi(am[1].str());
253 | 		auto bi = stoi(bm[1].str());
254 | 
255 | 		if (ai < bi)
256 | 		{
257 | 			// A is older
258 | 
259 | 			return -1;
260 | 		}
261 | 		
262 | 		if (ai > bi)
263 | 		{
264 | 			// A is newer
265 | 
266 | 			return 1;
267 | 		}
268 | 
269 | 		// if they're equal, fall-through to next
270 | 
271 | 		++ait;
272 | 		++bit;
273 | 
274 | 		if (ait == end && bit == end)
275 | 		{
276 | 			// if both end here, they're equal
277 | 
278 | 			return 0;
279 | 		}
280 | 		
281 | 		if (ait == end)
282 | 		{
283 | 			// if A ends here and B doesn't, A is older
284 | 
285 | 			return -1;
286 | 		}
287 | 		
288 | 		if (bit == end)
289 | 		{
290 | 			// if B ends here and A doesn't, A is newer
291 | 
292 | 			return 1;
293 | 		}
294 | 	}
295 | }
296 | 


--------------------------------------------------------------------------------
/Utils.h:
--------------------------------------------------------------------------------
  1 | #pragma once
  2 | #include "Stdafx.h"
  3 | #include 
  4 | #include 
  5 | #include 
  6 | #include 
  7 | 
  8 | #if HAVE_CURL
  9 | 	#include 
 10 | #else
 11 | 	typedef void CURL;
 12 | #endif
 13 | 
 14 | /*!
 15 |  * Executes a command and returns its output.
 16 |  *
 17 |  * \param cmd Command to execute.
 18 |  */
 19 | std::string execute(const char* cmd);
 20 | 
 21 | /*!
 22 |  * Gets the path to the executable.
 23 |  *
 24 |  * \return Path to executable.
 25 |  */
 26 | std::string getAppPath();
 27 | 
 28 | /*!
 29 |  * Gets the path to the current working directory.
 30 |  *
 31 |  * \return Path to working directory.
 32 |  */
 33 | std::string getWorkDir();
 34 | 
 35 | /*!
 36 |  * Gets the value of the requested environment variable.
 37 |  *
 38 |  * \param env Variable name.
 39 |  *
 40 |  * \return Environment variable, or empty string.
 41 |  */
 42 | std::string getEnvVar(const std::string& env);
 43 | 
 44 | /*!
 45 |  * Splits the path, second item will be top-level file or
 46 |  * directory, while the first will be the rest of the path.
 47 |  *
 48 |  * \param path Path to split.
 49 |  *
 50 |  * \return Rest of the path, top-level file or directory.
 51 |  */
 52 | std::tuple splitPath(const std::string& path);
 53 | 
 54 | /*!
 55 |  * Pluralizes the specified unit based on the quantity.
 56 |  *
 57 |  * \param quantity Quantity of unit.
 58 |  * \param unit Unit to pluralize.
 59 |  * \param addIs Append `is` or `are`.
 60 |  * \param past Whether to append `addIs` in past tense.
 61 |  *
 62 |  * \return String with quantity and unit, pluralized if needed.
 63 |  */
 64 | std::string pluralize(int quantity, const std::string& unit, bool addIs = false, bool past = false);
 65 | 
 66 | /*!
 67 |  * Fetches the content behind the specified URL.
 68 |  *
 69 |  * \param url Location to download.
 70 |  * \param opts Optional callback function called right after setting up curl,
 71 |  *             and before performing the request. Within this function, you may
 72 |  *             manipulate any aspect of the request by calling `curl_easy_setopt`
 73 |  *             on the passed `CURL` pointer.
 74 |  *
 75 |  * \return Tuple containing two strings and the response code:
 76 |  *         the downloaded string, if any, and the error message, if any.
 77 |  */
 78 | std::tuple getURL(const std::string& url, const std::function& opts = nullptr);
 79 | 
 80 | /*!
 81 |  * Retrieves the error message for the last I/O error.
 82 |  * 
 83 |  * On Windows, it uses `WSAGetLastError()` with `FormatMessage()`.
 84 |  * On Linux, it uses `errno` with `strerror()`.
 85 |  *
 86 |  * \param code Optional error code. If not specified, will retrieve
 87 |  * 			   the last code from the operating system.
 88 |  * 
 89 |  * \return The net error string.
 90 |  */
 91 | std::string getNetErrStr(boost::optional code = boost::none);
 92 | 
 93 | /*!
 94 |  * Converts textual dates to Unix timestamps.
 95 |  * 
 96 |  * \param datetime Textual date.
 97 |  * \param format Format string, RFC1123 by default.
 98 |  *
 99 |  * \return Number of seconds elapsed until specified date
100 |  * 		   since Unix epoch.
101 |  */
102 | long dateToUnix(const std::string& datetime, const std::string& format = "%a, %d %b %Y %H:%M:%S");
103 | 
104 | /*!
105 |  * Converts Unix timestamps to textual dates.
106 |  * 
107 |  * \param datetime Textual date.
108 |  * \param format Format string, RFC1123 by default.
109 |  *
110 |  * \return Unix timestamp formatted as specified.
111 |  */
112 | std::string unixToDate(long datetime, const std::string& format = "%a, %d %b %Y %H:%M:%S");
113 | 
114 | /*!
115 |  * Escapes the specified input in order to be used in a regular expression.
116 |  *
117 |  * \param input String to be escaped.
118 |  *
119 |  * \return String with characters having special meanings in regular
120 |  * 		   expressions escaped safely.
121 |  */
122 | std::string escapeRegex(const std::string& input);
123 | 
124 | /*!
125 |  * Compares the two specified dates.
126 |  *
127 |  * \param a First date.
128 |  * \param b Second date.
129 |  *
130 |  * \return -1 if the first date is older,
131 |  *          0 if the two dates are equal,
132 |  *          1 if the first date is newer.
133 |  */
134 | int compareDates(const std::string& a, const std::string& b);
135 | 
136 | /*!
137 |  * Compares the two specified version numbers.
138 |  *
139 |  * \param a First version number.
140 |  * \param b Second version number.
141 |  *
142 |  * \return -1 if the first version is older,
143 |  *          0 if the two versions are equal,
144 |  *          1 if the first version is newer.
145 |  */
146 | int compareVersions(const std::string& a, const std::string& b);
147 | 


--------------------------------------------------------------------------------
/VendorLookupFactory.cpp:
--------------------------------------------------------------------------------
 1 | #include "VendorLookupFactory.h"
 2 | #include "DebianLookup.h"
 3 | #include "UbuntuLookup.h"
 4 | #include "EnterpriseLinuxLookup.h"
 5 | 
 6 | VendorPackageLookup* VendorLookupFactory::Get(OpSys opSys)
 7 | {
 8 | 	switch (opSys)
 9 | 	{
10 | 	case Debian:
11 | 		return new DebianLookup();
12 | 
13 | 	case Ubuntu:
14 | 		return new UbuntuLookup();
15 | 
16 | 	case Fedora:
17 | 	case EnterpriseLinux:
18 | 		return new EnterpriseLinuxLookup();
19 | 
20 | 	default:
21 | 		return nullptr;
22 | 	}
23 | }
24 | 


--------------------------------------------------------------------------------
/VendorLookupFactory.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include "VendorPackageLookup.h"
 3 | 
 4 | /*
 5 |  * Implements the factory pattern for retrieving vendor package lookup instances.
 6 |  */
 7 | class VendorLookupFactory
 8 | {
 9 | public:
10 | 
11 | 	/*!
12 | 	 * Gets a vendor package lookup instance which supports the specified operating system.
13 | 	 *
14 | 	 * \param opSys Operating system for which to retrieve lookup instance.
15 | 	 * 
16 | 	 * \return Instance to be used for package lookups, or nullptr for unsupported systems.
17 | 	 */
18 | 	static VendorPackageLookup* Get(OpSys opSys);
19 | 
20 | };
21 | 


--------------------------------------------------------------------------------
/VendorPackageLookup.cpp:
--------------------------------------------------------------------------------
 1 | #include "VendorPackageLookup.h"
 2 | #include 
 3 | 
 4 | using namespace std;
 5 | using namespace boost;
 6 | 
 7 | bool VendorPackageLookup::ValidateCVE(const string& cve)
 8 | {
 9 | 	static regex cvergx("^CVE-\\d{4}-\\d{4,}$", regex::icase);
10 | 
11 | 	return regex_match(cve, cvergx);
12 | }
13 | 
14 | VendorPackageLookup::~VendorPackageLookup()
15 | {
16 | }
17 | 


--------------------------------------------------------------------------------
/VendorPackageLookup.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include "Stdafx.h"
 3 | #include "Host.h"
 4 | #include 
 5 | #include 
 6 | 
 7 | /*!
 8 |  * Provides the functionality to search vendor repositories.
 9 |  */
10 | class VendorPackageLookup
11 | {
12 | public:
13 | 
14 | 	/*!
15 | 	 * Looks up the status of a vulnerability in the vendor's repository.
16 | 	 *
17 | 	 * \param cve Identifier of the vulnerability.
18 | 	 * \param distrib Operating system distribution.
19 | 	 * \param ver Version number of distribution.
20 | 	 *
21 | 	 * \return If not affected an empty map, otherwise a map of vulnerable
22 | 	 *         packages associated to the version number that patches it,
23 | 	 *         or empty string if package is not yet fixed.
24 | 	 */
25 | 	virtual std::unordered_map FindVulnerability(const std::string& cve, OpSys distrib = OpSys::Unidentified, double ver = 0.0) = 0;
26 | 
27 | 	/*!
28 | 	 * Gets the changelog of a package from the vendor's repository.
29 | 	 *
30 | 	 * \param pkg Name of the package.
31 | 	 * \param distrib Operating system distribution.
32 | 	 * \param ver Version number of distribution.
33 | 	 * 
34 | 	 * \return List of version numbers and their release dates.
35 | 	 */
36 | 	virtual std::vector> GetChangelog(const std::string& pkg, OpSys distrib = OpSys::Unidentified, double ver = 0.0) = 0;
37 | 
38 | 	/*!
39 | 	 * Generates a command which upgrades the specified vulnerable packages
40 | 	 * on the host system.
41 | 	 *
42 | 	 * \param pkgs Vulnerable packages to upgrade.
43 | 	 * \param distrib Operating system distribution.
44 | 	 * \param ver Version number of distribution.
45 | 	 *
46 | 	 * \return Upgrade command.
47 | 	 */
48 | 	virtual std::string GetUpgradeCommand(const std::unordered_set& pkgs, OpSys distrib = OpSys::Unidentified, double ver = 0.0) = 0;
49 | 
50 | 	/*!
51 | 	 * Determines whether the specified CVE identifier is syntactically correct.
52 | 	 *
53 | 	 * \param cve Identifier of the vulnerability.
54 | 	 *
55 | 	 * \return true if it is correct, false if it is not.
56 | 	 */
57 | 	static bool ValidateCVE(const std::string& cve);
58 | 
59 | 	/*!
60 | 	 * Frees up the resources allocated during the lifetime of this instance.
61 | 	 */
62 | 	virtual ~VendorPackageLookup();
63 | 	
64 | };
65 | 


--------------------------------------------------------------------------------
/VulnerabilityLookup.cpp:
--------------------------------------------------------------------------------
  1 | #include "VulnerabilityLookup.h"
  2 | #include "Utils.h"
  3 | #include 
  4 | #include 
  5 | 
  6 | using namespace std;
  7 | using namespace boost;
  8 | namespace fs = boost::filesystem;
  9 | 
 10 | vector VulnerabilityLookup::Scan(const string& cpe)
 11 | {
 12 | 	vector matches;
 13 | 
 14 | 	if ((db == nullptr && !openDatabase()) || cpe.empty() || count(cpe.begin(), cpe.end(), ':') < 3)
 15 | 	{
 16 | 		return matches;
 17 | 	}
 18 | 
 19 | 	sqlite3_stmt *stmt;
 20 | 	const char *err = nullptr;
 21 | 
 22 | 	auto rc = sqlite3_prepare_v2(db, "select cve, cpe, date, descr, severity, access from affected join vulns on vulns.id = affected.vuln_id where cpe like ? or cpe like ? order by id desc", -1, &stmt, &err);
 23 | 
 24 | 	if (rc != SQLITE_OK)
 25 | 	{
 26 | 		log(ERR, "Failed to prepare statement: " + string(err));
 27 | 
 28 | 		delete err;
 29 | 		return matches;
 30 | 	}
 31 | 
 32 | 	auto vsep = cpe.find(';');
 33 | 	auto name = vsep == string::npos ? cpe : cpe.substr(0, vsep);
 34 | 
 35 | 	sqlite3_bind_text(stmt, 1,  name.c_str(),         -1, nullptr);
 36 | 	sqlite3_bind_text(stmt, 2, (name + ":%").c_str(), -1, nullptr);
 37 | 
 38 | 	while (sqlite3_step(stmt) == SQLITE_ROW)
 39 | 	{
 40 | 		CveEntry ent;
 41 | 		
 42 | 		ent.cve      = string(reinterpret_cast(sqlite3_column_text(stmt, 0)));
 43 | 		ent.descr    = string(reinterpret_cast(sqlite3_column_text(stmt, 3)));
 44 | 		ent.access   = string(reinterpret_cast(sqlite3_column_text(stmt, 5)));
 45 | 		ent.date     = long(sqlite3_column_int64(stmt, 2));
 46 | 		ent.severity = float(sqlite3_column_double(stmt, 4));
 47 | 
 48 | 		matches.push_back(ent);
 49 | 	}
 50 | 
 51 | 	sqlite3_finalize(stmt);
 52 | 
 53 | 	return matches;
 54 | }
 55 | 
 56 | unordered_map> VulnerabilityLookup::Scan(const vector& cpes)
 57 | {
 58 | 	unordered_map> matches;
 59 | 
 60 | 	if ((db == nullptr && !openDatabase()) || cpes.size() == 0)
 61 | 	{
 62 | 		return matches;
 63 | 	}
 64 | 
 65 | 	for (auto& cpe : cpes)
 66 | 	{
 67 | 		if (cpe.empty() || count(cpe.begin(), cpe.end(), ':') < 3)
 68 | 		{
 69 | 			continue;
 70 | 		}
 71 | 
 72 | 		sqlite3_stmt *stmt;
 73 | 		const char *err = nullptr;
 74 | 
 75 | 		auto rc = sqlite3_prepare_v2(db, "select cve, cpe, date, descr, severity, access from affected join vulns on vulns.id = affected.vuln_id where cpe like ? or cpe like ? order by id desc", -1, &stmt, &err);
 76 | 
 77 | 		if (rc != SQLITE_OK)
 78 | 		{
 79 | 			log(ERR, "Failed to prepare statement: " + string(err));
 80 | 
 81 | 			delete err;
 82 | 			continue;
 83 | 		}
 84 | 
 85 | 		auto vsep = cpe.find(';');
 86 | 		auto name = vsep == string::npos ? cpe : cpe.substr(0, vsep);
 87 | 
 88 | 		sqlite3_bind_text(stmt, 1,  name.c_str(),         -1, nullptr);
 89 | 		sqlite3_bind_text(stmt, 2, (name + ":%").c_str(), -1, nullptr);
 90 | 
 91 | 		while (sqlite3_step(stmt) == SQLITE_ROW)
 92 | 		{
 93 | 			CveEntry ent;
 94 | 
 95 | 			ent.cve      = string(reinterpret_cast(sqlite3_column_text(stmt, 0)));
 96 | 			ent.descr    = string(reinterpret_cast(sqlite3_column_text(stmt, 3)));
 97 | 			ent.access   = string(reinterpret_cast(sqlite3_column_text(stmt, 5)));
 98 | 			ent.date     = long(sqlite3_column_int64(stmt, 2));
 99 | 			ent.severity = float(sqlite3_column_double(stmt, 4));
100 | 
101 | 			matches[cpe].push_back(ent);
102 | 		}
103 | 
104 | 		sqlite3_finalize(stmt);
105 | 	}
106 | 
107 | 	return matches;
108 | }
109 | 
110 | bool VulnerabilityLookup::openDatabase()
111 | {
112 | 	static mutex mtx;
113 | 	auto locked = mtx.try_lock();
114 | 	if (!locked)
115 | 	{
116 | 		// wait until running opener finishes before returning
117 | 		lock_guard guard(mtx);
118 | 		return db != nullptr;
119 | 	}
120 | 
121 | 	// open entries database
122 | 
123 | 	string dbpath;
124 | 	string name = "cve-list";
125 | 
126 | 	vector paths = {
127 | #if Windows
128 | 		get<0>(splitPath(getAppPath())) + "\\data\\" + name + ".db3",
129 | 		getEnvVar("APPDATA") + "\\RoliSoft\\Host Scanner\\data\\" + name + ".db3",
130 | #else
131 | 		get<0>(splitPath(getAppPath())) + "/data/" + name + ".db3",
132 | 		"/var/lib/HostScanner/data/" + name + ".db3",
133 | #endif
134 | 	};
135 | 
136 | 	for (auto path : paths)
137 | 	{
138 | 		fs::path fp(path);
139 | 
140 | 		if (!fs::exists(fp) || !fs::is_regular_file(fp))
141 | 		{
142 | 			continue;
143 | 		}
144 | 
145 | 		dbpath = path;
146 | 	}
147 | 
148 | 	if (dbpath.empty())
149 | 	{
150 | 		log(ERR, "Failed to locate CVE database.");
151 | 
152 | 		mtx.unlock();
153 | 		return false;
154 | 	}
155 | 
156 | 	auto rc = sqlite3_open(dbpath.c_str(), &db);
157 | 
158 | 	if (rc != SQLITE_OK)
159 | 	{
160 | 		log(ERR, "Failed to open CVE database: " + string(sqlite3_errmsg(db)));
161 | 
162 | 		sqlite3_close(db);
163 | 		db = nullptr;
164 | 	}
165 | 
166 | 	// clean up
167 | 
168 | 	mtx.unlock();
169 | 
170 | 	return db != nullptr;
171 | }
172 | 
173 | VulnerabilityLookup::~VulnerabilityLookup()
174 | {
175 | 	if (db != nullptr)
176 | 	{
177 | 		sqlite3_close(db);
178 | 	}
179 | }
180 | 


--------------------------------------------------------------------------------
/VulnerabilityLookup.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include "Stdafx.h"
 3 | #include 
 4 | #include 
 5 | #include 
 6 | #include 
 7 | #include 
 8 | 
 9 | /*!
10 |  * Represents a CPE dictionary entry.
11 |  */
12 | struct CveEntry
13 | {
14 | 
15 | 	/*!
16 | 	 * CVE identifier of the entry.
17 | 	 */
18 | 	std::string cve;
19 | 
20 | 	/*!
21 | 	 * Vulnerability publication date.
22 | 	 */
23 | 	long date;
24 | 
25 | 	/*!
26 | 	 * Summary of the vulnerability.
27 | 	 */
28 | 	std::string descr;
29 | 
30 | 	/*!
31 | 	 * Severity of the vulnerability.
32 | 	 */
33 | 	float severity;
34 | 
35 | 	/*!
36 | 	 * Access vector of the vulnerability.
37 | 	 * Possible values are `l` for local, `a` for adjacent and `n` for network.
38 | 	 */
39 | 	std::string access;
40 | 
41 | };
42 | 
43 | /*!
44 |  * Implements the lookup of CPE names in the vulnerability database.
45 |  */
46 | class VulnerabilityLookup
47 | {
48 | public:
49 | 	
50 | 	/*!
51 | 	 * Searches the vulnerability database for entries affecting the specified CPE name.
52 | 	 * 
53 | 	 * \param cpe CPE name to lookup.
54 | 	 * 
55 | 	 * \return Matching CVE entries.
56 | 	 */
57 | 	std::vector Scan(const std::string& cpe);
58 | 	
59 | 	/*!
60 | 	 * Searches the vulnerability database for entries affecting the specified CPE names.
61 | 	 * 
62 | 	 * \param cpes CPE names to lookup.
63 | 	 * 
64 | 	 * \return Matching CVE entries.
65 | 	 */
66 | 	std::unordered_map> Scan(const std::vector& cpes);
67 | 
68 | 	/*!
69 | 	 * Frees up the resources allocated during the lifetime of this instance.
70 | 	 */
71 | 	~VulnerabilityLookup();
72 | 
73 | private:
74 | 
75 | 	/*!
76 | 	 * Database of CVE entries with their affected product list.
77 | 	 */
78 | 	sqlite3* db = nullptr;
79 | 
80 | 	/*!
81 | 	 * Opens the CVE database.
82 | 	 *
83 | 	 * \return true if it succeeds, false if it fails.
84 | 	 */
85 | 	bool openDatabase();
86 | 
87 | };
88 | 


--------------------------------------------------------------------------------
/WindowsIdentifier.cpp:
--------------------------------------------------------------------------------
 1 | #include "WindowsIdentifier.h"
 2 | #include 
 3 | 
 4 | using namespace std;
 5 | using namespace boost;
 6 | 
 7 | bool WindowsIdentifier::Scan(Host* host)
 8 | {
 9 | 	auto isWin = false;
10 | 
11 | 	// check if any Microsoft services are running
12 | 
13 | 	static regex wintag("\\b(?:microsoft.?(iis|ftp|esmtp|httpapi)|cygwin|windows|win32|win64)\\b", regex::icase);
14 | 
15 | 	for (auto service : *host->services)
16 | 	{
17 | 		if (service->banner.empty())
18 | 		{
19 | 			continue;
20 | 		}
21 | 
22 | 		smatch sm;
23 | 
24 | 		if (regex_search(service->banner, sm, wintag))
25 | 		{
26 | 			isWin = true;
27 | 		}
28 | 	}
29 | 
30 | 	if (isWin)
31 | 	{
32 | 		host->opSys = OpSys::WindowsNT;
33 | 		host->cpe.push_back("o:microsoft:windows");
34 | 	}
35 | 
36 | 	return isWin;
37 | }
38 | 
39 | WindowsIdentifier::~WindowsIdentifier()
40 | {
41 | }
42 | 


--------------------------------------------------------------------------------
/WindowsIdentifier.h:
--------------------------------------------------------------------------------
 1 | #pragma once
 2 | #include "Stdafx.h"
 3 | #include "OperatingSystemIdentifier.h"
 4 | 
 5 | /*!
 6 |  * Implements functionality for identifying Windows based on service banners.
 7 |  */
 8 | class WindowsIdentifier : public OperatingSystemIdentifier
 9 | {
10 | public:
11 | 	
12 | 	/*!
13 | 	 * Processes the specified host.
14 | 	 * 
15 | 	 * \param host Scanned host.
16 | 	 * 
17 | 	 * \return true if the operating system was identified,
18 | 	 * 		   otherwise false.
19 | 	 */
20 | 	bool Scan(Host* host) override;
21 | 
22 | 	/*!
23 | 	 * Frees up the resources allocated during the lifetime of this instance.
24 | 	 */
25 | 	~WindowsIdentifier() override;
26 | 
27 | };
28 | 


--------------------------------------------------------------------------------
/build/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoliSoft/Host-Scanner/f502ac85e7b49c07c5986953bb6e354938f276fd/build/.gitkeep


--------------------------------------------------------------------------------
/cmake/FindSQLite.cmake:
--------------------------------------------------------------------------------
 1 | # - Try to find SQLite
 2 | # Once done this will define
 3 | #
 4 | #  SQLITE_FOUND - system has SQLite
 5 | #  SQLITE_INCLUDE_DIR - the SQLite include directory
 6 | #  SQLITE_LIBRARIES - Link these to use SQLite
 7 | #  SQLITE_DEFINITIONS - Compiler switches required for using SQLite
 8 | #
 9 | # Copyright (c) 2008, Gilles Caulier, 
10 | #
11 | # Redistribution and use in source and binary forms, with or without
12 | # modification, are permitted provided that the following conditions
13 | # are met:
14 | #
15 | # 1. Redistributions of source code must retain the copyright
16 | #    notice, this list of conditions and the following disclaimer.
17 | # 2. Redistributions in binary form must reproduce the copyright
18 | #    notice, this list of conditions and the following disclaimer in the
19 | #    documentation and/or other materials provided with the distribution.
20 | # 3. The name of the author may not be used to endorse or promote products
21 | #    derived from this software without specific prior written permission.
22 | #
23 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
24 | # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25 | # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
26 | # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
28 | # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
32 | # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 | 
34 | if (SQLITE_INCLUDE_DIR AND SQLITE_LIBRARIES)
35 |     # in cache already
36 |     set(SQLite_FIND_QUIETLY TRUE)
37 | endif (SQLITE_INCLUDE_DIR AND SQLITE_LIBRARIES)
38 | 
39 | # use pkg-config to get the directories and then use these values
40 | # in the find_path() and find_library() calls
41 | if (NOT WIN32)
42 |     find_package(PkgConfig)
43 | 
44 |     pkg_check_modules(PC_SQLITE sqlite3)
45 | 
46 |     set(SQLITE_DEFINITIONS ${PC_SQLITE_CFLAGS_OTHER})
47 | endif (NOT WIN32)
48 | 
49 | find_path(SQLITE_INCLUDE_DIR NAMES sqlite3.h
50 |     PATHS
51 |     ${PC_SQLITE_INCLUDEDIR}
52 |     ${PC_SQLITE_INCLUDE_DIRS}
53 | )
54 | 
55 | find_library(SQLITE_LIBRARIES NAMES sqlite3
56 |     PATHS
57 |     ${PC_SQLITE_LIBDIR}
58 |     ${PC_SQLITE_LIBRARY_DIRS}
59 | )
60 | 
61 | include(FindPackageHandleStandardArgs)
62 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(SQLite DEFAULT_MSG SQLITE_INCLUDE_DIR SQLITE_LIBRARIES)
63 | 
64 | # show the SQLITE_INCLUDE_DIR and SQLITE_LIBRARIES variables only in the advanced view
65 | mark_as_advanced(SQLITE_INCLUDE_DIR SQLITE_LIBRARIES)


--------------------------------------------------------------------------------
/conanfile.txt:
--------------------------------------------------------------------------------
1 | [requires]
2 | zlib/1.2.8@lasote/stable
3 | libcurl/7.47.1@lasote/stable
4 | sqlite3/3.10.2@TyRoXx/stable
5 | Boost/1.60.0@lasote/stable
6 | 
7 | [generators]
8 | cmake


--------------------------------------------------------------------------------
/data/README.md:
--------------------------------------------------------------------------------
1 | # Host Scanner Data
2 | 
3 | This folder contains the data which the application may use for various features.
4 | 
5 | See the [Host-Scanner-Scripts](https://github.com/RoliSoft/Host-Scanner-Scripts) repository for a list of file types and currently implemented data generator scripts.
6 | 
7 | The `download.sh` script will download an older snapshot of the data files for testing purposes. To get newer data, see the aforementioned repository for generation instructions.


--------------------------------------------------------------------------------
/data/copy-build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | mkdir -p ../build/data/
3 | cp -v *.dat *.dat.gz *.db3 ../build/data/


--------------------------------------------------------------------------------
/data/download.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/bash
 2 | echo -e "\e[31mWARNING: \e[33mThese data files may not be the latest. See the README on how to compile them yourself.\e[39m"
 3 | echo -e "\e[32mDownloading CPE dictionary...\e[39m"
 4 | wget https://github.com/RoliSoft/Host-Scanner-Scripts/releases/download/v0.3.0/cpe-list.dat.gz
 5 | echo -e "\e[32mDownloading CPE aliases...\e[39m"
 6 | wget https://github.com/RoliSoft/Host-Scanner-Scripts/releases/download/v0.1.0/cpe-aliases.dat.gz
 7 | echo -e "\e[32mDownloading service banner database...\e[39m"
 8 | wget https://github.com/RoliSoft/Host-Scanner-Scripts/releases/download/v0.1.0/cpe-regex.dat.gz
 9 | echo -e "\e[32mDownloading UDP payloads...\e[39m"
10 | wget https://github.com/RoliSoft/Host-Scanner-Scripts/releases/download/v0.1.0/payloads.dat.gz
11 | echo -e "\e[32mDownloading CVE database...\e[39m"
12 | wget https://github.com/RoliSoft/Host-Scanner-Scripts/releases/download/v0.3.0/cve-list.db3.bz2
13 | echo -e "\e[32mDecompressing CVE database...\e[39m"
14 | bzip2 -d cve-list.db3.bz2


--------------------------------------------------------------------------------
/distrib/README.md:
--------------------------------------------------------------------------------
 1 | # Host Scanner DEB/RPM Distributions
 2 | 
 3 | The `deb` and `rpm` folders contain the `Dockerfile` and scripts to pull, compile and package the latest revision. The `.deb` package is generated with the current Debian Stable (`debian:latest` in Docker Hub) while the `.rpm` package is generated with the current Fedora version. (`fedora:latest`)
 4 | 
 5 | Since Boost is statically linked during compilation, the dynamic dependencies for now are `libcurl`, `libsqlite3` and `zlib` for both distributions.
 6 | 
 7 | The files will be generated by CMake's packaging component, CPack, whose configuration can be found in `CPackConfig.cmake`.
 8 | 
 9 | In order to set up these build environments, you must first compile the container:
10 | 
11 |     cd distrib/deb
12 |     docker build -t debbuild .
13 | 
14 | After this, the `debbuild` container will be available for any compilations needs. To bake a fresh `.deb`, just run:
15 | 
16 |     docker run -it debbuild
17 | 
18 | This will pull a fresh copy of the repository _inside_ the container, therefore any changes to the repository on your host machine will not be reflected. If you wish to compile you own fork or a different branch, you'll have to modify the `compile.sh` file next to the preferred `Dockerfile` and rebuild the container.


--------------------------------------------------------------------------------
/distrib/deb/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM debian
2 | MAINTAINER RoliSoft
3 | 
4 | RUN apt-get update && apt-get install -y git curl build-essential cmake libcurl4-openssl-dev libsqlite3-dev libboost-all-dev libz-dev
5 | 
6 | COPY compile.sh /root/compile.sh
7 | COPY upload.sh /root/upload.sh
8 | 
9 | ENTRYPOINT /root/compile.sh DEB


--------------------------------------------------------------------------------
/distrib/deb/README.md:
--------------------------------------------------------------------------------
 1 | # Host Scanner DEB Distribution
 2 | 
 3 | This folder contain the `Dockerfile` and scripts to pull, compile and package the latest revision. The scripts here are tailored to produce a `.deb` package, which is generated with the current Debian Stable, `debian:latest` in Docker Hub.
 4 | 
 5 | Since Boost is statically linked during compilation, the dynamic dependencies for now are `libcurl`, `libsqlite3` and `zlib` for both distributions.
 6 | 
 7 | The files will be generated by CMake's packaging component, CPack, whose configuration can be found in `CPackConfig.cmake`.
 8 | 
 9 | In order to set up these build environments, you must first compile the container:
10 | 
11 |     cd distrib/deb
12 |     docker build -t debbuild .
13 | 
14 | After this, the `debbuild` container will be available for any compilations needs. To bake a fresh `.deb`, just run:
15 | 
16 |     docker run -it debbuild
17 | 
18 | This will pull a fresh copy of the repository _inside_ the container, therefore any changes to the repository on your host machine will not be reflected. If you wish to compile you own fork or a different branch, you'll have to modify the `compile.sh` file next to the preferred `Dockerfile` and rebuild the container.


--------------------------------------------------------------------------------
/distrib/deb/compile.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/bash
 2 | 
 3 | set -e
 4 | set -x
 5 | shopt -s extglob
 6 | 
 7 | cd /root
 8 | git clone https://github.com/RoliSoft/Host-Scanner
 9 | cd Host-Scanner/build
10 | cmake ..
11 | make HostScanner
12 | rm -rf !(HostScanner) .gitkeep
13 | cd ..
14 | cpack -D CPACK_GENERATOR="${1^^}"
15 | ../upload.sh *."${1,,}"


--------------------------------------------------------------------------------
/distrib/deb/upload.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | 
3 | # file to upload is in $1, implement your own uploader
4 | 
5 | ls -lah "$1"
6 | curl --upload-file "$1" https://transfer.sh/


--------------------------------------------------------------------------------
/distrib/rpm/Dockerfile:
--------------------------------------------------------------------------------
 1 | FROM fedora
 2 | MAINTAINER RoliSoft
 3 | 
 4 | RUN grep -q "fastestmirror=true" /etc/dnf/dnf.conf || echo "fastestmirror=true" >> /etc/dnf/dnf.conf
 5 | RUN dnf install -y git curl rpm-build gcc-c++ make cmake libcurl-devel sqlite-devel boost-devel-static zlib-devel
 6 | 
 7 | COPY compile.sh /root/compile.sh
 8 | COPY upload.sh /root/upload.sh
 9 | 
10 | ENTRYPOINT /root/compile.sh RPM


--------------------------------------------------------------------------------
/distrib/rpm/README.md:
--------------------------------------------------------------------------------
 1 | # Host Scanner RPM Distribution
 2 | 
 3 | This folder contain the `Dockerfile` and scripts to pull, compile and package the latest revision. The scripts here are tailored to produce an `.rpm` package, which is generated with the current Fedora version, `fedora:latest` in Docker Hub.
 4 | 
 5 | Since Boost is statically linked during compilation, the dynamic dependencies for now are `libcurl`, `libsqlite3` and `zlib` for both distributions.
 6 | 
 7 | The files will be generated by CMake's packaging component, CPack, whose configuration can be found in `CPackConfig.cmake`.
 8 | 
 9 | In order to set up these build environments, you must first compile the container:
10 | 
11 |     cd distrib/rpm
12 |     docker build -t rpmbuild .
13 | 
14 | After this, the `rpmbuild` container will be available for any compilations needs. To bake a fresh `.rpm`, just run:
15 | 
16 |     docker run -it rpmbuild
17 | 
18 | This will pull a fresh copy of the repository _inside_ the container, therefore any changes to the repository on your host machine will not be reflected. If you wish to compile you own fork or a different branch, you'll have to modify the `compile.sh` file next to the preferred `Dockerfile` and rebuild the container.


--------------------------------------------------------------------------------
/distrib/rpm/compile.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/bash
 2 | 
 3 | set -e
 4 | set -x
 5 | shopt -s extglob
 6 | 
 7 | cd /root
 8 | git clone https://github.com/RoliSoft/Host-Scanner
 9 | cd Host-Scanner/build
10 | cmake ..
11 | make HostScanner
12 | rm -rf !(HostScanner) .gitkeep
13 | cd ..
14 | cpack -D CPACK_GENERATOR="${1^^}"
15 | ../upload.sh *."${1,,}"


--------------------------------------------------------------------------------
/distrib/rpm/upload.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | 
3 | # file to upload is in $1, implement your own uploader
4 | 
5 | ls -lah "$1"
6 | curl --upload-file "$1" https://transfer.sh/


--------------------------------------------------------------------------------
/doxyextra.css:
--------------------------------------------------------------------------------
 1 | #nav-sync, .navpath li.footer img {
 2 | 	display: none;
 3 | }
 4 | 
 5 | .navpath li.footer a:after {
 6 | 	content: 'Doxygen';
 7 | }
 8 | 
 9 | #titlearea {
10 | 	padding-bottom: 13px;
11 | }
12 | 
13 | #projectalign {
14 | 	padding-left: 10px !important;
15 | 	padding-top: 2px;
16 | }


--------------------------------------------------------------------------------