1

This code is supposed to create a memfd (anonymous file), copy shellcode as a Vec<u8>, then finally execute using fexecve().

// A method that takes a u8 vector and copies it to a memfd_create file, then executes using fexecve()

use std::ffi::{CStr, CString};
use nix::sys::memfd::{memfd_create, MemFdCreateFlag};
use nix::unistd::fexecve;
use nix::unistd::write;

fn fileless_exec(code: Vec<u8>) {
    
    // Name using CStr 
    let name = CStr::from_bytes_with_nul(b"memfd\0").unwrap();

    // Create a new memfd file.
    let fd = memfd_create(&name, MemFdCreateFlag::MFD_CLOEXEC).unwrap();

    // Write to the file
    let _nbytes = write(fd, &code);

    // args for fexecve
    let arg1 = CStr::from_bytes_with_nul(b"memfd\0").unwrap();

    // enviroment variables
    let env = CString::new("").unwrap();

    // fexecve
    let _ = match fexecve(fd, &[&arg1], &[&env]) {
        Ok(_) => {
            println!("Success!");
        },
        Err(e) => {
            println!("Error: {}", e);
        }
    };
}

fn main() {

    // Read the file `hello_world` into a vector of bytes.
    let code = std::fs::read("/tmp/hello_world").unwrap();
    fileless_exec(code);
}

(hello_world is just a simple C hello world example).

The binary executes and writes to stdout normally. How would I capture the output as, say, a String in Rust? I've seen this example do it in C which is ultimately what I'm trying to achieve here.

The whole point here is to execute a file using its fd and capture its output. The input could be coming from anywhere (not always from disk as with the hello_world executable): from a web endpoint, other processes, etc.

I'm aware this code isn't that "Rust"-y.

John Kugelman
  • 330,190
  • 66
  • 504
  • 555
aminu_moh
  • 21
  • 1
  • It should be pretty much the same as you capture output from any other process. Remember that it's not mandatory to use `fexecve`; you can pass `/dev/fd/NNN` to APIs that expect a string filename. – o11c Nov 29 '21 at 05:29
  • You can do the same thing that the C example does. The `nix` crate (which you already use) should provide safe or mostly-safe wrappers for all the primitives you need, such as `pipe()` and `fork()`. – user4815162342 Nov 29 '21 at 08:36
  • BTW whenever I see `let _nbytes = write(fd, &code)`, I die a little on the inside. `write()` returns how much it has actually written, and it can be fewer than requested! A `write()` that is not part of a loop is almost certainly a bug waiting to happen. – user4815162342 Nov 29 '21 at 08:38

1 Answers1

0

So following some very bad practices I was able to make this:

// A method that takes a u8 vector and copies it to a memfd_create file.

use std::ffi::{CStr, CString};
use nix::sys::memfd::{memfd_create, MemFdCreateFlag};
use nix::unistd::{read, write, fexecve, dup2, close, fork};


fn fileless_exec(code: Vec<u8>, fd_name: &[u8], stdout: &mut String) {
    
    // Name using CStr 
    let name = CStr::from_bytes_with_nul(fd_name).unwrap();

    // Create a new memfd file.
    let fd = memfd_create(&name, MemFdCreateFlag::MFD_CLOEXEC).unwrap();

    // Write to the file
    let _nbytes = write(fd, &code);

    // args for fexecve
    let arg1 = CStr::from_bytes_with_nul(fd_name).unwrap();

    // enviroment variables
    let env = CString::new("").unwrap();

    // to capture the output we need to use a pipe
    let pipe = nix::unistd::pipe().unwrap();

    unsafe {
        let mut output = [0u8; 1024];
        
        // fork and exec
        let pid = fork().unwrap();

        if pid.is_child() {
            
            // dup the read end of the pipe to stdout
            dup2(pipe.1, nix::libc::STDOUT_FILENO).unwrap();
            
            // close the write end of the pipe
            close(pipe.0).unwrap();

            // close the read end of the pipe
            close(pipe.1).unwrap();

            // fexecve
            fexecve(fd, &[&arg1], &[&env]).unwrap();
        } else {

            // close the read end of the pipe
            close(pipe.1).unwrap();

            // write to the pipe
            let _nbytes = read(pipe.0, &mut output);

            // close the write end of the pipe
            close(pipe.0).unwrap();

            // convert output to a string
            *stdout = String::from_utf8(output.to_vec()).unwrap();
        }
    }
}

fn main() {

    // Read the file `/bin/ls` into a vector of bytes.
    let code = std::fs::read("/bin/ls").unwrap();
    let mut output = String::new();
    

    fileless_exec(code, b"anonymous\0", &mut output);

    print!("File output: {}", output);
}

This works for now... thanks for answers

aminu_moh
  • 21
  • 1
  • I see no bad practices (other than not checking the result of `write()`, already mentioned). There's a bug though: you have a single `read()`, which won't work for programs that write output in multiple writes, or those whose output is larger than the pipe buffer. You can use `File::from_raw_fd()` to create a `File` from `pipe.0` and then read it to the end using `File::read_to_end()`. – user4815162342 Nov 29 '21 at 08:51