/* * icmpid.c - host OS identification via icmp packets. * * Written by Simple Nomad, based upon Ofir Arkin's ICMP research. * * Requirements: Libnet 1.0 or higher (http://www.packetfactory.net/libnet/) * libpcap 0.4 (ftp://ftp.ee.lbl.gov/libpcap.tar.Z) * * Compilation instructions: * gcc `libnet-config --defines` -o icmpid icmpid.c -lnet -lpcap * * Tested on RedHat 6.1, but should work most places that libnet and libpcap * compile and install properly. * * Initial revision 15May2001 * * 21May2001 - More bugs removed, only working on figure_results() logic. * 22May2001 - Made sure we are using IP_RF in the ECHO packet. Added 5th * packet as combining IP_DF and IP_RF in the same packet * caused problems. Fixed a couple of greater-than signs that * were pointing the wrong way. * 23May2001 - The second echo packet reply could have the IP_DF bit set, * so we now make sure we take that into consideration when * when checking for IP_RF. Added code to differentiate ME from * 98/98SE. Added code in case one of the echo replies is dropped * to prevent a false positive OS ID. * 24May2001 - Fixed a few minor bugs and typos. Added check to ensure * user is root when running. * * Notes: * This is intended as a proof of concept piece of code. It is not very * accurate going across the open Internet, mainly because of two things -- * first, there is a distinct possibility that one of the five ICMP packets * gets dropped, and second, there is a distinct possibility that all or part * of the ICMP packets will be blocked (via firewalling, etc) before reaching * the target. Considering that at least part of the OS identification * involves determining whether the OS actually replies or ignores certain * types of ICMP packets, a dropped or firewalled packet could lead to a false * OS identification. * * Nonetheless, it is extremely accurate on a local network, and if dropped * packets may be a problem then of course multiple runs can ID the OS. * I hope you enjoy the tool, as always let me know if you find it useful. * If you have questions regarding the logic behind the tool, I recommend you * review Ofir Arkin's paper on ICMP available at * http://www.sys-security.com/html/projects/icmp.html. * * Simple Nomad * Sun Jul 15 17:05:06 CDT 2001 */ #include #include #include #define VER "1.0" /* * More globals */ struct bpf_program pcapfilter; struct in_addr net, mask; pcap_t *pd; char pcap_err[PCAP_ERRBUF_SIZE]; u_char *ebuf; char *dev; struct libnet_link_int *l; int link_offset, verbose, packets_answered, unexpected, icmp_type; u_short icmp_header, id0, id1, id2, id3, id4; u_int8_t ttl_reply, code_field = 0, precedence_bits = 0; int tstamp_reply = 0, addrmask_reply = 0, info_reply = 0, first_echo = 0, second_echo = 0; int df_bit = 0, unused_bit = 0; u_short precedence_info_reply = 0, ip_id = 0; u_long src_ip, dst_ip; char *targetname; /* * build and send the icmp packets to remotely ID the OS of the target */ void send_os_packets(void) { int packet_size, network, i, os, checksum_header; u_char *packet; char echodata[48]; u_short id = 0; u_short df = 0; u_char tos = 0; u_char ttl; /* main sending loop */ /* do some random stuff to avoid IDS */ ttl = libnet_get_prand(LIBNET_PR8); /* Time To Live */ if(ttl < 32) ttl = ttl + 32; /* make sure TTL isn't too small */ /* loop to build the individual packets for OS identification */ for (os=0;os<5;os++) { switch(os) { case 0: checksum_header = LIBNET_ICMP_ECHO_H; break; case 1: checksum_header = LIBNET_ICMP_TS_H; break; case 2: checksum_header = LIBNET_ICMP_MASK_H; break; case 3: /* libnet doesn't support info request, so we lie about the header */ checksum_header = LIBNET_ICMP_ECHO_H; break; case 4: checksum_header = LIBNET_ICMP_ECHO_H; break; } /* compute packet size */ packet_size = LIBNET_IP_H + checksum_header; /* get mem for packet */ libnet_init_packet(packet_size, &packet); if (packet == NULL) { libnet_error(LIBNET_ERR_FATAL, "unable to init packet mem\n"); } /* get network ready */ network = libnet_open_raw_sock(IPPROTO_RAW); if (network == -1) { libnet_error(LIBNET_ERR_FATAL, "unable to open network for sending\n"); } /* build ICMP section */ switch (os) { case 0: /* ICMP_ECHO with code != 0 and precedence != 0 */ if (verbose) fprintf(stdout,"Sending first echo,"); libnet_build_icmp_echo(ICMP_ECHO,0x20,id0,0,NULL,0,packet + LIBNET_IP_H); tos = 0x6; df = IP_DF; break; case 1: /* ICMP_TSTAMP */ if (verbose) fprintf(stdout," timestamp,"); libnet_build_icmp_timestamp(ICMP_TSTAMP,0,id1,0,0x00000000,0x00000000,0x00000000,NULL,0,packet + LIBNET_IP_H); tos = 0x00; df = 0; break; case 2: /* ICMP_MASKREQ */ if (verbose) fprintf(stdout," mask request,"); libnet_build_icmp_mask(ICMP_MASKREQ,0,id2,0,0,NULL,0,packet + LIBNET_IP_H); break; case 3: /* ICMP_INFO_REQUEST with precedence != 0 */ /* we use the ECHO build but use the INFOREQ type to get around libnet */ if (verbose) fprintf(stdout," info request,"); libnet_build_icmp_echo(ICMP_INFO_REQUEST,0,id3,0,NULL,0,packet + LIBNET_IP_H); tos = 0x6; break; case 4: /* ICMP_ECHO with code != 0 and precedence != 0 */ if (verbose) fprintf(stdout," second echo packets\n"); libnet_build_icmp_echo(ICMP_ECHO,0,id4,0,NULL,0,packet + LIBNET_IP_H); tos = 0; df = IP_RF; break; } /* randomize the IP id */ id = libnet_get_prand(LIBNET_PR16); /* build IP section */ libnet_build_ip(checksum_header,tos,id,df,ttl,IPPROTO_ICMP,src_ip,dst_ip,NULL,0,packet); /* do the checksum */ if (libnet_do_checksum(packet, IPPROTO_ICMP, checksum_header) == -1) { libnet_error(LIBNET_ERR_FATAL, "checksum failed on packet\n"); } /* send the packet */ i = libnet_write_ip(network, packet, packet_size); if (i == -1) { libnet_error(LIBNET_ERR_FATAL, "failed to write to network\n"); } /* * this isn't *too* bad, but if we're trying to be stealth there * is no reason to send part of a packet in case it does get there * as it might set off an alarm. therefore if we start trying to * send just parts, abandon ship */ if (i < packet_size) { libnet_error(LIBNET_ERR_FATAL, "only wrote %d bytes\n", i); } /* * miniscule delay between packets to prevent overflowing the local * interface */ usleep(1); /* clean things up */ if (libnet_close_raw_sock(network) == -1) { libnet_error(LIBNET_ERR_WARNING, "couldn't close the interface after sending"); } libnet_destroy_packet(&packet); } } /* * Receive the packets normally, i.e. no sniffing involved. */ void receive_packets_normal(void) { char buf[1500]; struct ip *ip = (struct ip *)buf; struct icmp *icmp = (struct icmp *)(ip + 1); int s, err = 0, on = 1; long int fromlen = 0; ulong src_ip; packets_answered = 0; unexpected = 0; /* for non-sniffing we listen the old-fashioned way */ if ((s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) { fprintf(stderr, "Problem opening socket for listening"); exit(-1); } if (setsockopt(s, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0) { fprintf(stderr, "Problem setting option on socket"); exit(-1); } while (packets_answered < 6) { if ((err = recvfrom(s, buf, sizeof buf, 0, NULL, (int *)&fromlen)) < 0) { fprintf(stderr,"error receiving on network"); } if (dst_ip == ip->ip_src.s_addr) { if (IPPROTO_ICMP == ip->ip_p) { switch (icmp->icmp_type) { case ICMP_ECHOREPLY: /* ECHO with code != 0, precedence != 0, DF bit set */ if (htons(icmp->icmp_hun.ih_idseq.icd_id) == id0) { code_field = icmp->icmp_code; precedence_bits = ip->ip_tos; ttl_reply = ip->ip_ttl; if(ip->ip_off == 0x0000) df_bit = 0; if(ip->ip_off == 0x0040) df_bit = 1; ip_id = ip->ip_id; first_echo++; packets_answered++; } else if (htons(icmp->icmp_hun.ih_idseq.icd_id) == id4) { if(ip->ip_off == 0x0000) unused_bit = 0; if((ip->ip_off == 0x0080) || (ip->ip_off == 0x00c0)) unused_bit = 1; second_echo++; packets_answered++; } else { if (verbose) fprintf(stdout,"Received unexpected ICMP_ECHOREPLY packet from %s\n",inet_ntoa(ip->ip_src)); unexpected++; } break; case ICMP_TSTAMPREPLY: /* ICMP_TSTAMP */ if (htons(icmp->icmp_hun.ih_idseq.icd_id) == id1) { tstamp_reply = 1; packets_answered++; } else { if (verbose) fprintf(stdout,"Received unexpected ICMP_TSTAMPREPLY packet from %s\n",inet_ntoa(ip->ip_src)); unexpected++; } break; case ICMP_MASKREPLY: /* ICMP_MASKREQ */ if (htons(icmp->icmp_hun.ih_idseq.icd_id) == id2) { addrmask_reply = 1; packets_answered++; } else { if (verbose) fprintf(stdout,"Received unexpected ICMP_MASKREPLY packet from %s\n",inet_ntoa(ip->ip_src)); unexpected++; } break; case ICMP_INFO_REPLY: /* ICMP_INFO_REQUEST */ if (htons(icmp->icmp_hun.ih_idseq.icd_id) == id3) { info_reply = 1; precedence_info_reply = ip->ip_tos; packets_answered++; } else { if (verbose) fprintf(stdout,"Received unexpected ICMP_INFO_REPLY packet from %s\n",inet_ntoa(ip->ip_src)); unexpected++; } break; default: if (verbose) printf("Received unexpected ICMP packet from %s\n",inet_ntoa(ip->ip_src)); unexpected++; break; } } else { /* we do not wish to see any odd packets from our own address */ src_ip = libnet_get_ipaddr(l, dev, ebuf); src_ip = htonl(src_ip); if (!(src_ip == ip->ip_src.s_addr)) { if (verbose) printf("Received unexpected packet from %s\n",inet_ntoa(ip->ip_src)); unexpected++; } } } if (packets_answered == 5) break; } } /* * this handles packets as they are sniffed and processes them, similar to * the receive_packets_normal routine */ void grab_icmp(u_char *data1, struct pcap_pkthdr* h, u_char *p) { struct ip* ip = (struct ip *)(p + link_offset); struct icmp *icmp = (struct icmp *)(ip + 1); int i,current; while (packets_answered < 5) { if (dst_ip == ip->ip_src.s_addr) { if (IPPROTO_ICMP == ip->ip_p) { switch (icmp->icmp_type) { case ICMP_ECHOREPLY: /* ECHO with code != 0, precedence != 0, DF bit set */ if (icmp->icmp_hun.ih_idseq.icd_id == id0) { code_field = icmp->icmp_code; precedence_bits = ip->ip_tos; ttl_reply = ip->ip_ttl; if(ip->ip_off == 0x0000) df_bit = 0; if(ip->ip_off == 0x0040) df_bit = 1; ip_id = ip->ip_id; first_echo++; packets_answered++; } else if (icmp->icmp_hun.ih_idseq.icd_id == id4) { if(ip->ip_off == 0x0000) unused_bit = 0; if((ip->ip_off == 0x0080) || (ip->ip_off == 0x00c0)) unused_bit = 1; second_echo++; packets_answered++; } else { if (verbose) fprintf(stdout,"Received unexpected ICMP_ECHOREPLY packet from %s\n",inet_ntoa(ip->ip_src)); unexpected++; } break; case ICMP_TSTAMPREPLY: /* ICMP_TSTAMP */ if (icmp->icmp_hun.ih_idseq.icd_id == id1) { tstamp_reply = 1; packets_answered++; } else { if (verbose) fprintf(stdout,"Received unexpected ICMP_TSTAMPREPLY packet from %s\n",inet_ntoa(ip->ip_src)); unexpected++; } break; case ICMP_MASKREPLY: /* ICMP_MASKREQ */ if (icmp->icmp_hun.ih_idseq.icd_id == id2) { addrmask_reply = 1; packets_answered++; } else { if (verbose) fprintf(stdout,"Received unexpected ICMP_MASKREPLY packet from %s\n",inet_ntoa(ip->ip_src)); unexpected++; } break; case ICMP_INFO_REPLY: /* ICMP_INFO_REQUEST */ if (htons(icmp->icmp_hun.ih_idseq.icd_id) == id3) { info_reply = 1; precedence_info_reply = ip->ip_tos; packets_answered++; } else { if (verbose) fprintf(stdout,"Received unexpected ICMP_INFO_REPLY packet from %s\n",inet_ntoa(ip->ip_src)); unexpected++; } break; default: if (verbose) printf("Received unexpected ICMP packet from %s\n",inet_ntoa(ip->ip_src)); unexpected++; break; } } else { /* we do not wish to see any odd packets from our own address */ src_ip = libnet_get_ipaddr(l, dev, ebuf); src_ip = htonl(src_ip); if (!(src_ip == ip->ip_src.s_addr)) { if (verbose) printf("Received unexpected packet from %s\n",inet_ntoa(ip->ip_src)); unexpected++; } } } if (packets_answered == 5) break; } } /* * So why do a "normal" and a "sniffing" routine separately? Sniffing requires * putting the receiving interface in promiscuous mode, something you may not * want to do. */ void receive_packets_sniffing(void) { int snaplen = 65535, promisc = 1, to_ms = 1000; if (pcap_lookupnet(dev,&net.s_addr,&mask.s_addr, pcap_err) == -1) { perror(pcap_err); exit(-1); } if (verbose) printf("listening for replies on %s: %s\n", dev, inet_ntoa(net)); if ((pd = pcap_open_live(dev, snaplen, promisc, to_ms, pcap_err)) == NULL) { perror(pcap_err); exit(-1); } switch(pcap_datalink(pd)) { case DLT_EN10MB: case DLT_IEEE802: link_offset = 14; break; case DLT_SLIP: link_offset = 16; break; case DLT_PPP: case DLT_NULL: link_offset = 4; break; case DLT_RAW: link_offset = 0; break; default: fprintf(stderr,"unsupported interface type\n"); exit(-1); } while (pcap_loop(pd,0,(pcap_handler)grab_icmp,0)); } /* * Called by caughtsig, if we're sniffing to turn off the promisc bit */ void stop_sniffing(void) { int fd, i; struct ifreq buf[16]; struct ifconf ifc; if ((fd = socket (AF_INET, SOCK_DGRAM, 0)) >= 0) { ifc.ifc_len = sizeof buf; ifc.ifc_buf = (caddr_t) buf; if (!(ioctl (fd, SIOCGIFCONF, (char *) &ifc))) { i = ifc.ifc_len / sizeof (struct ifreq); if (!i) { fprintf(stderr, "Can't find an interface.\n"); exit(-1); } while (i-- > 0) { /* cmp only length of dev since ifr_name may have extra chars */ if (!(strncmp(buf[i].ifr_name,dev,strlen(dev)))) { if(!(ioctl(fd, SIOCGIFFLAGS, (char *) &buf[i]))) { if (buf[i].ifr_flags & IFF_PROMISC) { buf[i].ifr_flags = buf[i].ifr_flags ^ IFF_PROMISC; if(ioctl(fd, SIOCSIFFLAGS, &buf[i])) perror("ioctl"); } } else perror("ioctl"); } } /* end while loop */ } else perror ("ioctl"); } else perror ("socket"); close (fd); } void totals(void) { fprintf(stdout,"Total expected packets received : %d out of 5\n",packets_answered); fprintf(stdout,"Total unexpected packets received : %d\n",unexpected); } void figure_results(void) { if (packets_answered == 0) fprintf(stdout,"No answer back from target, possibly ICMP blocked or target down\n"); else if (!first_echo) fprintf(stdout,"WARNING: No reply to the first echo packet, unable to fingerprint the OS.\n"); else { if ((second_echo) && (!tstamp_reply) && (!addrmask_reply) && (!info_reply)) { fprintf(stdout,"\nWARNING: Only Echo packets were received back, results could be in error if a\nfirewall allowed Echo only to the target.\n\n"); } if (!second_echo) fprintf(stdout,"\nWARNING: The second echo packet was not received (possibly dropped), the results\ncould be in error.\n\n"); if (verbose) { fprintf(stdout,"Echo Packets results:\n"); fprintf(stdout," IP ID - 0x%x\n",ip_id); fprintf(stdout," Code - 0x%x\n",code_field); fprintf(stdout," TOS - 0x%x\n",precedence_bits); fprintf(stdout," DF Bit - %d\n",df_bit); fprintf(stdout," Unused bit - %d\n",unused_bit); fprintf(stdout," TTL - %d\n",ttl_reply); fprintf(stdout,"Timestamp results:\n"); if (tstamp_reply) fprintf(stdout," Replied\n"); else fprintf(stdout," No Reply\n"); fprintf(stdout,"Address Mask results:\n"); if (addrmask_reply) fprintf(stdout," Replied\n"); else fprintf(stdout," No Reply\n"); fprintf(stdout,"Info Request results:\n"); if (info_reply) { fprintf(stdout," Replied\n"); fprintf(stdout," TOS - 0x%x\n",precedence_info_reply); } else fprintf(stdout," No Reply\n"); } fprintf(stdout,"%s is ",targetname); if (code_field == 0) /* Windows OS */ { if (ttl_reply < 32) fprintf(stdout,"Windows 95\n"); else if (precedence_bits == 0) fprintf(stdout,"Windows 2000\n"); else { if (tstamp_reply != 0) { if (addrmask_reply == 0) fprintf(stdout,"Windows ME\n"); else fprintf(stdout,"Windows 98/98SE\n"); } else { if (addrmask_reply == 0) fprintf(stdout,"Windows NT SP4 or higher\n"); else fprintf(stdout,"Windows NT SP3 or lower\n"); } } } else /* other OSes */ { if (ip_id == 0) fprintf(stdout,"Linux 2.4.x\n"); else if (precedence_bits == 0) /* Ultrix or Novell */ { if (ttl_reply > 128) fprintf(stdout,"Ultrix\n"); else fprintf(stdout,"Novell\n"); } else { if (unused_bit == 0) { if(df_bit == 0) { if(ttl_reply < 64) fprintf(stdout,"Linux 2.0.x\n"); else fprintf(stdout,"Linux 2.2.x\n"); } else { if(info_reply == 0) fprintf(stdout,"Unknown\n"); else { if(precedence_info_reply == 0) fprintf(stdout,"OpenVMS\n"); else fprintf(stdout,"AIX\n"); } } } else { if(tstamp_reply == 0) fprintf(stdout,"HPUX 11.x\n"); else { if(info_reply == 0) fprintf(stdout,"Sun Solaris\n"); else fprintf(stdout,"HPUX 10.x or older\n"); } } } } } } void caughtsig(int sig) { if (pd) stop_sniffing(); if (verbose) totals(); figure_results(); exit(sig); } /* * usage */ void usage(char *prog) { fprintf(stderr,"USAGE: "); fprintf(stderr,"%s [opts] [-d dev] [-s src] [-t sec] target_system\n\n",prog); fprintf(stderr," opts are h n p r v V\n"); fprintf(stderr," -h this help screen\n"); fprintf(stderr," -n no sending of packets\n"); fprintf(stderr," -p promiscuous receive mode\n"); fprintf(stderr," -r receiving packets only (no sending)\n"); fprintf(stderr," -v verbose\n"); fprintf(stderr," -V version info and exit\n"); fprintf(stderr," -d device to grab local IP or sniff from, default is eth0\n"); fprintf(stderr," -s spoofed source address\n"); fprintf(stderr," -t time in seconds to wait for all replies (default 5)\n"); fprintf(stderr," target_system is hostname or IP address\n"); fprintf(stderr,"\n"); } int main(int argc, char **argv) { char *prog; extern char *optarg; extern int optind; extern int optopt; extern int opterr; char ch; int spoof = 0, receive = 0, promiscuous = 0, no_send = 0, setdev = 0; int i, sent, timeout = 5, icmpenum = 1, class_c = 0, results_counter = 0; pid_t pid; dev = "eth0"; /* default */ verbose = 0; prog = argv[0]; while ((ch = getopt(argc, argv, "hrnpvVd:s:t:")) != EOF) switch(ch) { case 'h': usage(prog); exit(0); case 'r': receive = 1; break; case 'p': promiscuous = 1; break; case 'n': no_send = 1; break; case 'd': dev = optarg; setdev = 1; break; case 's': if (!(src_ip = libnet_name_resolve(optarg, LIBNET_RESOLVE))) { fprintf(stderr,"=== Unable to resolve source host,\n"); fprintf(stderr,"=== try spoofing with an IP address\n"); usage(prog); exit(-1); } spoof = 1; break; case 't': timeout = (int) strtol(optarg, NULL, 10); break; case 'v': verbose = 1; break; case 'V': fprintf(stderr,"ICMPID v%s -- Written by Simple Nomad\n",VER); fprintf(stderr,"http://www.nmrc.org/ -- thegnome@nmrc.org\n\n"); fprintf(stderr,"See http://www.sys-security.com/html/projects/icmp.html\nfor more information.\n\n"); exit(0); default: usage(prog); exit(-1); } argc -= optind; argv += optind; if(getuid()!=0) { fprintf(stderr, "=== You must be root to run %s!\n\n", prog); exit(-1); } /* post arg processing */ if ((setdev) && (spoof)) { fprintf(stderr, "=== You cannot specify a device for a source IP address\n"); fprintf(stderr, "=== and spoof your source IP address at the same time.\n"); usage(prog); exit(-1); } if ((no_send) && (!promiscuous) && (!receive)) { fprintf(stderr,"=== You are not sending and receiving any packets,\n"); fprintf(stderr,"=== so not much is going to happen.\n"); usage(prog); exit(-1); } /* if the receive flag is set, set the no_send flag so we don't send any packets */ if ((receive) && (!no_send)) no_send++; if ((spoof) && (!receive) && (!promiscuous)) { printf("NOTE: This machine will not see responses while spoofing.\n"); timeout = 0; /* no need to wait around for replies */ } if(!spoof) { src_ip = libnet_get_ipaddr(l, dev, ebuf); if ((src_ip == -1) || (src_ip == 0)) { fprintf(stderr, "=== Grabbing address from %s failed,\n",dev); fprintf(stderr, "=== try a different device.\n"); usage(prog); exit(-1); } src_ip = htonl(src_ip); } if ((receive) && (promiscuous)) { fprintf(stderr,"=== Receive mode is for normal receiving of spoofed packets\n"); fprintf(stderr,"=== while Promiscuous mode is for sniffing of spoofed packets\n"); fprintf(stderr,"=== passing nearby. They cannot be used together.\n"); usage(prog); exit(-1); } if (!argv[0] || !strlen(argv[0])) { fprintf(stderr,"=== You must specify a target\n"); usage(prog); exit(-1); } targetname = *argv; dst_ip = libnet_name_resolve(targetname,LIBNET_RESOLVE); /* end post arg processing */ /* seed the randomness */ i = libnet_seed_prand(); if (i == -1) { libnet_error(LIBNET_ERR_WARNING, "unable to properly seed the prng\n"); } /* build our id's to track our 5 packets */ id0 = libnet_get_prand(LIBNET_PR16); id1 = libnet_get_prand(LIBNET_PR16); id2 = libnet_get_prand(LIBNET_PR16); id3 = libnet_get_prand(LIBNET_PR16); id4 = libnet_get_prand(LIBNET_PR16); /* set up timeout*/ signal(SIGALRM, caughtsig); alarm(timeout); /* if sending packets, fork off the process so we can start listening to replies immediately */ pid = fork(); switch(pid) { case -1: /* if not sending we don't care that much about fork failures */ if (!no_send) { fprintf(stderr, "Unable to fork off for sending packets\n"); exit(-1); } break; case 0: if (!no_send) send_os_packets(); break; default: if (!spoof) { receive_packets_normal(); if (verbose) totals(); break; } if (promiscuous) { receive_packets_sniffing(); break; } } exit(0); }