May is almost upon us, and it is time for another quick post on Rustrat progress. Implementing the random seed “generators” from last post went smoothly as soon as I realized that inline assembly is only available in nightly, and figured I needed to do something else. I decided to go for separate files containing the assembly for the required functionality, adding a custom build script to build the assembly file using CC. This post is not about my adventures with MASM, however, although I might revisit that later on. As you may have guessed from the title, this post is another cautionary tale about what can go wrong when you play with unsafe rust and pointers.

WARNING I do not know if anyone else actually read this blog, but if you do: hello! I got a bit carried away when writing this post, starting out with one idea for what I wanted to write. What I first wanted to write is not what I ended up with, neither is the second or third idea I had. If you simply want to know what the problem was, scroll down to the bottom of this post, and you should see a code block, the explanation should be around there.

The first communication method supported by the rats will be HTTP callbacks, and I decided to use WinINet to send requests and read responses as it is already included in Windows. I have decided to not to link against wininet.dll directly, but rather to load the dll and get the function pointers using the libffi bindings already in the rat. The main reason for this is because I want to make it easier to use the functions I have created for loading libraries and calling arbitrary functions, but I do not know how yet. So I figured the best thing to do would be to actually use the functions, hoping that I sooner or later will get a good idea of how to improve how ergonomic it is to use the functionality.

Moving on, in order to make outbound HTTP request and read the response headers and bodies, I am using five functions:

These five functions are all that is needed to perform HTTP GET requests. Additional functions are required if you want to do more exotic things like POST requests. However, the first functions I will implement for the rat only require sending small amount of data, so POST (and PUT and other exotic verbs) requests will not be implemented until later, when I want to send data back from the rats to the server.

Calling all of these functions require the use of unsafe code in rust. It is not necessarily dangerous to call the functions themselves, but it gets very easy to mess up, especially when pointers are involved. InternetReadFile reads part of the response into a buffer. If the buffer is not large for the whole response, you only part of the response is written to the buffer, and you will have to increase the buffer size and call the function again.

I chose to solve this using rust vectors, as they are nice to work with and have all the functionality I want, they can be created with a certain capacity and has functionality to grow in size when needed. You can also get pointers to the data stored in the vectors, which is a requirement to be able to pass buffers to the Win32 API functions.

This generally works fine as long as you are careful with what you write where and how much you write of it. Doing this correctly is not really a very challenging task, and I (think I) managed to get it correct more or less right away. The code worked as it was supposed to, and I was quickly able to do a GET request and print out the response. The initial buffer size I had chosen was quite large, and when I first tested, InternetReadFile only had to write data to the buffer the first time because its capacity was sufficient. While everything seemed blissful on the surface, a SEGFAULT was lurking in the code. Curious as I am, I wanted to verify that the functionality also worked when the buffer was so small that InternetReadFile had to be called several times. It did not.

The program crashed. I executed the program a couple of more times hoping the problem would simply go away, but it did not. Repeating the experiment with a debugger attached did not provide any useful hints until I looked at the local variables just before the SEGFAULT. Values that made no sense whatsoever were suddenly placed into variables that were not changed in the part of the code that was executing. This made me realise that something was messing with the stack, and after some good old fashioned code staring, I found the one character responsible for all the problems. The responsible code looked like this:

1
2
3
4
5
6
7
8
9
is_error = self.fn_table.call_fn(
    "InternetReadFile".to_string(),
    &[
        arg(&self.handle),
        arg(&(&out[total_bytes_written as usize..].as_mut_ptr())),
        arg(&(out.capacity() as u32 - total_bytes_written)),
        arg(&(&mut bytes_written as *mut u32)),
    ],
)?;

Do you see the problem? I am not quite sure why, but an extra & snuck into the code at line 5 in the snippet. The main theory is that I went a bit overboard with the copying and pasting. With that code, instead of providing a pointer to the vector containing the response, I ended up providing a reference to that pointer. In the end, that reference turned out to be passed as a pointer to where the pointer was stored, on the stack, and InternetReadFile happily obliged and proceeded to fill the stack with HTML.

Simply deleting the superfluous & fixed the problem, causing the code to behave as intended. As a side note I would like to mention that I later figured that I did not want to have to deal with creating new slices all the time, and resorted to the dark magic that is pointer arithmetic instead. This does mean that I lose out on a free bounds check, but I figured that if you are using unsafe code playing dangerous games with pointers, you might as well go all the way. The current version of the line looks like this: arg(&out.as_mut_ptr().add(total_bytes_written as usize)).