/* $Id$ */ /* Copyright (C) 2005 Nicholas Harbour This program 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, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* This file is part of Tcpxtract, a sniffer that extracts files based on headers by Nick Harbour */ #define _BSD_SOURCE 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sessionlist.h" #include "util.h" #include "confy.h" #include "search.h" #ifndef DEFAULT_CONFIG_FILE #define DEFAULT_CONFIG_FILE "/usr/local/etc/tcpxtract.conf" #endif extern FILE *yyin; /* the lex/yacc input file */ static char *yyinfname = DEFAULT_CONFIG_FILE; /* * remember, statically declared variables are * implicitly initialized to zero. */ static char *progname; /* the name of this program; */ static void got_packet(u_char *, const struct pcap_pkthdr *, const u_char *); pcap_t *handle; /* Sniff handler */ void quit_signal(int); slist_t *sessions; static uintmax_t num_packets; /* the running total of packets */ enum protos { TCP_PROTO = 6, UDP_PROTO = 17 /* anything else that sits on top of ip (like icmp) will be dumped in full */ }; /* Ethernet header */ struct sniff_ethernet { u_char ether_dhost[ETHER_ADDR_LEN]; /* Destination host address */ u_char ether_shost[ETHER_ADDR_LEN]; /* Source host address */ u_short ether_type; /* IP? ARP? RARP? etc */ }; /* IP header */ struct sniff_ip { #if BYTE_ORDER == LITTLE_ENDIAN u_int ip_hl:4, /* header length */ ip_v:4; /* version */ #endif #if BYTE_ORDER == BIG_ENDIAN u_int ip_v:4, /* version */ ip_hl:4; /* header length */ #endif u_char ip_tos; /* type of service */ u_short ip_len; /* total length */ u_short ip_id; /* identification */ u_short ip_off; /* fragment offset field */ #define IP_RF 0x8000 /* reserved fragment flag */ #define IP_DF 0x4000 /* dont fragment flag */ #define IP_MF 0x2000 /* more fragments flag */ #define IP_OFFMASK 0x1fff /* mask for fragmenting bits */ u_char ip_ttl; /* time to live */ u_char ip_p; /* protocol */ u_short ip_sum; /* checksum */ struct in_addr ip_src,ip_dst; /* source and dest address */ }; /* TCP header */ struct sniff_tcp { u_short th_sport; /* source port */ u_short th_dport; /* destination port */ tcp_seq th_seq; /* sequence number */ tcp_seq th_ack; /* acknowledgement number */ #if BYTE_ORDER == LITTLE_ENDIAN u_int th_x2:4, /* (unused) */ th_off:4; /* data offset */ #endif #if BYTE_ORDER == BIG_ENDIAN u_int th_off:4, /* data offset */ th_x2:4; /* (unused) */ #endif u_char th_flags; #define TH_FIN 0x01 #define TH_SYN 0x02 #define TH_RST 0x04 #define TH_PUSH 0x08 #define TH_ACK 0x10 #define TH_URG 0x20 #define TH_ECE 0x40 #define TH_CWR 0x80 #define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR) u_short th_win; /* window */ u_short th_sum; /* checksum */ u_short th_urp; /* urgent pointer */ }; /* UDP header */ struct sniff_udp { u_short uh_sport; /* source port */ u_short uh_dport; /* destination port */ u_short uh_length; /* message length */ u_short uh_sum; /* checksum */ }; static void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet) { /* Define pointers for packet's attributes */ struct sniff_ethernet *ethernet; /* The ethernet header */ struct sniff_ip *ip; /* The IP header */ struct sniff_tcp *tcp; /* The TCP header */ struct sniff_udp *udp; /* The UDP header */ uint8_t *payload; /* The data */ /* And define the size of the structures we're using */ int size_ethernet = sizeof(struct sniff_ethernet); int size_ip; int size_tcp; int size_udp = 8; /* just trust me */ size_t header_size, payload_size; slist_t *session = NULL; connection_t conn; srch_results_t *results; num_packets++; /* -- Define our packet's attributes -- */ /* There is obviously a lot of unused potential here since we only want to dump */ ethernet = (struct sniff_ethernet*)(packet); ip = (struct sniff_ip*)(packet + size_ethernet); size_ip = ip->ip_hl << 2; tcp = (struct sniff_tcp*)(packet + size_ethernet + size_ip); size_tcp = tcp->th_off << 2; udp = (struct sniff_udp*)(packet + size_ethernet + size_ip); /* if it ain't IP, bail, hard */ if (ethernet->ether_type != 0x08) /* I think 0x08 is IP, at least it looks that way */ return; switch (ip->ip_p) { case TCP_PROTO: header_size = size_ethernet + size_ip + size_tcp; break; case UDP_PROTO: header_size = size_ethernet + size_ip + size_udp; break; default: return; /* at this point, I only care about tcp and udp */ } payload_size = header->len - header_size; if (payload_size <= 0) return; payload = (uint8_t *)(packet + header_size); conn.ip_src = ip->ip_src.s_addr; conn.ip_dst = ip->ip_dst.s_addr; memcpy(conn.eth_src, ethernet->ether_shost, ETHER_ADDR_LEN); memcpy(conn.eth_dst, ethernet->ether_dhost, ETHER_ADDR_LEN); conn.port_src = tcp->th_sport; conn.port_dst = tcp->th_dport; if (sessions != NULL) { session = find_session(&sessions, &conn); } if (session == NULL) { session = add_session(&sessions, &conn); assert(session); } session->last_seqnum = tcp->th_seq; session->last_recvd = time(NULL); results = search(srch_machine, &session->srchptr_list, payload, payload_size); extract(&session->extract_list, results, session, payload, payload_size); free_results_list(&results); } static void usage(void) { printf("Usage: %s [OPTIONS] [[-d ] [-f ]]\n" "Valid options include:\n" " --file, -f to specify an input capture file instead of a device\n" " --device, -d to specify an input device (i.e. eth0)\n" " --config, -c use FILE as the config file\n" " --output, -o dump files to DIRECTORY instead of current directory\n" " --version, -v display the version number of this program\n" " --help, -h display this lovely screen\n", progname); exit(1); } int main(int argc, char *argv[]) { int c; char *capfname = NULL; /* Capture file for input */ char *dev; /* Sniffing devise */ char errbuf[PCAP_ERRBUF_SIZE]; /* Error buffer */ struct bpf_program filter; /* hold compiled program */ bpf_u_int32 mask; /* subnet mask */ bpf_u_int32 net; /* ip */ char filter_app[] = ""; progname = strdup(argv[0]); sessions = NULL; srch_machine = NULL; output_prefix = NULL; filenum = 0; if (argc == 1) usage(); while (1) { int option_index = 0; static struct option long_options[] = { {"file", 1, 0, 'f'}, {"device", 1, 0, 'd'}, {"config", 1, 0, 'c'}, {"output", 1, 0, 'o'}, {"version", 0, 0, 'v'}, {"help", 0, 0, 'h'}, {0, 0, 0, 0} }; c = getopt_long(argc, argv, "f:d:o:c:hv", long_options, &option_index); if (c == -1) break; switch (c) { case 'f': capfname = strdup(optarg); break; case 'd': dev = strdup(optarg); break; case 'c': yyinfname = strdup(optarg); break; case 'o': if (optarg[strlen(optarg) - 1] != '/') { output_prefix = emalloc(strlen(optarg) + 1); strcpy(output_prefix, optarg); output_prefix[strlen(optarg)] = '/'; output_prefix[strlen(optarg) + 1] = '\0'; } else output_prefix = strdup(optarg); break; case 'h': usage(); break; case 'v': printf("%s v%s\n", PACKAGE, VERSION); exit(1); break; default: printf("Barf: '%c'\n", c); exit(0); } } yyin = fopen(yyinfname, "r"); if (yyin == NULL) { perror(yyinfname); exit(0); } yyparse(); if (optind < argc) { if (!dev) dev = argv[optind]; } if (!dev && !capfname) dev = pcap_lookupdev(errbuf); if (capfname) { handle = pcap_open_offline(capfname, errbuf); if (handle == NULL) { fprintf(stderr, "Couldn't open file %s: %s\n", capfname, errbuf); return(2); } } else { if (dev == NULL) { fprintf(stderr, "Couldn't find default device: %s\n", errbuf); return(2); } /* Find the properties for the device */ if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) { fprintf(stderr, "Can't get netmask for device %s\n", dev); net = 0; mask = 0; } /* Open the session in promiscuous mode */ handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf); if (handle == NULL) { fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf); return(2); } } signal(SIGTERM, quit_signal); signal(SIGQUIT, quit_signal); signal(SIGINT, quit_signal); /* Compile and apply the filter */ if (pcap_compile(handle, &filter, filter_app, 0, net) == -1) { fprintf(stderr, "Couldn't parse filter %s: %s\n", filter, pcap_geterr(handle)); return(2); } if (pcap_setfilter(handle, &filter) == -1) { fprintf(stderr, "Couldn't install filter %s: %s\n", filter, pcap_geterr(handle)); return(2); } for (;;) { int ncapd = pcap_dispatch(handle, 100, got_packet, NULL); sweep_sessions(&sessions); if (ncapd < 0) error(pcap_geterr(handle)); else if (ncapd == 0 && capfname != NULL) break; } pcap_close(handle); return(0); } void quit_signal(int sig) { pcap_close(handle); exit(1); }