RustRAT uses libffi (more specifically the libffi-rs bindings) to call arbitrary functions at run-time. This works by putting the function’s argument and return types into a struct and providing libffi with a function pointer. Libffi then makes sure that all the arguments are placed in the correct registers or on the stack and calls the function pointer. I am not that proficient with Rust yet, but if anything is unsafe, I am pretty sure this is it.

While developing RustRAT, my tests would initially contain calls to libffi-rs’s own call function and everything was proceeding smoothly. The call function is essentially something like this:

pub unsafe fn call<R>(function_definition, arguments) -> R {
    let mut result: R;

    // Witchcraft/using libffi to call the function defined in the
    // definition with the provided arguments. Then put the return value
    // from the function directly into the variable result. Note that
    // there is no check that the return value actually is of type R.

    result
}

Because I do not want to define a function signature every time I call a function, RustRAT stores function definitions in a HashMap. Eventually I was ready to implement a function to call functions from definitions in the HashMap. The function was supposed to check whether a given function was defined (that is, in the HashMap), and call the function with the provided arguments if it was. Because my code could fail to return a proper result if the function was not defined, it returned a Result type containing either the return value or an error complaining about the function not being defined.

The code I wrote looked something like the following snippet. I had basically just copied the code for calling functions from my test.

pub unsafe fn call_fn<R>(&self,
                         function: String,
                         arguments: &[libffi::middle::Arg])
                         -> error::Result<R> {
    // Get the function definition from the HashMap.
    let foreign_fn = self.0.get(&function)
        .ok_or(error::Error::FunctionNotDefined(function))?;

    // Function is defined, go ahead and call it and return the result.
    foreign_fn.call(arguments)
}

I rewrote my test to put function definitions in the HashMap, and attempted to call VirtualAlloc using a stored function definition expecting my test to pass as I had not really done any major changes. Instead my test crashed with a STATUS_ACCESS_VIOLATION.

Several hours were spent trying to figure out what caused this problem. My own code was written in Rust, so I figured the problem had to be in libffi, as it was written in C. I thought it was most likely that I had somehow managed to corrupt the structs passed to libffi, which caused it to wreak havoc. All this despite the fact that the code responsible for creating the structs had not changed.

After two sessions of desperate debugging I resorted to some good old fashioned staring at my own code. Libffi’s call function automatically inferred the return type, but I figured I could try to define the return type explicitly and see what happened. I put in a “::<R>” after “foreign_fn.call”, and suddenly Rust complained that my call_fn did not return the correct type.

Then it dawned on me what had happened. Normally, Rust will not let you assign just anything to any variable regardless of type (I think?). However, this was unsafe, dangerous code playing with pointers containing unknown data types, so Rust accepted that I wanted a Result type returned from the function even though it only returned a pointer. When I called unwrap() on the result in my test, all sorts of undefined behavior happened as the function did not really return a Result type.

Luckily, the fix was simple, and with the addition of four characters my tests were passing again.

pub unsafe fn call_fn<R>(&self,
                         function: String,
                         arguments: &[libffi::middle::Arg])
                         -> error::Result<R> {
    // Get the function definition from the HashMap.
    let foreign_fn = self.0.get(&function)
        .ok_or(error::Error::FunctionNotDefined(function))?;

    // Function is defined, go ahead and call it and return the result.
    // Make sure to wrap return value in Ok.
    Ok(foreign_fn.call(arguments))
}

The lesson I learned from all of this is to be very careful about what assumptions I make when working with unsafe Rust code.