If you've ever been a Unix system administrator, you may have encountered "open but unlinked" files in the course of your normal duties. The typical scenario is a user who's launched a process that creates an unexpectedly large output file which consumes all of the free space in the partition. In a panic, the user deletes the output file but leaves the process running. Unfortunately, the operating system is not allowed to reclaim the space until the last process that has the output file open actually exits. So until the user kills their process, the space is still in use and the file system is full. But when you as the system administrator logs in to free some space in the partition, you're unable to find the massive file that's consuming all of the space with your normal file system tools because the file has been unlinked (deleted) from the file system. Finding the process that's holding the file open and killing it would free the space, but that requires some specialized knowledge and trickery which we'll see a little later.
In an incident, attackers have been known to use open but unlinked files to hide their data. For example, suppose the attacker were running a packet sniffer that was capturing usernames and passwords off your network and storing it in a file. Perhaps they have another process that's reading the data as it's placed in the file and using some covert channel to move it off system. At this point the attacker could delete the data file: the packet sniffer would continue writing data to the file and their reader process could continue reading the data because they opened the file before it was removed from the file system, but the system administrator would have trouble locating the file because it's now unlinked from the file system. In fact, the attacker can even delete the executables for the packet sniffer and the reader process from the file system and the current processes will continue to run.
This kind of open but unlinked file data can be difficult to recover from a "cold" system image, because the minute the system is shut down and the attacker's processes are terminated, the data in these files just becomes part of the free block collection and must be recovered like any other deleted file data. However, if you have the luxury of analyzing the running system, it is extremely easy to spot and recover this kind of file data.
Creating Our Test Case
As a stand-in for our attacker's hypothetical packet sniffer, I'm going to start a tcpdump process and have it dump its packet captures into a file. Since I plan on removing the tcpdump binary as part of my demonstration, I'm first going to make a copy of the binary in /tmp:
# cp /usr/sbin/tcpdump /tmp/tcpdump # /tmp/tcpdump -w /tmp/capture &  12437 tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes # ls -l /tmp/tcpdump /tmp/capture -rw-r--r-- 1 root root 4096 2009-01-21 18:08 /tmp/capture -rwxr-xr-x 1 root root 639416 2009-01-21 18:07 /tmp/tcpdump
So far, so good. Our tcpdump process is running and has captured a little bit of data. Now I'm going to remove the binary and the output file and verify that the process is still running:
# rm /tmp/tcpdump /tmp/capture # ls -l /tmp/tcpdump /tmp/capture ls: cannot access /tmp/tcpdump: No such file or directory ls: cannot access /tmp/capture: No such file or directory # ps -ef | grep tcpdump root 12437 12289 0 18:08 pts/1 00:00:00 /tmp/tcpdump -w /tmp/capture
Great! The process is still active, but neither the binary nor its output file are visible to standard Unix tools like ls. Now let's have some fun.
The Power of lsof
It turns out that the highly indispensible lsof utility has an option for detecting exactly these open but unlinked files:
# lsof +L1 COMMAND PID USER FD TYPE DEVICE SIZE NLINK NODE NAME init 1 root 0u CHR 5,1 0 623 /dev/console (deleted) init 1 root 1u CHR 5,1 0 623 /dev/console (deleted) init 1 root 2u CHR 5,1 0 623 /dev/console (deleted) wineserve 10510 hal 31u REG 254,1 16777216 0 42233 /tmp/.wine-1000/server-fe05-4ee00e/anonmap.WKvI4J (deleted) firefox 10826 hal 60u REG 254,4 8200 0 139466 /var/tmp/etilqs_wkK3U9h1sAcQpD3 (deleted) tcpdump 12437 root txt REG 254,1 639416 0 25463 /tmp/tcpdump (deleted) tcpdump 12437 root 4w REG 254,1 65536 0 25500 /tmp/capture (deleted)
The "+L1" option is translated as "show me all files with link count less than one"- in other words, "show me all files with link count zero" which is just another way of saying "files which have been unlinked from the file system". It's almost anti-climactic how easy it is to spot these files with lsof.
However, the above output also demonstrates another important aspect of this discussion: not all open but unlinked files are necessarily cause for concern. There are a few processes that are common to the Unix/Linux platform that sometimes make use of open but unlinked files as part of their normal operations. This is clearly a situation where one must "know thy systems"- be familiar with how the operating systems you'll be investigating appear during normal operations- so that you can spot discrepancies. That being said, programs that I've seen regularly using open but unlinked files include the Linux init process, Firefox, the Wine Windows emulator (all of which you can see in the output above), and VMware Server.
OK, at this point we've spotted the open but unlinked files, but how can we recover the data that's in them? The lsof output above gives us the inode numbers for the files if you look in the "NODE" column (25463 for our deleted tcpdump binary and 25500 for the output file). This means we could use a tool like icat from the Sleuthkit to dump the contents of these files. But it turns out that there's another approach that doesn't require any tools that aren't already present in most Unix-like operating systems.
/proc to the Rescue!
The lsof output also gives us the process ID of the processes with open but unlinked files. In this case, our tcpdump process is PID 12437. Let's head over to the /proc/12437 directory and see what we can see:
# cd /proc/12437 # ls attr cpuset io mountinfooom_score smaps auxv cwd latency mountspagemap stat cgroup environ limits mountstatsroot statm clear_refs exe loginuid netsched status cmdline fd maps numa_mapsschedstat task coredump_filter fdinfo mem oom_adjsessionid wchan
There's a lot of interesting data in /proc, but for our purposes the "exe" object is the first interesting thing: "exe" is always a copy of the binary image of the running process. So all we have to do to recover the deleted executable is make a copy of this "file" under /proc. Obviously in a real incident you wouldn't want to copy the file back into the file system of the compromised machine because you'd potentially be overwriting important evidence. You'd either want to copy it to a portable drive you've connected to the system, or move the executable off the machine via the network with a command line like:
# cat /proc/12437/exe | nc 192.168.1.1 9999
This assumes you've got a machine acting as a "collector" at 192.168.1.1 with another netcat process listening on port 9999/tcp and writing the incoming data into a file.
Just to prove to you that this works, however, let me just copy the binary into my local file system and demonstrate that the result is a working executable:
# cp /proc/12437/exe /tmp/testing # /tmp/testing --version testing version 3.9.8 libpcap version 0.9.8 Usage: testing [-aAdDeflLnNOpqRStuUvxX] [-c count] [ -C file_size ] [ -E algo:secret ] [ -F file ] [ -i interface ] [ -M secret ] [ -r file ] [ -s snaplen ] [ -T type ] [ -w file ] [ -W filecount ] [ -y datalinktype ] [ -Z user ] [ expression ]
Again, in a real incident you wouldn't just blindly execute a suspicious program you'd recovered in this manner. You'd only want to experiment with a copy of the executable in an isolated "sandbox" type system.
OK, so what about recovering the data file that the tcpdump process is writing? If you look at the directory listing of /proc/12437, you'll notice that there's an object called /proc/12437/fd. "fd" here stands for "file descriptor" and /proc/12437/fd is actually a directory that contains links to all files this process currently has open. Let's take a look at this directory in more detail:
# ls -l /proc/12437/fd total 0 lrwx------ 1 root root 64 2009-01-21 18:14 0 -> /dev/pts/1 lrwx------ 1 root root 64 2009-01-21 18:14 1 -> /dev/pts/1 lrwx------ 1 root root 64 2009-01-21 18:12 2 -> /dev/pts/1 lrwx------ 1 root root 64 2009-01-21 18:14 3 -> socket: l-wx------ 1 root root 64 2009-01-21 18:14 4 -> /tmp/capture (deleted)
Notice that the link names are named for the internal file descriptor number used by the process, but the names of the files associated with these file descriptors can be clearly seen, along with the fact that the /tmp/capture file has been deleted.
The cool thing is that you can use these links as arguments to normal Unix file system commands. For example, if you wanted to copy the data in the deleted file over the network to your capture workstation:
# cat < /proc/12437/fd/4 | nc 192.168.1.1 9999
There's a little bit of subtlety to the command above. Notice that I'm not just doing "cat /proc/12437/fd/4", because that would only give me a snapshot of the contents of the file at a particular instant in time. Instead I'm using input redirection ("<") to dump the current state of the file and then continue reading data and dumping it into netcat as it's being written into the file. Our cat process will only terminate when the tcpdump file closes its output file. This is one of the things that makes this approach superior to just using a tool like icat to dump the current state of the file.
Some Final Thoughts
It's fairly uncommon to find attackers making use of open but unlinked files in this manner. But if you're accessing the compromised system anyway- for example to capture a memory dump before you shut the machine down- it doesn't hurt to run a quick lsof command to check for any instances of these files. In the cases where they do exist, the techniques described above can allow you to quickly recover some information that is typically critical to analyzing what the attacker is doing to your system.
Also, don't forget about the /proc/<pid>/cwd link, which is a link to the current working directory of the process. If the attacker was foolish enough to start the suspicious process from their rootkit installation directory, you may be able to zero-in on the compromise in record time!
Hal Pomeranz is an independent Computer Security/IT consultant and SANS Institute Faculty Fellow. He wants to name his first child "Elesso'ef Slashproc", which probably means it's a good thing he's not planning on having kids.