Close

Example 3: Buffer overflow with function pointers

A project log for Simple Security Risk Examples on Arduino

Simple Arduino examples of common cyber-security risks, such as buffer overflows and stack smashing

jake-wachlinJake Wachlin 09/19/2020 at 16:140 Comments

This next example is a simple way to cause code execution using a buffer overflow bug. Like all the examples it is on the Github and here.

typedef struct
{
  uint8_t buffer[16];
  void (*cb)(int);
} databall_t;

void other_secret_function(int a)
{
  Serial.println("Secret function found!");
}

void secret_function(int a)
{
  Serial.println("Secret function found!");
}

void handle_sum(int s)
{
  Serial.print("Sum of buffer: ");
  Serial.println(s);
}

void handle_data(databall_t * db)
{
  Serial.println("Summing data");
  int sum = 0;
  for(int i = 0; i < 16; i++)
  {
    sum += db->buffer[i];
  }
  db->cb(sum);
}

void setup() {
  Serial.begin(115200);
  delay(1000);

  Serial.println("Booting...");
  delay(100);

 
}

void loop() {
  delay(1000);
  Serial.println("Loop still running");

  databall_t db;

  char pointer_addr[128];
  snprintf(pointer_addr, 128, "Secret function address: %p", secret_function);
  Serial.println(pointer_addr);

  snprintf(pointer_addr, 128, "Other secret function address: %p", other_secret_function);
  Serial.println(pointer_addr);

  snprintf(pointer_addr, 128, "Handle sum function address: %p", handle_sum);
  Serial.println(pointer_addr);

  Serial.print("Size of databall: ");
  Serial.println(sizeof(db));

  db.cb = handle_sum;

  int start = millis();
  int index = 0;
  Serial.println("Enter the data");
  while((millis() - start) < 5000)
  {
    if(Serial.available())
    {
      db.buffer[index++] = Serial.read();
    }
  }
  handle_data(&db);

}

We make a new struct which has a data buffer and a function pointer for a callback function. Although contrived for this example, this setup is not uncommon. For example, a communication driver for UART, I2C, or SPI might have a buffer for data and a callback function to call when the transaction is complete for further data processing. Here, we sum the data and call the callback function with that sum.

Yet again, we are not checking the length of the buffer as we fill it with data, so we can overflow and overwrite other variables on the stack. Because of the struct, the address of the callback function can easily be overwritten. When the summing is complete, the callback is called, which now points somewhere else. This allows an attacker to execute code they should not be able to.

Using an Adafruit Feather M0, I see the following. We see that the size of the databall is 20, and that the secret function address is 0x2111.

We overwrite 18 bytes, to go past the buffer onto the callback address, as shown here. The Feather M0 is little endian, so we have to provide the address as 0x11 0x21. Depending on the address, this can be difficult or impossible to enter through the Arduino serial monitor. All data through there is sent as ASCII codes, some of which can't be typed or sent.

With this input, we see the following. The secret function is found! The hardware does not crash, since the callback function is a valid address and the hardware doesn't know any better.

Discussions