/******************************************************************************
 * T T L S C A N
 *
 * ttlscan is a libnet/libpcap/libdnet based program that sends a TCP SYN packet
 * to each port of the host given via the command line. The answer is
 * sniffed of the wire.
 * I use it to detect hosts that fake services by forwarding packets to
 * another host (behind a firewall).
 * By reading header entries like the TTL, window size and IPID you might be
 * able to see the OS running on the host behind the firewall.
 *
 * (c) 2002 by Hendrik Scholz <hendrik@scholz.net> http://www.raisdorf.net/
 *
 * $Id: ttlscan.c,v 1.7 2002/10/22 18:38:17 hscholz Exp $
 *
 ******************************************************************************
 * C O N F I G U R A T I O N
 *
 * most variables can be modified during run-time. If you want to change
 * the defaults edit config.h
 *
 ******************************************************************************
 * C O M P I L E
 *
 * Use the Makefile to compile ttlscan. As of now it works on FreeBSD and
 * NetBSD. It should be easy to add other systems.
 *
 ******************************************************************************
 * G P L   B L U R B
 *
 * ttlscan is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * ttlscan 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.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ttlscan; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *****************************************************************************/

#include "config.h"

#include "src/error.c"
#include "src/usage.c"
#include "src/setoffset.c"

#define CVS_VERSION "$Id: ttlscan.c,v 1.7 2002/10/22 18:38:17 hscholz Exp $"

