How would one load 2 files into memory, and execute one with an argument of the other from memory?

To give an example, a toy fuzzer (that fuzzes blackbox binaries).

One would intend to load both the binary thats being fuzzed, and potentially an input file into memory, edit the input file whilst in memory, then execute the target with the modified input file from memory, handling crashes of the derived process as positive fuzzing examples.

How could one do this in rust? What is the proper method to run a file from memory and supply a parameter from memory as well? Does one need to write to the disk?

You can read any file into memory using std::fs::read().

However, there are two problems/unclarities.

  1. Why would you want to load the executable into memory? If you've got an executable file, you can directly run it via std::process::Command. Executing it from memory would basically mean that you are reimplementing the operating system's loader and dynamic linker, which is not something you want to/should do.
  2. What do you mean by "execute it with the modified file as input from memory"? It's not like executables are functions which take direct arguments. There are several ways to pass information to a subprocess:
  • command-line parameters (as strings)
  • piping data (text or binary) into their standard input
  • performing inter-process communication (eg. writing to shared memory, to a socket, or sending an HTTP request)

Which method will work depends entirely on how the program was designed and what it expects. There isn't a single general solution. But each option above can be done in Rust easily.

2 Likes
  1. That's true, one might not need to edit the executable in this toy example, in this example it doesn't make sense to load the executable into memory since we won't be editing it. My mistake.

  2. by "execute it with the modified file as input from memory" I meant: pass to the executable a modified input file, one that we have directly modified the bytes of in memory. I think the most common example is command-line parameters, but a much faster one for our toy example would be piping data via standard input. How would one pipe a modified input file resting in memory using standard input to an executable initiated via std::process::Command?

  3. Can one execute a file from memory using std::process::Command? I understand that it is not ideal to steal the tasks of the operating system, I'm aware I could possibly jump to it using unsafe. I ask simply for curiosity at this point.

No.

Also no. That's exactly the "reimplementing the dynamic loader" part. You can't just jump to the entry point of an executable. You need relocation, virtual memory mapping, loading of dynamic libraries the program depends on, etc. It's not as simple as unsafe { (main_addr as fn())() }.

I realize, but the "pass" part is what is ambiguous. If you want to pipe data to the standard input of the Command, then you can look at its methods and proceed as reasonable (set up the pipe via .output(Stdio::piped()), .spawn() the command to obtain a Child, and then write to child.stdin).

4 Likes

"fuzz blackbox binaries" is a terrifying concept. Are you sure that's what you want to do?

To some extent, doing your own in-memory loader (using eg goblin) and stubbing and or whitelisting it's imports isn't the worst idea for fuzzing native code. You will need to isolate it somehow still, using memory protection or the like, unfortunately operating systems don't give you the ability to create an empty process you can push a memory space into, so you will need to play with fork and memory protection so it can't scribble over your host.

The best option would be running the machine code with an interpreter that verifies that all reads and writes are valid: eg no writes outside the stack frame or heap, etc..., but I'll guess that's at minimum a year of work to run even simple programs.

You can use memfd_create to create a file inside of memory (rather than on the disk) and then execveat to execute this in-memory file by passing the fd returned by memfd_create. You will likely want to use seccomp to restrict what the modified executable can do.

4 Likes