diff options
Diffstat (limited to 'targets.cc')
-rw-r--r-- | targets.cc | 583 |
1 files changed, 583 insertions, 0 deletions
diff --git a/targets.cc b/targets.cc new file mode 100644 index 0000000..16d318c --- /dev/null +++ b/targets.cc @@ -0,0 +1,583 @@ + +/*************************************************************************** + * targets.cc -- Functions relating to "ping scanning" as well as * + * determining the exact IPs to hit based on CIDR and other input * + * formats. * + * * + ***********************IMPORTANT NMAP LICENSE TERMS************************ + * + * The Nmap Security Scanner is (C) 1996-2023 Nmap Software LLC ("The Nmap + * Project"). Nmap is also a registered trademark of the Nmap Project. + * + * This program is distributed under the terms of the Nmap Public Source + * License (NPSL). The exact license text applying to a particular Nmap + * release or source code control revision is contained in the LICENSE + * file distributed with that version of Nmap or source code control + * revision. More Nmap copyright/legal information is available from + * https://nmap.org/book/man-legal.html, and further information on the + * NPSL license itself can be found at https://nmap.org/npsl/ . This + * header summarizes some key points from the Nmap license, but is no + * substitute for the actual license text. + * + * Nmap is generally free for end users to download and use themselves, + * including commercial use. It is available from https://nmap.org. + * + * The Nmap license generally prohibits companies from using and + * redistributing Nmap in commercial products, but we sell a special Nmap + * OEM Edition with a more permissive license and special features for + * this purpose. See https://nmap.org/oem/ + * + * If you have received a written Nmap license agreement or contract + * stating terms other than these (such as an Nmap OEM license), you may + * choose to use and redistribute Nmap under those terms instead. + * + * The official Nmap Windows builds include the Npcap software + * (https://npcap.com) for packet capture and transmission. It is under + * separate license terms which forbid redistribution without special + * permission. So the official Nmap Windows builds may not be redistributed + * without special permission (such as an Nmap OEM license). + * + * Source is provided to this software because we believe users have a + * right to know exactly what a program is going to do before they run it. + * This also allows you to audit the software for security holes. + * + * Source code also allows you to port Nmap to new platforms, fix bugs, and add + * new features. You are highly encouraged to submit your changes as a Github PR + * or by email to the dev@nmap.org mailing list for possible incorporation into + * the main distribution. Unless you specify otherwise, it is understood that + * you are offering us very broad rights to use your submissions as described in + * the Nmap Public Source License Contributor Agreement. This is important + * because we fund the project by selling licenses with various terms, and also + * because the inability to relicense code has caused devastating problems for + * other Free Software projects (such as KDE and NASM). + * + * The free version of Nmap is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Warranties, + * indemnification and commercial support are all available through the + * Npcap OEM program--see https://nmap.org/oem/ + * + ***************************************************************************/ + +/* $Id$ */ + + +#include <nbase.h> +#include "targets.h" +#include "timing.h" +#include "tcpip.h" +#include "NmapOps.h" +#include "NewTargets.h" +#include "Target.h" +#include "scan_engine.h" +#include "nmap_dns.h" +#include "utils.h" +#include "nmap_error.h" +#include "xml.h" + +extern NmapOps o; +#ifdef WIN32 +/* from libdnet's intf-win32.c */ +extern "C" int g_has_npcap_loopback; +#endif + +/* Conducts an ARP ping sweep of the given hosts to determine which ones + are up on a local ethernet network */ +static void arpping(Target *hostbatch[], int num_hosts) { + /* First I change hostbatch into a std::vector<Target *>, which is what ultra_scan + takes. I remove hosts that cannot be ARP scanned (such as localhost) */ + std::vector<Target *> targets; + int targetno; + targets.reserve(num_hosts); + + for (targetno = 0; targetno < num_hosts; targetno++) { + initialize_timeout_info(&hostbatch[targetno]->to); + /* Default timout should be much lower for arp */ + hostbatch[targetno]->to.timeout = MAX(o.minRttTimeout(), MIN(o.initialRttTimeout(), INITIAL_ARP_RTT_TIMEOUT)) * 1000; + if (!hostbatch[targetno]->SrcMACAddress()) { + bool islocal = islocalhost(hostbatch[targetno]->TargetSockAddr()); + if (islocal) { + log_write(LOG_STDOUT|LOG_NORMAL, + "ARP ping: Considering %s UP because it is a local IP, despite no MAC address for device %s\n", + hostbatch[targetno]->NameIP(), hostbatch[targetno]->deviceName()); + hostbatch[targetno]->flags = HOST_UP; + } else { + log_write(LOG_STDOUT|LOG_NORMAL, + "ARP ping: Considering %s DOWN because no MAC address found for device %s.\n", + hostbatch[targetno]->NameIP(), + hostbatch[targetno]->deviceName()); + hostbatch[targetno]->flags = HOST_DOWN; + } + continue; + } + targets.push_back(hostbatch[targetno]); + } + if (!targets.empty()) { + if (targets[0]->af() == AF_INET) + ultra_scan(targets, NULL, PING_SCAN_ARP); + else + ultra_scan(targets, NULL, PING_SCAN_ND); + } + return; +} + +static void hoststructfry(Target *hostbatch[], int nelem) { + genfry((unsigned char *)hostbatch, sizeof(Target *), nelem); + return; +} + +/* Returns the last host obtained by nexthost. It will be given again the next + time you call nexthost(). */ +void returnhost(HostGroupState *hs) { + assert(hs->next_batch_no > 0); + hs->next_batch_no--; +} + +/* Is the host passed as Target to be excluded? Much of this logic had + to be rewritten from wam's original code to allow for the objects */ +static int hostInExclude(struct sockaddr *checksock, size_t checksocklen, + const struct addrset *exclude_group) { + if (exclude_group == NULL) + return 0; + + if (checksock == NULL) + return 0; + + if (addrset_contains(exclude_group,checksock)) + return 1; + return 0; +} + +/* Load an exclude list from a file for --excludefile. */ +int load_exclude_file(struct addrset *excludelist, FILE *fp) { + char host_spec[1024]; + size_t n; + + while ((n = read_host_from_file(fp, host_spec, sizeof(host_spec))) > 0) { + if (n >= sizeof(host_spec)) + fatal("One of your exclude file specifications was too long to read (>= %u chars)", (unsigned int) sizeof(host_spec)); + if(!addrset_add_spec(excludelist, host_spec, o.af(), 1)){ + fatal("Invalid address specification:"); + } + } + + return 1; +} + +/* Load a comma-separated exclude list from a string, the argument to + --exclude. */ +int load_exclude_string(struct addrset *excludelist, const char *s) { + const char *begin, *p; + + p = s; + while (*p != '\0') { + begin = p; + while (*p != '\0' && *p != ',') + p++; + std::string addr_str = std::string(begin, p - begin); + if (!addrset_add_spec(excludelist, addr_str.c_str(), o.af(), 1)) { + fatal("Invalid address specification: %s", addr_str.c_str()); + } + if (*p == '\0') + break; + p++; + }; + + return 1; +} + + +/* A debug routine to dump some information to stdout. Invoked if debugging is + set to 4 or higher. */ +int dumpExclude(const struct addrset *exclude_group) { + addrset_print(stdout, exclude_group); + return 1; +} + +static void massping(Target *hostbatch[], int num_hosts, const struct scan_lists *ports) { + static struct timeout_info group_to = { 0, 0, 0 }; + static char prev_device_name[16] = ""; + const char *device_name; + std::vector<Target *> targets; + int i; + + /* Get the name of the interface used to send to this group. We assume the + device used to send to the first target is used to send to all of them. */ + device_name = NULL; + if (num_hosts > 0) + device_name = hostbatch[0]->deviceName(); + if (device_name == NULL) + device_name = ""; + + /* group_to is a static variable that keeps track of group timeout values + between invocations of this function. We reuse timeouts as long as this + invocation uses the same device as the previous one. Otherwise we + reinitialize the timeouts. */ + if (group_to.srtt == 0 || group_to.rttvar == 0 || group_to.timeout == 0 + || strcmp(prev_device_name, device_name) != 0) { + initialize_timeout_info(&group_to); + Strncpy(prev_device_name, device_name, sizeof(prev_device_name)); + } + + for (i = 0; i < num_hosts; i++) { + if (hostbatch[i]->flags & HOST_DOWN) + continue; + initialize_timeout_info(&hostbatch[i]->to); + targets.push_back(hostbatch[i]); + } + + ultra_scan(targets, ports, PING_SCAN, &group_to); +} + +/* Returns true iff this target is incompatible with the other hosts in the host + group. This happens when: + 1. it uses a different interface, or + 2. it uses a different source address, or + 3. it is directly connected when the other hosts are not, or vice versa, or + 4. it has the same IP address as another target already in the group. + These restrictions only apply for raw scans, including host discovery. */ +bool target_needs_new_hostgroup(Target **targets, int targets_sz, const Target *target) { + int i = 0; + + /* We've just started a new hostgroup, so any target is acceptable. */ + if (targets_sz == 0) + return false; + + /* There are no restrictions on non-root scans. */ + if (!(o.isr00t && target->deviceName() != NULL)) + return false; + + /* Different address family? */ + if (targets[0]->af() != target->af()) + return true; + + /* Different interface name? */ + if (targets[0]->deviceName() != NULL && + target->deviceName() != NULL && + strcmp(targets[0]->deviceName(), target->deviceName()) != 0) { + return true; + } + + /* Different source address? */ + if (sockaddr_storage_cmp(targets[0]->SourceSockAddr(), target->SourceSockAddr()) != 0) + return true; + + /* Different direct connectedness? */ + if (targets[0]->directlyConnected() != target->directlyConnected()) + return true; + + /* Is there already a target with this same IP address? ultra_scan doesn't + cope with that, because it uses IP addresses to look up targets from + replies. What happens is one target gets the replies for all probes + referring to the same IP address. */ + for (i = 0; i < targets_sz; i++) { + if (sockaddr_storage_cmp(targets[i]->TargetSockAddr(), target->TargetSockAddr()) == 0) + return true; + } + + return false; +} + +/* Lookahead is the number of hosts that can be + checked (such as ping scanned) in advance. Randomize causes each + group of up to lookahead hosts to be internally shuffled around. + The target_expressions array MUST REMAIN VALID IN MEMORY as long as + this class instance is used -- the array is NOT copied. + */ +HostGroupState::HostGroupState(int lookahead, int rnd, int argc, const char **argv) { + assert(lookahead > 0); + this->argc = argc; + this->argv = argv; + hostbatch = (Target **) safe_zalloc(sizeof(Target *) * lookahead); + defer_buffer = std::list<Target *>(); + undeferred = std::list<Target *>(); + max_batch_sz = lookahead; + current_batch_sz = 0; + next_batch_no = 0; + randomize = rnd; +} + +HostGroupState::~HostGroupState() { + free(hostbatch); +} + +/* Returns true iff the defer buffer is not yet full. */ +bool HostGroupState::defer(Target *t) { + this->defer_buffer.push_back(t); + return this->defer_buffer.size() < HostGroupState::DEFER_LIMIT; +} + +void HostGroupState::undefer() { + this->undeferred.splice(this->undeferred.end(), this->defer_buffer); +} + +const char *HostGroupState::next_expression() { + if (o.max_ips_to_scan == 0 || o.numhosts_scanned + this->current_batch_sz < o.max_ips_to_scan) { + const char *expr; + expr = grab_next_host_spec(o.inputfd, o.generate_random_ips, this->argc, this->argv); + if (expr != NULL) + return expr; + } + +#ifndef NOLUA + /* Add any new NSE discovered targets to the scan queue */ + static char buf[1024]; + + if (o.script) { + unsigned long new_targets = NewTargets::get_queued(); + if (new_targets > 0) { + std::string expr_string; + expr_string = NewTargets::read().c_str(); + if (o.debugging > 3) { + log_write(LOG_PLAIN, + "New targets: retrieved one of %ld pending in queue.\n", + new_targets); + } + if (!expr_string.empty()) { + Strncpy(buf, expr_string.c_str(), sizeof(buf)); + return buf; + } + } + } +#endif + + return NULL; +} + +/* Add a <target> element to the XML stating that a target specification was + ignored. This can be because of, for example, a DNS resolution failure, or a + syntax error. */ +static void log_bogus_target(const char *expr) { + xml_open_start_tag("target"); + xml_attribute("specification", "%s", expr); + xml_attribute("status", "skipped"); + xml_attribute("reason", "invalid"); + xml_close_empty_tag(); + xml_newline(); +} + +/* Returns a newly allocated Target with the given address. Handles all the + details like setting the Target's address and next hop. */ +static Target *setup_target(const HostGroupState *hs, + const struct sockaddr_storage *ss, size_t sslen, + int pingtype) { + struct route_nfo rnfo; + Target *t; + + t = new Target(); + + t->setTargetSockAddr(ss, sslen); + + /* Special handling for the resolved address (for example whatever + scanme.nmap.org resolves to in scanme.nmap.org/24). */ + if (hs->current_group.is_resolved_address(ss)) { + if (hs->current_group.get_namedhost()) + t->setTargetName(hs->current_group.get_resolved_name()); + t->unscanned_addrs = hs->current_group.get_unscanned_addrs(); + } + + /* We figure out the source IP/device IFF + * the scan type requires us to */ + if (o.RawScan()) { + if (!nmap_route_dst(ss, &rnfo)) { + log_bogus_target(inet_ntop_ez(ss, sslen)); + error("%s: failed to determine route to %s", __func__, t->NameIP()); + goto bail; + } + if (rnfo.direct_connect) { + t->setDirectlyConnected(true); + } else { + t->setDirectlyConnected(false); + t->setNextHop(&rnfo.nexthop, sizeof(rnfo.nexthop)); + } + t->setIfType(rnfo.ii.device_type); + if (rnfo.ii.device_type == devt_ethernet) { + if (o.spoofMACAddress()) + t->setSrcMACAddress(o.spoofMACAddress()); + else + t->setSrcMACAddress(rnfo.ii.mac); + } +#ifdef WIN32 + else if (g_has_npcap_loopback && rnfo.ii.device_type == devt_loopback) { + if (o.spoofMACAddress()) + t->setSrcMACAddress(o.spoofMACAddress()); + else + t->setSrcMACAddress(rnfo.ii.mac); + t->setNextHopMACAddress(t->SrcMACAddress()); + } +#endif + t->setSourceSockAddr(&rnfo.srcaddr, sizeof(rnfo.srcaddr)); + if (hs->current_batch_sz == 0) /* Because later ones can have different src addy and be cut off group */ + o.decoys[o.decoyturn] = t->source(); + t->setDeviceNames(rnfo.ii.devname, rnfo.ii.devfullname); + t->setMTU(rnfo.ii.mtu); + // printf("Target %s %s directly connected, goes through local iface %s, which %s ethernet\n", t->NameIP(), t->directlyConnected()? "IS" : "IS NOT", t->deviceName(), (t->ifType() == devt_ethernet)? "IS" : "IS NOT"); + } + + return t; + +bail: + delete t; + return NULL; +} + +static Target *next_target(HostGroupState *hs, struct addrset *exclude_group, + const struct scan_lists *ports, int pingtype) { + struct sockaddr_storage ss; + size_t sslen; + Target *t; + + /* First handle targets deferred in the last batch. */ + if (!hs->undeferred.empty()) { + t = hs->undeferred.front(); + hs->undeferred.pop_front(); + return t; + } + +tryagain: + + if (hs->current_group.get_next_host(&ss, &sslen) != 0) { + const char *expr; + /* We are going to have to pop in another expression. */ + for (;;) { + expr = hs->next_expression(); + if (expr == NULL) + /* That's the last of them. */ + return NULL; + if (hs->current_group.parse_expr(expr, o.af()) == 0) + break; + else + log_bogus_target(expr); + } + goto tryagain; + } + + assert(ss.ss_family == o.af()); + + /* If we are resuming from a previous scan, we have already finished scanning + up to o.resume_ip. */ + if (o.resume_ip.ss_family != AF_UNSPEC) { + if (!sockaddr_storage_cmp(&o.resume_ip, &ss)) + /* We will continue starting with the next IP. */ + o.resume_ip.ss_family = AF_UNSPEC; + goto tryagain; + } + + /* Check exclude list. */ + if (hostInExclude((struct sockaddr *) &ss, sslen, exclude_group)) + goto tryagain; + + t = setup_target(hs, &ss, sslen, pingtype); + if (t == NULL) + goto tryagain; + + if (o.unique) { + // Use the exclude list to avoid scanning this IP again if the user requested it. + addrset_add_spec(exclude_group, t->targetipstr(), o.af(), 0); + } + return t; +} + +static void refresh_hostbatch(HostGroupState *hs, struct addrset *exclude_group, + const struct scan_lists *ports, int pingtype) { + int i; + bool arpping_done = false; + struct timeval now; + + hs->current_batch_sz = hs->next_batch_no = 0; + hs->undefer(); + while (hs->current_batch_sz < hs->max_batch_sz) { + Target *t; + + t = next_target(hs, exclude_group, ports, pingtype); + if (t == NULL) + break; + + /* Does this target need to go in a separate host group? */ + if (target_needs_new_hostgroup(hs->hostbatch, hs->current_batch_sz, t)) { + if (hs->defer(t)) + continue; + else + break; + } + + o.decoys[o.decoyturn] = t->source(); + hs->hostbatch[hs->current_batch_sz++] = t; + } + + if (hs->current_batch_sz == 0) + return; + + /* OK, now we have our complete batch of entries. The next step is to + randomize them (if requested) */ + if (hs->randomize) { + hoststructfry(hs->hostbatch, hs->current_batch_sz); + } + + /* First I'll do the ARP ping if all of the machines in the group are + directly connected over ethernet. I may need the MAC addresses + later anyway. */ + if (hs->hostbatch[0]->ifType() == devt_ethernet && + hs->hostbatch[0]->af() == AF_INET && + hs->hostbatch[0]->directlyConnected() && + o.sendpref != PACKET_SEND_IP_STRONG && + o.implicitARPPing) { + arpping(hs->hostbatch, hs->current_batch_sz); + arpping_done = true; + } + + /* No other interface types are supported by ND ping except devt_ethernet + at the moment. */ + if (hs->hostbatch[0]->ifType() == devt_ethernet && + hs->hostbatch[0]->af() == AF_INET6 && + hs->hostbatch[0]->directlyConnected() && + o.sendpref != PACKET_SEND_IP_STRONG && + o.implicitARPPing) { + arpping(hs->hostbatch, hs->current_batch_sz); + arpping_done = true; + } + + gettimeofday(&now, NULL); + if ((o.sendpref & PACKET_SEND_ETH) && + hs->hostbatch[0]->ifType() == devt_ethernet) { + for (i=0; i < hs->current_batch_sz; i++) { + if (!(hs->hostbatch[i]->flags & HOST_DOWN) && + !hs->hostbatch[i]->timedOut(&now)) { + if (!setTargetNextHopMAC(hs->hostbatch[i])) { + error("%s: Failed to determine dst MAC address for target %s", + __func__, hs->hostbatch[i]->NameIP()); + hs->hostbatch[i]->flags = HOST_DOWN; + } + } + } + } + + /* Then we do the mass ping (if required - IP-level pings) */ + if ((pingtype == PINGTYPE_NONE && !arpping_done) || hs->hostbatch[0]->ifType() == devt_loopback) { + for (i=0; i < hs->current_batch_sz; i++) { + if (!(hs->hostbatch[i]->flags & HOST_DOWN || hs->hostbatch[i]->timedOut(&now))) { + initialize_timeout_info(&hs->hostbatch[i]->to); + hs->hostbatch[i]->flags |= HOST_UP; /*hostbatch[i].up = 1;*/ + if (pingtype == PINGTYPE_NONE && !arpping_done) + hs->hostbatch[i]->reason.reason_id = ER_USER; + else + hs->hostbatch[i]->reason.reason_id = ER_LOCALHOST; + } + } + } else if (!arpping_done) { + massping(hs->hostbatch, hs->current_batch_sz, ports); + } + + if (!o.noresolve) + nmap_mass_rdns(hs->hostbatch, hs->current_batch_sz); +} + +Target *nexthost(HostGroupState *hs, struct addrset *exclude_group, + const struct scan_lists *ports, int pingtype) { + if (hs->next_batch_no >= hs->current_batch_sz) + refresh_hostbatch(hs, exclude_group, ports, pingtype); + if (hs->next_batch_no >= hs->current_batch_sz) + return NULL; + + return hs->hostbatch[hs->next_batch_no++]; +} |