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:
TCP_STEALTH
enables sender authentication by providing a symmetric secret to the network stack. The ISN is derived from (among other information) the symmetric secret on the connection initiator’s side and validated on the connection responder’s side.TCP_STEALTH_INTEGRITY
additionally offers to embed information about the first payload bytes that are to be sent once the TCP handshake is completed.
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)