[EN] A-Z: Introducing SocketInjectingFuzzer and its first target - Teeworlds

The idea behind Socket Injecting Fuzzer is simple - fuzz applications working in the client-server architecture by mutating real data packets that are exchanged by them in real usage scenarios. Implementation is also simple because it is based on hooking network-related functions on one side (or both) of a connection and modifying outgoing data. Thanks to this approach, it is not required (but still may be helpful) to understand communication protocol or to prepare initial input for targets.

As my first target, I decided to take a real-time multiplayer action game Teeworlds. The only thing that I had to know is to what network functions are used by the game. It was only a matter of running strace to find out that sendto and recvfrom are used. With this information, it was enough to start implementing hooks.

static ssize_t (*sf_sendto)(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) = NULL;

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) {

  if (sf_sendto == NULL) {
    sf_sendto = dlsym(RTLD_NEXT, "sendto");
  }

  return sf_sendto(sockfd, buf, len, flags, dest_addr, addrlen);
}

In fact, at this stage, this code just hooks invocations of sendto and passes all arguments to the real sendto. It doesn’t perform any fuzzing yet, but it’s is important to know how it will be launched. To do it I use LD_PRELOAD trick that relies on compiling the fuzzer as a shared object and running it along with the application.

$ gcc -fPIC -shared -o fuzzer.o fuzzer.c

If I run it this way, all outgoing data will go through the fuzzer.

$ LD_PRELOAD="/home/mmm/sif/fuzzer.o" ./teeworlds

As the whole mechanism is known, it is time to actually modify data. I decided to use radamsa, a fuzzer that we used in our previous sessions. Primarily, radamsa is an external library that takes any data, mutates it, and returns, but it can be also compiled as a library.

$ git clone --depth 1 https://gitlab.com/akihe/radamsa
$ CFLAGS="-fPIC" make lib/libradamsa.so

Its API is really simple, it should be initialized and later invoked with input and output buffers.

#include <radamsa.h>

static int seed = 0;

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) {

  if (sf_sendto == NULL) {
    sf_sendto = dlsym(RTLD_NEXT, "sendto");
    radamsa_init();
  }
  char* out = malloc(BUFSIZE);
  int n = radamsa((uint8_t*) buf, len, (uint8_t*) out, BUFSIZE, seed++);
  sf_sendto(sockfd, out, n, flags, dest_addr, addrlen);
  free(out);

  return len;
}
$ gcc -I`pwd`/radamsa/c -fPIC -shared -o fuzzer.o fuzzer.c radamsa/lib/libradamsa.o -ldl

Now all data sent by the application will be mutated. Now you may ask why len is returned instead of result from sf_sendto. Radamsa’s output can be different in length than input (len != n). The application probably will check if len bytes were sent, otherwise, some error handling code may be triggered. So it is necessary to fool the application and make it believe that it sent the expected number of bytes.

I also implemented some additional features which may be really helpful, for example fuzzing and sending a single packet many times (repeating), not fuzzing some packets to deliver them in their original state (to avoid disconnecting if the second side complains about malformed data), logging received data for easier analysis of crashes. For the sake of simplicity, I’ll skip describing it, but if you are interested in how it’s done, I recommended looking directly into the repository.

Time for action

As the fuzzer is ready to go, now we can run it with the game compiled with Address Sanitizer and keep our fingers crossed.

LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libasan.so.4 /home/mmm/sif/fuzzer.o" ./teeworlds
./teeworlds-srv

teeworlds

In this scenario the client sends mutated data and the server is a victim. On the gif we can see that fuzzer works. I didn’t touch a keyboard, all of these movements were accidentally made by the fuzzer while mutating data.

If we wanted to fuzz from the server-side we should just move LD_PRELOAD from the client running the command to the server running command. Or if we wanted to simultaneously fuzz from both sides, we should apply this variable to both commands.

Results

After some hours of fuzzing in different configurations (also applying different repeating and skipping parameters), I found five unique crashes. All of them apply to the client’s code so in real scenario a player would connect to a malicious server. Fortunately, none of them seems to be exploitable in terms of code execution but it’s enough for the server to cause clients’ crashes.

All of the bugs were reported on the project’s GitHub. It’s impressive how quickly developers reacted. Most of the fixes were committed in just a few hours.

Summary

A simple idea for fuzzing resulted in a few bugs detected in the very first target. These results proved to me that even a naive approach in fuzzing can give some results. So stay tuned for more.

Written on July 1, 2020 by Michał Dardas

We invite you to contact us

through the following form:

LogicalTrust sp. z o.o.
sp. k.

al. Aleksandra Brücknera 25-43
51-411 Wrocław, Poland, EU

NIP: 8952177980
KRS: 0000713515