Im currently on a Security course and one of the tasks we got set today was to create a a small script that would close a port only once a TCP connection had fully established. There was another lab on using nessus which i was suppose to do first but lets be honest nessus isn’t exactly rocket science and this seemed much more fun. It didn’t take long before i hacked something together and came up with the following script
#!/bin/sh
while :
do
netstat -ant | awk '$6=="ESTABLISHED" {print $4,$5}' | while read IN
do
SRC=${IN% *}
DST=${IN#* }
if [ "${SRC#*:}" -eq "5555" ]
then
echo "Kill: ${DST%:*}"
iptables -I INPUT -s ${DST%:*} -p tcp --dport ${SRC#*:} -j REJECT --reject-with icmp-host-prohibited
fi
done
sleep 1
iptables -F
done
However i didn’t really like this because it left the old connection hanging around in the CLOSE_WAIT state. After my previous fairway in to the proc system I wondered if i could improve on the above script by closing the file handle directly. So the first thing to do is fetch the inode associated with the listening port. Previously i used `netstat -tnpo` for this but we shouldn’t need to call a binary to get this information so i decided to just parse the dev system which gave me the following script
#!/bin/bash
PORT=5555
H_PORT=$(echo "obase=16; ${PORT}" | bc)
while :
do
awk '{print $2,$10}' /proc/net/tcp | while read IN
do
SRC=${IN% *}
INODE=${IN#* }
print "Port: ${SRC#*:}, Inode: ${INODE}
done
done
Adding this with the hackary from the previous post we can get the associate pid and file handle, we could probably simplify the below as i suspect we could find out the pid in a better manner as we will likely already know the listening PID. Anyway i had the code from before so i may as well use it, this gives us
while :
do
awk '{print $2,$10}' /proc/net/tcp | while read IN
do
SRC=${IN% *}
INODE=${IN#* }
if [ "${SRC#*:}" == "${H_PORT}" -a ${SRC%:*} != '00000000' ]
then
for FD in /proc/*/fd/* ; do
SOCKET_INODE=$(stat -c %N ${FD} 2>/dev/null | awk -F\: '/socket:\[[0-9]+\]/ {gsub(/[\[\]]/, "", $NF);print $NF}')
if [ "${INODE}" == "${SOCKET_INODE%?}" ]
then
echo "Kill: ${FD}"
fi
done
fi
done
sleep 1
done
So now all we need to do is kill this file descriptor. A quick google showed this should be pretty simple to do with gdb, however every-time i tried this i kept getting the following error
GNU gdb Fedora (6.8-24.fc9)
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu".
Attaching to process 16157
Reading symbols from /usr/local/bin/nc...(no debugging symbols found)...done.
(no debugging symbols found)
0x0805c008 in ?? ()
(gdb) p close(4)
No symbol table is loaded. Use the "file" command.
(gdb) quit
The program is running. Quit anyway (and detach it)? (y or n) y
Detaching from program: /usr/local/bin/nc, process 16157
Now im a long way from being a gdb expert but it seemed that nc had not been compiled with the right options, i would have still expected it to be able to preform this function but alas i couldn’t kick it in the right place to make it listen to me. So i thought i would give ncat a go, nc is pretty obsolete anyway and its likely that ncat was compiled with symbols. A quick test and it looked like it worked.
gdb -p 6592
GNU gdb Fedora (6.8-24.fc9)
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu".
Attaching to process 6592
Reading symbols from /usr/bin/ncat...(no debugging symbols found)...done.
Reading symbols from /lib/libdl.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/libdl.so.2
Reading symbols from /lib/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
(no debugging symbols found)
0x00892416 in __kernel_vsyscall ()
Missing separate debuginfos, use: debuginfo-install nmap.i386
(gdb) p close (4)
$1 = 0
(gdb) quit
The program is running. Quit anyway (and detach it)? (y or n) y
Detaching from program: /usr/bin/ncat, process 6592
Awesome now we are in business just a matter of putting it all together and we get the following
#!/bin/bash
PORT=5555
H_PORT=$(echo "obase=16; ${PORT}" | bc)
while :
do
awk '{print $2,$10}' /proc/net/tcp | while read IN
do
SRC=${IN% *}
INODE=${IN#* }
if [ "${SRC#*:}" == "${H_PORT}" -a ${SRC%:*} != '00000000' ]
then
for FD in /proc/*/fd/* ; do
SOCKET_INODE=$(stat -c %N ${FD} 2>/dev/null | awk -F\: '/socket:\[[0-9]+\]/ {gsub(/[\[\]]/, "", $NF);print $NF}')
if [ "${INODE}" == "${SOCKET_INODE%?}" ]
then
PROC=(${FD//\// })
gdb -p ${PROC[1]} <<< "p close(${PROC[3]})"
fi
done
fi
done
sleep 1
done
Unfortunately there is a small problem with this. it seems that been so heavy handed and forcing the file handle to close causes ncat to get into a strange state. new connections seem to be assigned inode 0 and i can’t find a file handle associated with them so it doesn’t really fit the initial use case and means it is pretty useless, although, i think you will agree, it is elegant is its uselessness :). Its worth mentioning that the socket is still open and i can send data through it so its possible this trick may open other avenues we can exploit.
[root@linux ~]# netstat -tnpe
Proto Recv-Q Send-Q Local Address Foreign Address State User Inode PID/Program name
tcp 0 0 10.10.75.5:5555 10.10.75.3:57851 ESTABLISHED 0 0 -
tcp 0 0 10.10.75.5:5555 10.10.75.3:57859 ESTABLISHED 0 0 -
[root@linux ~]# tcpdump -nnvvi eth0 port 5555
23:05:04.160869 IP (tos 0x10, ttl 64, id 14484, offset 0, flags [DF], proto TCP (6), length 59)
10.10.75.3.57851 > 10.10.75.5.5555: Flags [P.], cksum 0x61e0 (correct), seq 111:118, ack 1, win 8235, options [nop,nop,TS val 31297540 ecr 5667926], length 7
23:05:04.160890 IP (tos 0x0, ttl 64, id 7215, offset 0, flags [DF], proto TCP (6), length 52)
10.10.75.5.5555 > 10.10.75.3.57851: Flags [.], cksum 0xae42 (correct), seq 1, ack 118, win 181, options [nop,nop,TS val 5668951 ecr 31297540], length 0
23:06:59.551591 IP (tos 0x10, ttl 64, id 27876, offset 0, flags [DF], proto TCP (6), length 59)
10.10.75.3.57859 > 10.10.75.5.5555: Flags [P.], cksum 0xba4f (correct), seq 2737832488:2737832495, ack 2038745254, win 8235, options [nop,nop,TS val 31408622 ecr 5085920], length 7
23:06:59.551611 IP (tos 0x0, ttl 64, id 21062, offset 0, flags [DF], proto TCP (6), length 52)
10.10.75.5.5555 > 10.10.75.3.57859: Flags [.], cksum 0x6570 (correct), seq 1, ack 7, win 181, options [nop,nop,TS val 5784341 ecr 31408622], length 0