Pure In-Memory (Shell)Code Injection In Linux Userland

EDB-ID:

46043

CVE:

N/A


Author:

rb

Type:

papers


Platform:

Linux

Date:

2018-12-14


# Pure In-Memory (Shell)Code Injection In Linux Userland

##### *date: 2018-12-14, author: rb*

## Introduction

Typical post-exploitation activities include reconnaissance, information gathering and privilege escalation. Sometimes an adversary may need additional functionality, such as when the target system does not provide the necessary tools by default, or when they need to speed up one of these post-exploitation actions.

In most cases dedicated tools are uploaded to the target system and ran. The biggest caveat of this approach is that artifacts left on disk, if detected, may reveal additional information to the defenders and potentially compromise the whole operation.

A lot of research has been conducted in recent years on performing code injection in the Windows operating system without touching the disk ([[1]](#References), [[2]](#References), [[3]](#References), [[4]](#References), [[5]](#References) to name a few). The same cannot be said about *NIX (and Linux specifically), but there are some great works from the past: skape and jt [[2]](#References), the grugq [[6]](#References), Z0MBiE [[7]](#References), Pluf and Ripe [[8]](#References), Aseem Jakhar [[9]](#References), mak [[10]](#References) or Rory McNamara [[11]](#References).

## Scenario

Imagine yourself sitting in front of a blinking cursor, using a shell on a freshly compromised Linux server, and you want to move forward without leaving any traces behind. You need to run additional tools, but you don't want to upload anything to the machine. Or, you simply cannot run anything because the *noexec* option is set on mounted partitions. What options remain?

This paper will show how to bypass execution restrictions and run code on the machine, using only tools available on the system. It's a bit challenging in an *everything-is-a-file* OS, but doable if you think outside the box and use the power this system provides.

The following paper is a direct result of experiments conducted by Sektor7 labs where new and improved offensive methods are researched and published.

## Payload (Shellcode) Delivery

Finding a reliable and stealthy way to deliver a payload/tool to a target machine is always a challenge for an adversary.

The most common method is to establish a new connection with C2 or a 3rd party server which hosts the desired tool and download it to the victim. This potentially generates additional artifacts on the network infrastructure (ie.  netflow, proxy logs, etc.).

In many situations, an attacker forgets that there is already an open control channel to the target machine - the shell session. This session can be used as a data link to upload a payload to the victim without the need to establish a new TCP connection with external systems. The downside of this approach is that a network glitch could result in the loss of both the data transfer and control channel.

In this paper, the two delivery methods will be referred to as out-of-band and in-band, respectively.  The latter option will be used as the primary way of transferring (shell)code.

![](img/in-band.png "Payload delivery scenario")

## Demonstration Environment

Our demonstrations and experiments will use the following setup: 

 * **Victim machine** running recent Kali Linux as a virtual machine
 * **Attacker machine** – Arch Linux running as a host system for VMs
 * **SSH connection** from the Attacker's machine to the Victim, simulating **shell access**
 * Simple ‘Hello world’ **shellcode** for x86_64 architecture (see [Appendix A](#Appendix_A))

## In-Memory-Only Methods

### Tmpfs

The first place an adversary can store files is **tmpfs**. It puts everything into the **kernel internal caches** and grows and shrinks to accommodate the files it contains. Additionally, starting from glibc 2.2, *tmpfs* is expected to be mounted at */dev/shm* for POSIX shared memory (*shm_open(), shm_unlink()*).

Here is an example view on mounted *tmpfs* virtual filesystems (from Kali):

![](img/tmpfs1.png)

By default */dev/shm* is mounted without the *noexec* flag set. If a paranoid administrator turns it on, it effectively kills this method – we can store data but cannot execute (*execve()* will fail).

![](img/tmpfs2.png)

We will come back to */dev/shm* later.


### GDB

**GNU Debugger** is a default debugging tool for Linux. It’s **not commonly installed** on production  servers, but sometimes can be found in development environments and in a few embedded/dedicated systems. According to the *gdb(1)* manual:

```
GDB can do four main kinds of things (plus other things in support of these) to 
help you catch bugs in the act:
 * Start your program, specifying anything that might affect its behavior.
 * Make your program stop on specified conditions.
 * Examine what has happened, when your program has stopped.
 * Change things in your program, so you can experiment with correcting the effects
   of one bug and go on to learn about another.
```

The last aspect of GDB can be used to run shellcode in memory only, without touching disk.

First we convert our shellcode into a byte string:

![](img/gdb1.png)

Then, run */bin/bash* under the control of *gdb*, set a breakpoint at *main()*, inject the shellcode and continue. Below is a one-liner:

![](img/gdb2.png)

### Python

**Python** is a very popular interpreted programming language and, unlike GDB, is **commonly found in many default Linux deployments**.

Its functionality can be extended with many modules including * **ctypes** *, which provides C compatible data types and allows calling functions in DLLs or shared libraries. In other words, ** *ctypes* enables** the construction of a C-like script, combining the power of external libraries and **direct access to kernel syscalls**.

To run our shellcode in memory with Python, our script has to: 

 * **load the *libc* ** library into the Python process
 * **mmap() a new W+X memory** region for the shellcode
 * **copy the shellcode** into a newly allocated buffer
 * make the buffer 'callable' (**casting**)
 * and **call the buffer**

Below is the complete script (Python 2):

![](img/py1.png)

The whole script is converted into **a Base64-encoded string**:

![](img/py2.png)

And delivered to a target machine with a one-liner:

![](img/py3.png)

### Self-modifying dd

On rare occasions, when none of the above methods are possible, there's one more tool installed by default on many Linux systems (part of the *coreutils* package) that may be used. The tool is called *dd* and is commonly used to convert and copy files. If we combine it with a *procfs* filesystem and the */proc/self/mem* special file - exposing the process’s own memory - there is, potentially, a small window in which to run shellcode in-memory only. To do that, **we need to force *dd* to modify itself on the fly** (aka * **to shinji-nize itself** *).

The default *dd* runtime behavior is depicted below:

![](img/dd1.png)

And this is how a self-modifying *dd* runtime should look like:

![](img/dd2.png)

The first thing needed is **a place to copy shellcode inside the *dd* process**. The entire procedure must be stable and reliable across runs since it's a running process overwriting its own memory.

A good candidate is the code that’s called after the copy/overwrite is successful. It directly translates to **process exit**. Shellcode injection can be done either in the PLT (*Procedure Linkage Table*) or somewhere inside the main code segment at *exit()* call, or just before the *exit()*.

Overwriting the PLT is highly unstable, because if our shellcode is too long it can overwrite some critical parts that are used before the *exit()* call is invoked.

After some investigation, it appears the *fclose(3)* function is called just before the *exit()*:

![](img/dd3.png)

*fclose()* is called only from 2 places:

![](img/dd4.png)

Further tests show that the code at **0x9c2b** (```jmp 1cb0```) is the one used at runtime and it’s followed by a large chunk of code which, potentially, can be overwritten without crashing the process.

There are **two additional obstacles** we have to address to make this technique to work:

1. ** *stdin, stdout* **  and ** *stderr* **  file descriptors are **being closed** by *dd*  after the copy:
![](img/dd5.png)
2. **Address Space Layout Randomization**

The first problem can be solved by creating stdin  and stdout **duplicate file descriptors** with the help of bash (see *bash(1)*):

``` 
Duplicating File Descriptors
       The redirection operator

              [n]<&word

       is used to duplicate input file descriptors. If word expands to one or
       more digits, the file descriptor denoted by n is made to be a copy of
       that file descriptor.

```

and prefixing our shellcode with *dup()* syscalls:

![](img/dd6.png)

The second problem is more serious. Nowadays, in most Linux distributions, binaries are compiled as *PIE* (*Position Independent Executable*) objects:

![](img/dd7.png)

and ASLR is turned on by default:

![](img/dd8.png)

Fortunately, Linux supports different *execution domains* (aka * **personalities** *) for each process. Among other things, execution domains tell Linux how to map signal numbers into signal actions. The execution domain system allows Linux to provide limited support for binaries compiled under other UNIX-like operating systems.  Since **Linux 2.6.12**,  the ```ADDR_NO_RANDOMIZE``` flag is available which disables ASLR in a running process.

To turn off ASLR in userland at runtime, *setarch* tool can be used to set different personality flags:

![](img/dd9.png)

Now all the necessary pieces are in place to run the self-modifying *dd*:

![](img/dd10.png)

### System Calls

All of the above methods have one huge downside (except *tmpfs*) – they allow execution of shellcode, but not an executable object (ELF file). **Pure assembly shellcode has limited usage and is not scalable** if we need more sophisticated functionality.

Once again, kernel developers came to the rescue – **starting from Linux 3.17** a new system call was introduced called * **memfd_create()** *. It creates an anonymous file and returns a file descriptor that refers to it. The file behaves like a regular file. However, it lives in RAM and is automatically released when all references to it are dropped.

**In other words, the Linux kernel provides a way to create a memory-only file which looks and feels like a regular file and can be mmap()’ed/execve()’ed.**

The following plan covers creating a *memfd*-based file in a virtual memory and, eventually, uploading our tools of choice to the victim machine without storing them on a disk:

 - generate a shellcode which will create a *memfd* file in a memory
 - inject the shellcode into a *dd* process (see [Self-modifying dd](#Self-modifying_dd) section)
 - 'suspend' the *dd* process (also done by the shellcode)
 - prepare a tool of choice to be uploaded (statically linked *uname* is used as an example)
 - transfer base64-encoded tool into the victim machine via an in-band data link (over a shell session) directly into *memfd* file
 - finally, run the tool

The first thing is to create a new shellcode (see [Appendix B](#Appendix_B)). The new shellcode reopens closed *stdin* and *stdout* file descriptors, calls *memfd_create()* creating a memory-only file (named ```AAAA```), and invokes the *pause()* syscall to 'suspend' the calling process (*dd*). Suspending is necessary because we want to prevent *dd* process from exiting and, instead, make its *memfd* file accessible to other processes (via *procfs*). The *exit()* syscall in the shellcode should never be reached.

Then we shinjinize *dd*, suspend it and check if *memfd* file is exposed in the memory:

![](img/sc1.png)

The next step is to prepare our tool for uploading. Please note that **attackers’ tools** have to be either **statically linked** or **use the same dynamic libs** as on a **target** machine.

![](img/sc2.png)

Now just ‘echo’ the Base64-encoded tool into *memfd*-file and run it:

![](img/sc3.png)

Note that the *memfd* file can be 'reused'; the same file descriptor can 'store' the next tool if necessary (overwriting the previous one):

![](img/sc4.png)

#### What if a victim machine runs a kernel older than 3.17?

There is a C library function called *shm_open(3)*. It creates a new POSIX shared object in memory. A POSIX shared memory object is, in effect, a handle which can be used by unrelated processes to *mmap()* the same region of shared memory.

Let’s look into Glibc source code. *shm_open()* calls *open()* on some *shm_name*:
(from <a href="https://code.woboq.org/userspace/glibc/sysdeps/posix/shm_open.c.html">glibc/sysdeps/posix/shm_open.c</a>)

![](img/sc5.png)

Which, in turn, is dynamically allocated with *shm_dir*:
(from <a href="https://code.woboq.org/userspace/glibc/sysdeps/posix/shm-directory.h.html">glibc/sysdeps/posix/shm-directory.h</a>)

![](img/sc6.png)

*shm_dir* is a concatenation of ```_PATH_DEV``` with "*shm/*":
(from <a href="https://code.woboq.org/userspace/glibc/sysdeps/posix/shm_open.c.html">glibc/sysdeps/posix/shm_open.c</a>)

![](img/sc7.png)

and ```_PATH_DEV``` is defined as */dev/*.

So, it turns out that *shm_open()* just creates/opens a file on the *tmpfs* file system, but that was already covered in the [tmpfs](#Tmpfs) section.

## OPSEC Considerations

Any offensive activity on the target machine requires thinking about side-effects. Even if we try not to touch the disk with any code, our actions might still leave some 'residue'.

These include (but are not limited to):
1. **Logs** (ie. shell history).  In this case adversary has to make sure logs are either removed or overwritten (sometimes not possible due to lack of privileges).
2. **Process list** – occasionally another user viewing processes running on the victim machine might spot weird process names (ie. */proc/< num >/fd/3*). This can be circumvented by changing the *argv[0]* string in the target process.
3. **Swappiness** – even if our artifacts live in virtual memory, in most cases they can be swapped out to disk (analysis of swap space is a separate topic). It potentially can be dodged with:

 - *mlock(), mlockall(), mmap()* - requires ```root``` or at least ```CAP_IPC_LOCK``` capability
 - *sysctl vm.swappiness* or */proc/sys/vm/swappiness* – requires ```root``` privileges
 - cgroups (*memory.swappiness*) – requires ```root``` or privilege to modify cgroup

The last one does not guarantee that under heavy load the memory manager will not swap the process to disk anyway (ie. root cgroup allows swapping and needs memory).

## Acknowledgements

Hasherezade for unintended inspiration
mak for interesting discussions and content review
hardkor for content review

## References

1. In-Memory PE EXE Execution by Z0MBiE/29A
   https://github.com/fdiskyou/Zines/blob/master/29a/29a-6.zip
2. Remote Library Injection by skape & jt
   http://www.hick.org/code/skape/papers/remote-library-injection.pdf
3. Reflective DLL Injection by Stephen Fewer
   https://www.dc414.org/wp-content/uploads/2011/01/242.pdf
4. Loading a DLL from memory by Joachim Bauch
   https://www.joachim-bauch.de/tutorials/loading-a-dll-from-memory/
5. Reflective DLL Injection with PowerShell by clymb3r
   https://clymb3r.wordpress.com/2013/04/06/reflective-dll-injection-with-powershell/
6. The Design and Implementation of Userland Exec by the grugq
   https://grugq.github.io/docs/ul_exec.txt
7. Injected Evil by Z0MBiE/29A
   http://z0mbie.daemonlab.org/infelf.html
8. Advanced Antiforensics : SELF by Pluf & Ripe
   http://phrack.org/issues/63/11.html
9. Run-time Thread Injection The Jugaad way by Aseem Jakhar
   http://www.securitybyte.org/resources/2011/presentations/runtime-thread-injection-and-execution-in-linux-processes.pdf
10. Implementation of SELF in python by mak
    https://github.com/mak/pyself
11. Linux based inter-process code injection without ptrace(2) by Rory McNamara
    https://blog.gdssecurity.com/labs/2017/9/5/linux-based-inter-process-code-injection-without-ptrace2.html

----

## Appendix A

Example 'Hello world' shellcode used in the experiments:

<img src="img/app1.png">

## Appendix B

*Memfd-create()* shellcode:

<img src="img/app2.png">