├── INSTALL.md ├── SECURITY.md ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md ├── README.md ├── LICENSE └── udpy_proto_scanner.py /INSTALL.md: -------------------------------------------------------------------------------- 1 | ### Your System 2 | 3 | copy udpy_proto_scanner.py /usr/local/bin/ 4 | chmod +x /usr/local/bin/udpy_proto_scanner.py 5 | 6 | ### Pivot 7 | 8 | Copy udpy_proto_scanner.py to target system. 9 | 10 | It should should have no dependencies and run with a default install of python 2.7 or 3.? (TBC). 11 | ``` 12 | $ python udpy_proto_scanner.py 13 | $ python2 udpy_proto_scanner.py 14 | $ python3 udpy_proto_scanner.py 15 | ``` 16 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policies and Procedures 2 | 3 | This document outlines security procedures and general policies for the 4 | `udpy_proto_scanner` project. 5 | 6 | - [Reporting a Bug](#reporting-a-bug) 7 | - [Disclosure Policy](#disclosure-policy) 8 | - [Comments on this Policy](#comments-on-this-policy) 9 | 10 | ## Reporting a Bug 11 | 12 | The `udpy_proto_scanner` team and community take all security bugs in 13 | `udpy_proto_scanner` seriously. Thank you for improving the security of 14 | `udpy_proto_scanner`. We appreciate your efforts and responsible disclosure and 15 | will make every effort to acknowledge your contributions. 16 | 17 | Report security bugs by emailing `oss-security@cisco.com`. 18 | 19 | The lead maintainer will acknowledge your email within 48 hours, and will send a 20 | more detailed response within 48 hours indicating the next steps in handling 21 | your report. After the initial reply to your report, the security team will 22 | endeavor to keep you informed of the progress towards a fix and full 23 | announcement, and may ask for additional information or guidance. 24 | 25 | ## Disclosure Policy 26 | 27 | When the security team receives a security bug report, they will assign it to a 28 | primary handler. This person will coordinate the fix and release process, 29 | involving the following steps: 30 | 31 | - Confirm the problem and determine the affected versions. 32 | - Audit code to find any potential similar problems. 33 | - Prepare fixes for all releases still under maintenance. These fixes will be 34 | released as quickly as possible. 35 | 36 | ## Comments on this Policy 37 | 38 | If you have suggestions on how this process could be improved please submit a 39 | pull request. 40 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | Thanks for your interest in contributing to `udpy_proto_scanner`! Here are a few general guidelines on contributing and 4 | reporting bugs that we ask you to review. Following these guidelines helps to communicate that you respect the time of 5 | the contributors managing and developing this open source project. In return, they should reciprocate that respect in 6 | addressing your issue, assessing changes, and helping you finalize your pull requests. In that spirit of mutual respect, 7 | we endeavor to review incoming issues and pull requests within 10 days, and will close any lingering issues or pull 8 | requests after 60 days of inactivity. 9 | 10 | Please note that all of your interactions in the project are subject to our [Code of Conduct](/CODE_OF_CONDUCT.md). This 11 | includes creation of issues or pull requests, commenting on issues or pull requests, and extends to all interactions in 12 | any real-time space e.g., Slack, Discord, etc. 13 | 14 | ## Reporting Issues 15 | 16 | Before reporting a new issue, please ensure that the issue was not already reported or fixed by searching through our 17 | [issues list](https://github.com/CiscoCXSecurity/udpy_proto_scanner/issues). 18 | 19 | When creating a new issue, please be sure to include a **title and clear description**, as much relevant information as 20 | possible, and, if possible, a test case. 21 | 22 | **If you discover a security bug, please do not report it through GitHub. Instead, please see security procedures in 23 | [SECURITY.md](/SECURITY.md).** 24 | 25 | ## Sending Pull Requests 26 | 27 | Before sending a new pull request, take a look at existing pull requests and issues to see if the proposed change or fix 28 | has been discussed in the past, or if the change was already implemented but not yet released. 29 | 30 | We expect new pull requests to include tests for any affected behavior, and, as we follow semantic versioning, we may 31 | reserve breaking changes until the next major version release. 32 | 33 | ## Other Ways to Contribute 34 | 35 | We welcome anyone that wants to contribute to `udpy_proto_scanner` to triage and reply to open issues to help troubleshoot 36 | and fix existing bugs. Here is what you can do: 37 | 38 | - Help ensure that existing issues follows the recommendations from the _[Reporting Issues](#reporting-issues)_ section, 39 | providing feedback to the issue's author on what might be missing. 40 | - Review existing pull requests, and testing patches against real existing applications that use `udpy_proto_scanner`. 41 | - Write a test, or add a missing test case to an existing test. 42 | 43 | Thanks again for your interest on contributing to `udpy_proto_scanner`! 44 | 45 | :heart: 46 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [oss-conduct@cisco.com][conduct-email]. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | [conduct-email]: mailto:oss-conduct@cisco.com 69 | 70 | ## Attribution 71 | 72 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 73 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 74 | 75 | [homepage]: https://www.contributor-covenant.org 76 | 77 | For answers to common questions about this code of conduct, see 78 | https://www.contributor-covenant.org/faq 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UDP Protocol Scanner 2 | 3 | A tool for identifying UDP services running on remote hosts. This tool may be of use to those performing security testing - e.g. during penetration testing, vulnerability assessments or while pivoting. 4 | 5 | This is python version of https://github.com/CiscoCXSecurity/udp-proto-scanner (which is written in Perl). 6 | 7 | # Quick Start 8 | 9 | udpy_proto_scanner scans by sending UDP probes (embedded in source code - no config file necessary) 10 | to a list of targets: 11 | ``` 12 | $ udpy_proto_scanner.py -f ips.txt 13 | $ udpy_proto_scanner.py -p ntp -f ips.txt 14 | $ udpy_proto_scanner.py -p all -b 32k 10.0.0.0/16 10.1.0.0-10.1.1.9 192.168.0.1 15 | ``` 16 | List probe names using the -l option: 17 | ``` 18 | $ udpy_proto_scanner.py -l 19 | The following probe names (-p argument) are available: 20 | * all 21 | * ike 22 | * echo 23 | * systat 24 | * daytime 25 | * chargen 26 | * time 27 | * net-support 28 | * gtpv1 29 | * l2tp 30 | * rpc 31 | * ntp 32 | * snmp-public 33 | * ms-sql 34 | * ms-sql-slam 35 | * netop 36 | * tftp 37 | * db2 38 | * citrix 39 | * RPCCheck 40 | * DNSVersionBindReq 41 | * Help 42 | * NBTStat 43 | * SNMPv1public 44 | * SNMPv3GetRequest 45 | * DNS-SD 46 | * DNSStatusRequest 47 | * SIPOptions 48 | * NTPRequest 49 | * AFSVersionRequest 50 | * Citrix 51 | * Kerberos 52 | * DTLSSessionReq 53 | * Sqlping 54 | * xdmcp 55 | * QUIC 56 | * sybaseanywhere 57 | * NetMotionMobility 58 | * LDAPSearchReqUDP 59 | * ibm-db2-das-udp 60 | * SqueezeCenter 61 | * Quake2_status 62 | * Quake3_getstatus 63 | * serialnumberd 64 | * vuze-dht 65 | * pc-anywhere 66 | * pc-duo 67 | * pc-duo-gw 68 | * memcached 69 | * svrloc 70 | * ARD 71 | * Quake1_server_info 72 | * Quake3_master_getservers 73 | * BackOrifice 74 | * Murmur 75 | * Ventrilo 76 | * TeamSpeak2 77 | * TeamSpeak3 78 | * FreelancerStatus 79 | * ASE 80 | * AndroMouse 81 | * AirHID 82 | * OpenVPN 83 | * ipmi-rmcp 84 | * coap-request 85 | * UbiquitiDiscoveryv1 86 | * UbiquitiDiscoveryv2 87 | ``` 88 | Targets files can be in CIDR format, or a range of IP addresses separated by a hyphen: 89 | ``` 90 | $ cat ips.txt 91 | # targets file can contain comments 92 | # and blank lines 93 | 94 | # and lines that contain only whitespace 95 | 96 | # list IPs to scan. Whitespace will be trimmed. 97 | 127.0.0.1 98 | 127.0.0.2 99 | 127.0.0.3 100 | 101 | # IP ranges are supported if the whole start IP and end IP is given: 102 | 127.0.0.4-127.0.1.7 103 | 104 | # CIDR notation is supported: 105 | 127.0.2.128/30 106 | ``` 107 | 108 | ## What is udpy_proto_scanner used for? 109 | 110 | It's used in the host-discovery and service-discovery phases of a pentest. 111 | It can be helpful if you need to discover hosts that only offer UDP services 112 | and are otherwise well firewalled - e.g. if you want to find all the DNS 113 | servers in a range of IP addresses. Alternatively on a LAN, you might want 114 | a quick way to find all the TFTP servers. 115 | 116 | Not all UDP services can be discovered in this way (e.g. SNMPv1 won't respond 117 | unless you know a valid community string). However, many UDP services can be 118 | discovered, e.g.: 119 | * DNS 120 | * TFTP 121 | * NTP 122 | * NBT 123 | * SunRPC 124 | * MS SQL 125 | * DB2 126 | * SNMPv3 127 | 128 | It can sometimes be useful to upload udpy_proto_scanner.py to a compromised 129 | host and run scans from there. 130 | 131 | ## udpy_proto_scanner is not a Port Scanner 132 | 133 | It won't give you a list of open and closed ports for each host. It's simply 134 | looking for specific UDP services. 135 | 136 | ## Usage Message 137 | ``` 138 | usage: udpy_proto_scanner.py [options] [ -p probe_name ] -f ipsfile 139 | udpy_proto_scanner.py [options] [ -p probe_name ] 10.0.0.0/16 10.1.0.0-10.1.1.9 192.168.0.1 140 | 141 | optional arguments: 142 | -h, --help show this help message and exit 143 | -f FILE, --file FILE File of ips 144 | -p PROBE_NAME_STR_LIST, --probe_name PROBE_NAME_STR_LIST 145 | Name of probe or "all". Default: all 146 | -l, --list_probes List all available probe name then exit 147 | -b BANDWIDTH, --bandwidth BANDWIDTH 148 | Bandwidth to use in bits/sec. Default 250k 149 | -c COMMONNESS, --commonness COMMONNESS 150 | Commonness of probes to send 1-9. 9 is common, 1 is 151 | rare. Implies -p all. Default 4 152 | -P PACKETRATE, --packetrate PACKETRATE 153 | Max packets/sec to send. Default unlimited 154 | -H RETRYRATE, --retryrate RETRYRATE 155 | Max rate (packets/sec) for retrying the same probe. 156 | Default 2 157 | -R RTT, --rtt RTT Max round trip time for probe. Default 1s 158 | -r RETRIES, --retries RETRIES 159 | No of packets to sent to each host. Default 2 160 | -d, --debug Debug mode 161 | -B BLOCKLIST, --blocklist BLOCKLIST 162 | List of blacklisted ips. Useful on windows to 163 | blocklist network addresses. Separate with commas: 164 | 127.0.0.0,192.168.0.0. Default None 165 | ``` 166 | 167 | ## Features and Design Goals 168 | 169 | ### Speed 170 | 171 | While scanning speed is not the primary goal of udpy_proto_scanner, it IS designed to not make you wait too long for scan results. udpy_proto_scanner is designed to be fast as it could easily be made to be given that it's coded in python. 172 | 173 | One way to speed up scans is to only send probes for the most common UDP services; or to just probes for the services you're interested in: 174 | ``` 175 | udpy_proto_scanner.py -c 9 10.0.0.0/24 # Probe only the most common services - 7 in total 176 | udpy_proto_scanner.py -c 4 10.0.0.0/24 # -c 4 is the default and send 35 probes 177 | udpy_proto_scanner.py -c 1 10.0.0.0/24 # Send all probes including uncommon services - 105 in total 178 | udpy_proto_scanner.py -p NBTStat 10.0.0.0/24 # Send only one probe of interest: NBTStat 179 | ``` 180 | 181 | Another is to send fewer retries: 182 | ``` 183 | udpy_proto_scanner.py -r 2 10.0.0.0/24 # -r 2 is the default. Each probe is sent 3 times. 184 | udpy_proto_scanner.py -r 0 10.0.0.0/24 # -r 0 is 3 times faster. Each probe is sent once. 185 | ``` 186 | 187 | For small numbers of hosts, the -H parameter can be adjusted to increase speed. This controls the number of times per second the same probe can be resent: 188 | ``` 189 | udpy_proto_scanner.py -p NBTStat -H 2 127.0.0.1 # Sends 3 probes, 0.5 seconds apart 190 | udpy_proto_scanner.py -p NBTStat -H 10 127.0.0.1 # Sends 3 probes, 0.1 seconds apart 191 | ``` 192 | 193 | Limits on the bandwidth used and packets per second are described below. However, be cautious of setting these too high. 194 | 195 | ### Big Scans 196 | 197 | udpy_proto_scanner is designed to be able to scan large numbers of hosts - hundreds of thousands or even millions of hosts. 198 | 199 | It will scan a Class B network with one probe with no retries in about 95 seconds (for a small probe size): 200 | ``` 201 | udpy_proto_scanner.py -p echo -r 0 127.0.0.1/16 202 | ``` 203 | 204 | ### Safety 205 | 206 | When pentesting badly configured networks or fragile hosts, scanning can sometimes cause outages. This tends to be rare, but udpy_proto_scanner aims to give testers ways to manage the risk of outages: 207 | * Specify maximum bandwidth in bits per section with -b or --bandwidth. Example: `-b 1m` or `-b 32k` 208 | * Sensible default of 250Kbit/sec for maximum bandwidth 209 | * Option to specify the maximum packets per second the scanner will send. Example `-P 3000` will send no more than 3000 packets per second. 210 | * Specify maximum rate at which a service should receive retry probes(*). Example: `-H 5` will send up to 5 packets per second to each service. (*) Just be cautious that this rate can be exceeded if you send multiple probe types that apply to the same port (e.g. ms-sql and ms-sql-slam both send to 1434/UDP - so that service will receive packets at double the rate specified by -H). 211 | * Cautious default of 2 packets per service for retries. This is not as slow as it might seem: if your host list is long, you can scan a lot of other hosts in 0.5 seconds, so efficiency is still high for large scans. 212 | 213 | If you choose to upload udpy_proto_scanner to a compromised host, so you can scan from there, the following may help to manage the risk of adversely affecting that host: 214 | * The script doesn't use forking or threading, which helps to manage the risk of accidentally swamping the target with processes or threads. 215 | * There is a maximum amount of memory that the script will use. Even when host lists are huge, the script will not read / generate the entire list in memory. This helps to manage the risk that the script will consume all available memory. udpy_proto_scanner will break scans up into chunks of around 100k hosts. 216 | * The code attempts to be efficient to keep CPU utilisation low. If CPU utilisation is too high for you (and it generally shouldn't be high with the default settings), try scanning at a slower speed (-b). 217 | * The program uses a single UDP socket to send each probe type from. This reduces the risk of exhausting the number of available sockets. e.g. if you send 105 different probe types, you will use no more than 105 sockets. 218 | * No output is written to disk, so the script should not use up disk space unexpectedly. 219 | 220 | ### Verbose Output 221 | 222 | To aid pentesters with record keeping and answering detailed questions about their scans, udpy_proto_scanner outputs verbose information about scan time, scan rates and configuration. It doesn't output to a file, though, so it's recommended you use `script' or output redirection if you need to keep a record of your scan. 223 | 224 | ### Portable 225 | 226 | The script was designed to work with python2 and python3 (but so far has only been tested with 2.7.18 and 3.10.8 on Linux and 3.10.10 on Windows 10) because you can't be sure how old a version of python you're going to find on a compromised host. 227 | 228 | The script has no dependencies and should work with a base python install. 229 | 230 | You should be able to copy-paste the .py file and run it. All probe data is in the source code and no external configuration file is needed. 231 | 232 | Note: No consideration is given to opsec. 233 | 234 | Note: Cursory testing has been carried out on Windows. It seems to work, but CPU utilisation seems a lot higher. On Windows, the code cannot currently handle sending to the network address (e.g. 127.0.0.0), so use the -B option to blocklist any network addresses in your target list. You'll get a useful error if you don't and the scan will abort. 235 | 236 | ### Reliability 237 | 238 | Retries are supported and enabled by default in case any probes are dropped on their way to the target. Example: `-r 2` will send a probe and then 2 retries (3 packets to each host in total). 239 | 240 | Scanning time is predictable. The time taken for scans should depend only on the parameters used and the length of the host list. If networks are congested or hosts are slow to respond or there's some sort of rate-limiting with replies, this will not affect scan time - although it could mean that you should scan at a lower rate. This is a feature, not a bug so that pentesters are not left wondering if their scan their scan will ever finish. 241 | 242 | Suitable for testing over slow links. udpy_proto_scanner will wait 1 second by default for replies. You can wait longer using RTT option: `-R 2.5` 243 | 244 | ## Risks: Beta quality code 245 | 246 | Aside from the usual risks of scanning, the code was written around March 2023, so it will take a while to test thoroughly. There might still be bugs that cause the scanner to behave badly. 247 | 248 | ## Credits 249 | 250 | The UDP probes are mainly taken from amap, nmap and ike-scan. 251 | Inspiration for the scanning code was drawn from ike-scan. 252 | Code is based on the original udp-proto-scanner with some small improvements. 253 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /udpy_proto_scanner.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # udpy_proto_scanner - UDP Service Discovery Tool 3 | # Copyright Cisco Systems, Inc. and its affiliates 4 | # 5 | # This tool may be used for legal purposes only. Users take full responsibility 6 | # for any actions performed using this tool. The author accepts no liability 7 | # for damage caused by this tool. If these terms are not acceptable to you, then 8 | # you are not permitted to use this tool. 9 | # 10 | # In all other respects the GPL version 2 applies: 11 | # 12 | # This program is free software; you can redistribute it and/or modify 13 | # it under the terms of the GNU General Public License as published by 14 | # the Free Software Foundation; either version 2 of the License, or 15 | # (at your option) any later version. 16 | # 17 | # This program is distributed in the hope that it will be useful, 18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | # GNU General Public License for more details. 21 | # 22 | # You should have received a copy of the GNU General Public License along 23 | # with this program; if not, write to the Free Software Foundation, Inc., 24 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 25 | # 26 | 27 | import argparse 28 | import collections 29 | import ipaddress 30 | import math 31 | import os 32 | import re 33 | import select 34 | import socket 35 | import sys 36 | import time 37 | 38 | class ScannerBase(object): 39 | def __init__(self): 40 | self._sleep_total = 0 41 | self.header = "Starting Scan" 42 | self.sleep_multiplier = 1.87 # if we total up the time we sleep for, it doesn't match the time cProfile reports we spent in the sleep function, so we use this multiplier to adjust the estimate 43 | self.reply_callback_function = None 44 | self.bandwidth_bits_per_second = 32000 45 | self.max_probes = 3 46 | self.probes = [] # [(None, None, None),] # List of probe tuples 47 | self.inter_packet_interval = None 48 | self.inter_packet_interval_per_host = None 49 | self.backoff = 1.5 50 | self.rtt = 0.5 # https://www.nature.com/articles/s41598-019-46208-6 51 | self.bytes_sent = 0 52 | self.resolve_names = False # TODO not implemented yet 53 | self.target_source = None 54 | self.target_list_unprocessed = [] 55 | self.target_filename = None 56 | self.scan_start_time_internal = None 57 | self.scan_start_time = None 58 | self.probes_sent_count = 0 59 | self.replies = 0 60 | self.packet_rate = None 61 | self.packet_rate_per_host = None 62 | self.probe_index_to_socket_dict = {} 63 | self.host_count = 0 64 | self.next_recv_time = time.time() 65 | self.recv_interval = 0.1 66 | self.debug = False 67 | self.log_reply_tuples = [] 68 | self.debug_reply_log = "debug_reply_log.txt" 69 | self.blocklist = [] 70 | self.count_in_queue = {} # how many probes are in the queue for each probe type 71 | self.sleep_reasons = {} 72 | 73 | # 74 | # Properties 75 | # 76 | 77 | @property 78 | def bytes_sent_target(self): 79 | if self.scan_start_time_internal: 80 | return self.bandwidth_bits_per_second * (time.time() - self.scan_start_time_internal) / 8 81 | else: 82 | return 0 83 | 84 | @property 85 | def probes_sent_target(self): 86 | if self.scan_start_time_internal: 87 | return self.packet_rate * (time.time() - self.scan_start_time_internal) 88 | else: 89 | return 0 90 | 91 | @property 92 | def sleep_total(self): 93 | return self._sleep_total * self.sleep_multiplier 94 | 95 | # 96 | # Setters 97 | # 98 | 99 | def set_reply_callback(self, reply_callback): 100 | self.reply_callback_function = reply_callback 101 | 102 | def set_debug(self, debug): 103 | self.debug = debug 104 | 105 | # set max_probes 106 | def set_max_probes(self, n): # int 107 | self.max_probes = int(n) 108 | 109 | def set_blocklist(self, blocklist_ips): 110 | # check ips are valid 111 | for ip in blocklist_ips: 112 | self.add_to_blocklist(ip) 113 | 114 | # set bandwidth 115 | def set_bandwidth(self, bandwidth): # string like 250k, 1m, 1g 116 | self.bandwidth_bits_per_second = expand_number(bandwidth) 117 | 118 | if self.bandwidth_bits_per_second < 1: 119 | print("[E] Bandwidth %s is too low" % self.bandwidth_bits_per_second) 120 | sys.exit(0) 121 | 122 | if self.bandwidth_bits_per_second > 1000000: 123 | print("[W] Bandwidth %s is too high. Continuing anyway..." % self.bandwidth_bits_per_second) 124 | 125 | self.set_inter_packet_interval() 126 | 127 | def set_inter_packet_interval(self): 128 | if self.packet_overhead is None or self.packet_overhead == 0: 129 | print("[E] Code error: Packet overhead not set prior to calculating inter-packet interval") 130 | sys.exit(0) 131 | if self.bandwidth_bits_per_second is None or self.bandwidth_bits_per_second == 0: 132 | print("[E] Code error: Bandwidth not set not set prior to calculating inter-packet interval") 133 | sys.exit(0) 134 | self.inter_packet_interval = 8 * (self.payload_len_estimate + self.packet_overhead) / float(self.bandwidth_bits_per_second) 135 | 136 | def set_packet_rate(self, packet_rate): 137 | self.packet_rate = expand_number(packet_rate) 138 | 139 | def set_packet_rate_per_host(self, packet_rate_per_host): 140 | self.packet_rate_per_host = packet_rate_per_host 141 | self.inter_packet_interval_per_host = 1 / float(self.packet_rate_per_host) 142 | 143 | def set_header(self, header): 144 | self.header = header 145 | 146 | def add_targets(self, targets): # list 147 | self.target_source = "list" 148 | self.target_list_unprocessed = targets 149 | 150 | def add_targets_from_file(self, file): # str 151 | self.target_source = "file" 152 | self.target_filename = file 153 | 154 | # 155 | # Adders 156 | # 157 | 158 | def add_to_blocklist(self, ip): 159 | try: 160 | socket.inet_aton(ip) 161 | except socket.error: 162 | print("[E] Invalid IP address in blocklist: %s" % ip) 163 | sys.exit(1) 164 | if ip not in self.blocklist: 165 | self.blocklist.append(ip) 166 | 167 | # 168 | # Getters 169 | # 170 | 171 | def get_probe_port(self, probe_index): 172 | probe = self.probes[probe_index] 173 | return int(probe[0]) 174 | 175 | def get_probe_payload_hex(self, probe_index): 176 | probe = self.probes[probe_index] 177 | return probe[2] 178 | 179 | def get_probe_payload_bin(self, probe_index): 180 | probe = self.probes[probe_index] 181 | return probe[3] 182 | 183 | def get_probe_name(self, probe_index): 184 | probe = self.probes[probe_index] 185 | return probe[1] 186 | 187 | def get_probe_index_from_socket(self, s): 188 | for probe_index, socket in self.probe_index_to_socket_dict.items(): 189 | if s == socket: 190 | return probe_index 191 | return None 192 | 193 | def get_available_bandwidth_quota_packets(self): 194 | 195 | packet_quota_left = None 196 | # return 100 if there is no bandwidth quota 197 | if self.bandwidth_bits_per_second is None: 198 | packet_quota_left = 100 199 | else: 200 | # return 0 if we exceed our bandwidth quota 201 | bytes_left = self.bytes_sent_target - self.bytes_sent 202 | if bytes_left <= 0: 203 | packet_quota_left = 0 204 | else: 205 | packet_quota_left = int(8 * bytes_left / float(self.packet_overhead)) 206 | 207 | # return the number of packets we can send 208 | return packet_quota_left 209 | 210 | def get_available_packet_rate_quota_packets(self): 211 | packet_quota_left = None 212 | # return 100 if there is no packet rate quota 213 | if self.packet_rate is None or self.packet_rate == 0: # TODO messy 214 | packet_quota_left = 100 215 | else: 216 | # return 0 if we exceed our packet rate quota 217 | packets_left = self.probes_sent_target - self.probes_sent_count 218 | if packets_left <= 0: 219 | packet_quota_left = 0 220 | else: 221 | packet_quota_left = packets_left 222 | 223 | # return the number of packets we can send 224 | return packet_quota_left 225 | 226 | def get_available_quota_packets(self): 227 | return int(min(self.get_available_bandwidth_quota_packets(), self.get_available_packet_rate_quota_packets())) 228 | 229 | # 230 | # Debug 231 | # 232 | 233 | # Note that recording results in memory could use too much memory for large scans 234 | # so is disabled by default. This feature is used for automated testing. 235 | def debug_log_reply(self, probe_name, srcip, port, data): 236 | self.log_reply_tuples.append((probe_name, srcip, port, data)) 237 | 238 | def debug_write_log(self): 239 | with open(self.debug_reply_log, "w") as f: 240 | for probe_name, srcip, port, data in self.log_reply_tuples: 241 | f.write("%s,%s,%s,%s\n" % (probe_name, srcip, port, str_or_bytes_to_hex(data))) 242 | print("[i] Wrote debug log to %s" % self.debug_reply_log) 243 | 244 | def __repr__(self): # TODO 245 | return "%s()" % type(self).__name__ 246 | 247 | def __str__(self): # TODO 248 | return "%s()" % type(self).__name__ 249 | 250 | # 251 | # Others 252 | # 253 | 254 | def wait_for_quotas(self): 255 | bandwidth_quota_ok = False 256 | packet_rate_quota_ok = False 257 | probe_send_ok = False 258 | bandwidth_quota_packets_left = 0 259 | packet_quota_packets_left = 0 260 | wait_time = 0 261 | while not (packet_rate_quota_ok and bandwidth_quota_ok and probe_send_ok): 262 | 263 | # check if we're within bandwidth quota 264 | force_bandwidth_quota_wait = True 265 | force_packet_quota_wait = True 266 | force_probe_state_wait = True 267 | bandwidth_quota_ok = False 268 | packet_rate_quota_ok = False 269 | probe_send_ok = False 270 | wait_time = 0 271 | bandwidth_quota_packets_left = self.get_available_bandwidth_quota_packets() 272 | if bandwidth_quota_packets_left > 0: 273 | bandwidth_quota_ok = True 274 | force_bandwidth_quota_wait = False 275 | 276 | # check if we're within packet rate quota 277 | force_packet_quota_wait = False 278 | packet_quota_packets_left = self.get_available_packet_rate_quota_packets() 279 | if packet_quota_packets_left > 0: 280 | packet_rate_quota_ok = True 281 | force_packet_quota_wait = False 282 | 283 | # Check all of the probe states to see if any are ready to send 284 | # This is expesnive, so we only do it if we're within the other quotas 285 | # if self.probe_state_ready(): 286 | # probe_send_ok = True 287 | # force_probe_state_wait = False 288 | if self.get_queue_length() > 0: 289 | 290 | next_probe_state = self.queue_peek_first() 291 | last_probe_time = next_probe_state.probe_sent_time 292 | now = time.time() 293 | 294 | if last_probe_time is None or now > last_probe_time + self.inter_packet_interval_per_host: 295 | probe_send_ok = True 296 | force_probe_state_wait = False 297 | else: 298 | wait_time = last_probe_time + self.inter_packet_interval_per_host - now 299 | else: 300 | self.probe_state_ready_last_result = True 301 | return self.probe_state_ready_last_result 302 | 303 | # update stats 304 | if force_bandwidth_quota_wait: 305 | self.sleep_reasons["bandwidth_quota"] += 1 306 | elif force_packet_quota_wait: 307 | self.sleep_reasons["packet_quota"] += 1 308 | elif force_probe_state_wait: 309 | self.sleep_reasons["port_states"] += 1 310 | 311 | if not (packet_rate_quota_ok and bandwidth_quota_ok and probe_send_ok): 312 | # sleep for self.inter_packet_interval seconds 313 | wait_time = max(self.inter_packet_interval, wait_time) 314 | 315 | # Do an extra receive if we have spare time 316 | # we must not sleep for more than the receive interval or we won't check for reponses when we're supposed to 317 | # Without this shorter sleep, very small scans tend to miss responses because they recv too quickly after sending and then wait for the next retry. Then the same problem occurs. 318 | if wait_time > self.recv_interval: 319 | self.receive_packets(self.get_socket_list()) 320 | self._sleep_total += self.recv_interval 321 | time.sleep(self.recv_interval) 322 | else: 323 | self._sleep_total += wait_time 324 | time.sleep(wait_time) 325 | 326 | # 327 | # Abstract methods # TODO is abc module portable? 328 | # 329 | 330 | def dump(self): 331 | raise NotImplementedError 332 | 333 | def set_rtt(self, rtt): 334 | raise NotImplementedError 335 | 336 | def set_probes(self, probes): 337 | raise NotImplementedError 338 | 339 | def start_scan(self): 340 | raise NotImplementedError 341 | 342 | def receive_packets(self, socket_list): 343 | raise NotImplementedError 344 | 345 | def inform_starting_probe_type(self, probe_index): 346 | raise NotImplementedError 347 | 348 | def decrease_count_in_queue(self): 349 | raise NotImplementedError 350 | 351 | def get_queue_length(self): 352 | raise NotImplementedError 353 | 354 | def queue_peek_first(self): 355 | raise NotImplementedError 356 | 357 | def get_socket_list(self): 358 | raise NotImplementedError 359 | 360 | ip_regex = r"(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])" 361 | cidr_regex = r"(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([0-9]{1,2})$" 362 | 363 | class TargetGenerator: 364 | def __init__(self, make_probe_state_callback, list=None, filename=None, custom=False): 365 | if list is None: 366 | list = [] 367 | self.target_filename = filename 368 | self.target_list_unprocessed = list 369 | self.target_source = None 370 | self.custom = custom 371 | self.make_probe_state_callback = make_probe_state_callback 372 | if len(self.target_list_unprocessed) > 0: 373 | self.target_source = "list" 374 | elif self.target_filename: 375 | self.target_source = "file" 376 | elif custom: 377 | self.target_source = "custom" 378 | else: 379 | raise Exception("[E] __init__: No target source set") 380 | 381 | def get_probe_state_generator(self, probes): 382 | if self.custom: # format: (ip, port, name, payload_bin) 383 | probe_index = 0 384 | for probe_tuple in probes: 385 | ip = probe_tuple[0] 386 | port = probe_tuple[1] 387 | name = probe_tuple[2] 388 | payload_bin = probe_tuple[3] 389 | cps = self.make_probe_state_callback(ip, probes, probe_index) 390 | cps.payload_bin = payload_bin 391 | yield cps 392 | else: 393 | for probe_index in range(len(probes)): 394 | for target in self._get_targets(): 395 | yield self.make_probe_state_callback(target, probes, probe_index) 396 | 397 | def get_generator(self): 398 | return self._get_targets() 399 | 400 | # generator in case we are passed more hosts than we can fit in memory 401 | def _get_targets(self): 402 | if self.target_source == "list": 403 | for t in self._get_targets_from_list(self.target_list_unprocessed): 404 | yield t 405 | elif self.target_source == "file": 406 | for t in self._get_targets_from_file(self.target_filename): 407 | yield t 408 | else: 409 | raise Exception("[E] _get_targets: No target source set") 410 | 411 | # unexpanded list like [ 10.0.0.1, 10.0.0.10-10.0.0.20, 10.0.2.0/24 ] 412 | def _get_targets_from_list(self, targets): # list 413 | for target in targets: 414 | for t in self._get_targets_from_string(target): 415 | yield t 416 | 417 | def _get_targets_from_string(self, target): # str 418 | if re.match(r"^%s-%s$" % (ip_regex, ip_regex), target): 419 | for t in self._get_targets_from_ip_range(target): 420 | yield t 421 | 422 | elif re.match(r"^%s$" % ip_regex, target): 423 | yield target 424 | 425 | elif re.match(r"^%s$" % cidr_regex, target): 426 | for t in self._get_target_ips_from_cidr(target): 427 | yield t 428 | 429 | else: 430 | print("[E] %s is not a valid ip, ip range or cidr" % target) 431 | sys.exit(0) 432 | 433 | # add targets from file 434 | def _get_targets_from_file(self, file): # str 435 | if not os.path.isfile(file): 436 | print("[E] File %s does not exist" % file) 437 | sys.exit(0) 438 | with open(file, 'r') as f: 439 | for target in f: 440 | # strip leading/trailing whitespace 441 | target = target.strip() 442 | 443 | # ignore comments 444 | if target.startswith('#'): 445 | continue 446 | 447 | # ignore empty lines 448 | if not target: 449 | continue 450 | 451 | # ignore lines with only whitespace 452 | if re.match(r'^\s+$', target): 453 | continue 454 | 455 | # yield from self._get_targets_from_string(target) 456 | for t in self._get_targets_from_string(target): 457 | yield t 458 | 459 | # add targets from ip range like 10.0.0.1-10.0.0.10 460 | def _get_targets_from_ip_range(self, ip_range): # str 461 | # check ip_range is in the right format 462 | if not re.match(r"^%s-%s$" % (ip_regex, ip_regex), ip_range): 463 | print("[E] IP range %s is not in the right format" % ip_range) 464 | sys.exit(0) 465 | 466 | # get ip range 467 | ip_range = ip_range.split('-') 468 | 469 | # get ip range start and end 470 | start_ip = ip_range[0] 471 | if sys.version_info.major == 2: 472 | start_ip = start_ip.decode("utf8") 473 | 474 | end_ip = ip_range[1] 475 | if sys.version_info.major == 2: 476 | end_ip = end_ip.decode("utf8") 477 | 478 | ip_range_start = ipaddress.ip_address(start_ip) 479 | ip_range_end = ipaddress.ip_address(end_ip) 480 | 481 | # add targets 482 | for ip_int in range(int(ip_range_start), int(ip_range_end) + 1): 483 | yield str(ipaddress.ip_address(ip_int)) 484 | 485 | def _get_target_ips_from_cidr (self, cidr): # str 486 | # check cidr is in the right format 487 | m = re.match(cidr_regex, cidr) 488 | if not m: 489 | print("[E] CIDR %s is not in the right format" % cidr) 490 | sys.exit(0) 491 | if int(m.group(1)) > 32: 492 | print("[E] Netmask for %s is > 32" % cidr) 493 | sys.exit(0) 494 | if int(m.group(1)) < 8: 495 | print("[E] Netmask for %s is < 8" % cidr) 496 | sys.exit(0) 497 | 498 | # if running python2, cidr must be unicode, not str 499 | if sys.version_info.major == 2: 500 | cidr = cidr.decode("utf8") 501 | 502 | ip_range = ipaddress.ip_network(cidr, False) 503 | # add targets 504 | for ip_int in range(int(ip_range.network_address), int(ip_range.broadcast_address) + 1): 505 | yield str(ipaddress.ip_address(ip_int)) 506 | 507 | class ProbeStateUdp: 508 | def __init__(self, target, probe_index): 509 | self.target_ip = target 510 | self.probe_index = probe_index 511 | self.probe_sent_time = None 512 | self.probes_sent = 0 513 | 514 | def __repr__(self): 515 | return "%s(%s, %s, %s)" % (type(self).__name__, self.target_ip, self.probe_sent_time, self.probes_sent) 516 | 517 | # Each probe state can have a different probe 518 | 519 | class ProbeStateUdpCustom(ProbeStateUdp): 520 | def __init__(self, target, probe_index): 521 | super().__init__(target, probe_index) 522 | self.payload_bin = None 523 | 524 | def __repr__(self): 525 | return "%s(%s, %s, %s)" % (type(self).__name__, self.target, self.probe_sent_time, self.probes_sent) 526 | 527 | class ScannerUDP(ScannerBase): 528 | def __init__(self): 529 | super(ScannerUDP, self).__init__() 530 | 531 | # 532 | # Specific to UDP 533 | # 534 | 535 | self.next_timer_adjust = None 536 | self.custom_probes = None 537 | self.probe_states_queue = collections.deque() 538 | self.unexpected_replies = 0 539 | self.send_buffer_warning_displayed = False 540 | 541 | # 542 | # Common to TCP and UDP, but set to different values 543 | # 544 | 545 | self.payload_len_estimate = 10 # a guess 546 | self.packet_overhead = 42 # 14 bytes for ethernet frame + 20 bytes IP header + 8 bytes UDP header 547 | self.host_count_high_water = 100000 548 | self.host_count_low_water = 90000 549 | 550 | # 551 | # Methods that are implemented differently for TCP and UDP Scanners 552 | # 553 | def dump(self): 554 | print("") 555 | print_header(self.header) 556 | if self.target_filename: 557 | print("Targets file: ................ %s" % self.target_filename) 558 | if self.target_list_unprocessed: 559 | print("Targets: ..................... %s" % ", ".join(self.target_list_unprocessed)) 560 | if self.probes: # may not be used if caller is using custom probes 561 | print("Probes: ...................... %s Probes: %s" % (len(self.probes), ", ".join([p[1] for p in self.probes]))) 562 | print("Retries: ..................... %s" % (self.max_probes - 1)) 563 | print("Bandwidth: ................... %s bits/second" % self.bandwidth_bits_per_second) 564 | if self.packet_rate: 565 | print("Packet rate: ................. %s packets/second" % self.packet_rate) 566 | print("RTT: ......................... %s seconds" % self.rtt) 567 | print("Interpacket Interval Per Host: %s seconds" % self.inter_packet_interval_per_host) 568 | print("Inter-packet interval: ....... %s seconds" % self.inter_packet_interval) 569 | print("Packet overhead: ............. %s" % self.packet_overhead) 570 | # Note that we can't print targets / target_count here because we'd drain the generator (which could contain millions of targets) 571 | print_footer() 572 | 573 | def set_rtt(self, rtt): 574 | self.rtt = float(rtt) 575 | 576 | def set_probes(self, probes): # tuple of (port, probe_name, payload_hex) 577 | self.probes = [] 578 | for probe in probes: 579 | probe_bin = None 580 | if probe[2] is not None: 581 | probe_bin = bytes(bytearray.fromhex(probe[2])) 582 | 583 | probe_with_bin = probe + (probe_bin,) 584 | self.probes.append(probe_with_bin) 585 | 586 | def start_scan(self): 587 | # check we have probes 588 | if not self.probes: 589 | print("[E] No probes set. Call set_probes() method before starting scan.") 590 | sys.exit(0) 591 | 592 | # Convert payload hex into binary; calculate inter-packet interval 593 | self.inter_packet_interval = 8 * (self.packet_overhead + self.payload_len_estimate) / float(self.bandwidth_bits_per_second) 594 | 595 | def make_probe_state_callback(target, probes, probe_index): 596 | if self.custom_probes: 597 | return ProbeStateUdpCustom(target, probe_index) 598 | else : 599 | return ProbeStateUdp(target, probe_index) 600 | 601 | # Set up target generator 602 | target_generator = None 603 | if self.target_source == "file": 604 | target_generator = TargetGenerator(make_probe_state_callback, filename=self.target_filename) 605 | elif self.target_source == "list": 606 | target_generator = TargetGenerator(make_probe_state_callback, list=self.target_list_unprocessed) 607 | elif self.target_source == "custom": 608 | target_generator = TargetGenerator(make_probe_state_callback, custom=True) 609 | else: 610 | print("[E] Unknown target source: %s. Call add_targets_from_file() or add_targets() method before starting scan." % self.target_source) 611 | sys.exit(0) 612 | probes_state_generator_function = None 613 | if self.target_source == "custom": 614 | probes_state_generator_function = target_generator.get_probe_state_generator(self.custom_probes) 615 | else: 616 | probes_state_generator_function = target_generator.get_probe_state_generator(self.probes) 617 | 618 | # Initialize stats for how many of each probe type are in the queue 619 | for probe_index in range(len(self.probes)): 620 | self.count_in_queue[probe_index] = 0 621 | 622 | last_send_time = None 623 | 624 | self.dump() 625 | 626 | self.scan_start_time_internal = time.time() # used for user-facing stats 627 | self.scan_start_time = time.time() # used for scanner timings only 628 | scan_running = True 629 | more_hosts = True 630 | highest_probe_index_seen = -1 631 | self.sleep_reasons["packet_quota"] = 0 632 | self.sleep_reasons["bandwidth_quota"] = 0 633 | self.sleep_reasons["port_states"] = 0 634 | while scan_running: 635 | # 636 | # add probes to queue 637 | # 638 | # if queue has capacity, create more probestate objects for up to host_count_high_water hosts; add them to queue 639 | if more_hosts and len(self.probe_states_queue) < self.host_count_low_water: 640 | more_hosts = False # if we complete the for loop, there are no more probes to add 641 | for ps in probes_state_generator_function: 642 | # Don't add to queue if target is in blocklist 643 | if ps.target_ip in self.blocklist: 644 | print("[i] Skipping target %s because it is in the blocklist" % ps.target_ip) 645 | continue 646 | 647 | # Count the number of hosts we are scanning 648 | if ps.probe_index == 0: 649 | self.host_count += 1 650 | 651 | # Inform user went we start scanning a new probe type 652 | if ps.probe_index > highest_probe_index_seen: 653 | self.inform_starting_probe_type(ps.probe_index) 654 | highest_probe_index_seen = ps.probe_index 655 | 656 | # Add to queue 657 | self.probe_states_queue.append(ps) 658 | 659 | # Increment count of probes of this type in queue 660 | self.count_in_queue[ps.probe_index] += 1 661 | 662 | # Create socket if needed 663 | if ps.probe_index not in self.probe_index_to_socket_dict: 664 | # Create socket to send packets from. All probes of the same type are sent from same source port. 665 | # We don't use the same source port for all probes because if we have two DNS probes (for example) 666 | # it will be difficult to match the replies to the probe. 667 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 668 | sock.setblocking(False) 669 | 670 | # This should set buffer to 425984, but maybe some OS's have larger buffers. 671 | # A large buffer is important if users scan locally attached networks. UDP packets 672 | # will be buffered while ARP fails to resolve the MAC address of the target. 673 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, int(1000000)) 674 | 675 | # Allow sending to broadcast addresses 676 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) 677 | 678 | self.probe_index_to_socket_dict[ps.probe_index] = sock 679 | 680 | # If we've reached the high watermark, exit the for loop 681 | if len(self.probe_states_queue) >= self.host_count_high_water: 682 | # If we exit the for loop early, there are more probes to add 683 | more_hosts = True 684 | break 685 | 686 | # If we're not within quotas, wait until we are: 687 | # * bandwidth quota 688 | # * packet rate quota 689 | # * probe state < at least one probe state is ready to send 690 | self.wait_for_quotas() # TODO doesn't work properly unless inter_packet_interface_per_host is about 25% of rtt 691 | 692 | # if queue has items, pop one off 693 | packet_count_to_send = self.get_available_quota_packets() 694 | 695 | # if sending a packet now won't exceed quotas, send a packet 696 | for packet_counter in range(min(packet_count_to_send, len(self.probe_states_queue))): 697 | now = time.time() 698 | # if queue has items, pop one off 699 | if len(self.probe_states_queue) > 0: 700 | send_buffer_full = False 701 | ps = self.probe_states_queue.popleft() 702 | 703 | # Check if we already sent all probes to this host + we're past the RTT window 704 | if ps.probes_sent >= self.max_probes: 705 | if time.time() > ps.probe_sent_time + self.rtt: 706 | # We've sent max probes to this host and we're past the RTT window, so we're done with this host 707 | # we don't add this host back to the queue 708 | 709 | # Decrement count of probes of this type in queue 710 | self.decrease_count_in_queue(ps.probe_index) 711 | 712 | else: 713 | self.probe_states_queue.append(ps) # add back to queue, but don't send more probes to this host 714 | 715 | # We need to send a packet. Also add back to queue so we can check for replies later 716 | else: 717 | self.probe_states_queue.append(ps) # add back to queue 718 | 719 | # Check if probe is due for this host: i.e. if we're past the inter-packet interval for this host; or we never sent a probe; or no inter-packet interval is configured 720 | if (ps.probe_sent_time is None) or (self.packet_rate_per_host and (time.time() > ps.probe_sent_time + self.inter_packet_interval_per_host)): 721 | # Send probe 722 | sent = False 723 | remove_and_blacklist = False 724 | payload_bin = self.get_probe_payload_bin(ps.probe_index) 725 | if payload_bin is None: # It's a custom probe state 726 | payload_bin = ps.payload_bin 727 | while not sent and not remove_and_blacklist: 728 | sock = self.probe_index_to_socket_dict[ps.probe_index] 729 | port = self.get_probe_port(ps.probe_index) 730 | 731 | # For the last few probes, start noting the time we send the last probe. For the stats. 732 | #if more_hosts == 0 and ps.probes_sent == self.max_probes - 1: # This doesn't work if we get a reply before we send the last probe 733 | if not more_hosts: 734 | last_send_time = time.time() 735 | 736 | # At around 16Mbit/s we get occassional errors on sendto: PermissionError: [Errno 1] Operation not permitted 737 | # so we catch these errors and retry 738 | try: 739 | sock.sendto(payload_bin, (ps.target_ip, port)) 740 | sent = True 741 | except socket.error as e: 742 | # check if we're running on windows 743 | if sys.platform == 'win32': 744 | # The socket will no longer be usable 745 | print("[E] %s: sending to %s:%s. Use -B to blocklist. Fatal error on Windows." % (e, ps.target_ip, port)) 746 | sys.exit(1) 747 | else: 748 | if "Errno 13" in str(e): 749 | print("[W] %s: sending to %s:%s. Use -B to blocklist. Auto-adding to blocklist" % (e, ps.target_ip, port)) 750 | remove_and_blacklist = True 751 | if "Errno 11" in str(e): 752 | if not self.send_buffer_warning_displayed: 753 | print("[W] %s: sending to %s:%s." % (e, ps.target_ip, port)) 754 | print("[I] Errno 11 means send buffer is full (SO_SNDBUF). This will slow the scan down.") 755 | print(""" 756 | Cause: Scanning locally attached networks (i.e. not through a gateway). Loopback is not affected by this problem. 757 | Buffers fill up while the kernel fails to resolve the MAC address of the target (using ARP). 758 | 759 | Possible workarounds: 760 | 1) Scan only live hosts on the local network (do an ARP scan to find them) 761 | 2) Fix the code 1: use a pool of sockets for sending; or 762 | 3) Fix the code 2: check if target is local and skip it if there's no ARP cache entry 763 | """) 764 | print("[I] Attempting to auto-throttle scan (this is going to make it slow)") 765 | self.send_buffer_warning_displayed = True 766 | 767 | # Sleep to letter the buffer empty a bit 768 | # We can't sleep too long or we might miss replies 769 | time.sleep(0.1) 770 | 771 | if self.next_timer_adjust is None or time.time() > self.next_timer_adjust: 772 | # Intervals of 0.002 - 0.003 worked will during testing. So increments of 773 | # 0.0003 should work to help us locate a suitable interval. 774 | old_interval = self.inter_packet_interval 775 | self.inter_packet_interval += 0.0003 776 | 777 | # As we just slept, the scanner timers will be disrupted, so we need to reset 778 | new_bandwidth_bits_per_second = int(8 * (self.payload_len_estimate + self.packet_overhead) / self.inter_packet_interval) 779 | print("[I] Auto-adjusting bandwidth to %s bits per second" % new_bandwidth_bits_per_second) 780 | self.set_bandwidth(new_bandwidth_bits_per_second) 781 | self.scan_start_time_internal = time.time() # TODO need to track the real start time AND the start time used for timing. 782 | 783 | # 0.9 seconds should be enough to let the buffer empty a bit 784 | # after that we'll wait another 0.1 seconds to let the buffer empty a bit more 785 | # This seems a slow way to adjust the timer, but if we go faster, we risk 786 | # overshooting and the scan will run more slowly than necessary 787 | self.next_timer_adjust = time.time() + 0.9 788 | 789 | # We didn't send a packet, but we need to set this to avoid a retry 790 | # Retrying is bad because we know the send buffer is full. 791 | sent = True 792 | send_buffer_full = True 793 | 794 | if remove_and_blacklist: 795 | print("[W] Target IP %s will be removed from scan queue and added to blocklist" % ps.target_ip) 796 | self.add_to_blocklist(ps.target_ip) 797 | try: 798 | self.probe_states_queue.remove(ps) 799 | except ValueError: 800 | print("[W] Couldn't remove from queue") 801 | print(self.probe_states_queue) 802 | exit(1) 803 | 804 | if send_buffer_full: 805 | # Break out of the for-loop so we don't send any more packets right away 806 | break 807 | 808 | # Update stats 809 | self.probes_sent_count += 1 810 | ps.probes_sent += 1 811 | ps.probe_sent_time = time.time() 812 | self.bytes_sent += len(payload_bin) + self.packet_overhead 813 | 814 | else: 815 | # sleep for self.inter_packet_interval seconds 816 | time.sleep(self.inter_packet_interval) # python might sleep for too long - e.g. minimum of 1-10ms. That's OK, we'll be less likely to sleep next time round the loop. 817 | 818 | # recv some packets 819 | # for efficiency we only receive after every 10 packets sent, or if we're past the next recv time 820 | # whichever is sooner 821 | now = time.time() 822 | if self.probes_sent_count % 10 == 0 or self.next_recv_time < now: 823 | self.next_recv_time = now + self.recv_interval 824 | scan_running = self.receive_packets(self.get_socket_list()) or more_hosts 825 | 826 | # recv any remaining packets 827 | self.receive_packets(self.get_socket_list()) 828 | 829 | # self.scan_duration = time.time() - self.scan_start_time 830 | self.scan_duration = last_send_time - self.scan_start_time 831 | 832 | # scan_duration can be 0 for quick scans on windows 833 | if self.scan_duration == 0: 834 | self.scan_duration = 0.001 835 | self.scan_rate_bits_per_second = int(8 * self.bytes_sent / self.scan_duration) 836 | 837 | if self.debug: 838 | self.debug_write_log() 839 | 840 | def reset_scan_timers(self): 841 | pass 842 | 843 | # returns True if we have more targets to probe; False if not 844 | def receive_packets(self, socket_list): 845 | if socket_list: 846 | # check if there are any packets to receive 847 | readable, _, _ = select.select(socket_list, [], [], 0) 848 | 849 | # recv if there are 850 | for s in readable: 851 | data, addr = (None, None) 852 | try: 853 | data, addr = s.recvfrom(1024) # 854 | except socket.error as e: 855 | continue 856 | srcip = addr[0] 857 | srcport = addr[1] 858 | socket_probe_index = self.get_probe_index_from_socket(s) 859 | if socket_probe_index is None: 860 | print("[W] Received reply from %s:%s but don't know which probe it's for: %s" % (srcip, srcport, str_or_bytes_to_hex(data))) 861 | continue 862 | port = self.get_probe_port(socket_probe_index) 863 | probe_name = self.get_probe_name(socket_probe_index) 864 | 865 | found = False 866 | # search for probe state 867 | for probe_state in self.probe_states_queue: 868 | if socket_probe_index == probe_state.probe_index and probe_state.target_ip == srcip and port == srcport: 869 | if self.reply_callback_function: 870 | self.reply_callback_function(probe_name, srcip, port, str_or_bytes_to_hex(data)) 871 | else: 872 | print("Received reply to probe %s (target port %s) from %s:%s: %s" % (probe_name, port, srcip, srcport, str_or_bytes_to_hex(data))) 873 | self.probe_states_queue.remove(probe_state) 874 | self.decrease_count_in_queue(probe_state.probe_index) 875 | found = True 876 | self.replies += 1 877 | if self.debug: 878 | self.debug_log_reply(probe_name, srcip, port, data) 879 | break 880 | if not found: 881 | print("[W] Received unexpected reply to probe %s (target port %s) reply from %s:%s: %s" % (probe_name, port, srcip, srcport, str_or_bytes_to_hex(data))) 882 | self.unexpected_replies += 1 883 | 884 | if len(self.probe_states_queue) == 0: 885 | return False 886 | return True 887 | 888 | def inform_starting_probe_type(self, probe_index): 889 | print("[i] Sending probe %s to targets on port %s..." % (self.get_probe_name(probe_index), self.get_probe_port(probe_index))) 890 | 891 | def decrease_count_in_queue(self, probe_index): 892 | self.count_in_queue[probe_index] -= 1 893 | if self.count_in_queue[probe_index] == 0: 894 | self.close_socket_for_probe_index(probe_index) 895 | 896 | def get_queue_length(self): 897 | return len(self.probe_states_queue) 898 | 899 | def queue_peek_first(self): 900 | return self.probe_states_queue[0] 901 | 902 | def get_socket_list(self): 903 | return list(self.probe_index_to_socket_dict.values()) 904 | 905 | # 906 | # UDP Specific Methods 907 | # 908 | 909 | def set_custom_probes(self, probes): # tuple of (ip, port, probe_name, payload_bin) 910 | self.custom_probes = probes 911 | self.target_source = "custom" 912 | 913 | def close_socket_for_probe_index(self, probe_index): 914 | socket = self.probe_index_to_socket_dict[probe_index] 915 | if self.debug: 916 | print("[D] Closing socket for probe index %s (%s on port %s)" % (probe_index, self.get_probe_name(probe_index), self.get_probe_port(probe_index))) 917 | socket.close() 918 | del self.probe_index_to_socket_dict[probe_index] 919 | 920 | # 921 | # Helper functions 922 | # 923 | 924 | # recvfrom returns bytes in python3 and str in python3. This function converts either to hex string 925 | def str_or_bytes_to_hex(str_or_bytes): 926 | return "".join("{:02x}".format(c if type(c) is int else ord(c)) for c in str_or_bytes) 927 | 928 | def get_time(): 929 | offset = time.timezone 930 | if time.localtime().tm_isdst: 931 | offset = time.altzone 932 | offset = int(offset / 60 / 60 * -1) 933 | if offset > 0: 934 | offset = "+" + str(offset) 935 | else: 936 | offset = str(offset) 937 | return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + " UTC" + offset 938 | 939 | def round_pretty(x): 940 | # avoid math errors 941 | if x < 0.01: 942 | x = 0.01 943 | if x <= 100: 944 | # round to 3 significant figures 945 | return round(x, 2-int(math.floor(math.log10(abs(x))))) 946 | else: 947 | # Otherwise, just covert to int 948 | return int(x) 949 | 950 | def print_header(message, width=80): 951 | message_len = len(message) + 2 # a space either side 952 | pad_left = int((width - message_len) / 2) 953 | pad_right = width - message_len - pad_left 954 | print("%s %s %s" % ("=" * pad_left, message, "=" * pad_right)) 955 | 956 | def print_footer(width=80): 957 | print("=" * width) 958 | 959 | # Convert a string to a number, with support for K, M, G suffixes 960 | def expand_number(number): # int or str 961 | number_as_string = str(number) 962 | if number_as_string.lower().endswith('k'): 963 | return int(number_as_string[:-1]) * 1000 964 | elif number_as_string.lower().endswith('m'): 965 | return int(number_as_string[:-1]) * 1000000 966 | elif number_as_string.lower().endswith('g'): 967 | return int(number_as_string[:-1]) * 1000000000 968 | else: 969 | if not number_as_string.isdigit(): 970 | print("[E] %s should be an integer or an integer with k, m or g suffix" % number_as_string) 971 | sys.exit(0) 972 | else: 973 | return int(number_as_string) 974 | 975 | # return list of ports from a string like "1,2,3-5,6" 976 | def expand_port_list(ports): 977 | ports_list = [] 978 | for port in ports.split(','): 979 | if '-' in port: 980 | port_range = port.split('-') 981 | if len(port_range) != 2: 982 | print("[E] Port range %s is not in the right format" % port) 983 | sys.exit(0) 984 | for p in range(int(port_range[0]), int(port_range[1]) + 1): 985 | if 0 < p < 65536: 986 | ports_list.append(p) 987 | else: 988 | print("[E] Port %s is not in in range 1-65535" % p) 989 | sys.exit(0) 990 | else: 991 | port = int(port) 992 | if 0 < port < 65536: 993 | ports_list.append(port) 994 | else: 995 | print("[E] Port %s is not in in range 1-65535" % port) 996 | sys.exit(0) 997 | return ports_list 998 | 999 | # hex to bytes 1000 | def hex_decode(hex_string): 1001 | return bytes(bytearray.fromhex(hex_string)) 1002 | 1003 | def hex_encode(hex_bytes): 1004 | return hex_bytes.hex() 1005 | 1006 | # Define probes 1007 | probe_list = [] 1008 | 1009 | # from ike-scan 1010 | probe_list.append({'source': 'ups', 'name': 'ike', 'payload': '5b5e64c03e99b51100000000000000000110020000000000000001500000013400000001000000010000012801010008030000240101', 'rarity': '1', 'ports': '500'}) 1011 | 1012 | # small services 1013 | probe_list.append({'source': 'ups', 'name': 'echo', 'payload': '313233', 'rarity': '3', 'ports': '7'}) 1014 | probe_list.append({'source': 'ups', 'name': 'systat', 'payload': '313233', 'rarity': '3', 'ports': '11'}) 1015 | probe_list.append({'source': 'ups', 'name': 'daytime', 'payload': '313233', 'rarity': '3', 'ports': '13'}) 1016 | probe_list.append({'source': 'ups', 'name': 'chargen', 'payload': '313233', 'rarity': '3', 'ports': '19'}) 1017 | probe_list.append({'source': 'ups', 'name': 'time', 'payload': '313233', 'rarity': '3', 'ports': '37'}) 1018 | 1019 | # misc 1020 | probe_list.append({'source': 'ups', 'name': 'net-support', 'payload': '01000000000000000000000000000000000080000000000000000000000000000000000000', 'rarity': '6', 'ports': '5405'}) 1021 | probe_list.append({'source': 'ups', 'name': 'gtpv1', 'payload': '320100040000000050000000', 'rarity': '6', 'ports': '2123'}) 1022 | 1023 | # https://community.cisco.com/t5/vpn/problems-getting-l2tp-over-ipsec-on-ios/td-p/1438134 1024 | probe_list.append({'source': 'ups', 'name': 'l2tp', 'payload': 'c8020060000000000000000080080000000000018008000000020100800a0000000300000001800a00000004000000000008000000060500800900000007776655000f000000084d6963726f736f6674800800000009000180080000000a0008', 'rarity': '2', 'ports': '1701'}) 1025 | 1026 | # These are some probes from amap 5.2 1027 | probe_list.append({'source': 'amap', 'name': 'rpc', 'payload': '039b65420000000000000002000f4243000000000000000000000000000000000000000000000000', 'rarity': '1', 'ports': '111'}) 1028 | probe_list.append({'source': 'amap', 'name': 'ntp', 'payload': 'cb0004fa000100000001000000000000000000000000000000000000000000000000000000000000bfbe7099cdb34000', 'rarity': '1', 'ports': '123'}) 1029 | probe_list.append({'source': 'amap', 'name': 'snmp-public', 'payload': '3082002f02010004067075626c6963a082002002044c33a756020100020100308200103082000c06082b060102010105000500', 'rarity': '1', 'ports': '161'}) 1030 | probe_list.append({'source': 'amap', 'name': 'ms-sql', 'payload': '02', 'rarity': '2', 'ports': '1434'}) 1031 | probe_list.append({'source': 'amap', 'name': 'ms-sql-slam', 'payload': '0a', 'rarity': '6', 'ports': '1434'}) 1032 | probe_list.append({'source': 'amap', 'name': 'netop', 'payload': 'd6818152000000f3874e01023200a8c000000113c1d904dd037d00000d005448435448435448435448435448432020202020202020202020202020202020023200a8c00000000000000000000000000000000000000000000000000000000000000000000000000000000000', 'rarity': '6', 'ports': '6502'}) 1033 | probe_list.append({'source': 'amap', 'name': 'tftp', 'payload': '00012f6574632f706173737764006e6574617363696900', 'rarity': '1', 'ports': '69'}) 1034 | probe_list.append({'source': 'amap', 'name': 'db2', 'payload': '444232474554414444520053514c3038303230', 'rarity': '6', 'ports': '523'}) 1035 | probe_list.append({'source': 'amap', 'name': 'citrix', 'payload': '1e00013002fda8e300000000000000000000000000000000000000000000', 'rarity': '3', 'ports': '1604'}) 1036 | 1037 | # Nmap lists a lot of ports for some probe types. Reduce those by listing a smaller portlist instead. 1038 | nmap_preferences = [] 1039 | nmap_preferences.append({'name': 'RPCCheck', 'ports': '111'}) 1040 | nmap_preferences.append({'name': 'DNSVersionBindReq', 'ports': '53'}) 1041 | nmap_preferences.append({'name': 'Help', 'ports': '42'}) # TODO what is port 42? 1042 | nmap_preferences.append({'name': 'DNSStatusRequest', 'ports': '53'}) 1043 | nmap_preferences.append({'name': 'NTPRequest', 'ports': '123'}) 1044 | nmap_preferences.append({'name': 'AFSVersionRequest', 'ports': '7001'}) 1045 | nmap_preferences.append({'name': 'DTLSSessionReq', 'ports': '443'}) 1046 | nmap_preferences.append({'name': 'Sqlping', 'ports': '1434'}) 1047 | 1048 | # These are from nmap 1049 | nmap_probe_list = [] 1050 | # The lines below can be updated with newer nmap probes. 1051 | # This list was taken from nmap 7.80, which has a GPLv2 compatible license. 1052 | # To extract probes from a different version of nmap: 1053 | # python3 parse-nmap.py nmap-service-probes 1054 | # === 1055 | nmap_probe_list.append({'source': 'nmap', 'name': 'RPCCheck', 'payload': '72fe1d130000000000000002000186a00001977c0000000000000000000000000000000000000000', 'rarity': '1', 'ports': '17,88,111,407,500,517,518,1419,2427,4045,10000,10080,12203,27960,32750-32810,38978'}) 1056 | nmap_probe_list.append({'source': 'nmap', 'name': 'DNSVersionBindReq', 'payload': '0006010000010000000000000776657273696f6e0462696e640000100003', 'rarity': '1', 'ports': '53,1967,2967'}) 1057 | nmap_probe_list.append({'source': 'nmap', 'name': 'Help', 'payload': '68656c700d0a0d0a', 'rarity': '3', 'ports': '7,13,37,42'}) 1058 | nmap_probe_list.append({'source': 'nmap', 'name': 'NBTStat', 'payload': '80f00010000100000000000020434b4141414141414141414141414141414141414141414141414141414141410000210001', 'rarity': '4', 'ports': '137'}) 1059 | nmap_probe_list.append({'source': 'nmap', 'name': 'SNMPv1public', 'payload': '3082002f02010004067075626c6963a082002002044c33a756020100020100308200103082000c06082b060102010105000500', 'rarity': '4', 'ports': '161'}) 1060 | nmap_probe_list.append({'source': 'nmap', 'name': 'SNMPv3GetRequest', 'payload': '303a020103300f02024a69020300ffe30401040201030410300e0400020100020100040004000400301204000400a00c020237f00201000201003000', 'rarity': '4', 'ports': '161'}) 1061 | nmap_probe_list.append({'source': 'nmap', 'name': 'DNS-SD', 'payload': '000000000001000000000000095f7365727669636573075f646e732d7364045f756470056c6f63616c00000c0001', 'rarity': '4', 'ports': '5353'}) 1062 | nmap_probe_list.append({'source': 'nmap', 'name': 'DNSStatusRequest', 'payload': '000010000000000000000000', 'rarity': '5', 'ports': '53,69,135,1761'}) 1063 | nmap_probe_list.append({'source': 'nmap', 'name': 'SIPOptions', 'payload': '4f5054494f4e53207369703a6e6d205349502f322e300d0a5669613a205349502f322e302f554450206e6d3b6272616e63683d666f6f3b72706f72740d0a46726f6d3a203c7369703a6e6d406e6d3e3b7461673d726f6f740d0a546f3a203c7369703a6e6d32406e6d323e0d0a43616c6c2d49443a2035303030300d0a435365713a203432204f5054494f4e530d0a4d61782d466f7277617264733a2037300d0a436f6e74656e742d4c656e6774683a20300d0a436f6e746163743a203c7369703a6e6d406e6d3e0d0a4163636570743a206170706c69636174696f6e2f7364700d0a0d0a', 'rarity': '5', 'ports': '5060'}) 1064 | nmap_probe_list.append({'source': 'nmap', 'name': 'NTPRequest', 'payload': 'e30004fa000100000001000000000000000000000000000000000000000000000000000000000000c54f234b71b152f3', 'rarity': '5', 'ports': '123,5353,9100'}) 1065 | nmap_probe_list.append({'source': 'nmap', 'name': 'AFSVersionRequest', 'payload': '000003e7000000000000006500000000000000000d0500000000000000000000', 'rarity': '5', 'ports': '7001,1719'}) 1066 | nmap_probe_list.append({'source': 'nmap', 'name': 'Citrix', 'payload': '1e00013002fda8e300000000000000000000000000000000000000000000', 'rarity': '5', 'ports': '1604'}) 1067 | nmap_probe_list.append({'source': 'nmap', 'name': 'Kerberos', 'payload': '6a816e30816ba103020105a20302010aa4815e305ca00703050050800010a2041b024e4da3173015a003020100a10e300c1b066b72627467741b024e4da511180f31393730303130313030303030305aa70602041f1eb9d9a8173015020112020111020110020117020101020103020102', 'rarity': '5', 'ports': '88'}) 1068 | nmap_probe_list.append({'source': 'nmap', 'name': 'DTLSSessionReq', 'payload': '16feff000000000000000000360100002a000000000000002afefd000000007c77401e8ac822a0a018ff9308caac0a642fc92264bc08a81689193000000002002f0100', 'rarity': '5', 'ports': '443,853,4433,4740,5349,5684,5868,6514,6636,8232,10161,10162,12346,12446,12546,12646,12746,12846,12946,13046'}) 1069 | nmap_probe_list.append({'source': 'nmap', 'name': 'Sqlping', 'payload': '02', 'rarity': '6', 'ports': '1434,19131-19133'}) 1070 | nmap_probe_list.append({'source': 'nmap', 'name': 'xdmcp', 'payload': '00010002000100', 'rarity': '6', 'ports': '177'}) 1071 | nmap_probe_list.append({'source': 'nmap', 'name': 'QUIC', 'payload': '0d89c19c1c2afffcf15139393900', 'rarity': '6', 'ports': '3310'}) 1072 | nmap_probe_list.append({'source': 'nmap', 'name': 'sybaseanywhere', 'payload': '1b00003d0000000012434f4e4e454354494f4e4c4553535f544453000000010000040005000500000102000003010104080000000000000000070204b1', 'rarity': '7', 'ports': '2638'}) 1073 | nmap_probe_list.append({'source': 'nmap', 'name': 'NetMotionMobility', 'payload': '00405000000000855db491280000000000017c9140000000aa39da423765cf010000000000000000000000000000000000000000000000000000000000000000', 'rarity': '7', 'ports': '5008'}) 1074 | nmap_probe_list.append({'source': 'nmap', 'name': 'LDAPSearchReqUDP', 'payload': '30840000002d02010763840000002404000a01000a0100020100020164010100870b6f626a656374436c617373308400000000', 'rarity': '8', 'ports': '389'}) 1075 | nmap_probe_list.append({'source': 'nmap', 'name': 'ibm-db2-das-udp', 'payload': '444232474554414444520053514c303830313000', 'rarity': '8', 'ports': '523'}) 1076 | nmap_probe_list.append({'source': 'nmap', 'name': 'SqueezeCenter', 'payload': '6549504144004e414d45004a534f4e00564552530055554944004a56494406123456781234', 'rarity': '8', 'ports': '3483'}) 1077 | nmap_probe_list.append({'source': 'nmap', 'name': 'Quake2_status', 'payload': 'ffffffff737461747573', 'rarity': '8', 'ports': '27910-27914'}) 1078 | nmap_probe_list.append({'source': 'nmap', 'name': 'Quake3_getstatus', 'payload': 'ffffffff676574737461747573', 'rarity': '8', 'ports': '26000-26004,27960-27964,30720-30724,44400'}) 1079 | nmap_probe_list.append({'source': 'nmap', 'name': 'serialnumberd', 'payload': '534e51554552593a203132372e302e302e313a4141414141413a78737672', 'rarity': '8', 'ports': '626'}) 1080 | nmap_probe_list.append({'source': 'nmap', 'name': 'vuze-dht', 'payload': 'fff0970d2e60d16f000004000055abec32000000000032040a00c875f816005cb965000000004ed1f528', 'rarity': '8', 'ports': '17555,49152-49156'}) 1081 | nmap_probe_list.append({'source': 'nmap', 'name': 'pc-anywhere', 'payload': '4e51', 'rarity': '8', 'ports': '5632'}) 1082 | nmap_probe_list.append({'source': 'nmap', 'name': 'pc-duo', 'payload': '00808008ff00', 'rarity': '8', 'ports': '1505'}) 1083 | nmap_probe_list.append({'source': 'nmap', 'name': 'pc-duo-gw', 'payload': '20908008ff00', 'rarity': '8', 'ports': '2303'}) 1084 | nmap_probe_list.append({'source': 'nmap', 'name': 'memcached', 'payload': '000100000001000073746174730d0a', 'rarity': '8', 'ports': '11211'}) 1085 | nmap_probe_list.append({'source': 'nmap', 'name': 'svrloc', 'payload': '0201000036200000000000010002656e00000015736572766963653a736572766963652d6167656e74000764656661756c7400000000', 'rarity': '8', 'ports': '427'}) 1086 | nmap_probe_list.append({'source': 'nmap', 'name': 'ARD', 'payload': '0014000103', 'rarity': '8', 'ports': '3283'}) 1087 | nmap_probe_list.append({'source': 'nmap', 'name': 'Quake1_server_info', 'payload': '8000000c025155414b450003', 'rarity': '9', 'ports': '26000-26004'}) 1088 | nmap_probe_list.append({'source': 'nmap', 'name': 'Quake3_master_getservers', 'payload': 'ffffffff6765747365727665727320363820656d7074792066756c6c', 'rarity': '9', 'ports': '27950,30710'}) 1089 | nmap_probe_list.append({'source': 'nmap', 'name': 'BackOrifice', 'payload': 'ce63d1d216e713cf38a5a586b2754b99aa3258', 'rarity': '9', 'ports': '19150'}) 1090 | nmap_probe_list.append({'source': 'nmap', 'name': 'Murmur', 'payload': '000000006162636465666768', 'rarity': '9', 'ports': '64738'}) 1091 | nmap_probe_list.append({'source': 'nmap', 'name': 'Ventrilo', 'payload': '01e7e57531a3170b21cfbf2b994edd19acde085f8b240a1119b6736fad2813d20ab91275', 'rarity': '9', 'ports': '3784'}) 1092 | nmap_probe_list.append({'source': 'nmap', 'name': 'TeamSpeak2', 'payload': 'f4be03000000000000000000010000003278ba85095465616d537065616b00000000000000000000000000000000000000000a57696e646f7773205850000000000000000000000000000000000000000200000020003c000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000086e69636b6e616d65000000000000000000000000000000000000000000', 'rarity': '9', 'ports': '8767'}) 1093 | nmap_probe_list.append({'source': 'nmap', 'name': 'TeamSpeak3', 'payload': '05ca7f169c11f98900000000029d748b45aa7befb99efead0819bacf41e016a2326cf3cff48e3c4483c88d51456f9095233e00972b1c71b24ec061f1d76fc57ef64852bf826aa23b65aa187a1738c38127c347fca735bafc0f9d9d72249dfc02176d6bb12d72c6e3171c95d9699957cedddf05dc039456043a14e5ad9a2b14303a23a325ade8e6398a852ac6dfe55d2da02f5d9cd72b24fbb09cc2ba89b41b17a2b6', 'rarity': '9', 'ports': '9987'}) 1094 | nmap_probe_list.append({'source': 'nmap', 'name': 'FreelancerStatus', 'payload': '0002f1260126f090a6f026574eaca0ecf868e48d21', 'rarity': '9', 'ports': '2302'}) 1095 | nmap_probe_list.append({'source': 'nmap', 'name': 'ASE', 'payload': '73', 'rarity': '9', 'ports': '1258,2126,3123,12444,13200,23196,26000,27138,27244,27777,28138'}) 1096 | nmap_probe_list.append({'source': 'nmap', 'name': 'AndroMouse', 'payload': '414d534e494646', 'rarity': '9', 'ports': '8888'}) 1097 | nmap_probe_list.append({'source': 'nmap', 'name': 'AirHID', 'payload': '66726f6d3a616972686964', 'rarity': '9', 'ports': '13246'}) 1098 | nmap_probe_list.append({'source': 'nmap', 'name': 'OpenVPN', 'payload': '3864c17801b89bcb8f0000000000', 'rarity': '9', 'ports': '1962'}) 1099 | nmap_probe_list.append({'source': 'nmap', 'name': 'ipmi-rmcp', 'payload': '0600ff07000000000000000000092018c88100388e04b5', 'rarity': '9', 'ports': '623'}) 1100 | nmap_probe_list.append({'source': 'nmap', 'name': 'coap-request', 'payload': '400101cebb2e77656c6c2d6b6e6f776e04636f7265', 'rarity': '9', 'ports': '5683'}) 1101 | nmap_probe_list.append({'source': 'nmap', 'name': 'UbiquitiDiscoveryv1', 'payload': '01000000', 'rarity': '9', 'ports': '10001'}) 1102 | nmap_probe_list.append({'source': 'nmap', 'name': 'UbiquitiDiscoveryv2', 'payload': '02080000', 'rarity': '9', 'ports': '10001'}) 1103 | # === 1104 | 1105 | if __name__ == "__main__": 1106 | VERSION = "0.9.1" 1107 | 1108 | # Defaults 1109 | DEFAULT_MAX_PROBES = 2 1110 | DEFAULT_BANDWIDTH = "250k" 1111 | DEFAULT_PACKET_RATE = 0 1112 | DEFAULT_PACKET_HOST_RATE = 2 1113 | DEFAULT_RTT = 1 1114 | DEFAULT_RARITY = 6 1115 | DEFAULT_PROBES = "all" 1116 | 1117 | # These get overriden later 1118 | max_probes = DEFAULT_MAX_PROBES 1119 | bandwidth = DEFAULT_BANDWIDTH 1120 | packet_rate = DEFAULT_PACKET_RATE 1121 | packet_rate_per_host = DEFAULT_PACKET_HOST_RATE 1122 | rtt = DEFAULT_RTT 1123 | rarity = DEFAULT_RARITY 1124 | 1125 | probe_dict = {} # populated later with all possible probes from config above 1126 | probe_names_selected = [] # from command line 1127 | blocklist_ips = [] # populated later with all ips in blocklist 1128 | 1129 | script_name = sys.argv[0] 1130 | 1131 | # parse command line options 1132 | parser = argparse.ArgumentParser(usage='%s [options] [ -p probe_name ] -f ipsfile\n %s [options] [ -p probe_name ] 10.0.0.0/16 10.1.0.0-10.1.1.9 192.168.0.1' % (script_name, script_name)) 1133 | 1134 | parser.add_argument('-f', '--file', dest='file', help='File of ips') 1135 | parser.add_argument('-p', '--probe_name', dest='probe_name_str_list', default=DEFAULT_PROBES, type=str, help='Name of probe or "all". Default: %s' % (DEFAULT_PROBES)) 1136 | parser.add_argument('-l', '--list_probes', dest='list_probes', action="store_true", help='List all available probe name then exit') 1137 | parser.add_argument('-b', '--bandwidth', dest='bandwidth', default=DEFAULT_BANDWIDTH, type=str, help='Bandwidth to use in bits/sec. Default %s' % (DEFAULT_BANDWIDTH)) 1138 | parser.add_argument('-c', '--commonness', dest='commonness', default=argparse.SUPPRESS, type=int, help='Commonness of probes to send 1-9. 9 is common, 1 is rare. Implies -p all. Default %s' % (10-DEFAULT_RARITY)) 1139 | parser.add_argument('-P', '--packetrate', dest='packetrate', default=DEFAULT_PACKET_RATE, type=str, help='Max packets/sec to send. Default unlimited') 1140 | parser.add_argument('-H', '--retryrate', dest='packehosttrate', default=DEFAULT_PACKET_HOST_RATE, type=int, help='Max rate (packets/sec) for retrying the same probe. Default %s' % (DEFAULT_PACKET_HOST_RATE)) 1141 | parser.add_argument('-R', '--rtt', dest='rtt', default=DEFAULT_RTT, type=str, help='Max round trip time for probe. Default %ss' % (DEFAULT_RTT)) 1142 | parser.add_argument('-r', '--retries', dest='retries', default=DEFAULT_MAX_PROBES, type=int, help='No of packets to sent to each host. Default %s' % (DEFAULT_MAX_PROBES)) 1143 | parser.add_argument('-d', '--debug', dest='debug', action="store_true", help='Debug mode') 1144 | parser.add_argument('-B', '--blocklist', dest='blocklist', default=None, type=str, help='List of blacklisted ips. Useful on windows to blocklist network addresses. Separate with commas: 127.0.0.0,192.168.0.0. Default None') 1145 | args, targets = parser.parse_known_args() 1146 | 1147 | # 1148 | # Change defaults based on command line options 1149 | # 1150 | 1151 | # set max_probes from retries 1152 | if args.retries is not None: 1153 | max_probes = args.retries + 1 1154 | 1155 | # set bandwidth 1156 | if args.bandwidth is not None: 1157 | bandwidth = args.bandwidth 1158 | 1159 | # set packet rate 1160 | if args.packetrate is not None: 1161 | packet_rate = args.packetrate 1162 | 1163 | # set packet rate per host 1164 | if args.packehosttrate is not None: 1165 | packet_rate_per_host = args.packehosttrate 1166 | 1167 | # set rtt 1168 | if args.rtt is not None: 1169 | rtt = args.rtt 1170 | 1171 | # set probe names 1172 | if args.probe_name_str_list is not None: 1173 | probe_names_selected = args.probe_name_str_list.split(',') 1174 | 1175 | # set rarity 1176 | if 'commonness' in args: 1177 | rarity = 10 - args.commonness 1178 | if "all" not in probe_names_selected: 1179 | probe_names_selected.append("all") 1180 | else: 1181 | rarity = DEFAULT_RARITY 1182 | 1183 | # set blocklist 1184 | if args.blocklist is not None: 1185 | blocklist_ips = args.blocklist.split(',') 1186 | 1187 | # 1188 | # Parse probe data, probe options 1189 | # 1190 | 1191 | # Populate probe_dict from probe_list 1192 | for probe in probe_list + nmap_probe_list: 1193 | source = probe['source'] 1194 | name = probe['name'] 1195 | payload = probe['payload'] 1196 | probe_rarity = probe['rarity'] 1197 | ports_string = probe['ports'] 1198 | 1199 | # Check types to keep things neat 1200 | if not type(probe_rarity) == str: 1201 | print("[E] Rarity should be a string, not %s" % type(probe_rarity)) 1202 | sys.exit(0) 1203 | if not type(ports_string) == str: 1204 | print("[E] Ports should be a string, not %s" % type(ports_string)) 1205 | sys.exit(0) 1206 | 1207 | probe_rarity = int(probe_rarity) 1208 | 1209 | # Use a different port list than nmap if specified in nmap_preferences 1210 | if source == 'nmap': 1211 | for pref_dict in nmap_preferences: 1212 | if name == pref_dict['name']: 1213 | ports_string = pref_dict['ports'] 1214 | 1215 | if name not in probe_dict.keys(): 1216 | probe_dict[name] = {} 1217 | 1218 | for port in expand_port_list(ports_string): 1219 | if port not in probe_dict[name].keys(): 1220 | probe_dict[name][port] = {} 1221 | probe_dict[name][port]["config_tuple"] = (port, name, payload) 1222 | probe_dict[name][port]["rarity"] = probe_rarity 1223 | 1224 | # check probe_names are valid 1225 | for probe_name in probe_names_selected: 1226 | if probe_name not in list(probe_dict.keys()) + ["all"]: 1227 | print("[E] Probe name %s (from -p) unknown. Use -l to list valid names" % (probe_name)) 1228 | sys.exit(0) 1229 | 1230 | # check probe_names are valid 1231 | probes_to_use_tuple_list = [] 1232 | all_probe_names = list(probe_dict.keys()) 1233 | 1234 | # Iterate over probe_dict and select required probes, adding them to probe_names and probe_tuples_list 1235 | for probe_name in probe_dict.keys(): 1236 | if probe_name in probe_names_selected: 1237 | # Add regardless of rarity 1238 | for port in probe_dict[probe_name].keys(): 1239 | probes_to_use_tuple_list.append(probe_dict[probe_name][port]["config_tuple"]) 1240 | 1241 | if "all" in probe_names_selected: 1242 | for port in probe_dict[probe_name].keys(): 1243 | # Add if rarity matches 1244 | if probe_dict[probe_name][port]["rarity"] <= rarity: 1245 | probes_to_use_tuple_list.append(probe_dict[probe_name][port]["config_tuple"]) 1246 | 1247 | # print a unique list of ports used by the probes - used by test-server to prove the scanner is working 1248 | if args.debug: 1249 | ports_used = [] 1250 | for probe_name in probe_dict.keys(): 1251 | for port in probe_dict[probe_name].keys(): 1252 | if port not in ports_used: 1253 | ports_used.append(port) 1254 | ports_used.sort() 1255 | print("[D] Using ports %s" % (ports_used)) 1256 | 1257 | # 1258 | # Check for illegal command line options 1259 | # 1260 | 1261 | # error if any targets start with - or -- as this will be interpreted as an option 1262 | for target in targets: 1263 | if target.startswith('-'): 1264 | print("[E] Target \"%s\" starts with - or -- which is interpreted as an option" % target) 1265 | sys.exit(0) 1266 | 1267 | # if --list_probes is set, print out all the probe names like this 1268 | if args.list_probes: 1269 | print("The following probe names (-p argument) are available:") 1270 | for probe_name in ["all"] + all_probe_names: 1271 | print("* %s" % probe_name) 1272 | sys.exit(0) 1273 | 1274 | # error if rarity is not 1-9 1275 | if rarity < 1 or rarity > 9: 1276 | print("[E] Rarity must be between 1 and 9") 1277 | sys.exit(0) 1278 | 1279 | # error if no targets were specified 1280 | if not args.file and not targets: 1281 | parser.print_help() 1282 | sys.exit(0) 1283 | 1284 | # error if --file and targets were specified 1285 | if args.file and targets: 1286 | print("[E] You cannot specify both a file of targets and a list of targets") 1287 | sys.exit(0) 1288 | 1289 | print("Starting udpy_proto_scanner v%s ( https://github.com/CiscoCXSecurity/udpy_proto_scanner ) at %s" % (VERSION, get_time())) 1290 | 1291 | # Send each type of probe separately 1292 | scanner = ScannerUDP() 1293 | 1294 | # Set up options for scan 1295 | if args.file: 1296 | scanner.add_targets_from_file(args.file) 1297 | else: 1298 | scanner.add_targets(targets) 1299 | 1300 | scanner.set_bandwidth(bandwidth) 1301 | scanner.set_max_probes(max_probes) 1302 | scanner.set_rtt(rtt) 1303 | scanner.set_packet_rate(packet_rate) 1304 | scanner.set_packet_rate_per_host(packet_rate_per_host) 1305 | scanner.set_probes(probes_to_use_tuple_list) 1306 | scanner.set_debug(args.debug) 1307 | scanner.set_blocklist(blocklist_ips) 1308 | 1309 | # Start scan 1310 | scanner.start_scan() 1311 | 1312 | # Print stats 1313 | print("") 1314 | print("Total replies: %s (+%s unexpected replies)" % (scanner.replies, scanner.unexpected_replies)) 1315 | print("Scan for complete at %s" % get_time()) 1316 | print("Sent %s bytes (%s bits) in %s probes in %ss to %s hosts: %s bits/s, %s bytes/s, %s packets/s" % (scanner.bytes_sent, scanner.bytes_sent * 8, scanner.probes_sent_count, round_pretty(scanner.scan_duration), scanner.host_count, scanner.scan_rate_bits_per_second, round_pretty(scanner.bytes_sent / scanner.scan_duration), round_pretty(scanner.probes_sent_count / scanner.scan_duration))) 1317 | --------------------------------------------------------------------------------