Cherry referring to my last name kirschju.re Forward and Reverse Engineering

Knock: Stealthy TCP Servers

We presented a small kernel patch for Linux that allows to stealthily host TCP services on the Internet at 30c3. (slides)

The core idea is to extend the TCP protocol such that the connection-initiator-picked initial sequence number (ISN) conveys authentication (and optionally integrity) information as part of the SYN segment. We implemented a prototype for Linux 3.12.4, where userspace can call setsockopt with two new options:

A patch for Linux 3.12.4 can be found below:

diff -urNp linux-3.12.4.old/include/linux/tcp.h linux-3.12.4/include/linux/tcp.h
--- linux-3.12.4.old/include/linux/tcp.h	2013-12-08 17:18:58.000000000 +0100
+++ linux-3.12.4/include/linux/tcp.h	2013-12-10 15:47:18.000082837 +0100
@@ -20,6 +20,7 @@
 
 #include <linux/skbuff.h>
 #include <linux/dmaengine.h>
+#include <linux/cryptohash.h>
 #include <net/sock.h>
 #include <net/inet_connection_sock.h>
 #include <net/inet_timewait_sock.h>
@@ -314,6 +315,19 @@ struct tcp_sock {
 	struct tcp_md5sig_info	__rcu *md5sig_info;
 #endif
 
+#ifdef CONFIG_TCP_STEALTH
+/* Stealth TCP socket configuration */
+	struct {
+		#define TCP_STEALTH_MODE_AUTH		BIT(0)
+		#define TCP_STEALTH_MODE_INTEGRITY	BIT(1)
+		#define TCP_STEALTH_MODE_INTEGRITY_LEN	BIT(2)
+		int mode;
+		u8 secret[MD5_MESSAGE_BYTES];
+		int integrity_len;
+		u16 integrity_hash;
+	} stealth;
+#endif
+
 /* TCP fastopen related information */
 	struct tcp_fastopen_request *fastopen_req;
 	/* fastopen_rsk points to request_sock that resulted in this big
diff -urNp linux-3.12.4.old/include/net/secure_seq.h linux-3.12.4/include/net/secure_seq.h
--- linux-3.12.4.old/include/net/secure_seq.h	2013-12-08 17:18:58.000000000 +0100
+++ linux-3.12.4/include/net/secure_seq.h	2013-12-10 15:47:18.004082863 +0100
@@ -10,8 +10,12 @@ extern u32 secure_ipv6_port_ephemeral(co
 				      __be16 dport);
 extern __u32 secure_tcp_sequence_number(__be32 saddr, __be32 daddr,
 					__be16 sport, __be16 dport);
+extern __u32 stealth_tcp_sequence_number(__be32 daddr, __be16 dport, __u8 *secret,
+					 __u16 integrity);
 extern __u32 secure_tcpv6_sequence_number(const __be32 *saddr, const __be32 *daddr,
 					  __be16 sport, __be16 dport);
+extern __u32 stealth_tcpv6_sequence_number(const __be32 *daddr, __be16 dport,
+					   __u8 *secret, __u16 integrity);
 extern u64 secure_dccp_sequence_number(__be32 saddr, __be32 daddr,
 				       __be16 sport, __be16 dport);
 extern u64 secure_dccpv6_sequence_number(__be32 *saddr, __be32 *daddr,
diff -urNp linux-3.12.4.old/include/net/tcp.h linux-3.12.4/include/net/tcp.h
--- linux-3.12.4.old/include/net/tcp.h	2013-12-08 17:18:58.000000000 +0100
+++ linux-3.12.4/include/net/tcp.h	2013-12-10 15:47:18.012082891 +0100
@@ -448,6 +448,7 @@ extern void tcp_parse_options(const stru
 			      struct tcp_options_received *opt_rx,
 			      int estab, struct tcp_fastopen_cookie *foc);
 extern const u8 *tcp_parse_md5sig_option(const struct tcphdr *th);
+extern int tcp_stealth_integrity(u16 *hash, u8 *secret, u8 *payload, int len);
 
 /*
  *	TCP v4 functions exported for the inet6 API
diff -urNp linux-3.12.4.old/include/uapi/linux/tcp.h linux-3.12.4/include/uapi/linux/tcp.h
--- linux-3.12.4.old/include/uapi/linux/tcp.h	2013-12-08 17:18:58.000000000 +0100
+++ linux-3.12.4/include/uapi/linux/tcp.h	2013-12-10 15:51:19.417279938 +0100
@@ -112,6 +112,9 @@ enum {
 #define TCP_FASTOPEN		23	/* Enable FastOpen on listeners */
 #define TCP_TIMESTAMP		24
 #define TCP_NOTSENT_LOWAT	25	/* limit number of unsent bytes in write queue */
+#define TCP_STEALTH		26
+#define TCP_STEALTH_INTEGRITY	27
+#define TCP_STEALTH_INTEGRITY_LEN	28
 
 struct tcp_repair_opt {
 	__u32	opt_code;
diff -urNp linux-3.12.4.old/net/core/secure_seq.c linux-3.12.4/net/core/secure_seq.c
--- linux-3.12.4.old/net/core/secure_seq.c	2013-12-08 17:18:58.000000000 +0100
+++ linux-3.12.4/net/core/secure_seq.c	2013-12-10 15:47:18.028082976 +0100
@@ -72,6 +72,26 @@ __u32 secure_tcpv6_sequence_number(const
 }
 EXPORT_SYMBOL(secure_tcpv6_sequence_number);
 
+#ifdef CONFIG_TCP_STEALTH
+__u32 stealth_tcpv6_sequence_number(const __be32 *daddr, __be16 dport,
+				    __u8 *secret, __u16 integrity)
+{
+	u32 hash[MD5_DIGEST_WORDS];
+	u32 sec[MD5_MESSAGE_BYTES / sizeof(u32)];
+	u32 i;
+
+	for (i = 0; i < MD5_DIGEST_WORDS; i++)
+		hash[i] = be32_to_cpu(daddr[i]);
+	hash[2] ^= (integrity << 16) | be16_to_cpu(dport);
+	for (i = 0; i < MD5_MESSAGE_BYTES / sizeof(u32); i++)
+		sec[i] = be32_to_cpu(((__be32 *)secret)[i]);
+
+	md5_transform(hash, sec);
+
+	return hash[0] ^ hash[1] ^ hash[2] ^ hash[3];
+}
+#endif
+
 u32 secure_ipv6_port_ephemeral(const __be32 *saddr, const __be32 *daddr,
 			       __be16 dport)
 {
@@ -137,6 +157,29 @@ __u32 secure_tcp_sequence_number(__be32
 	return seq_scale(hash[0]);
 }
 
+#ifdef CONFIG_TCP_STEALTH
+__u32 stealth_tcp_sequence_number(__be32 daddr, __be16 dport, __u8 *secret,
+				  __u16 integrity)
+{
+	u32 hash[MD5_DIGEST_WORDS];
+	u32 sec[MD5_MESSAGE_BYTES / sizeof(u32)];
+	u32 i;
+
+
+	hash[0] = be32_to_cpu(((__be32 *)secret)[10]);
+	hash[1] = be32_to_cpu(daddr);
+	hash[2] = (integrity << 16) | be16_to_cpu(dport);
+	hash[3] = be32_to_cpu(((__be32 *)secret)[15]);
+
+	for (i = 0; i < MD5_MESSAGE_BYTES / sizeof(u32); i++)
+		sec[i] = be32_to_cpu(((__be32 *)secret)[i]);
+
+	md5_transform(hash, sec);
+
+	return hash[0] ^ hash[1] ^ hash[2] ^ hash[3];
+}
+#endif
+
 u32 secure_ipv4_port_ephemeral(__be32 saddr, __be32 daddr, __be16 dport)
 {
 	u32 hash[MD5_DIGEST_WORDS];
diff -urNp linux-3.12.4.old/net/ipv4/Kconfig linux-3.12.4/net/ipv4/Kconfig
--- linux-3.12.4.old/net/ipv4/Kconfig	2013-12-08 17:18:58.000000000 +0100
+++ linux-3.12.4/net/ipv4/Kconfig	2013-12-10 15:47:18.028082976 +0100
@@ -618,3 +618,13 @@ config TCP_MD5SIG
 	  on the Internet.
 
 	  If unsure, say N.
+
+config TCP_STEALTH
+	bool "TCP: Stealth TCP socket support"
+	default n
+	---help---
+	  This option enables support for stealth TCP sockets. If you do not
+	  know what this means, you do not need it.
+
+	  If unsure, say N.
+
diff -urNp linux-3.12.4.old/net/ipv4/tcp.c linux-3.12.4/net/ipv4/tcp.c
--- linux-3.12.4.old/net/ipv4/tcp.c	2013-12-08 17:18:58.000000000 +0100
+++ linux-3.12.4/net/ipv4/tcp.c	2013-12-10 15:47:18.028082976 +0100
@@ -2380,6 +2380,43 @@ static int tcp_repair_options_est(struct
 	return 0;
 }
 
+#ifdef CONFIG_TCP_STEALTH
+int tcp_stealth_integrity(u16 *hash, u8 *secret, u8 *payload, int len)
+{
+	struct scatterlist sg[2];
+	struct crypto_hash *tfm;
+	struct hash_desc desc;
+	u16 h[MD5_DIGEST_WORDS * 2];
+	int i;
+	int err = 0;
+
+	tfm = crypto_alloc_hash("md5", 0, CRYPTO_ALG_ASYNC);
+	if (IS_ERR(tfm)) {
+		err = -PTR_ERR(tfm);
+		goto out;
+	}
+	desc.tfm = tfm;
+	desc.flags = 0;
+
+	sg_init_table(sg, 2);
+	sg_set_buf(&sg[0], secret, MD5_MESSAGE_BYTES);
+	sg_set_buf(&sg[1], payload, len);
+
+	if (crypto_hash_digest(&desc, sg, MD5_MESSAGE_BYTES + len, (u8 *)h)) {
+		err = -EFAULT;
+		goto out;
+	}
+
+	*hash = 0;
+	for (i = 0; i < MD5_DIGEST_WORDS * 2; i++)
+		*hash ^= h[i];
+
+out:
+	crypto_free_hash(tfm);
+	return err;
+}
+#endif
+
 /*
  *	Socket option code for TCP.
  */
@@ -2410,6 +2447,65 @@ static int do_tcp_setsockopt(struct sock
 		release_sock(sk);
 		return err;
 	}
+#ifdef CONFIG_TCP_STEALTH
+	case TCP_STEALTH: {
+		u8 secret[MD5_MESSAGE_BYTES];
+
+		if (optlen < MD5_MESSAGE_BYTES)
+			return -EINVAL;
+
+		val = copy_from_user(secret, optval, MD5_MESSAGE_BYTES);
+		if (val != 0)
+			return -EFAULT;
+
+		lock_sock(sk);
+		memcpy(tp->stealth.secret, secret, MD5_MESSAGE_BYTES);
+		tp->stealth.mode = TCP_STEALTH_MODE_AUTH;
+		release_sock(sk);
+		return err;
+	}
+	case TCP_STEALTH_INTEGRITY: {
+		u8 *payload;
+
+		lock_sock(sk);
+
+		if (!(tp->stealth.mode & TCP_STEALTH_MODE_AUTH)) {
+			err = -EOPNOTSUPP;
+			goto stealth_integrity_out_1;
+		}
+
+		if (optlen < 1 || optlen > USHRT_MAX) {
+			err = -EINVAL;
+			goto stealth_integrity_out_1;
+		}
+
+		payload = vmalloc(optlen);
+		if (!payload) {
+			err = -ENOMEM;
+			goto stealth_integrity_out_1;
+		}
+
+		val = copy_from_user(payload, optval, optlen);
+		if (val != 0) {
+			err = -EFAULT;
+			goto stealth_integrity_out_2;
+		}
+
+		err = tcp_stealth_integrity(&tp->stealth.integrity_hash,
+					    tp->stealth.secret, payload,
+					    optlen);
+		if (err)
+			goto stealth_integrity_out_2;
+
+		tp->stealth.mode |= TCP_STEALTH_MODE_INTEGRITY;
+
+stealth_integrity_out_2:
+		vfree(payload);
+stealth_integrity_out_1:
+		release_sock(sk);
+		return err;
+	}
+#endif
 	default:
 		/* fallthru */
 		break;
@@ -2651,6 +2747,18 @@ static int do_tcp_setsockopt(struct sock
 		tp->notsent_lowat = val;
 		sk->sk_write_space(sk);
 		break;
+#ifdef CONFIG_TCP_STEALTH
+	case TCP_STEALTH_INTEGRITY_LEN:
+		if (!(tp->stealth.mode & TCP_STEALTH_MODE_AUTH)) {
+			err = -EOPNOTSUPP;
+		} else if (val < 1 || val > USHRT_MAX) {
+			err = -EINVAL;
+		} else {
+			tp->stealth.integrity_len = val;
+			tp->stealth.mode |= TCP_STEALTH_MODE_INTEGRITY_LEN;
+		}
+		break;
+#endif
 	default:
 		err = -ENOPROTOOPT;
 		break;
diff -urNp linux-3.12.4.old/net/ipv4/tcp_input.c linux-3.12.4/net/ipv4/tcp_input.c
--- linux-3.12.4.old/net/ipv4/tcp_input.c	2013-12-08 17:18:58.000000000 +0100
+++ linux-3.12.4/net/ipv4/tcp_input.c	2013-12-10 15:47:18.028082976 +0100
@@ -4293,6 +4293,31 @@ err:
 	return -ENOMEM;
 }
 
+#ifdef CONFIG_TCP_STEALTH
+static int tcp_stealth_integrity_check(struct sock *sk, struct sk_buff *skb)
+{
+	struct tcphdr *th = tcp_hdr(skb);
+	struct tcp_sock *tp = tcp_sk(sk);
+	u16 hash;
+	u16 seq = TCP_SKB_CB(skb)->seq - 1;
+	char *data = skb->data + th->doff * 4;
+	int len = skb->len - th->doff * 4;
+
+	if (len < tp->stealth.integrity_len)
+		return 1;
+
+	if (tcp_stealth_integrity(&hash, tp->stealth.secret, data,
+				  tp->stealth.integrity_len))
+		return 1;
+
+	if (seq != hash)
+		return 1;
+
+	tp->stealth.mode &= ~TCP_STEALTH_MODE_INTEGRITY_LEN;
+	return 0;
+}
+#endif
+
 static void tcp_data_queue(struct sock *sk, struct sk_buff *skb)
 {
 	const struct tcphdr *th = tcp_hdr(skb);
@@ -4303,6 +4328,14 @@ static void tcp_data_queue(struct sock *
 	if (TCP_SKB_CB(skb)->seq == TCP_SKB_CB(skb)->end_seq)
 		goto drop;
 
+#ifdef CONFIG_TCP_STEALTH
+	if (unlikely(tp->stealth.mode & TCP_STEALTH_MODE_INTEGRITY_LEN) &&
+	    tcp_stealth_integrity_check(sk, skb)) {
+		tcp_reset(sk);
+		goto drop;
+	}
+#endif
+
 	skb_dst_drop(skb);
 	__skb_pull(skb, th->doff * 4);
 
@@ -5157,6 +5190,15 @@ void tcp_rcv_established(struct sock *sk
 			int copied_early = 0;
 			bool fragstolen = false;
 
+#ifdef CONFIG_TCP_STEALTH
+			if (unlikely(tp->stealth.mode &
+				     TCP_STEALTH_MODE_INTEGRITY_LEN) &&
+			    tcp_stealth_integrity_check(sk, skb)) {
+				tcp_reset(sk);
+				goto discard;
+			}
+#endif
+
 			if (tp->copied_seq == tp->rcv_nxt &&
 			    len - tcp_header_len <= tp->ucopy.len) {
 #ifdef CONFIG_NET_DMA
diff -urNp linux-3.12.4.old/net/ipv4/tcp_ipv4.c linux-3.12.4/net/ipv4/tcp_ipv4.c
--- linux-3.12.4.old/net/ipv4/tcp_ipv4.c	2013-12-08 17:18:58.000000000 +0100
+++ linux-3.12.4/net/ipv4/tcp_ipv4.c	2013-12-10 15:47:18.028082976 +0100
@@ -235,6 +235,20 @@ int tcp_v4_connect(struct sock *sk, stru
 	sk->sk_gso_type = SKB_GSO_TCPV4;
 	sk_setup_caps(sk, &rt->dst);
 
+#ifdef CONFIG_TCP_STEALTH
+	if (!tp->write_seq && likely(!tp->repair) &&
+ 	    unlikely(tp->stealth.mode & TCP_STEALTH_MODE_AUTH)) {
+		tp->write_seq = stealth_tcp_sequence_number(inet->inet_daddr,
+							    usin->sin_port,
+							    tp->stealth.secret,
+							    tp->stealth.integrity_hash);
+		if (tp->stealth.mode & TCP_STEALTH_MODE_INTEGRITY) {
+			tp->write_seq &= (BIT(16) - 1) << 16;
+			tp->write_seq |= tp->stealth.integrity_hash;
+		}
+	}
+#endif
+
 	if (!tp->write_seq && likely(!tp->repair))
 		tp->write_seq = secure_tcp_sequence_number(inet->inet_saddr,
 							   inet->inet_daddr,
@@ -1776,6 +1790,8 @@ static __sum16 tcp_v4_checksum_init(stru
  */
 int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
 {
+	struct tcp_sock *tp = tcp_sk(sk);
+	struct tcphdr *th = tcp_hdr(skb);
 	struct sock *rsk;
 #ifdef CONFIG_TCP_MD5SIG
 	/*
@@ -1806,6 +1822,32 @@ int tcp_v4_do_rcv(struct sock *sk, struc
 	if (skb->len < tcp_hdrlen(skb) || tcp_checksum_complete(skb))
 		goto csum_err;
 
+#ifdef CONFIG_TCP_STEALTH
+	if (sk->sk_state == TCP_LISTEN && th->syn && !th->fin &&
+	    unlikely(tp->stealth.mode & TCP_STEALTH_MODE_AUTH)) {
+		u32 hash;
+		u32 seq = be32_to_cpu(th->seq);
+		if (tp->stealth.mode & TCP_STEALTH_MODE_INTEGRITY_LEN)
+			tp->stealth.integrity_hash = seq;
+		hash = stealth_tcp_sequence_number(ip_hdr(skb)->daddr,
+						   th->dest,
+						   tp->stealth.secret,
+						   tp->stealth.integrity_hash);
+		if (tp->stealth.mode & TCP_STEALTH_MODE_INTEGRITY_LEN) {
+			if ((seq & ((BIT(16) - 1) << 16)) !=
+			    (hash & ((BIT(16) - 1) << 16))) {
+				rsk = sk;
+				goto reset;
+			}
+		} else {
+			if (seq != hash) {
+				rsk = sk;
+				goto reset;
+			}
+		}
+	}
+#endif
+
 	if (sk->sk_state == TCP_LISTEN) {
 		struct sock *nsk = tcp_v4_hnd_req(sk, skb);
 		if (!nsk)
diff -urNp linux-3.12.4.old/net/ipv6/tcp_ipv6.c linux-3.12.4/net/ipv6/tcp_ipv6.c
--- linux-3.12.4.old/net/ipv6/tcp_ipv6.c	2013-12-08 17:18:58.000000000 +0100
+++ linux-3.12.4/net/ipv6/tcp_ipv6.c	2013-12-10 15:47:18.032082996 +0100
@@ -296,6 +296,20 @@ static int tcp_v6_connect(struct sock *s
 	if (err)
 		goto late_failure;
 
+#ifdef CONFIG_TCP_STEALTH
+	if (!tp->write_seq && likely(!tp->repair) &&
+	    unlikely(tp->stealth.mode & TCP_STEALTH_MODE_AUTH)) {
+		tp->write_seq = stealth_tcpv6_sequence_number(np->daddr.s6_addr32,
+							      inet->inet_dport,
+							      tp->stealth.secret,
+							      tp->stealth.integrity_hash);
+		if (tp->stealth.mode & TCP_STEALTH_MODE_INTEGRITY) {
+			tp->write_seq &= (BIT(16) - 1) << 16;
+			tp->write_seq |= tp->stealth.integrity_hash;
+		}
+	}
+#endif
+
 	if (!tp->write_seq && likely(!tp->repair))
 		tp->write_seq = secure_tcpv6_sequence_number(np->saddr.s6_addr32,
 							     np->daddr.s6_addr32,
@@ -1305,7 +1319,8 @@ static __sum16 tcp_v6_checksum_init(stru
 static int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb)
 {
 	struct ipv6_pinfo *np = inet6_sk(sk);
-	struct tcp_sock *tp;
+	struct tcp_sock *tp = tcp_sk(sk);
+	struct tcphdr *th = tcp_hdr(skb);
 	struct sk_buff *opt_skb = NULL;
 
 	/* Imagine: socket is IPv6. IPv4 packet arrives,
@@ -1369,6 +1384,29 @@ static int tcp_v6_do_rcv(struct sock *sk
 	if (skb->len < tcp_hdrlen(skb) || tcp_checksum_complete(skb))
 		goto csum_err;
 
+#ifdef CONFIG_TCP_STEALTH
+	if (sk->sk_state == TCP_LISTEN && th->syn && !th->fin &&
+	    tp->stealth.mode & TCP_STEALTH_MODE_AUTH) {
+		u32 hash;
+		u32 seq = be32_to_cpu(th->seq);
+		if (tp->stealth.mode & TCP_STEALTH_MODE_INTEGRITY_LEN)
+			tp->stealth.integrity_hash = seq;
+		hash = stealth_tcpv6_sequence_number(ipv6_hdr(skb)->
+						     daddr.s6_addr32,
+						     th->dest,
+						     tp->stealth.secret,
+						     tp->stealth.integrity_hash);
+	    	if (tp->stealth.mode & TCP_STEALTH_MODE_INTEGRITY_LEN) {
+			if ((seq & ((BIT(16) - 1) << 16)) !=
+			    (hash & ((BIT(16) - 1) << 16)))
+				goto reset;
+		} else {
+			if (seq != hash)
+				goto reset;
+		}
+	}
+#endif
+
 	if (sk->sk_state == TCP_LISTEN) {
 		struct sock *nsk = tcp_v6_hnd_req(sk, skb);
 		if (!nsk)