diff options
author | Anton Luka Šijanec <anton@sijanec.eu> | 2022-01-05 22:08:17 +0100 |
---|---|---|
committer | Anton Luka Šijanec <anton@sijanec.eu> | 2022-01-05 22:08:17 +0100 |
commit | 7bdd83308e2a7032502ad5a1273d9dcda2f586bf (patch) | |
tree | 2b04bfd6766fb60507c4983729870986de46d35b | |
parent | prvi commit (diff) | |
download | dnsfind-7bdd83308e2a7032502ad5a1273d9dcda2f586bf.tar dnsfind-7bdd83308e2a7032502ad5a1273d9dcda2f586bf.tar.gz dnsfind-7bdd83308e2a7032502ad5a1273d9dcda2f586bf.tar.bz2 dnsfind-7bdd83308e2a7032502ad5a1273d9dcda2f586bf.tar.lz dnsfind-7bdd83308e2a7032502ad5a1273d9dcda2f586bf.tar.xz dnsfind-7bdd83308e2a7032502ad5a1273d9dcda2f586bf.tar.zst dnsfind-7bdd83308e2a7032502ad5a1273d9dcda2f586bf.zip |
-rw-r--r-- | main.c | 343 |
1 files changed, 274 insertions, 69 deletions
@@ -1,4 +1,4 @@ -#include <sys/socket.h> /* udp(7) */ /* TODO: random XID */ +#include <sys/socket.h> /* udp(7) */ #include <netinet/in.h> #include <netinet/udp.h> #include <poll.h> /* poll(2) */ @@ -10,7 +10,9 @@ #include <fcntl.h> #include <errno.h> /* errno(3) */ #include <arpa/inet.h> /* byteorder(3) */ -#include <string.h> /* strlen(3) */ +#include <netdb.h> /* getaddrinfo(3) */ +#include <stdlib.h> /* atoi(3) */ +#include <string.h> /* strchr(3) */ #include "domain2name.c" #define MIN(a,b) ((a) < (b) ? (a) : (b)) #define MAX(a,b) ((a) > (b) ? (a) : (b)) @@ -18,18 +20,19 @@ #define QUERYDOMAIN "http://sijanec.eu/link?r=.sijanec.eu." #define EXPECTEDA 93.103.235.126 #define HELP "find recursive DNS resolvers on IPv4 networks\n" \ - "%s [-h] [-d domain] [-a ipv4] network1 [network2 [network3 ...]]\n" \ + "%s [-a ipv4] [-b ipv4] [-d domain] [-h] [-o filename] [-p port] network1 [network2 ...]\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" \ " -h Show this help and exit.\n" \ - "Networks are specified as domain names or IPv4 addresses followed by a forward slash\n" \ - "and the netmask in decimal (0-32) or dot notation. For example: example.com/32. \n" \ + " -o Output PCAP to filename. Any existing file is truncated.\n" \ + " -p Set the source port number to use instead of a dynamically asigned one\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 (f@sijanec.eu-http://sijanec.eu/link?r=.sijanec.eu). Make sure it\n" \ - "somehow contains your contact information so the network/server administrator when\n" \ - "looking at the logs will be able to contact you.\n" -/* DNS PACKET: HEADER (12 bytes) QUESTION ANSWER AUTHORITY ADDITIONAL - + "of the default one (dnsfind.sijanec.eu).\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 8 bits: first two bits one, then 6 bits as offset from first byte of packet @@ -37,8 +40,7 @@ DEFINITIONS: (those appear somewhere in the packet, packet does not start with d 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: +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 query 1 response | OPCODE 4 bits: type of query 0 std 1 invrs 2 srvst 3-15 reserved @@ -65,11 +67,114 @@ ANSWER: 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 < http://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 + NANOSEC 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 +*/ +#define MICROSECOND 0xA1B2C3D4 +#define NANOSECOND 0xA1B23C4D +#define PCAPVERMAJ 2 +#define PCAPVERMIN 4 +enum linktype { + Ethernet = 1, + 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)); + unsigned int fcs : 4 __attribute__((packed)); + enum linktype linktype : 28 __attribute__((packed)); +} __attribute__((packed)); +#define microseconds subseconds +#define nanoseconds subseconds +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)); +enum precedence { + Routine, + Priority, + Immediate, + Flash, + Flash_override, + Critical, + Inetctrl, + Netctrl +}; +enum srvtype { + Low_delay = 1 << 0, + High_throughput = 1 << 1, + High_reliability = 1 << 2, + Reserved_0 = 1 << 3, + Reserved_1 = 1 << 4 +}; +enum ip_flags { + Evil = 1 << 0, + Df = 1 << 1, + Mf = 1 << 2 +}; +enum protocol { + ICMP = 1, + TCP = 6, + UDP = 17 +}; +struct ip { + unsigned int version : 4 __attribute__((packed)); + unsigned int headlen : 4 __attribute__((packed)); + enum precedence precedence : 3 __attribute__((packed)); + enum srvtype srvtype : 5 __attribute__((packed)); + uint16_t length __attribute__((packed)); + uint16_t identifier __attribute__((packed)); + enum ip_flags flags : 3 __attribute__((packed)); + unsigned int foffset : 13 __attribute__((packed)); + uint8_t ttl /* ignored for uint8_t */; + enum protocol protocol : 8 __attribute__((packed)); + uint16_t checksum __attribute__((packed)); + struct in_addr src __attribute__((packed)); + struct in_addr dst __attribute__((packed)); + char options[] /* ignored for char[] */; /* options, padding and data */ +} __attribute__((packed)); enum qr { - Query, + Question, Response }; enum opcode { @@ -89,8 +194,7 @@ enum type { A = 1, Ns, Md, - Mf, - Cname, + Cname = 5, /* we skip the quite obsolete Mf record, luckily Mf (more fragments) is also 4 */ Soa, Mb, Mg, @@ -102,89 +206,190 @@ enum type { Minfo, Mx, Txt -} +}; enum class { In = 1, Cs, Ch, He, Any = 255 -} -struct header { - uint16_t xid; - enum qr qr : 1; - enum opcode opcode : 4; - unsigned int aa : 1; - unsigned int tc : 1; - unsigned int rd : 1; - unsigned int ra : 1; - unsigned int z : 3; - enum rcode rcode : 4; - uint16_t qdcount; - uint16_t ancount; - uint16_t nscount; - uint16_t arcount; }; -struct question { - char * qname; - enum qtype qtype : 16; - enum qclass qclass : 16; +enum dns_flags { + Aa = 1 << 0, + Tc = 1 << 1, + Rd = 1 << 2, + Ra = 1 << 3, + Z0 = 1 << 4, + Z1 = 1 << 5, + Z2 = 1 << 6 }; +struct header { + uint16_t xid __attribute__((packed)); + enum qr qr : 1 __attribute__((packed)); + enum opcode opcode : 4 __attribute__((packed)); + enum dns_flags flags : 7 __attribute__((packed)); + enum rcode rcode : 4 __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 question { + char * qname __attribute__((packed)); + enum type qtype : 16 __attribute__((packed)); + enum class qclass : 16 __attribute__((packed)); +} __attribute__((packed)); struct answer { - char * name; - enum class class : 16; - uint16_t rdlen; - char * rdata; + char * name __attribute__((packed)); + enum class class : 16 __attribute__((packed)); + uint16_t rdlen __attribute__((packed)); + char * rdata __attribute__((packed)); +}; +struct in_net { + struct in_addr addr; + struct in_addr mask; +}; +int resolve (const char * d, uint32_t * r) { + struct addrinfo hints = { + .ai_family = AF_INET, + .ai_socktype = SOCK_DGRAM, + .ai_flags = 0, + .ai_protocol = 0, + .ai_canonname = NULL, + .ai_addr = NULL, + .ai_next = NULL + }; + struct addrinfo * result; + int ret = getaddrinfo(d, NULL, &hints, &result); + *r = ((struct sockaddr_in *) result->ai_addr)->sin_addr.s_addr; /* ah yes, C */ + freeaddrinfo(result); + return ret; } int main (int argc, char ** argv) { int r = 0; - while (t) { - switch (getopt(argc, argv, ":a:d:h")) { - /* TODO */ + 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 */ + char buf[512]; /* max dns packet */ + int c; /* child process */ + struct in_net * n; /* networks */ + int l; /* count of networks */ + while (1) { + switch (getopt(argc, argv, ":a:b:d:h")) { + case 'a': + inet_aton(optarg, &a); + break; + case 'b': + inet_aton(optarg, &b.sin_addr); + break; + case 'd': + d = optarg; + break; case 'h': printf(HELP, argv[0]); r = 0; goto r; - case -1: - d = optarg; - if (!d || !*d) { - fprintf(stderr, "specify the domain :: " HELP, argv[0]); - r = 4; + case 'o': + if ((o = open(optarg, O_CREAT | O_TRUNC | O_WRONLY)) == -1) { + perror("open(optarg, O_CREAT | O_TRUNC | O_WRONLY)"); + r = 1; goto r; } - if (strlen(d) > MAXDOMAIN) { - fprintf(stderr, "domain is too long - max "#MAXDOMAIN".\n"); - r = 5; + write(o, "", 1); + break; + case 'p': + b.sin_port = htons(atoi(optarg)); + break; + case -1: + if (!(l = argc-optind)) { + fprintf(stderr, "specify targets to scan :: " HELP, argv[0]); + r = 2; goto r; } - t = 0; - break; + n = alloca(l*sizeof(*n)); + for (int i = optind; i < argc; i++) { + int w = i-optind; + char * m = strchr(argv[i], '/'); + int e; + if (m) + *m++ = '\0'; + else + m = "32"; + fprintf(stderr, "network %d: resolving %s ... ", w, argv[i]); + if ((e = resolve(argv[i], &n[w].addr.s_addr))) { + fprintf(stderr, "failed: %s\n", gai_strerror(e)); + r = 3; + goto r; + } + fprintf(stderr, " %s mask %s ...", inet_ntoa(n[w].addr), m); + char * p; + int x = strtoll(m, &p, 10); + n[w].mask.s_addr = 0; + for (int j = 0; j < x && j < 32; j++) + n[w].mask.s_addr = n[w].mask.s_addr >> 1 | 1 << 31; + n[w].mask.s_addr = htonl(n[w].mask.s_addr); + if (*p) + if ((e = resolve(m, &n[w].mask.s_addr))) { + fprintf(stderr, "no: %s\n", gai_strerror(e)); + r = 4; + goto r; + } + fprintf(stderr, " %s\n", inet_ntoa(n[w].mask)); + } + goto o; case '?': fprintf(stderr, "unknown option :: " HELP, argv[0]); - if (db != -1) - close(db); - r = 6; + r = 5; goto r; case ':': fprintf(stderr, "missing option argument :: " HELP, argv[0]); - r = 7; + 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; + } + if ((c = fork()) == -1) { + perror("fork()"); + r = 10; + goto r; + } r: if (s != -1) if (close(s)) perror("close(s)"); - } + if (o != -1) + if (close(o)) + perror("close(o)"); return r; } -int handle () { - char buf[512]; /* we don't support EDNS0 here, so max DNS packet is 512 bytes */ - recvfrom(); -} -int query () { - sendto(); -} -int erase (char * dbfn, int dbfd, char * db) { - -} |