visit
Having said that, traditionally speaking, pure shellcode attacks and attacks built from ROP gadgets are of limited use. At the end of the day they really just act as a way of opening the door to the house. You still want to steal the guns and the money and that happens post exploitation.
You really want to execute a different program of some kind other than the one that you are exploiting otherwise there is no point in attacking to begin with - a lot of people gloss over this fact.That program preferably comes in the form of a shell, but at least something like mysqldump or a monero cryptominer (which was all the rage in the past few years) or something. You don't want to stuff an entire mysql client as a payload written in shellcode or rop gadgets - that's just not really feasible.The trouble is with unikernels is that they are single process by definition. They don't have inherent facilities inside the kernel to run multiple processes although there are many implementations, included, that will allow you access to all the threads you might want or need so you can still have all the performance. What this means is that you can't just fork/exec and run your cryptominer - there is just no support in the kernel to do so.So - even if you discover a vulnerability inside a unikernel - how do you go about running that other program you want to run?
This is to prove they can detect and remediate it.This will catch a lot of low quality malware. Techniques like process hollowing and process injection on the other hand, act like good boy scouts (or burners) and leave no trace on the filesystem. Sometimes these are referred to as "in memory attacks".Windows has made this "feature" pretty straight-forward to exploit with calls such as which does exactly what it sounds like.However, if you are reading this article, you probably don't care about windows. That's a lost cause, unbeknownst to the poor city governments that suffer from ransomware. Most modern infrastructure runs on linux.
You see on linux launching a new process typically entails running fork and exec'ng it. Everyone knows unikernels don't support fork, but some people don't realize we don't support exec either.Let's look at some of the methods you could use on linux.
One method includes using
LD_PRELOAD
with a shared library to override code in the process you want to run although that implies a few things.One - it implies you are starting a process with local access and two it's not an existing process. This is classic process hollowing, basically spinning up an innocent program, then loading in your parasite and then running that all the while letting the rest of the system seem like everything is ok.Another method uses ptrace.Ptrace is one of those syscalls that gets overly abused by lots of different things. *ahem* gvisor *ahem*.
The technique here is pretty simple. You take your shared library and just inject it via ptrace.Good thing is that you are almost guaranteed to have this capability turned off on whatever box you are on currently - you can check to verify though via proc:/proc/sys/kernel/yama/ptrace_scope
mount | grep /dev/shm
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv) {
printf("parasite process ID : \t%d\n", getpid());
FILE *parasite = fopen(argv[0], "r");
if (parasite == NULL) {
while(1) {
printf("running from memory!\n");
sleep(3);
}
} else {
printf("exec'ing in disk\n");
}
return 0;
}
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/memfd.h>
#include <err.h>
#include <stdlib.h>
#include <stdio.h>
int main() {
FILE *f;
unsigned char *bin;
long filelen;
printf("original process id:\t%d\n", getpid());
f = fopen("parasite", "rb");
fseek(f, 0, SEEK_END);
filelen = ftell(f);
rewind(f);
bin = (unsigned char *)malloc(filelen * sizeof(unsigned char));
fread(bin, filelen, 1, f);
fclose(f);
int fd;
if((fd = syscall(SYS_memfd_create, "h4x0r", MFD_CLOEXEC)) == -1)
err(1, "memfd_create");
write(fd, bin, filelen*sizeof(char));
char *binpath;
asprintf(&binpath, "/proc/self/fd/%d", fd);
char* argv[] = { "h4x0r", NULL };
char* envp[] = { NULL };
execve(binpath, argv, envp);
free(bin);
free(binpath);
}
eyberg@box:~/scratch$ ./main
original process id: 1480
parasite process ID : 1480
running from memory!
running from memory!
A lot of the userland exec's that you'll find online are not going to work in our environment for a variety of reasons and they won't be transferrable from unikernel to unikernel.Some are old and are 32bit based - nanos only runs 64bit code. Some only work for statically linked binaries - most binaries on linux nowadays are dynamically linked. Then there's the issue of stack smashing of which is on by default.There are a few over things preventing this from working in today (including the fact that we don't have proc) but having said that - if I were to attack an unikernel this is one way I'd go.