int main (int argc, char **argv)
{
    extern int debug; // the default debug level is defined in config.h
    extern int timeout_secs;
    extern int timeout_msecs;
    extern int waittime;

    int verbose = 0; // scan output verbosity
    // getopt variables
    extern char *optarg;
    extern int optind;
    extern int optopt;
    extern int opterr;
    extern int optreset;
    int op;

    unsigned int cli_lowport=1;
    unsigned int cli_hiport=65535;
    unsigned int cli_singleport=0;

    // source
    unsigned long source;
    unsigned int source_port = 24223;
    
    // destination
    unsigned long dest;
    unsigned int dest_port;
    
    // network stuff
    char device[MAXBUF];
    pcap_t *pcap;
    struct bpf_program bpffilter;
    bpf_u_int32 localnet, netmask;
    char pcaperr[PCAP_ERRBUF_SIZE];
    char filter[MAXBUF];
    unsigned char *outpacket;
    int outpacket_size;
    int sentsize;
    int network; // libnet send socket
    char libneterr[LIBNET_ERRBUF_SIZE];
    
    // sniffing stuff
    int pcap_fd,ret;
    struct pcap_pkthdr packet_hdr; // pcap header of received packet
    int packet_len;
    fd_set sfd;
    struct timeval timeout;
    struct libnet_ip_hdr *iphdr;
    struct libnet_tcp_hdr *tcphdr; // only for verbose mode
    extern int offset; // IP header offset is defined in config.h
    u_char *packet;

    // libdnet stuff
    intf_t *intf;
    struct intf_entry *entry;
    struct addr addr;
    char dnet_buf[1024];
    rand_t *random;

    // misc
    int i; // 'nuff said :)

    /*
     * parse command line parameters
     *
     */

    while ((op = getopt(argc, argv, "L:H:s:t:i:hdvV")) != EOF)
    {
        switch (op)
        {
            case 'h':
                // user wants usage()
                usage(0);
                break;
            case 'd':
                // increase debug level
                debug++;
                break;
            case 'v':
                // verbose mode
                verbose++;
                break;
            case 's':
                // single port definition
                if (atoi(optarg) > 0 && atoi(optarg) < 65536)
                    cli_singleport = atoi(optarg);
                else
                    error(ERR_FATAL, "port must be between 1-65535");
                break;
            case 'L':
                // lowport definition
                if (atoi(optarg) > 0 && atoi(optarg) < 65536)
                    cli_lowport = atoi(optarg);
                else
                    error(ERR_FATAL, "port must be between 1-65535");
                break;
            case 'H':
                // hiport definition
                if (atoi(optarg) > 0 && atoi(optarg) < 65536)
                    cli_hiport = atoi(optarg);
                else
                    error(ERR_FATAL, "port must be between 1-65535");
                break;
            case 't':
                // timeout settings
                if (atoi(optarg) > 0)
                    timeout_secs=atoi(optarg)/1000;
                    timeout_msecs=atoi(optarg)-(timeout_secs*1000);
                    error(ERR_DEBUG, "setting timeout to %d milliseconds",
                                timeout_secs*1000+timeout_msecs);
                break;
            case 'i':
                // pause between tests
                // remember: !root users can flood the network with this
                if (atoi(optarg) > 0)
                    waittime = atoi(optarg);
                break;
            case 'V':
                // version info
                printf("ttlscan version %s\n%s\n", VERSION, CVS_VERSION);
                _exit(0);
                break;
            default:
                // got a wrong parameter
                usage(1);
        }
    }

    /*
     * Check for target hostname
     *
     */

    if (argc - optind != 1)
        // oops, no more hostname here ? or two ? or three ?
        usage(1);

    /*
     * check if lowport number is really lower than hiport, else we exit
     */
    if (cli_hiport < cli_lowport)
        error(ERR_FATAL, "high port is lower that low port!");

    printf("running ttlscan %s on %s (%s scan) ...\n", VERSION, argv[argc-1],
        (cli_singleport!=0)? "single port" : 
        ((cli_lowport != 1 && cli_hiport != 65535) ? "custom" : "default"));

    /*
     * Check for root privileges
     *
     * We need these for libnet and libpcap and drop privileges when not
     * longer needed
     *
     */

    if (geteuid() != 0)
    {
        error(ERR_FATAL,
            "Sorry, can't do that Dave (need root privileges)!");
    }

    // lookup destination address
    if ((dest = libnet_name_resolve(argv[argc-1], 1)) == 0xFFFFFFFF)
        error(ERR_FATAL, "cannot resolve destination address");

    entry=(struct intf_entry*) dnet_buf;
    memset(entry,0,sizeof(*entry));
    entry->intf_len=sizeof(dnet_buf);

    if ((intf=intf_open()) == NULL)
        printf("error\n");

    // argv[argc-1] is our target host/ip
    if (addr_aton(argv[argc-1],&addr) < 0)
        error(ERR_FATAL, "cannot convert supplied host/ip");

    error(ERR_DEBUG, "target: %s", addr_ntoa(&addr));

    if (intf_get_dst(intf,entry,&addr) == -1)
        error(ERR_FATAL, "intf_get_dst() failed");

    error(ERR_DEBUG,"interface: %s", entry->intf_name);
    // copy the device name to device
    snprintf(device, sizeof(device), entry->intf_name);

    intf_close(intf);

    // determine the source IP we should use using libnet_get_ipaddr()
    source = htonl(libnet_get_ipaddr(0, device, libneterr));

    // open raw socket
    network = libnet_open_raw_sock(IPPROTO_RAW);
    if (network == -1)
        error(ERR_FATAL, "cannot open raw socket for libnet writing");

    // drop root privileges
    setuid(getuid());
    // if we have root priveleges but are not Mr. Charlie Root we exit
    if ((getuid() != geteuid()) || ((getuid() == 0) && getuid() != 0))
         error(ERR_FATAL, "cannot drop privileges");

    // no we know the device we try to get the packet offset in the captured
    // packet
    if (setoffset(entry, pcap) < 0)
        error(ERR_FATAL, "could not determine correct capture offset");

    localnet=0; netmask=0;

    /*
     * initialize the random number generator based on libdnet
     */

    random = rand_open();
   
    /*
     * the main scanning loop
     * if -s was used on the command line we loop only once
     * if -l or|and -h are given we use them
     */ 

    if (cli_singleport !=0)
    {
        cli_lowport = cli_singleport;
        cli_hiport  = cli_singleport;
        error(ERR_DEBUGV, "using single port option");
    }

    for (dest_port=cli_lowport;dest_port<=cli_hiport;dest_port++)
    {
        if ((pcap = pcap_open_live(device,1500,0,100,pcaperr)) == NULL)
            error(ERR_FATAL, "cannot open pcap device %s: %s", device, pcaperr);

        if (pcap_lookupnet(device, &localnet, &netmask, pcaperr) == -1)
            error(ERR_FATAL, "pcap_lookupnet() failed: ", pcaperr);

        outpacket_size = LIBNET_IP_H + LIBNET_TCP_H;
        if (libnet_init_packet(outpacket_size, &outpacket) != 1)
            error(ERR_FATAL, "cannot reserve memory for outgoing packet");
    
        // build ip part
        libnet_build_ip(
            LIBNET_TCP_H,   // tcp packet
            0,              // Type of service
            rand_uint16(random),    // IPID
            0,              // DF bit
            255,            // ttl
            IPPROTO_TCP,    // protocol
            source,         // source address
            dest,           // dest. address
            NULL,           // payload pointer
            0,              // payload size
            outpacket);     // output buffer 
        
        libnet_build_tcp(
            source_port+dest_port,    // source port
            dest_port,      // dest. port
            0,              // seq. number
            0,              // ack. number
            TH_SYN,         // this is a SYN packet
            0,              // window size
            0,              // urgent pointer
            NULL,           // payload pointer
            0,              // payload size
            outpacket+LIBNET_IP_H); // output buffer
    
        // generate checksum
        libnet_do_checksum(outpacket, IPPROTO_TCP, LIBNET_TCP_H);

        // update the pcap filter to match this port
        snprintf(filter, MAXBUF-1, "tcp and src host %s and src port %d "
                "and dst port %d", argv[argc-1], dest_port, source_port+dest_port);

        error(ERR_DEBUGV, "pcap filter: %s", filter);
    
        if (pcap_compile(pcap, &bpffilter, filter,0, netmask) == -1)
            error(ERR_FATAL, "pcap_compile() failed:", pcaperr);
        if (pcap_setfilter(pcap, &bpffilter) == -1)
            error(ERR_FATAL, "pcap_setfilter() failed:", pcaperr);

        // sniff
        pcap_fd = pcap_fileno(pcap);
        FD_ZERO(&sfd);
        FD_SET(pcap_fd, &sfd);

        // define timeout
        timeout.tv_sec = timeout_secs;
        timeout.tv_usec = timeout_msecs/1000;

        // send the packet
        sentsize = libnet_write_ip(network, outpacket, outpacket_size);
        if (sentsize < outpacket_size)
            error(ERR_FATAL, "libnet_write_ip(): Attempted to write %d Bytes, but only %d Bytes written", outpacket_size, sentsize);
        else
            error(ERR_DEBUGV, "injected packet (dest port: %d)", dest_port);

        ret = -1;
        if ((ret = select(pcap_fd+1,&sfd,NULL,NULL, &timeout)) < 0)
            error(ERR_FATAL,"select() error");
        else if (ret == 0)
            error(ERR_WARNING, "select() timeout");
        else
        {
            // analyse the packet
            if (!(packet = (u_char *) pcap_next(pcap, &packet_hdr)))
            {
                // no packet
                printf("select returns. %d\n", ret);
                error(ERR_FATAL,"could not get pointer for received packet");
            }
            // we capture the packet including the ethernet header
            // as you can see this works only on ethernet *d'oh*
            packet += offset; // header offset
            packet_len = packet_hdr.caplen - offset; // size of the caputured
                                                     // packet
            error(ERR_DEBUG,"received packet (size: %d Bytes)", packet_len);
            
            // we want ip packets that need to have at least a complete
            // IP header and a TCP or ICMP header
            if ((packet_len < (LIBNET_IP_H + LIBNET_TCP_H)) && 
               (packet_len < (LIBNET_IP_H + LIBNET_ICMP_H)))
            {
                error(ERR_WARNING,"received packet doesn't contain full headers");
                printf("packet len: %d\nneeded: %d or %d\n", packet_len, (LIBNET_IP_H + LIBNET_TCP_H), (LIBNET_IP_H + LIBNET_ICMP_H));
                continue;
            }
           
            // get the header  
            iphdr = (struct libnet_ip_hdr *) packet; 
            
            if (iphdr->ip_v != 4)
            {
                error(ERR_WARNING, "ignoring this non-IPv4 packet");
                continue;
            }
            // check if this packet was really for us
            if ((iphdr->ip_dst.s_addr != source) ||
                (iphdr->ip_src.s_addr != dest))
            {
                error(ERR_WARNING, "captured packet was not for us");
                continue;
            }

            /*
             * this packet seems to be the one we were searching for
             * we just return the ttl or more if the user wants it
             *
             */    
           
            if (verbose != 0)
            { 
                // verbose output
                packet +=LIBNET_IP_H;
                tcphdr = (struct libnet_tcp_hdr*) packet;

                printf("port=%d ttl=%d\n", dest_port, iphdr->ip_ttl);
                printf("IPID: %u, window size: %u\n", 
                    ntohs(iphdr->ip_id), ntohs(tcphdr->th_win));
                printf("TCP flags: ");
                if (tcphdr->th_flags & TH_FIN)
                    printf("FIN ");
                if (tcphdr->th_flags & TH_SYN)
                    printf("SYN ");
                if (tcphdr->th_flags & TH_RST)
                    printf("RST ");
                if (tcphdr->th_flags & TH_PUSH)
                    printf("PUSH ");
                if (tcphdr->th_flags & TH_ACK)
                    printf("ACK ");
                if (tcphdr->th_flags & TH_URG)
                    printf("URG ");
                printf("\n");
            }
            else
                printf("port=%d ttl=%d\n", dest_port, iphdr->ip_ttl);
        }

    // we should wait here if -i was given (or the default time was set)
    if (waittime > 0)
        usleep(waittime*1000);

    pcap_close(pcap);
    } // end port loop

    // close random number generator
    rand_close(random);
    
    return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1