Finding Masquerading Processes With procfs

This post demonstrates a method to find masquerading processes on Linux systems. This technique may work on other operating systems which have implemented procfs, such as FreeBSD.

What is Process Masquerading?

Process masquerading is a technique used by malware which makes a malicious process appear benign with the intent to trick systems administrators or incident responders.

Malware can overwrite its own argument vector, making it appear to be a httpd process or a kernel thread rather than ./awesome_malware.py.

When an administrator views the process list with ps, top, or similar tools, the malware is easily overlooked as something that is supposed to be running.

Real-World Process Masquerading Example

In the following code snippet, the argument vector is overwritten with a random member of a list of innocent-looking processes:

my @rps = ("/usr/local/apache/bin/httpd -DSSL",
           "/usr/sbin/httpd -k start -DSSL",
           "/usr/sbin/httpd",
           "/usr/sbin/sshd -i",
           "/usr/sbin/sshd",
           "/usr/sbin/sshd -D",
           "/usr/sbin/apache2 -k start",
           "/sbin/syslogd",
           "/sbin/klogd -c 1 -x -x",
           "/usr/sbin/acpid",
           "/usr/sbin/cron");
my $process = $rps[rand scalar @rps];

The original source for this malware can be viewed here: https://gist.github.com/tlongren/afe81698cafaafcbe386#file-bot-pl-L35

If this malware is running, it will show up as if it were httpd, apache, sshd, syslogd, klogd, acpid, or cron in a process list rather than something like /dev/shm/ddos-perlbot.pl.

Since these bogus process names are so common on Linux hosts, it is very easy to overlook.

Methodology

One method to detect these processes is by comparing each running process’ /proc/PID/cmdline to the Name value in /proc/PID/status. If these don’t match, it is worth investigating the process.

In my experience, GNU screen and systemd are common false positives.

This is a simple shell script which reveals these processes:

#!/bin/sh

for process in $(find /proc/[0-9]*/ -maxdepth 0 -type d); do
    pid=$(echo $process | cut -d / -f 3)

    if [ ! -e ${process}cmdline ]; then
	continue
    fi

    cmdline=$(tr '\0' '|' < ${process}cmdline)
    if [ "$cmdline" = "" ]; then
	continue
    fi

    cmd=$(echo $cmdline |cut -d '|' -f 1)
    status=$(grep -E "^Name:" ${process}status)

    echo $cmdline | grep $(echo $status |awk '{print $2}') >/dev/null
    if [ $? = 1 ]; then
	echo "PID:             $pid"
        echo "command:         $cmd"
	echo "/proc/X/cmdline: $cmdline"
	echo "/proc/X/status:  $status"
	echo
    fi
done

Example

This C program will overwrite its process name with “fake”, which will make it show up as “fake” in the output of ps. It sleeps for 5 minutes, giving us plenty of time to observe its behavior.

#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]) {
	/* Overwrite argv[0] with "fake" */
	memset(argv[0], 0, strlen(argv[0]));
	strcpy(argv[0], "fake");

	sleep(300); /* Sleep for 5 minutes */

	return EXIT_SUCCESS;
}

Next, we will compile and run this program, and check what this process shows up as with ps.

daniel@wildcat ~ % gcc -o masquerade masquerade.c 
daniel@wildcat ~ % ./masquerade &
[1] 461828
daniel@wildcat ~ % ps ax |grep 461828
 461828 pts/2    SN     0:00 fake
 461911 pts/2    S+     0:00 grep --color 461828

Running the script from the previous section reveals this process. Notice the systemd false positive:

daniel@wildcat ~ % ./masq.sh 
PID:             1
command:         /sbin/init
/proc/X/cmdline: /sbin/init|splash|
/proc/X/status:  Name:	systemd

PID:             461828
command:         fake
/proc/X/cmdline: fake|||||||||
/proc/X/status:  Name:	masquerade