[TXT]     [HOME]     [TOOLS]     [GAMES]     [RSS]        [ABOUT ME]    [GITHUB]

.-----------------------------------------------------------------------------.
|      Linux: bind and connect to port 0 with iptables REDIRECT or DNAT       |
'-----------------------------------------------------------------------------'
updated: 2022-09-13


===[ How to use port 0 in Linux? ]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

TCP and UDP ports are represented as a 16 bit unsigned int, that means the ports are numbered from 0 to 65535. My question is: is it possible to use the port 0? From the IANA specification the port 0 is reserved, but it is still a valid port. Theoretically the port 0 should be "bindable", but on Linux it is not possible to directly use the value 0 as a port number, because if we call 'bind(2)' with the value 0, kernel will assign [ref2] some free port (from a pool of free ports [ref3]). First thing first: it is even possible to connect to the port 0 with typical network syscalls like 'connect(2)' and 'send(2)' (or 'sendto(2)'), without constructing a custom packet by using a RAW socket? Quick test: ----------------------------[ connnect_port_0.py ]-----------------------------
import socket
import sys

s = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
s.connect ((sys.argv[1], int (sys.argv[2])))
s.send (b'brm\n')
-------------------------------------------------------------------------------
# python3 connnect_port_0.py 172.19.1.101 0

# tcpdump -lni eth0
20:39:32.236669 eth0  In  IP 172.19.1.1.49812 > 172.19.1.101.0: Flags [S], seq 3766843908, win 64240, options [mss 1460,sackOK,TS val 4164711871 ecr 0,nop,wscale 7], length 0
Yes! That worked like a charm. Now, it is time we tried to listen on the port 0. If we cannot use 'bind(2)' directly, then what about iptables/netfilter? What if we create a DNAT rule that will redirect any connection to the port 0 to another port? PoC: We will create a 'REDIRECT' rule ('DNAT' would work as well), which will rewrite the destination port with the value 0 of any incoming packet to the port 1234 (on which we will listen):
iptables -t nat -A PREROUTING -p tcp --dport 0 -j REDIRECT --to-port 1234
iptables -A INPUT -s 172.19.1.1 -p tcp --dport 1234 -j ACCEPT
'nc' will be listening on the port 1234:
nc -nvl 172.19.1.101 1234
Now we will try to connect to the port 0:
python3 connnect_port_0.py 172.19.1.101 0
'nc' is showing the payload from the python script:
$ nc -nvl 172.19.1.101 1234
Listening on 172.19.1.101 1234
Connection received on 172.19.1.1 33722
brm
Success! Now we know at least one way to dispatch traffic from the port 0 on Linux. Practically it is not that useful. It is not even a great way to hide something, because any good pentester/attacker will sooner or later scan the port 0. But we have learned somewhat exotic thing and therefore we can be proud of ourselves!

===[ Note on Nmap behavior ]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Nmap can easily scan the port 0: 'nmap -p0', but it is not the default and more so if we would use 'nmap -p-' it would scan all the ports BUT 0! If we really want to scan ALL of them, we have to use option: 'nmap -p0-'.

===[ Netcat ]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

OpenBSD netcat ('nc.openbsd') have a condition, that forbids connection to the port 0, so it is not possible to use it for connection to port 0:
$ nc -nv 172.19.1.101 0
nc: port number too small: 0

===[ DNAT ]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DNAT rule can look like this:
iptables -t nat -A PREROUTING -p tcp --dport 0 -j DNAT --to 172.19.1.101:1234

===[ Some kernel details ]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

'bind(2)' is not working because a kernel code will automatically/dynamically assign "random" port if 'port' variable is set to 0 [ref1].
# nc -nvlp 0
Listening on 0.0.0.0 45887

# nc -nvlp 0
Listening on 0.0.0.0 38160
This kernel function does exactly what we saw above:
int inet_csk_get_port(struct sock *sk, unsigned short snum)
{
...
    if (!port) {
        head = inet_csk_find_open_port(sk, &tb, &port);
...
        goto success;
    }
We can nicely see this by using a kprobe (with stack trace) on the kernel function 'inet_csk_get_port':
# kprobe -s 'inet_csk_get_port %si:x16'

2021-12-31 21:36:10             <...>-16270   [000] .... 41153.935973: f1: (inet_csk_get_port+0x0/0x5d0) arg1=0x0
2021-12-31 21:36:10                nc-16270   [000] .... 41153.936005: <stack trace>
2021-12-31 21:36:10   => inet_csk_get_port
2021-12-31 21:36:10   => __inet_bind
2021-12-31 21:36:10   => __sys_bind
2021-12-31 21:36:10   => __x64_sys_bind
2021-12-31 21:36:10   => do_syscall_64
2021-12-31 21:36:10   => entry_SYSCALL_64_after_hwframe
2021-12-31 21:36:10                nc-16270   [000] .... 41153.936013: f1: (inet_csk_get_port+0x0/0x5d0) arg1=0xb33f
2021-12-31 21:36:10                nc-16270   [000] .... 41153.936016: <stack trace>
2021-12-31 21:36:10   => inet_csk_get_port
2021-12-31 21:36:10   => inet_csk_listen_start
2021-12-31 21:36:10   => inet_listen
2021-12-31 21:36:10   => __sys_listen
2021-12-31 21:36:10   => __x64_sys_listen
2021-12-31 21:36:10   => do_syscall_64
2021-12-31 21:36:10   => entry_SYSCALL_64_after_hwframe
Firstly we see that the argument to 'inet_csk_get_port' is 0 (that is our argument to 'nc', which is calling '__x64_sys_bind(0)'), this call is choosing a random port (from a pool of free ports) and the second call (when 'listen(2)' is being called) shows that the argument is 0xb33f. If we convert it from big endian to little endian, we get the number 45887 and that is the port number of the first invocation of 'nc' (as we can see above). This looks like it is not possible to bind the port 0 without rewriting part of the Linux network stack.

===[ References ]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

[ref1] https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml [ref2] https://elixir.bootlin.com/linux/v5.10.89/source/net/ipv4/inet_connection_sock.c#L354 [ref3] https://elixir.bootlin.com/linux/v5.10.89/source/net/ipv4/inet_connection_sock.c#L182