TCP Stealth hides open ports
TCP Camouflage
Finding open UDP or TCP ports on Linux is easy. Hardcore hackers use Netcat [1]. If you prefer an easier approach, you can use Nmap [2]. In addition to identifying active services, you can even profile the underlying operating system in many cases. In fact, a port scan can be a useful troubleshooting tool for an administrator's bottom line.
There is a dark side to port scanning, too, however. Unfriendly people can use the same methods to spy on IT systems. After identifying what are basically open doors, an attacker can start on other investigations. In a worst-case scenario, the attacker learns which version of which program is keeping a port open. A short search on the Internet for potential vulnerabilities or exploits is quickly accomplished.
Administrators can effectively block this attack vector by obfuscating the open ports. For an outsider, it looks as if there were no easy targets; only experts know how to gain access. Probably the best-known technology in this context is port knocking, and there are several implementations [3].
What Happened Thus Far
Figure 1 shows a typical setup with port knocking. The components involved here are the client and the server application, with a firewall to keep out uninvited guests and another process that waits for the agreed upon knock signal. This process is often known as the port knock daemon. The daemon and the clients have a shared secret. This can be a knock sequence or network packets with specific content.
The connection is established in four steps. The client attempts to open a connection to the server, and the firewall blocks this. At the next step, the pre-shared key referred to previously – that is, a specific order in which the client attempts to address other ports or a specific packet content that is sent by the client – is used. The port knock daemon detects this and tells the firewall to let the client request through. The final step is then establishment of a totally normal connection.
If you want to try this out yourself, you can check out previous articles in Linux Magazine [4]-[6]. Alternatively, you can start with the Knockd [7] project. The TCP Stealth approach described here is also implemented in recent versions of Knoppix.
The various Internet protocols will behave differently. As a connectionless protocol, UDP needs to be treated differently as early as the scanning stage. Additionally, there are several scan type variants for identifying open ports on the TCP side (Table 1).
Tabelle 1: Known Port Scan Types for TCP
Name |
Description |
---|---|
Connect |
Performs the TCP handshake to complete the connection |
SYN |
Sends only one packet with the TCP |
FIN |
Sends only one packet with the TCP |
Xmas |
Sends only one packet with all TCP flags set |
The known port knocking setup has two inherent vulnerabilities, the first being that the lock mechanism is detectable if the attacker can sniff the network. The additional steps required to open a connection are obvious, and the original rejection of the connection followed by the connection then being accepted is a giveaway.
The second vulnerability relates to man-in-the-middle attacks. After successfully completing port knocking, the port knock daemon and the firewall leave the playing field. They cannot prevent the attacker from retroactively hijacking the established connection. In the rest of this article, I will just be looking at TCP as the transport protocol.
Initial Stealthy Steps
TCP Stealth is an extension of the initial three-way handshake for establishing a connection. A request for comment (RFC) [8] has already been submitted to the Internet Engineering Task Force (IETF) [9]. The method, also known as silent port knocking, was created by Julian Kirsch as a student project at the Technical University in Munich in the summer of 2013 [10]. Its original name was Knock, and the project website [11] still uses this moniker. Kirsch submitted his master's thesis on the subject of TCP Stealth in 2014 [12].
If you have been following the port knocking scenario for a few years, you might remember SilentKnock [13]. The project uses the same seven-year-old idea [14] as TCP Stealth. In 1997, Craig H. Rowland described how information can be transmitted camouflaged as the standard TCP header.
Listing 1 shows the TCP header. Most of its parts are predetermined by their function and are not suitable for embedding data. One exception to this is the initial sequence number (ISN) of the TCP connection. To a certain extent, this also applies to the acknowledgement number (ACK). The use of the timestamp as a carrier is also conceivable.
Listing 1: TCP Header
01 $ cat rfc793.txt 02 ... 03 0 1 2 3 04 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 05 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 06 | Source Port | Destination Port | 07 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 08 | Sequence Number | 09 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 10 | Acknowledgment Number | 11 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 12 | Data | |U|A|P|R|S|F| | 13 | Offset| Reserved |R|C|S|S|Y|I| Window | 14 | | |G|K|H|T|N|N| | 15 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 16 | Checksum | Urgent Pointer | 17 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 18 | Options | Padding | 19 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 20 | data | 21 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 22 ...
The RFC specifies a maximum size of 32 bits for the ISN. This limits the space for payload data. The input data for the random ISN is the shared secret, the IP address and port on the target, and possibly also the timestamp. The secret is known to both the client and server and is thus a pre-shared key (PSK). In the current implementation, this key is a simple string, and there is precisely one string per port.
To generate the ISN, I will use the well-known MD5 [15] hashing method. If you take a closer look, you will see that timestamps play an important role. They are in fact the only variables in the computations – the ISN, PSK, target IP, and port are constants. The precise approach is described elsewhere [8] [12].
Stealth Socks
How does TCP Stealth work in practical terms, though? Targeted manipulation of the ISN requires modifications to the TCP stack. On Linux, the stack is implemented in the kernel. When I was writing this article, the plain vanilla kernel knew nothing about TCP Stealth. In other words, users wanting to try this have no alternative to patching the Linux kernel. For more details, see the box "TCP Stealth in Daily Linux Use."
The patch only changes 11 files all told and only a handful of functions in those files. These include changing the way the client part of TCP opens connections (e.g., tcp_v4_connect()
and tcp_v6_connect()
) and their counterparts on the server side, of course; this mainly means tcp_v4_do_rcv()
and tcp_v6_do_rcv()
.
You need to enable TCP Stealth in your network stack before opening the connection. This means the required configurations start with the sockets. On Linux, a system call to setsockopt()
takes care of this. Finally, the kernel has a new configuration option (Figure 2) and an extended TCP socket structure.
A Linux kernel that supports TCP Stealth is only one part of the preparations that you need to make. In the second step you need to prepare your choice of application for accessing menu functions. A sample implementation of both the client and the server side is available online [11] [12]. The decisive part is shown in Listing 2.
Listing 2: Source Code Snippet
01 #define TCP_STEALTH 26 02 [...] 03 # Password: 04 char secret[64] = "This is my magic ID."; 05 [...] 06 # Setsocket-call: 07 if (setsockopt(sock, IPPROTO_TCP, TCP_STEALTH, secret, sizeof(secret))) { 08 printf("setsockopt() failed, %s\n", strerror(errno)); 09 return 1; 10 } 11 [...]$
The new system call setsockopt()
can also be traced using analysis tools such as strace
:
$ grep setsockopt tcp_stealth_server.strace 29392 setsockopt(3, SOL_TCP, 0x1a /* TCP_??? */, "This is mymagic \ ID.\0\0 \0\0\0\0\0\0\0 ...\0\0\0\0\0\0\0\0\0\ 0\0", 64) = 0
If you trace setsockopt()
with Strace (Listing 2), you can easily see that TCP Stealth does not perform any encryption.
Despite this, TCP Stealth can ensure data integrity (more on that later). At this point, it is important to note that a local attacker can sniff certain information because they are simply available in the clear. Additionally, authentication only works in one direction. The server checks whether the client is authorized to open a connection, and it checks the PSK as a credential. However, the client cannot check whether it is really talking to the right server.
Data Integrity
As I mentioned previously, the typical port knocking setup does not provide any protection against man-in-the-middle attacks. The typical argument here is that the application itself is responsible for this kind of protection. However, a very short time slot still provides an attack vector. This is the period of time between successfully knocking and the connection being established at the protocol level.
In the present case, this is equivalent to the phase between successfully authenticating via the PSK through completion of the three-way handshake. TCP Stealth can provide protection here by checking the integrity of a small volume of data. The idea is that the client injects additional information into the first data packet. The server checks the correctness of the data and either allows the connection or rejects it.
The implementation follows principles that are similar to authentication using the pre-shared key. The integrity check is performed before establishing the connection by configuring the network sockets appropriately with setsockopt()
. To allow this to happen, the kernel patch mentioned previously introduces the TCP_STEALTH_INTEGRITY
and TCP_STEALTH_INTEGRITY_LEN
options. The former is only relevant for the client side; the additional data is defined separately.
In contrast to this socket option, TCP_STEALTH_INTEGRITY_LEN
is only used on the server side; it states the volume of data that is relevant for the integrity check. At the current point in time, this check is not possible without authenticating.
Figure 3 shows the three-way handshake with TCP Stealth enabled and an integrity check. In the first step of establishing the TCP connection, the client sends information relating to the PSK in the ISN. Where the client normally completes the handshake, it initiates the integrity check. TCP Stealth again relies on the MD5 checksumming method. The input data is the PSK and the integrity-check data mentioned previously – simply concatenated.
The 128-bit checksum is broken down into eight chunks of equal length by TCP Stealth and then XOR'd. The result ends up in the second part of the 32-bit ISN. In the first 16 bits, TCP Stealth hides the PSK (see above). Once the additional data has been transferred in the third step of the TCP connection establishment, the server can perform the integrity check. You can read all about the precise details online [8] [12].
Closed Doors?
For an application to be allowed to use TCP Stealth, it must set the appropriate socket options. In other words, this makes both modifications to the source code and creating new binary code inevitable. That said, if you are a regular user of open source, you will not have any trouble doing this. However, for many applications, users only have access to the binary and have no access at all to the source code. Again, TCP Stealth has a trick up its sleeve to solve this, as well.
The magic word here is libknockify
[11] [12]. This is a library that the developer loads using the LD_PRELOAD
mechanism before executing the application itself. At program run time, libknockify
overwrites system calls such as getsockopt()
, listen()
, or connect()
with the TCP Stealth counterparts (see also Listing 3). You can configure the PSK or the required details for the integrity check either via the shell environmental variables KNOCK_SECRET
and KNOCK_INT_LEN
or you can store them in $HOME/.knockrc
.
Listing 3: Server Process with libknockify.so
01 $ cat $HOME/.knockrc 02 KNOCK_INTLEN=0 03 KNOCK_SECRET="Nuer fuer LM-Leser :-)" 04 KNOCK_LOGLVL=2 05 $ 06 $ LD_PRELOAD=/usr/local/lib64/libknockify.so ncat 127.0.0.1 -l 4242 07 setting loglvl to 2 08 Initializing hooks ... 09 Resolving symbol socket ... 10 Resolving symbol connect ... 11 Resolving symbol listen ... 12 Resolving symbol write ... 13 Resolving symbol send ... 14 Resolving symbol sendto ... 15 Resolving symbol sendmsg ... 16 Resolving symbol getsockopt ... 17 Resolving symbol close ... 18 Resolving symbol epoll_wait ... 19 Resolving symbol select ... 20 All dynamic symbols could be resolved. 21 socket(2, 1, 6) = 3 22 Socket 3 will be Knockified. 23 Knockified. 24 listen(3, 10) = 0
Listing 3 sets a log-level parameter. A 0
value tells libknockify
not to make so much noise, whereas a value of 3
offers developers maximum verbosity. This is very useful for troubleshooting. A matching shell variable goes by the name of KNOCK_LOGLVL
. In our lab, we continually experienced connection failures. When we asked Julian Kirsch about this, he confirmed that the library is only a workaround and that the focus is quite clearly on appropriate modifications in the source code of the application.
What Else?
The "TCP Stealth in Daily Linux Use" box describes modifying the Linux kernel and OpenSSH to use this technology. You will find patches for systemd
online [11]. If you feel inclined to do so, you can use the code snippet you find there to modify other network services.
One more network aspect also must be considered. It is not unusual for individual packets to be lost. TCP then ensures that they are resent. If, however, the packet happens to be the first packet in the three-way handshake, à la TCP Stealth, this is fatal. As I described previously, the timestamp plays an important role in computing the ISN. In case of a resent packet, it no longer fits the bill.
The procedure for resending packets is part of the TCP stack. For Linux, this would mean that more changes are required to the kernel. The patch I described already takes the necessary precautions. For sockets that run in TCP Stealth mode, the kernel uses the original timestamp and does not generate a new one.
Conclusions and Outlook
TCP Stealth looks pretty promising. The project website has comprehensive and useful documentation. Sample programs and prepared patches make it easy to get started. In contrast to alternatives such as SilentKnock, TCP Stealth has far less trouble with Network Address Translation, which makes it more attractive. The integrity check to prevent man-in-the-middle attacks is also something that is not to be sniffed at.
Inquisitive users could also take a look at the Bridge SPA [19] or Knockknock [20] projects. What remains at the end of the day is the limitation to TCP as the transport protocol. The project presented here could take a decisive step forward if it does manage to make it into the Linux kernel.