Inject strings on an already open TCP/IP connection using gdb

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.