#include /* udp(7) */ #include #include #include /* poll(2) */ #include /* socket(2) */ #include #include /* close(2) */ #include /* perror(3) */ #include /* open(2) */ #include #include /* errno(3) */ #include /* htons(3) */ #include /* getaddrinfo(3) */ #include /* atoi(3) */ #include /* strchr(3) */ #include /* clock_gettime(2) */ #include /* signal(2) */ /* #include */ /* prctl(2) */ /* #include */ /* waitpid(2) */ #include /* poll(2) */ #include "domain2name.c" #include "host.c" #define MIN(a,b) ((a) < (b) ? (a) : (b)) #define MAX(a,b) ((a) > (b) ? (a) : (b)) #define MAXDOMAIN 255 #define QUERYDOMAIN "http://sijanec.eu/link?r=.sijanec.eu." #define EXPECTEDA 93.103.235.126 #define XSTR(x) #x #define STR(x) XSTR(x) #define HELP "find recursive DNS resolvers on IPv4 networks\n" \ "%s [-a ip] [-b ip] [-d domain] [-eh] [-o file] [-p port] [-t μs] [-w μs] net1 [net2 ...]\n" \ " -a Specify the IPv4 of the -d domain to be used instead of getaddrinfo(3).\n" \ " -b Bind on a specific interface, defined by IPv4. Default is any interface.\n" \ " -d Specify the domain name to be used in queries that has a single A record.\n" \ " -e Exclude sent packets from -o PCAP output (they're all the same).\n" \ " -h Show this help and exit.\n" \ " -o Output PCAP to filename. Any existing file is truncated. No IP/UDP checksums.\n" \ " -p Set the source port number to use instead of a dynamically asigned one.\n" \ " -t Number of microseconds to wait between sent packets. (default 1000 - 64 KB/s)\n" \ " -w Finish after μs after recv'd last packet when done sending. (default 1000000)\n" \ "Network addresses are optionally followed by slash and netmask, otherwise networks are\n" \ "understood as single host addresses. Both network names and netmasks can be domains to\n" \ "be looked up or IP dot-notation addresses. Mask can also be a bit prefix (/32 default).\n" \ "When scanning the Internet please make sure that you specify your own domain instead\n" \ "of the default one (dnsfind.sijanec.eu).\n" \ "It would take a day to scan the entire address space with the default timings.\n" /* DNS PACKET: HEADER QUESTION ANSWER AUTHORITY ADDITIONAL datatracker.ietf.org/doc/html/rfc1035 DEFINITIONS: (those appear somewhere in the packet, packet does not start with definitions!) LABLEN 8 bits: first two bits zero, then 6 bits length of label POINTER 16 bits: first two bits one, then 14 bits as offset from first byte of packet STRING a single byte for defining length (0-256 - all eight bits) and then string of chars DOMAIN i.) one or more LABLEN followed by ASCII label (no dots) end with either LABLEN 0 or a POINTER that points to some LABLEN somewhere else in the packet -OR-: ii.) a POINTER that points to some LABLEN somewhere else in the packet HEADER: 12 bytes XID 16 bits: random string to be matched in response to prevent cache poisoning / QR 1 bit : what type is this packet? 0 quest 1 response | OPCODE 4 bits: type of query 0 query 1 iquer 2 sstat 3-15 reserved 1 byte AA 1 bit : is response authoritative? 0 no 1 yes | TC 1 bit : was response truncated? 0 no 1 yes \ RD 1 bit : does query desire recursion? 0 no 1 yes / RA 1 bit : does response server recurse? 0 no 1 yes 1 byte Z 3 bits: reserved for future 0 only option \ RCODE 4 bits: error condition 0 ok 1 fmter 2 srvfa 3 nxdom 4 N/I 5 forbidden QDCOUNT 16 bits: number of questions ANCOUNT 16 bits: number of answers NSCOUNT 16 bits: authority section (where to ask for actual response - NS RECORDS) ARCOUNT 16 bits: additional section (glue records) QUESTION: QNAME DOMAIN QTYPE 16 bits: 1 A 2 NS 5 CNAME 6 SOA 10 NULL 12 PTR 13 HINFO 15 MX 16 TXT ... QCLASS 16 bits: 1 INTERNET 2 CSNET (obsolete) 3 CHAOS 4 HESIOD 255 ANY ... ANSWER: NAME DOMAIN TYPE same as QTYPE CLASS same description as QCLASS, except class 255 ANY is not allowed here TTL 32 bits: unsigned intager of seconds. for this period the record is valid. RDLEN 16 bits: length of RDATA field - this is a number 0-65536, no two zero bits RDATA A: 4 bytes IP address NS: DOMAIN CNAME: DOMAIN SOA: NAME-ns1 NAME-email 32b-serial 32b-refresh 32b-retry 32b-expire 32b-nxdomainttl NULL: any data up to RDLEN PTR: DOMAIN HINFO: STRING-CPU, STRING-OS MX: 16 bit preference, NAME-like domain TXT: one or more STRING */ /* PCAP file format: GLOBALHEADER PACKETHEADER PACKETDATA PACKETHEADER2 PACKETDATA2 ... GLOBAL HEADER: 24 bytes MAGIC 32 bits: 0xA1B2C3D4 timestamp is s and micros 0xA1B23C4D timestamp is s and nanos MAJOR 16 bits: version 2 https://tools.ietf.org/id/draft-gharris-opsawg-pcap-00.html MINOR 16 bits: version 4 /-----------------------------------------------\ RESERV1 32 bits: unused and set to 0 | //en.wikipedia.org/wiki/Frame_check_sequence | RESERV2 32 bits: unused and set to 0 \--------------------------------------------\ | SNAPLEN 32 bits: larger or equal to size of largest capture of a single packet | FCS 4 bits: if last bit is 1, first 3 tell nr of bytes of FCS appended to every packet LINKTYP 28 bits: pkt type //tcpdump.org/linktypes.html 101 IPv4/v6 1 ether PACKET HEADER: 16 bytes SECONDS 32 bits: UNIX timestamp SUBSECS 32 bits: nanoseconds elapsed since the second, can also be microseconds - see MAGIC CAPTURE 32 bits: number of bytes captured from the packet following the header ORIGLEN 32 bits: number of bytes of the original packet size (can be more than CAPTURE) */ /* IPv4 PACKET: HEADER DATA https://datatracker.ietf.org/doc/html/rfc791 HEADER: /-- VERSION 4 bits: 4 IPv4 6 IPv6 | T HEADLEN 4 bits: >= 5. Header size 32 bit words (header is padded), so it points to data. |w SRVTYPE 8 bits: 3bPrecedence 1bLowDelay 1bHighThroughput 1bHighReliability 2bReserved |e Prec.: 0routine 1prio 2immediate 3flash 4flashoverride 5critic 110inetctrl 111netctrl |n LENGTH 16 bits: length including header and data. every host must accept at least 576. |t IDENTIF 16 bits: not so unique ID per src-dest persisted across fragmentations for reassembly |y FLAGS 3 bits: bit 0 (1. bit): evil bit, bit 1: don't fragment, bit 2: more fragments | B FOFFSET 13 bits: where in complete datagram this fragment belongs in 64 bit words-first has 0 |y TTL 8 bits: every router decreases by one, when zero, packet is destroyed |t PROTO 8 bits: datatracker.ietf.org/doc/html/rfc790#page-6 1 ICMP 6 TCP 17 UDP |e CHCKSUM 16 bits: 16 bit one's complement of the one's complement sum of 16b words in header |s SRCADDR 32 bits: \-- DSTADDR 32 bits: OPTIONS variable: depending on type, it may be single byte-type or byte-type, byte-len, data. Option type byte: 1b-copy to fragmented headers 2b-option class 5b-option number Option classes: 0 control 1 reserved 2 debugging 3 reserved Option byte zero denotes an end of options, NOOP is option number 1 PADDING variable: zero bytes ensuring header is aligned into 32 bit words */ /* UDP PACKET: HEADER (8 bytes) DATA https://www.ietf.org/rfc/rfc768.txt SRCPORT 16 bits DSTPORT 16 bits LENGTH 16 bits: size of packet including header in bytes+8 CHCKSUM 16 bits: same algo as IP, data: pseudoheader (srcip dstip 0x0011 LENGTH) header data */ #define MICROSECOND 0xA1B2C3D4 #define NANOSECOND 0xA1B23C4D #define PCAPMAJ 2 #define PCAPMIN 4 #define ETHERNET = 1, #define IP 101 struct pcap_global { uint32_t subsecond __attribute__((packed)); uint16_t major __attribute__((packed)); uint16_t minor __attribute__((packed)); uint32_t reserved[2] __attribute__((packed)); uint32_t snaplen __attribute__((packed)); uint32_t linktype __attribute__((packed)); /* FCS in included here for order */ } __attribute__((packed)); struct pcap_packet { uint32_t seconds __attribute__((packed)); uint32_t subseconds __attribute__((packed)); uint32_t capture_length __attribute__((packed)); uint32_t original_length __attribute__((packed)); } __attribute__((packed)); #define LOW_DELAY (1 << 4) #define HIGH_THROUGHPUT (1 << 3) #define HIGH_RELIABILITY (1 << 2) #define ROUTINE (0 << 5) #define PRIORITY (1 << 5) #define IMMEDIATE (1 << 6) #define FLASH (PRIORITY | IMMEDIATE) #define FLASH_OVERRIDE (1 << 7) #define CRITICAL (FLASH_OVERRIDE | PRIORITY) #define INETCTRL (FLASH_OVERRIDE | IMMEDIATE) #define NETCTRL (FLASH_OVERRIDE | FLASH) #define HEADLENOR (1 << 6) /* always bitwiseOR the headlen with this to apply the version number */ #define EVIL (1 << 15) #define DF (1 << 14) #define MF (1 << 13) #define ICMP 1 #define TCP 6 #define UDP 17 struct ip { uint8_t headlen; /* length of header in 32 bit words (min 5, max 15 = 60B). see HEADLENOR. */ uint8_t srvtype; /* OR here: LOW_DELAY, HIGH_THROUGHPUT, HIGH_RELIABILITY, ROUTINE, ... */ uint16_t length __attribute__((packed)); /* header + data in 8 bit words */ uint16_t identifier __attribute__((packed)); uint16_t foffset /* 64b wrds */ __attribute__((packed)); /* or any flags: EVIL, DF, MF */ uint8_t ttl; /* ignored for uint8_t */ uint8_t protocol; /* ignored for uint8_t */ uint16_t checksum; /* ignoref for uint8_t */ struct in_addr src __attribute__((packed)); struct in_addr dst __attribute__((packed)); /* ----------- 20 bytes */ char options[]; /* ignored for char[] */ } __attribute__((packed)); enum type { A = 1, Ns, Md, Cname = 5, /* we skip the quite obsolete Mf record, luckily Mf (more fragments) is also 4 */ Soa, Mb, Mg, Mr, Null, Wks, Ptr, Hinfo, Minfo, Mx, Txt }; enum class { In = 1, Cs, Ch, He, Any = 255 }; #define RESPONSE (1 << 15) /* :FLAGS */ #define QUESTION (0 << 15) #define QUERY (0 << 11) #define IQUERY (1 << 11) #define STATUS (1 << 12) #define AA (1 << 10) #define TC (1 << 9) #define RD (1 << 8) #define RA (1 << 7) #define SUCCESS (0 << 0) #define FORMAT_ERROR (1 << 0) #define SERVFAIL (1 << 1) #define NXDOMAIN (FORMAT_ERROR | SERVFAIL) #define NI (1 << 2) #define FORBIDDEN (NXDOMAIN | FORMAT_ERROR) struct header { uint16_t xid __attribute__((packed)); uint16_t flags __attribute__((packed)); uint16_t qdcount __attribute__((packed)); uint16_t ancount __attribute__((packed)); uint16_t nscount __attribute__((packed)); uint16_t arcount __attribute__((packed)); char data[]; /* ignored for char[] */ } __attribute__((packed)); struct rr { /* name is omitted, first byte of struct is first byte of type */ uint16_t type __attribute__((packed)); uint16_t class __attribute__((packed)); uint32_t ttl __attribute__((packed)); uint16_t len __attribute__((packed)); char data[]; /* ignored for char[] */ } __attribute__((packed)); struct question { uint16_t type __attribute__((packed)); uint16_t class __attribute__((packed)); } __attribute__((packed)); int logudp (int o /* fd */, struct sockaddr_in s, struct sockaddr_in d, char * u, size_t l /* d */) { struct timespec t; if (clock_gettime(CLOCK_REALTIME, &t) == -1) { perror("clock_gettime(CLOCK_REALTIME, &t)"); return -2; } struct pcap_packet p = { .seconds = t.tv_sec, .subseconds = t.tv_nsec, .capture_length = sizeof(struct ip) + l + 4*2, .original_length = sizeof(struct ip) + l + 4*2 }; struct ip i = { .headlen = 5 | HEADLENOR, .srvtype = ROUTINE, .length = htons(8+l+sizeof i), .identifier = 0x6969, .foffset = 0, .ttl = 69, .protocol = UDP, .checksum = 0, /* wireshark does not validate, at least not on debian 11 */ .src = s.sin_addr, .dst = d.sin_addr }; #define LOGUDP_L (sizeof p + sizeof i + 4*2 + l) char * c, * b = alloca(LOGUDP_L); /* to do in one write (thread safe) */ char * n = "\0"; /* as per udp rfc when no checksum was calculated (-: */ uint16_t v = htons(l+8); c = (char *) memcpy(b, &p, sizeof p) + sizeof p; c = (char *) memcpy(c, &i, sizeof i) + sizeof i; c = (char *) memcpy(c, &s.sin_port, 2) + 2; c = (char *) memcpy(c, &d.sin_port, 2) + 2; c = (char *) memcpy(c, &v, 2) + 2; c = (char *) memcpy(c, &n, 2) + 2; c = (char *) memcpy(c, u, l) + l; if (write(o, b, LOGUDP_L) == -1) { /* atomic and thread safe, as per posix */ perror("write(" STR(LOGUDP_L) ")"); return -3; } return LOGUDP_L; } struct in_addr parse_a (const char * u, int s /* buffer size of u */, const char * d, int l, int n) { struct in_addr r = { /* this uses heap and does not fix heap if realloc failed. */ .s_addr = 0 /* returns 0.0.0.0 in case of error or if no more results for n. */ }; int y = normalizedomain_len(d, l); if (y < 0) return r; char * ž = alloca(y); if (normalizedomain(ž, d, l) < 0) return r; const struct header * h = (const struct header *) u; int q = ntohs(h->qdcount); int a = ntohs(h->ancount+h->nscount+h->arcount); char * o = NULL; const char * c = u+sizeof(struct header); while (q--) { int č = name2domain_len(u, s, c); if (č < 0) goto r; if (!(o = realloc(o, č))) goto r; if (!(c = name2domain(o, u, s, c))) goto r; struct question * ć = (struct question *) (c+1); if ((c += sizeof(*ć)+1) >= u+512) goto r; } /* we skip over any questions */ while (a--) { int č = name2domain_len(u, s, c); if (č < 0) goto r; if (!(o = realloc(o, č))) goto r; if (!(c = name2domain(o, u, s, c))) goto r; struct rr * ć = (struct rr *) (c+1); if (c+sizeof(*ć) >= u+512) goto r; if ((c += ntohs(ć->len)+sizeof(*ć)+1) >= u+512) goto r; if (y != č) continue; if (memcmp(o, ž, y)) continue; if (ntohs(ć->type) != A) continue; if (ntohs(ć->class) != In) continue; if (ntohs(ć->len) != 4) /* this is actually a malformed packet, */ continue; /* A resource record must be four bytes */ if (n--) continue; r.s_addr = *(in_addr_t *) (ć->data); goto r; } /* we scroll over answers now and treat all three types the same, waiting for our A */ r: free(o); return r; } /* returns nth IP of A record for domain d of length l in response packet u or 0.0.0.0 for err */ int finish = 0; void handler () { if (++finish >= 3) exit(1); } int main (int argc, char ** argv) { int r = 2; struct in_addr a = { .s_addr = 0 }; struct sockaddr_in b = { .sin_family = AF_INET, .sin_port = 0, .sin_addr = { .s_addr = INADDR_ANY } }; char * d = "dnsfind.sijanec.eu"; int s = -1; /* socket */ int o = -1; /* output file */ struct in_net * n; /* networks */ int l; /* count of networks */ int i = 0; /* network index */ int j = -1; /* host in network index */ int t = 1000; int w = 1000000; int e = 0; /* whether to exclude sent packets in PCAP - they're all the same */ struct in_net h; /* host to scan is .addr, h as struct in_net is returned from host() */ signal(SIGINT, handler); signal(SIGTERM, handler); while (1) { switch (getopt(argc, argv, ":a:b:d:eho:p:t:w:")) { case 'a': inet_aton(optarg, &a); break; case 'b': inet_aton(optarg, &b.sin_addr); break; case 'd': d = optarg; break; case 'e': e++; break; case 'h': printf(HELP, argv[0]); r = 0; goto r; case 'o': if ((o = open(optarg, O_CREAT | O_TRUNC | O_WRONLY, 00664)) == -1) { perror("open(optarg, O_CREAT | O_TRUNC | O_WRONLY)"); r = 1; goto r; } struct pcap_global g = { .subsecond = NANOSECOND, .major = PCAPMAJ, .minor = PCAPMIN, .reserved[0] = 0, .reserved[1] = 0, .snaplen = 65535, .linktype = IP }; if (write(o, &g, sizeof g) == -1) { perror("write(o, &g, sizeof g)"); r = 2; goto r; } break; case 'p': b.sin_port = htons(atoi(optarg)); break; case 't': t = atoi(optarg); break; case 'w': w = atoi(optarg); break; case -1: if (!(l = argc-optind)) { fprintf(stderr, "specify targets to scan :: " HELP, argv[0]); r = 3; goto r; } n = alloca(l*sizeof *n); for (int i = optind; i < argc; i++) { int w = i-optind; n[w] = str2net(argv[i]); } goto o; case '?': fprintf(stderr, "unknown option :: " HELP, argv[0]); r = 4; goto r; case ':': fprintf(stderr, "missing option argument :: " HELP, argv[0]); r = 5; goto r; default: r = 6; goto r; } } o: if (!a.s_addr) { int e; fprintf(stderr, "resolving %s ... ", d); if ((e = resolve(d, &a.s_addr))) { fprintf(stderr, "failed: %s\n", gai_strerror(e)); r = 7; goto r; } fprintf(stderr, " %s\n", inet_ntoa(a)); } if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) { perror("socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)"); r = 8; goto r; } if (bind(s, (struct sockaddr *) &b, sizeof(struct sockaddr))) { perror("bind(s, (struct sokaddr *) &b, sizeof(struct sockaddr))"); r = 9; goto r; } struct timespec lp = { /* last packet */ .tv_sec = 0 }; while (!finish) { if ((h = host(n[i], ++j)).mask.s_addr != INADDR_BROADCAST) { if (++i >= l) { fprintf(stderr, "finished sending, waiting for last replies\n"); if (clock_gettime(CLOCK_MONOTONIC, &lp) == -1) { perror("clock_gettime(CLOCK_MONOTONIC, &z)"); r = 10; goto r; } goto i; } else h = host(n[i], (j = 0)); } struct sockaddr_in m = { /* see, I don't know much about scopes in C and I'm */ .sin_family = AF_INET, /* intentionally excercising them for the cost of */ .sin_port = htons(53), /* code unreadability. in this scope I defined h */ .sin_addr = h.addr /* as struct header, in parent scope it was in_net, */ }; /* and I used h as in_net in this scope as well, */ struct header h = { /* but h as header is declared after that use (; */ .xid = 0x6969, /* oh no, cache poisoning, whatever'll I do */ .flags = htons(QUESTION | QUERY | RD), .qdcount = htons(1), .ancount = 0, .nscount = 0, .arcount = 0 }; int v = domain2name_len(d, strlen(d)); #define L (sizeof h + v + 2*2) if (v < 0) { r = 11; goto r; } if (L > 65535) { /* pebkac, there'll be no error message here */ r = 12; goto r; } char u[65535]; /* max udp packet, alloca in a loop would be bad (not scope based) */ char * c; uint16_t y = htons(A); uint16_t k = htons(In); c = (char *) memcpy(u, &h, sizeof h) + sizeof h; c += domain2name(c, d, strlen(d)); c = (char *) memcpy(c, &y, 2) + 2; c = (char *) memcpy(c, &k, 2) + 2; int ž; if (!e && o != -1 && (ž = logudp(o, b, m, u, L)) < -1) { fprintf(stderr, "logudp(o, b, m, u, L) == %d\n", ž); r = 13; goto r; } if (sendto(s, u, L, 0, (struct sockaddr *) &m, sizeof(struct sockaddr)) == -1) { perror("sendto(s, u, L, 0, (struct sockaddr *) &m, sizeof(struct sockaddr))"); r = 14; goto r; } struct timespec z; i: if (clock_gettime(CLOCK_MONOTONIC, &z) == -1) { perror("clock_gettime(CLOCK_MONOTONIC, &z)"); r = 15; goto r; } if ((z.tv_sec*1000000 + z.tv_nsec/1000) - (lp.tv_sec*1000000 + lp.tv_nsec/1000) > w && lp.tv_sec) { fprintf(stderr, "no more packets were received for -w microseconds. done.\n"); r = 0; goto r; } struct pollfd q = { .fd = s, .events = POLLIN }; int p; if ((p = poll(&q, 1, t/1000 == 0 ? 1 : t/1000)) == -1) { perror("poll(&q, 1, t/1000 == 0 ? 1 : t/1000)"); r = 16; goto r; } if (!p) { if (lp.tv_sec) goto i; else continue; } if (q.revents & POLLERR || q.revents & POLLHUP || q.revents & POLLNVAL) { r = 17; goto r; } struct sockaddr_in f; socklen_t č = sizeof f; while (1) { int š; if ((š = recvfrom(s, u, 65535, MSG_DONTWAIT, (struct sockaddr *) &f, &č)) == -1) { if (errno != EWOULDBLOCK) { perror("recvfrom(s, u, 65535, MSG_DONTWAIT, (struct sock..."); r = 18; goto r; } break; } if (lp.tv_sec) lp = z; /* this loop ends nearly in an instant */ if (o != -1 && (ž = logudp(o, f, b, u, š)) < -1) { fprintf(stderr, "logudp(o, f, b, u, š) == %d\n", ž); return 3; } fprintf(stderr, "RESPONSE\t%s", inet_ntoa(f.sin_addr)); ž = 0; struct in_addr i = parse_a(u, 65535, d, strlen(d), ž++); while (parse_a(u, 65535, d, strlen(d), ž++).s_addr); if (i.s_addr == a.s_addr) /* if multithread, change printf to write. */ printf("\tWORKING"); if (i.s_addr && i.s_addr != a.s_addr) printf("\tLYINGWITH\t%s", inet_ntoa(i)); if (--ž > 1) printf("\tMORETHANONE\t%d", ž); if (!i.s_addr) printf("\tNOA"); printf("\n"); } if (z.tv_sec) goto i; } r: if (!r && j != -1) { /* TODO: tell EXACT packets that were sent before termination. */ char * x = alloca(l*31+strlen("SCANNED \n0")); /* currently, if scan was */ strcpy(x, "SCANNED "); /* terminated, only networks before */ for (int m = 0; m < (finish ? i : l); m++) { /* network at which scan was */ strcat(x, inet_ntoa(n[m].addr)); /* terminated are reported to be */ strcat(x, "/"); /* scanned, not mentioning the */ strcat(x, inet_ntoa(n[m].mask)); /* part of the last not mentioned */ strcat(x, " "); /* network that was scanned. */ } /* this may lead to statistical */ strcat(x, "\n"); /* issues because it would appear */ write(STDIN_FILENO, x, strlen(x)); /* as if we received packets from */ } /* hosts we haven't queried yet. */ if (s != -1) if (close(s)) perror("close(s)"); if (o != -1) if (close(o)) perror("close(o)"); return r; }