Had a situation at $WORK where network connections were just hanging there, open, with no activity. So I needed to send something, whatever, on the open connection, just to see how it behaves.
TL;DR: attach to the process using the debugger, gdb(1)
, then call send(2)
on the network socket
In very simple terms, when a program wants to talk to another program over the network, it will create what is called a network socket(2)
on the local computer. The local kernel then “connects” that socket to the remote computer, where another socket is open. In Unix a network socket is (like) a file. It can be written to or read from. So sending something over a network connection is just like writing something to a file.
When a program wants to write to a file, it asks the kernel to open the file first. That’s done by calling open(2)
for regular files or socket(2)
for sockets. The kernel returns a file descriptor that the program then uses to tell the kernel to write(2)
or read(2)
stuff from/to the file. The file descriptor is a number, int type.
Since that network socket is treated like a file, it will show up in the output of lsof(8)
(on Linux) or fstat(1)
(on BSD). Normally socket is owned by the process that created it, so it can’t be used by something else. In order to write to it, we need the respective process to do it, so we attach a debugger to the process.
When a debugger attaches to a process it will use the ptrace(2)
system call and put the process “on pause”, if you will. Which is very handy for a number of reasons, one being that it gives humans enough time to type things on the keyboard.
Here’s an example of how that works on FreeBSD:
First, let’s open a “server” using netcat:
nc -lk 12345
This will tell nc to start listening on port 12345
Connect a client to that “server”:
nc localhost 12345
Just to double check what is going on, start a tcpdump on that port:
sudo tcpdump -tnnX -i lo0 port 12345
Type something in either netcat and you should see it echoed in the other one and in the tcpdump session. The -X on tcpdump shows the contents of the packet, so whatever is typed in netcat should show up in tcpdump, since it’s a clear text connection.
We’re set. So far we’re simulating a real connection. Let’s see about injecting something. We’ll inject a string in the client netcat session. So first, we need the socket descriptor.
% ps www | grep "nc [l]ocalhost" 28312 5 I+ 0:00.00 nc localhost 12345 % fstat -p 28312 USER CMD PID FD MOUNT INUM MODE SZ|DV R/W usrnm nc 28312 text / 11805 -r-xr-xr-x 27520 r usrnm nc 28312 ctty /dev 104 crw--w---- pts/5 rw usrnm nc 28312 wd /usr/home/usrnm 4 drwxr-xr-x 70 r usrnm nc 28312 root / 4 drwxr-xr-x 28 r usrnm nc 28312 0 /dev 104 crw--w---- pts/5 rw usrnm nc 28312 1 /dev 104 crw--w---- pts/5 rw usrnm nc 28312 2 /dev 104 crw--w---- pts/5 rw usrnm nc 28312 3* internet stream tcp fffff8001cb25800 % lsof -p 28312 lsof: WARNING: compiled for FreeBSD release 10.0-RELEASE-p16; this is 10.0-RELEASE-p15. COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME nc 28312 usrnm cwd VDIR 232,2481586245 71 4 /usr/home/usrnm nc 28312 usrnm rtd VDIR 222,3180200036 28 4 / nc 28312 usrnm txt VREG 222,3180200036 27520 11805 /usr/bin/nc nc 28312 usrnm txt VREG 222,3180200036 259248 247605 /libexec/ld-elf.so.1 nc 28312 usrnm txt VREG 222,3180200036 29056 12387 /lib/libipsec.so.4 nc 28312 usrnm txt VREG 222,3180200036 1511136 12372 /lib/libc.so.7 nc 28312 usrnm 0u VCHR 0,104 0t13862 104 /dev/pts/5 nc 28312 usrnm 1u VCHR 0,104 0t13862 104 /dev/pts/5 nc 28312 usrnm 2u VCHR 0,104 0t13862 104 /dev/pts/5 nc 28312 usrnm 3u IPv4 0xfffff8001cb25800 0t0 TCP localhost:48193->localhost:12345 (ESTABLISHED) %
That’s the output of both fstat and lsof. Last line is what we are looking for. FD is the file descriptor column. In our case, it’s 3. 0, 1 and 2 are stdin, stdout and stderr, respectively. So the next descriptor is going to be 3. It so happens that this is our socket, because netcat doesn’t do much else, a real process will probably have a different number.
So now we have the process number and the file descriptor, let’s attach the debugger and send(2) a message:
% gdb GNU gdb 6.1.1 [FreeBSD] Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "amd64-marcel-freebsd". (gdb) file /usr/bin/nc Reading symbols from /usr/bin/nc...(no debugging symbols found)...done. (gdb) attach 39927 Attaching to program: /usr/bin/nc, process 39927 Reading symbols from /lib/libipsec.so.4...(no debugging symbols found)...done. Loaded symbols for /lib/libipsec.so.4 Reading symbols from /lib/libc.so.7...(no debugging symbols found)...done. Loaded symbols for /lib/libc.so.7 Reading symbols from /libexec/ld-elf.so.1...(no debugging symbols found)...done. Loaded symbols for /libexec/ld-elf.so.1 0x0000000800b1e53a in poll () from /lib/libc.so.7 (gdb) help call Call a function in the program. The argument is the function name and arguments, in the notation of the current working language. The result is printed and saved in the value history, if it is not void. (gdb) call send(3, "look, ma! no hands!", 20) $1 = 20 (gdb)
That’s it. Message sent, it should show up in the “server” netcat as well as tcpdump. It will not show up in the client, of course. The key line is line number 25, where a function in the program is called. send(2)
is a standard libc function, pretty much guaranteed to be present in any program. send(2)
is used for writing to sockets, write(2)
for writing to files, but since a socket is a file write(2)
would work just as good in this case. The arguments are the file descriptor (3), the string (look ma! no hands!) and the length of the string.