From 963b1072b11cc5b638e2f2725a18651d0ecbf664 Mon Sep 17 00:00:00 2001 From: emanuele-f Date: Wed, 8 Dec 2021 17:14:54 +0100 Subject: [PATCH] Spoof DNS replies for blocked hosts This prevents the DNS resolver from spamming multiple requests when no reply is seen. --- app/src/main/jni/common/utils.c | 35 ++++++++++++ app/src/main/jni/common/utils.h | 1 + app/src/main/jni/core/capture_vpn.c | 89 ++++++++++++++++++++++++++++- app/src/main/jni/core/pcapdroid.h | 1 - submodules/zdtun | 2 +- 5 files changed, 123 insertions(+), 5 deletions(-) diff --git a/app/src/main/jni/common/utils.c b/app/src/main/jni/common/utils.c index f3574beb..8b7fe852 100644 --- a/app/src/main/jni/common/utils.c +++ b/app/src/main/jni/common/utils.c @@ -212,3 +212,38 @@ char* humanSize(char *buf, int bufsize, double bytes) { snprintf(buf, bufsize, "%.02f %s", bytes, suffix[i]); return buf; } + +/* ******************************************************* */ + +/* Dumps packets in the hex format of "od -A x -t x1", which makes it compatible with + * text2pcap. */ +void hexdump(const char *buf, size_t bufsize) { + size_t off = 0; + char out[64]; + int idx = 0; + static const char hex[] = "0123456789abcdef"; + + while(off < bufsize) { + if((off % 16) == 0) { + if(off > 0) { + out[idx] = '\0'; + log_d("%s", out); + } + idx = sprintf(out, "%06x", off); + } + + out[idx++] = ' '; + out[idx++] = hex[(buf[off] & 0xF0) >> 4]; + out[idx++] = hex[buf[off] & 0x0F]; + off++; + } + + if((off % 16) != 0) { + out[idx] = '\0'; + log_d("%s", out); + idx = sprintf(out, "%06x", off); + } + + out[idx] = '\0'; + log_d("%s", out); +} \ No newline at end of file diff --git a/app/src/main/jni/common/utils.h b/app/src/main/jni/common/utils.h index dc4a1f95..a7da0bc3 100644 --- a/app/src/main/jni/common/utils.h +++ b/app/src/main/jni/common/utils.h @@ -49,5 +49,6 @@ jfieldID jniFieldID(JNIEnv *env, jclass cls, const char *name, const char *type) jobject jniEnumVal(JNIEnv *env, const char *class_name, const char *enum_key); int jniCheckException(JNIEnv *env); char* humanSize(char *buf, int bufsize, double bytes); +void hexdump(const char *buf, size_t bufsize); #endif // __LOG_UTILS_H__ diff --git a/app/src/main/jni/core/capture_vpn.c b/app/src/main/jni/core/capture_vpn.c index e8b67ac9..ea8d61a3 100644 --- a/app/src/main/jni/core/capture_vpn.c +++ b/app/src/main/jni/core/capture_vpn.c @@ -242,6 +242,82 @@ static bool check_dns_req_allowed(pcapdroid_t *pd, zdtun_conn_t *conn, pkt_conte /* ******************************************************* */ +static bool spoof_dns_reply(pcapdroid_t *pd, zdtun_conn_t *conn, pkt_context_t *pctx) { + // Step 1: ensure that this is a valid query + zdtun_pkt_t *pkt = pctx->pkt; + if(pkt->l7_len < (sizeof(dns_packet_t) + 5)) + return false; + + dns_packet_t *req = (dns_packet_t*) pkt->l7; + if(ntohs(req->questions) != 1) + return false; + + int remaining = pkt->l7_len - sizeof(dns_packet_t); + int qlen=0; + while(remaining >= 5) { + if(!req->queries[qlen]) + break; + qlen++; + remaining--; + } + + if((req->queries[qlen] != 0) || (req->queries[qlen + 1] != 0) || + (req->queries[qlen + 3] != 0) || (req->queries[qlen + 4] != 1)) + return false; // invalid + + uint8_t qtype = req->queries[qlen + 2]; + if((qtype != 0x01) && (qtype != 0x1c)) + return false; // invalid query type + + // Step 2: spoof the reply + log_d("Spoofing %s DNS reply", (qtype == 0x01) ? "A" : "AAAA"); + + const zdtun_5tuple_t *tuple = pctx->tuple; + uint8_t alen = (qtype == 0x01) ? 4 : 16; + int iplen = zdtun_iphdr_len(pd->zdt, conn); + unsigned int len = iplen + 8 /* UDP */ + sizeof(dns_packet_t) + qlen + 5 /* type, ... */ + 12 /* answer */ + alen; + char buf[len]; + memset(buf, 0, len); + + zdtun_make_iphdr(pd->zdt, conn, buf, len - iplen); + + struct udphdr *udp = (struct udphdr*)(buf + iplen); + udp->uh_sport = tuple->dst_port; + udp->uh_dport = tuple->src_port; + udp->len = htons(len - iplen); + + dns_packet_t *dns = (dns_packet_t*)(buf + iplen + 8); + dns->transaction_id = req->transaction_id; + dns->flags = htons(0x8180); + dns->questions = req->questions; + dns->answ_rrs = dns->questions; + dns->auth_rrs = dns->additional_rrs = 0; + + // Queries + memcpy(dns->queries, req->queries, qlen + 5); + + // Answers + uint8_t *answ = dns->queries + qlen + 5; + + answ[0] = 0xc0, answ[1] = 0x0c; // name ptr + answ[2] = 0x00, answ[3] = qtype; // type + answ[4] = 0x00, answ[5] = 0x01; // class IN + *(uint32_t*)(answ + 6) = htonl(10); // TTL: 10s + answ[10] = 0x00, answ[11] = alen; // addr length + memset(answ + 12, 0, alen); // addr: 0.0.0.0/:: + + // checksum + udp->uh_sum = 0; + udp->uh_sum = zdtun_l3_checksum(pd->zdt, conn, buf, (char*)udp, len - iplen); + + //hexdump(buf, len); + write(pd->vpn.tunfd, buf, len); + + return true; +} + +/* ******************************************************* */ + static int handle_new_connection(zdtun_t *zdt, zdtun_conn_t *conn_info) { pcapdroid_t *pd = ((pcapdroid_t *) zdtun_userdata(zdt)); const zdtun_5tuple_t *tuple = zdtun_conn_get_5tuple(conn_info); @@ -260,7 +336,7 @@ static int handle_new_connection(zdtun_t *zdt, zdtun_conn_t *conn_info) { /* ******************************************************* */ -static void connnection_closed(zdtun_t *zdt, const zdtun_conn_t *conn_info) { +static void connection_closed(zdtun_t *zdt, const zdtun_conn_t *conn_info) { pcapdroid_t *pd = (pcapdroid_t*) zdtun_userdata(zdt); pd_conn_t *data = zdtun_conn_get_userdata(conn_info); @@ -320,7 +396,7 @@ int run_vpn(pcapdroid_t *pd, int tunfd) { .account_packet = update_conn_status, .on_socket_open = protectSocketCallback, .on_connection_open = handle_new_connection, - .on_connection_close = connnection_closed, + .on_connection_close = connection_closed, }; // List of known DNS servers @@ -437,7 +513,14 @@ int run_vpn(pcapdroid_t *pd, int tunfd) { if(data->sent_pkts == 0) { data->to_block |= !check_dns_req_allowed(pd, conn, &pctx); - if(pd->socks5.enabled && !data->to_block && (tuple->ipproto == IPPROTO_TCP)) + if(data->to_block) { + // blocking a DNS query can cause multiple requests to be spammed. Better to + // spoof a reply with an invalid IP. + if((data->l7proto == NDPI_PROTOCOL_DNS) && (tuple->ipproto == IPPROTO_UDP)) { + spoof_dns_reply(pd, conn, &pctx); + zdtun_destroy_conn(zdt, conn); + } + } else if(pd->socks5.enabled && (tuple->ipproto == IPPROTO_TCP)) zdtun_conn_proxy(conn); } diff --git a/app/src/main/jni/core/pcapdroid.h b/app/src/main/jni/core/pcapdroid.h index 964a6430..10183c6f 100644 --- a/app/src/main/jni/core/pcapdroid.h +++ b/app/src/main/jni/core/pcapdroid.h @@ -219,7 +219,6 @@ typedef struct { uint16_t answ_rrs; uint16_t auth_rrs; uint16_t additional_rrs; - uint8_t initial_dot; // just skip uint8_t queries[]; } __attribute__((packed)) dns_packet_t; diff --git a/submodules/zdtun b/submodules/zdtun index 3e5e7492..a000e4f3 160000 --- a/submodules/zdtun +++ b/submodules/zdtun @@ -1 +1 @@ -Subproject commit 3e5e74921507bd0ae7a109fcb192b7167182515c +Subproject commit a000e4f3b12817b453ed728ac6f33e26b455c288