===[ 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') ------------------------------------------------------------------------------- -----------------------------------[ code ]------------------------------------ # python3 connnect_port_0.py 0 # tcpdump -lni eth0 20:39:32.236669 eth0 In IP > 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): -----------------------------------[ code ]------------------------------------ iptables -t nat -A PREROUTING -p tcp --dport 0 -j REDIRECT --to-port 1234 iptables -A INPUT -s -p tcp --dport 1234 -j ACCEPT ------------------------------------------------------------------------------- 'nc' will be listening on the port 1234: -----------------------------------[ code ]------------------------------------ nc -nvl 1234 ------------------------------------------------------------------------------- Now we will try to connect to the port 0: -----------------------------------[ code ]------------------------------------ python3 connnect_port_0.py 0 ------------------------------------------------------------------------------- 'nc' is showing the payload from the python script: -----------------------------------[ code ]------------------------------------ $ nc -nvl 1234 Listening on 1234 Connection received on 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: -----------------------------------[ code ]------------------------------------ $ nc -nv 0 nc: port number too small: 0 -------------------------------------------------------------------------------

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

DNAT rule can look like this: -----------------------------------[ code ]------------------------------------ iptables -t nat -A PREROUTING -p tcp --dport 0 -j DNAT --to -------------------------------------------------------------------------------

===[ 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]. -----------------------------------[ code ]------------------------------------ # nc -nvlp 0 Listening on 45887 # nc -nvlp 0 Listening on 38160 ------------------------------------------------------------------------------- This kernel function does exactly what we saw above: -----------------------------------[ code ]------------------------------------ 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': -----------------------------------[ code ]------------------------------------ # 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