diff --git a/src/Makefile b/src/Makefile index 234b107..3396519 100644 --- a/src/Makefile +++ b/src/Makefile @@ -68,6 +68,12 @@ install: zmap $(INSTALLDATA) ./zmap.1 $(mandir) @echo "\n**************\nSuccess! ZMap is installed. Try running (as root):\nzmap -p 80 -N 10 -B 1M -o -\n**************" +uninstall: + test -f $(oldmanfile) && rm -f $(oldmanfile) && mandb -f $(oldmanfile) || /bin/true # remove old man page if it's there + test -f $(mandir)/zmap.1 && rm -f $(mandir)/zmap.1 && mandb -f $(mandir)/zmap.1 || /bin/true # remove current man page if it's there + rm -f $(bindir)/zmap + + clean: -rm -f $(objects) $(redis_objects) $(TARGETS) diff --git a/src/get_gateway.c b/src/get_gateway.c index e015fa8..52e32b6 100644 --- a/src/get_gateway.c +++ b/src/get_gateway.c @@ -121,21 +121,33 @@ int get_hw_addr(struct in_addr *gw_ip, char *iface, unsigned char *hw_mac) int correct_ip = 0; rt_msg = (struct rtmsg *) NLMSG_DATA(nlhdr); - + if ((rt_msg->rtm_family != AF_INET)) { return -1; } - + rt_attr = (struct rtattr *) RTM_RTA(rt_msg); rt_len = RTM_PAYLOAD(nlhdr); while (RTA_OK(rt_attr, rt_len)) { switch (rt_attr->rta_type) { - case NDA_LLADDR: - assert(RTA_PAYLOAD(rt_attr) == IFHWADDRLEN); + case NDA_LLADDR: + if (RTA_PAYLOAD(rt_attr) != IFHWADDRLEN) { + // could be using a VPN + log_fatal("get_gateway", "Unexpected hardware address length (%d).\n\n" \ + " If you are using a VPN, supply the --vpn flag (and provide an interface via -i)", + RTA_PAYLOAD(rt_attr)); + exit(1); + } memcpy(mac, RTA_DATA(rt_attr), IFHWADDRLEN); break; case NDA_DST: - assert(RTA_PAYLOAD(rt_attr) == sizeof(dst_ip)); + if (RTA_PAYLOAD(rt_attr) != sizeof(dst_ip)) { + // could be using a VPN + log_fatal("get_gateway", "Unexpected IP address length (%d).\n" \ + " If you are using a VPN, supply the --vpn flag (and provide an interface via -i)", + RTA_PAYLOAD(rt_attr)); + exit(1); + } memcpy(&dst_ip, RTA_DATA(rt_attr), sizeof(dst_ip)); if (memcmp(&dst_ip, gw_ip, sizeof(dst_ip)) == 0) { correct_ip = 1; @@ -184,11 +196,11 @@ int get_default_gw(struct in_addr *gw, char *iface) int has_gw = 0; rt_msg = (struct rtmsg *) NLMSG_DATA(nlhdr); - + if ((rt_msg->rtm_family != AF_INET) || (rt_msg->rtm_table != RT_TABLE_MAIN)) { return -1; } - + rt_attr = (struct rtattr *) RTM_RTA(rt_msg); rt_len = RTM_PAYLOAD(nlhdr); while (RTA_OK(rt_attr, rt_len)) { diff --git a/src/output_modules/module_json.c b/src/output_modules/module_json.c index dd5b352..096540c 100644 --- a/src/output_modules/module_json.c +++ b/src/output_modules/module_json.c @@ -159,7 +159,7 @@ int json_output_file_ip(fieldset_t *fs) } else if (f->type == FS_NULL) { // do nothing } else { - log_fatal("csv", "received unknown output type"); + log_fatal("json", "received unknown output type"); } } diff --git a/src/recv.c b/src/recv.c index 8514144..7f15765 100644 --- a/src/recv.c +++ b/src/recv.c @@ -59,6 +59,8 @@ static inline void set_ip(uint32_t ip) ip_seen[ip >> 6] |= (uint64_t)1 << (ip & 0x3F); } +static u_char fake_eth_hdr[65535]; + void packet_cb(u_char __attribute__((__unused__)) *user, const struct pcap_pkthdr *p, const u_char *bytes) { @@ -74,13 +76,13 @@ void packet_cb(u_char __attribute__((__unused__)) *user, // length of entire packet captured by libpcap uint32_t buflen = (uint32_t) p->caplen; - if ((sizeof(struct iphdr) + sizeof(struct ethhdr)) > buflen) { + if ((sizeof(struct iphdr) + (zconf.send_ip_pkts ? 0 : sizeof(struct ethhdr))) > buflen) { // buffer not large enough to contain ethernet // and ip headers. further action would overrun buf return; } - struct iphdr *ip_hdr = (struct iphdr *)&bytes[sizeof(struct ethhdr)]; - + struct iphdr *ip_hdr = (struct iphdr *)&bytes[(zconf.send_ip_pkts ? 0 : sizeof(struct ethhdr))]; + uint32_t src_ip = ip_hdr->saddr; uint32_t validation[VALIDATE_BYTES/sizeof(uint8_t)]; @@ -88,7 +90,7 @@ void packet_cb(u_char __attribute__((__unused__)) *user, // and we must calculate off potential payload message instead validate_gen(ip_hdr->daddr, ip_hdr->saddr, (uint8_t *)validation); - if (!zconf.probe_module->validate_packet(ip_hdr, buflen - sizeof(struct ethhdr), + if (!zconf.probe_module->validate_packet(ip_hdr, buflen - (zconf.send_ip_pkts ? 0 : sizeof(struct ethhdr)), &src_ip, validation)) { return; } @@ -97,25 +99,36 @@ void packet_cb(u_char __attribute__((__unused__)) *user, fieldset_t *fs = fs_new_fieldset(); fs_add_ip_fields(fs, ip_hdr); + // HACK: + // probe modules (for whatever reason) expect the full ethernet frame + // in process_packet. For VPN, we only get back an IP frame. + // Here, we fake an ethernet frame (which is initialized to + // have ETH_P_IP proto and 00s for dest/src). + if (zconf.send_ip_pkts) { + if (buflen > sizeof(fake_eth_hdr)) { + buflen = sizeof(fake_eth_hdr); + } + memcpy(&fake_eth_hdr[sizeof(struct ethhdr)], bytes, buflen); + bytes = fake_eth_hdr; + } zconf.probe_module->process_packet(bytes, buflen, fs); fs_add_system_fields(fs, is_repeat, zsend.complete); int success_index = zconf.fsconf.success_index; assert(success_index < fs->len); int is_success = fs_get_uint64_by_index(fs, success_index); - + if (is_success) { zrecv.success_total++; if (!is_repeat) { zrecv.success_unique++; set_ip(src_ip); } - if (zsend.complete) { + if (zsend.complete) { zrecv.cooldown_total++; if (!is_repeat) { zrecv.cooldown_unique++; } } - } else { zrecv.failure_total++; } @@ -123,18 +136,18 @@ void packet_cb(u_char __attribute__((__unused__)) *user, // we need to translate the data provided by the probe module // into a fieldset that can be used by the output module if (!is_success && zconf.filter_unsuccessful) { - goto cleanup; + goto cleanup; } if (is_repeat && zconf.filter_duplicates) { goto cleanup; } - o = translate_fieldset(fs, &zconf.fsconf.translation); + o = translate_fieldset(fs, &zconf.fsconf.translation); if (zconf.output_module && zconf.output_module->process_ip) { zconf.output_module->process_ip(o); } cleanup: fs_free(fs); - free(o); + free(o); if (zconf.output_module && zconf.output_module->update && !(zrecv.success_unique % zconf.output_module->update_interval)) { zconf.output_module->update(&zconf, &zsend, &zrecv); @@ -184,6 +197,11 @@ int recv_run(pthread_mutex_t *recv_ready_mutex) log_fatal("recv", "couldn't install filter"); } } + if (zconf.send_ip_pkts) { + struct ethhdr *eth = (struct ethhdr *)fake_eth_hdr; + memset(fake_eth_hdr, 0, sizeof(fake_eth_hdr)); + eth->h_proto = htons(ETH_P_IP); + } log_debug("recv", "receiver ready"); if (zconf.filter_duplicates) { log_debug("recv", "duplicate responses will be excluded from output"); diff --git a/src/send.c b/src/send.c index c76f87e..ebb971c 100644 --- a/src/send.c +++ b/src/send.c @@ -288,7 +288,7 @@ int send_run(int sock) zconf.probe_module->print_packet(stdout, buf); } else { int l = zconf.probe_module->packet_length; - int rc = sendto(sock, buf, + int rc = sendto(sock, buf + zconf.send_ip_pkts*sizeof(struct ethhdr), l, 0, (struct sockaddr *)&sockaddr, sizeof(struct sockaddr_ll)); diff --git a/src/state.c b/src/state.c index 62d5a95..2742c7e 100644 --- a/src/state.c +++ b/src/state.c @@ -14,7 +14,7 @@ struct state_conf zconf = { .log_level = LOG_INFO, .source_port_first = 32768, // (these are the default - .source_port_last = 61000, // ephemeral range on Linux) + .source_port_last = 61000, // ephemeral range on Linux) .output_filename = NULL, .blacklist_filename = NULL, .whitelist_filename = NULL, @@ -36,6 +36,7 @@ struct state_conf zconf = { .probe_args = NULL, .gw_mac = {0}, .gw_mac_set = 0, + .send_ip_pkts = 0, .source_ip_first = NULL, .source_ip_last = NULL, .raw_output_fields = NULL, diff --git a/src/state.h b/src/state.h index a9f8f22..5bd3f75 100644 --- a/src/state.h +++ b/src/state.h @@ -70,7 +70,8 @@ struct state_conf { char *probe_args; char *output_args; macaddr_t gw_mac[IFHWADDRLEN]; - int gw_mac_set; + int gw_mac_set; + int send_ip_pkts; char *source_ip_first; char *source_ip_last; char *output_filename; diff --git a/src/zmap.1 b/src/zmap.1 index 63aa850..0ab49af 100644 --- a/src/zmap.1 +++ b/src/zmap.1 @@ -84,6 +84,12 @@ Ethernet address. .TP .B \-i, --interface=name Specify network interface to use. +.TP +.B \-X, --vpn +If using ZMap through a VPN, use this option. Instead of sending +raw Ethernet frames, ZMap will send IP packets. When using this +option, it is generally also necessary to provide the interface +(through the -i flag). .SS "Advanced options" .TP diff --git a/src/zmap.c b/src/zmap.c index a3bfaa9..2cdb68b 100644 --- a/src/zmap.c +++ b/src/zmap.c @@ -16,10 +16,12 @@ #include #include +#include #include #include #include #include +#include #include @@ -106,6 +108,17 @@ static void* start_recv(__attribute__((unused)) void *arg) return NULL; } +static void drop_privs() +{ + struct passwd *pw; + if ((pw = getpwnam("nobody")) != NULL) { + if (setuid(pw->pw_uid) == 0) { + return; // success + } + } + log_fatal("zmap", "Couldn't change UID to 'nobody'"); +} + static void *start_mon(__attribute__((unused)) void *arg) { set_cpu(); @@ -261,6 +274,8 @@ static void start_zmap(void) } } + drop_privs(); + // wait for completion for (int i=0; i < zconf.senders; i++) { int r = pthread_join(tsend[i], NULL); @@ -382,6 +397,11 @@ int main(int argc, char *argv[]) exit(EXIT_FAILURE); } } + if (args.vpn_given) { + zconf.send_ip_pkts = 1; + zconf.gw_mac_set = 1; + memset(zconf.gw_mac, 0, IFHWADDRLEN); + } if (cmdline_parser_required(&args, CMDLINE_PARSER_PACKAGE) != 0) { exit(EXIT_FAILURE); } diff --git a/src/zopt.c b/src/zopt.c index 24d2cab..3210e44 100644 --- a/src/zopt.c +++ b/src/zopt.c @@ -54,9 +54,10 @@ const char *gengetopt_args_info_help[] = { " -S, --source-ip=ip|range Source address(es) for scan packets", " -G, --gateway-mac=addr Specify gateway MAC address", " -i, --interface=name Specify network interface to use", + " -X, --vpn Sends IP packets instead of Ethernet (for VPNs)", "\nAdvanced options:", " -M, --probe-module=name Select probe module (default=`tcp_synscan')", - " -O, --output-module=name Select output module (default=`csv')", + " -O, --output-module=name Select output module (default=`simple_file')", " --probe-args=args Arguments to pass to probe module", " --output-args=args Arguments to pass to output module", " --list-output-modules List available output modules", @@ -137,6 +138,7 @@ void clear_given (struct gengetopt_args_info *args_info) args_info->source_ip_given = 0 ; args_info->gateway_mac_given = 0 ; args_info->interface_given = 0 ; + args_info->vpn_given = 0 ; args_info->probe_module_given = 0 ; args_info->output_module_given = 0 ; args_info->probe_args_given = 0 ; @@ -189,7 +191,7 @@ void clear_args (struct gengetopt_args_info *args_info) args_info->interface_orig = NULL; args_info->probe_module_arg = gengetopt_strdup ("tcp_synscan"); args_info->probe_module_orig = NULL; - args_info->output_module_arg = gengetopt_strdup ("csv"); + args_info->output_module_arg = gengetopt_strdup ("simple_file"); args_info->output_module_orig = NULL; args_info->probe_args_arg = NULL; args_info->probe_args_orig = NULL; @@ -226,19 +228,20 @@ void init_args_info(struct gengetopt_args_info *args_info) args_info->source_ip_help = gengetopt_args_info_help[19] ; args_info->gateway_mac_help = gengetopt_args_info_help[20] ; args_info->interface_help = gengetopt_args_info_help[21] ; - args_info->probe_module_help = gengetopt_args_info_help[23] ; - args_info->output_module_help = gengetopt_args_info_help[24] ; - args_info->probe_args_help = gengetopt_args_info_help[25] ; - args_info->output_args_help = gengetopt_args_info_help[26] ; - args_info->list_output_modules_help = gengetopt_args_info_help[27] ; - args_info->list_probe_modules_help = gengetopt_args_info_help[28] ; - args_info->list_output_fields_help = gengetopt_args_info_help[29] ; - args_info->config_help = gengetopt_args_info_help[31] ; - args_info->quiet_help = gengetopt_args_info_help[32] ; - args_info->summary_help = gengetopt_args_info_help[33] ; - args_info->verbosity_help = gengetopt_args_info_help[34] ; - args_info->help_help = gengetopt_args_info_help[35] ; - args_info->version_help = gengetopt_args_info_help[36] ; + args_info->vpn_help = gengetopt_args_info_help[22] ; + args_info->probe_module_help = gengetopt_args_info_help[24] ; + args_info->output_module_help = gengetopt_args_info_help[25] ; + args_info->probe_args_help = gengetopt_args_info_help[26] ; + args_info->output_args_help = gengetopt_args_info_help[27] ; + args_info->list_output_modules_help = gengetopt_args_info_help[28] ; + args_info->list_probe_modules_help = gengetopt_args_info_help[29] ; + args_info->list_output_fields_help = gengetopt_args_info_help[30] ; + args_info->config_help = gengetopt_args_info_help[32] ; + args_info->quiet_help = gengetopt_args_info_help[33] ; + args_info->summary_help = gengetopt_args_info_help[34] ; + args_info->verbosity_help = gengetopt_args_info_help[35] ; + args_info->help_help = gengetopt_args_info_help[36] ; + args_info->version_help = gengetopt_args_info_help[37] ; } @@ -426,6 +429,8 @@ cmdline_parser_dump(FILE *outfile, struct gengetopt_args_info *args_info) write_into_file(outfile, "gateway-mac", args_info->gateway_mac_orig, 0); if (args_info->interface_given) write_into_file(outfile, "interface", args_info->interface_orig, 0); + if (args_info->vpn_given) + write_into_file(outfile, "vpn", 0, 0 ); if (args_info->probe_module_given) write_into_file(outfile, "probe-module", args_info->probe_module_orig, 0); if (args_info->output_module_given) @@ -721,6 +726,7 @@ cmdline_parser_internal ( { "source-ip", 1, NULL, 'S' }, { "gateway-mac", 1, NULL, 'G' }, { "interface", 1, NULL, 'i' }, + { "vpn", 0, NULL, 'X' }, { "probe-module", 1, NULL, 'M' }, { "output-module", 1, NULL, 'O' }, { "probe-args", 1, NULL, 0 }, @@ -737,7 +743,7 @@ cmdline_parser_internal ( { 0, 0, 0, 0 } }; - c = getopt_long (argc, argv, "p:o:b:w:f:n:N:t:r:B:c:e:T:P:ds:S:G:i:M:O:C:qgv:hV", long_options, &option_index); + c = getopt_long (argc, argv, "p:o:b:w:f:n:N:t:r:B:c:e:T:P:ds:S:G:i:XM:O:C:qgv:hV", long_options, &option_index); if (c == -1) break; /* Exit from `while (1)' loop. */ @@ -970,6 +976,18 @@ cmdline_parser_internal ( additional_error)) goto failure; + break; + case 'X': /* Sends IP packets instead of Ethernet (for VPNs). */ + + + if (update_arg( 0 , + 0 , &(args_info->vpn_given), + &(local_args_info.vpn_given), optarg, 0, 0, ARG_NO, + check_ambiguity, override, 0, 0, + "vpn", 'X', + additional_error)) + goto failure; + break; case 'M': /* Select probe module. */ diff --git a/src/zopt.ggo b/src/zopt.ggo index 0446738..71f0aef 100644 --- a/src/zopt.ggo +++ b/src/zopt.ggo @@ -75,6 +75,8 @@ option "gateway-mac" G "Specify gateway MAC address" option "interface" i "Specify network interface to use" typestr="name" optional string +option "vpn" X "Sends IP packets instead of Ethernet (for VPNs)" + optional section "Advanced options" diff --git a/src/zopt.h b/src/zopt.h index c1fe727..d5bbead 100644 --- a/src/zopt.h +++ b/src/zopt.h @@ -92,6 +92,7 @@ struct gengetopt_args_info char * interface_arg; /**< @brief Specify network interface to use. */ char * interface_orig; /**< @brief Specify network interface to use original value given at command line. */ const char *interface_help; /**< @brief Specify network interface to use help description. */ + const char *vpn_help; /**< @brief Sends IP packets instead of Ethernet (for VPNs) help description. */ char * probe_module_arg; /**< @brief Select probe module (default='tcp_synscan'). */ char * probe_module_orig; /**< @brief Select probe module original value given at command line. */ const char *probe_module_help; /**< @brief Select probe module help description. */ @@ -137,6 +138,7 @@ struct gengetopt_args_info unsigned int source_ip_given ; /**< @brief Whether source-ip was given. */ unsigned int gateway_mac_given ; /**< @brief Whether gateway-mac was given. */ unsigned int interface_given ; /**< @brief Whether interface was given. */ + unsigned int vpn_given ; /**< @brief Whether vpn was given. */ unsigned int probe_module_given ; /**< @brief Whether probe-module was given. */ unsigned int output_module_given ; /**< @brief Whether output-module was given. */ unsigned int probe_args_given ; /**< @brief Whether probe-args was given. */