Skip to main content

Embedded devices running C code are often to exposed to two common security weaknesses that can be exploited to modify the device memory, resulting in memory leaks, denial of service, unauthorized code execution, etc. These weaknesses are:

Double Free (CWE-415) - calling the free() function multiple times, resulting in a memory leak. This might allow an attacker to write values to arbitrary memory spaces and create an interactive shell with elevated privileges.

Use After Free (CWE 416) - occurs when a program continues to use a memory pointer after it has been freed. If an attacker manages to overwrite one of those pointers with an address to shellcode, they could execute arbitrary code.

In this post, I`ll do a deep dive into each of these two weaknesses and provide some hypothetical and real-life examples of how they could be exploited.

What Is the Double Free (CWE-415)?

The free() function in the C programming languages releases memory blocks that were allocated using functions like calloc(), malloc(), or realloc(). A double free error occurs if free() is called multiple times with the same memory address.

            FIGURE: Example of Double Free Attack Flow

 

Calling free() twice on the same value causes a memory leak. If a program calls free() twice with the same arguments, it corrupts the program's memory management data structures. This can have several impacts:

  • Program crashes - one of the most common impacts of a double free vulnerability is a program crash. When a block of memory is freed twice, it can cause the program to access invalid or uninitialized memory, which can result in a crash.
  • Changing flow of execution - the double free vulnerability can cause the flow of execution in a program to change unexpectedly. This can occur when the memory that is freed twice is accessed by multiple variables or pointers, which can lead to unpredictable behavior.
  • Arbitrary code execution - in some cases, a double free vulnerability can be exploited by an attacker to execute arbitrary code. This can occur when the vulnerability allows the attacker to write to memory in a way that allows them to overwrite important data or control the flow of execution.

In many cases, an attacker who manages to write values to arbitrary memory spaces can create an interactive shell with elevated privileges. This is also known as a write-what-where condition.

What happens when a buffer is “freed”?‍

If the free() function is called on a buffer, it reads a concatenated list of free buffers, reorders them, and combines free memory blocks so that larger buffers can be allocated in the future. Blocks are arranged in a double-linked list pointing to the previous and next blocks.

Calling free() has the effect of unlinking an unused buffer. This could allow an attacker to write arbitrary values to memory—overwriting registers and calling shellcode from their own buffer.

Double Free Examples

Note that to illustrate the double free problem, we are showing simplified examples. In real life, double free vulnerabilities are much more complex and might be laid out across hundreds of lines of code and multiple files.

Simple Example

The following is a trivial example of a double free vulnerability.

Simple Double Free Example
int* pointer;
pointer = (int*)malloc(SIZE);
...
if (a > 10) {
free(pointer);
}
...
free(pointer);

Notice here that the pointer is being freed twice. This leads to uncertainties regarding the exact code section in charge of freeing the memory and which memory space, if available, to be reused.

Complex Example with Two Functions

Let’s now look at an example where the vulnerability exists in two different functions.

Two Functions Double Free Example
#include <stdio.h>
#include <stdlib.h>
void process_arr(int** arr, int n) {
...
free(*arr);
}
int main(int argc, char* argv[]) {
int* arr = malloc(sizeof(int)) * 10);
process_arr(&arr, 10);
free(arr);
return 0;
}

 

What Is a Use After Free (CWE 416)?

In a C or C++ memory is allocated when program instructions are executed. The ‘heap’ is the memory space available to programmers to allocate and deallocate for program instructions.

Referencing memory after it has been “freed” may cause a program to crash. Using memory allocated on the heap after freeing or deleting it results in undefined system behavior, most commonly a write-what-where condition (ability to write arbitrary values to memory spaces).

A use after free error occurs when the program continues to use the pointer after it has been freed. This is typically caused by error conditions at the application level, or confusion over which program elements are responsible for freeing memory (e.g. lack of coordination between different modules of the same program).

Use after free errors can have the following impacts:

  • Availability - can cause a program to crash. E.g., If the program attempts to merge blocks with previously freed data, and invalid data is used as block information, the operating system process can be disrupted.
  • Integrity - program data could be corrupted as a result of the error (more details below).
  • Code execution - the error can lead to a write-what-where condition, which can potentially be used to execute arbitrary code.

How does data corruption occur?

In a use after free error, memory might be effectively allocated to another pointer at some point after it was freed. The original pointer to the freed memory is reused and points somewhere in the new allocation. When the data changes, the validly used memory is destroyed. This causes undefined behavior in the process and can lead to data corruption.

How does arbitrary code execution occur?

When memory space is freed and then reused, if the newly allocated data might hold a class, multiple function pointers might exist within heap data. If an attacker manages to overwrite one of those pointers with an address to shellcode, they could execute arbitrary code.

Use After Free Examples

No security concept can protect the contents of specific memory blocks and ensure that only certain pointers can use them. A program’s memory space is entirely available for all pointers anywhere within the program.

Hypothetical Example: Dangling Pointer If a program uses a dangling pointer to freed memory blocks, it can still access everything stored in these locations. If someone subsequently reallocated the memory to another part of the program that manages sensitive data, it could accidentally change or disclose the data.

Here is an example of C code that uses a dangling pointer:

Dangling Pointer
#include <stdlib.h>
int main()
{
int *ptr = malloc(sizeof(int));
*ptr = 42;
free(ptr);
*ptr = 10; // UAF vulnerability
return 0;
}

In this code, the ptr pointer is dynamically allocated using the malloc function and is used to store the value 42. The memory is then freed using the free function.

However, the program continues to access the memory pointed to by ptr and assigns the value 10 to it. This opens the door for a ‘use after free’ vulnerability, as the program is attempting to use memory that has already been deallocated.

To fix this vulnerability, the program should ensure that it does not attempt to access or manipulate freed memory. One way to do this would be to set the ptr pointer to NULL after freeing the memory:

Preventing Use-After-Free
#include <stdlib.h>
int main()
{
int *ptr = malloc(sizeof(int));
*ptr = 42;
free(ptr);
ptr = NULL; // set ptr to NULL to prevent use after free
return 0;
}

 

Real-life Examples

CVE-2022-21661: WordPress SQL Injection Flaw

In January 2022, WordPress released a patch for the CVE-2022-21661 vulnerability. Attackers could exploit this use after free vulnerability to launch an SQL injection attack.

CVE-2022-226617: Use After Free Fix In iOS 15.4

Another example was in March 2022 when Apple launched iOS 15.4—the new release included a fix for the CVE-2022-22667 vulnerability in GPU drivers. This vulnerability enabled applications to execute arbitrary code using kernel privileges.

CVE-2022-23270: Windows Server VPN Remote Kernel Use After Free Vulnerability

This vulnerability was discovered in the Windows kernel through reverse engineering and fuzz testing of the raspptp.sys kernel driver. The vulnerability allows attackers to perform denial of service (DoS) and potentially achieve remote code execution against a target server. CVE-2022-23270 is dependent on the specific implementation of the Winsock Kernel (WSK) to be successfully triggered.

‍CVE-2022-37332: Foxit Software PDF Reader

The CVE-2022-37332 use after free vulnerability was discovered in the JavaScript engine of Foxit Software's PDF Reader, version 12.0.1.12430. Attackers can create a specially-crafted PDF document, which triggers reuse previously freed memory by misusing the media player API, resulting in arbitrary code execution. However, the vulnerability requires that the user opens a malicious file, or visits a malicious site when the Fox Software browser extension is enabled.

 

Mitigating CWEs 415 and 416 With Sternum

The vast majority of IoT/embedded devices use C code, and are prone to related memory and code vulnerabilities, including 'double free' and 'use after free' exploit attempts.

Sternum’s patented EIV™ technology protects from these with runtime (RASP-like) protection that deterministically prevents all memory and code manipulation attempts, offering blanket protection from a broad range software weaknesses (CWEs)—including 415 and 416.

Embedding itself directly in the firmware code, EIV™ is agentless and connection agnostic. Operating at the bytecode level, it is also universally compatible with any IoT device or operating system (RTOS, Linux, OpenWrt, Zephyr, Micirum, FreeRTOS, etc.) and has low overhead of only 1-3%, even on legacy devices.

Moreover, EIV’s runtime protection features are also augmented by (XDR-like) threat detection capabilities of our Cloud platform, leveraging its extended observability features. For a quick overview, check the video down below:

 

Sternum offers you :

  • Agentless security - integrates directly into firmware, making it a part of the core build. This ensures that the solution cannot be externally compromised and leveraged as a point of failure.
  • Automatic mitigation of known and zero-day threats - prevents 96.5% attacks in benchmark (RIPE) security tests. Its vulnerability-agnostic approach makes it equally effective in dealing with known and zero-day threats. This not only improves security but can also cut security patch management costs by as much as 60%.
  • Supply chain protection - relies on binary instrumentation, making it able to protect all running code. This extends to 3rd party and operating system libraries, effectively preventing all supply chain exploit attempts.
  • Protection of isolated devices - does not rely on external communication to secure devices, making it equally effective for connected and isolated devices.
  • Live attack information with zero false positives real-time alert system notifies about all blocked attacks, providing—for each—detailed logs and attack path analysis. The deterministic nature of EIV’s integrity checks ensures that all alerts are always valid.
  • Advanced threat intelligence - leveraging AI anomaly detection and granular device-level understanding of user interactions, user activity, and device behavior. Can automatically spot indicators of exposure and compromise, malicious behavior, security blindspots, stealthy and sophisticated threats.
  • Streamlined compliance - helps meet the latest cyber regulations for IoT devices (IEC 62443, FDA, NIST, etc) and the most current FBI recommendations for Internet of Medical Things (IoMT) endpoint protection. ‍

Learn more about our IoT security solutions

 

A few months ago we released our free OpenWrt security license, which provides full access to our patented EIV™ (embedded integrity verification) security solution.

In this video, I show how you can make use of our offering to download and deploy Sternum on your OpenWrt device, within just a few minutes. And how, once active, you can use our Attack Simulation kit to see how EIV handles security threats. In this case, a command injection attack.


 
 

Hi. My name is Amit Serper and I’m the Director of Security Research at Sternum. In this video I would like to show you how you can easily protect your OpenWrt device from various vulnerabilities and exploits by using the free tier of our Sternum product.

So let’s dive in. So we are now on our OpenWrt device connected to it via an SSH connection. Now I have here in the root home directory, I have our attack simulation kit, which is a tool that you can download off of our website, which I will show you later that can simulate various vulnerabilities and exploits.

Now if we run it, you can see that we have a few scenarios here. We have some heap memory attacks. We have some heap information leaks and we also have command injections.

So in this demo, I would like to focus on the command injections. So what are command injections? Just to quickly have a recap. A command injection is when a user control string is being passed directly into the System C API call. That means that if there is a webpage – for example in your router where you can ping an address to see if it’s responding, to see if your connection is up.

Usually what happens is that the developers are taking the input from that pinged form and they’re taking the input which is the IP address or the host name that you would like to ping and you’re passing it as is, as a parameter via the System C API call and they’re basically running the command ‘ping,’ space, whatever input is in the input box that the user can control.

Now in many cases when this input isn’t being sanitized properly, an attacker can inject an arbitrary system command. So instead of sending a ping, they could run whatever they want with the privileges of the web server simply by adding a semicolon.

So by adding something like semicolon “cat/etc/passwd,” you can run the ping command. The command will get to the semicolon, which will end the previous command and then execute whatever is after the semicolon.

So that means that you can run arbitrary commands on the router. So let’s try to simulate something like that with the attack simulation kit. So we can see that if we run it with three, which is the category of the command injection and then one, which is the simple without sanitation scenario, we can see that a ping is trying to be sent and then once the command is finished, we can see that the contents of /etc/passwd are being printed out to the terminal, which means that the command injection was successful.

So now let’s go to the browser. Let’s install Sternum and sign up and do the whole process and then let’s run this attack again and see what happens.

OK. So we’re now back at the browser. So in order to sign up for the free version of Sternum, you need to go to app.sternum.cloud/signup and you will get this page and after a very, very simple registration process, you would be able to get access.

So I filled in an email address and now I’m going to get a link to my inbox. So let’s meet up again after I finish the whole registration process.

OK. So now that we’ve completed the registration process, we are on this screen where we’re actually getting started with installing Sternum on our OpenWrt device.

So now it’s pretty straightforward. We need to prepare our OpenWrt device. So we’re assuming that you have either one of those versions of OpenWrt. If you do, just click “Next”. In here you need to select the proper architecture for your products.

Right now we support ARM 32 bits or ARM 64 bits. In my case, I am running on an ARM 32 bit device. So I will just pick this button right here and we can see this one liner here which is basically the line that we’re going to paste in our terminal to install Sternum.

So let’s copy this one liner and go to our OpenWrt device. Let’s clear the screen and let’s paste the command. Now what’s going to happen is there’s going to be a few old package updates and old package installs to get all of the dependencies that we need to install Sternum and then the Sternum installer will be downloaded and executed and all in all it’s a fairly quick process which shouldn’t take more than 20 or 30 seconds. So let’s go.

So we can see that things are being downloaded and Sternum is being installed and that’s it. So now let’s go back to the browser. Hit “Next” here and basically we’re ready. We’re ready to monitor our device. So let’s click “Start” and there we go. The device is connected and we can now see it here. Here is our OpenWrt device.

So now we can see here that we have – we can look at the dashboard and we can look at glances which shows us various items and all sorts of information on our device. We will touch on that soon. But now let’s go back to our device and try to run the same attack scenario that we did before.

So let’s do Attack Simulation Kit and do Attack Simulation Kit three and one. So let’s run this scenario. Again the same scenario that we ran before. So three, one. Let’s hit “Enter” and we can see now that it failed. We can see that the ping was not sent and “etc/passwd” was not printed out. This is because Sternum is catching, is detecting, catching that command injection exploitation and stopping it.

Now if we go back to our cloud system here, to the platform, we can see that there are two alerts. So let’s look at those alerts. The first alert is the new IP address. That just means that the device connected and it identified that the device that we have just installed Sternum on has a new IP address.

So this is fine. But we can also see that the command injection was detected. So let’s investigate that one real quick and if we’re looking at those alerts, we can see that there are a bunch of events here. We can see like regular events, regular executions that happen because the device needs to do its thing and run but then we can see here with that yellow circle that there’s a dangerous string, dangerous string and that dangerous string is the one that we were expecting.

The attack simulation kit with the ping command and the semicolon and cat/etc/passwd and you can see here that red circle indicates that the command injection was actually detected and prevented. So that means that the device is properly protected.

Now if you want to play with the Attack Simulation Kit as well, if you go to the dashboard screen right here on the right hand side, there is – this text, this textbox. Learn how to use Attack Simulation Kit and when you click “Learn More,” you can download the proper Attack Simulation Kit to your device whether you’re running on ARM 32 SF, HF or ARM 64 bits.

The text here will change accordingly of course and then you can just copy this one liner, paste it into your shell on your device and download it. You can also download the source code if you want.

Now, as you can properly see here, since this is the free tier, there are a bunch of options that are not open. So for the free tier, there’s only the dashboard screen and the glances screen open. But it still protects your device. It allows you to have up to three devices under that license and install Sternum and protect your device from all sorts of memory attacks and command injections and I will see you in the next video where we will demonstrate some cool memory corruption attacks.

So thank you for being with me.

If you are a Sternum user you certainly are aware of our Observability SDK that allows for monitoring and analysis of device-specific alerts, logs, metrics, and traces. The SDK is purpose-built for IoT devices and can be customized to provide accurate device-level information you need to maintain, secure, and enhance your unique products.

In this blog, I want to dive a bit deeper into some of the lesser-known facts about our SDK. But first, let's review what it does and how to deploy it.

SDK 101: An Easy Install Yields Powerful Insights

In a nutshell, the Observability SDK enables data collection and connectivity to the Sternum Cloud. As such, it has a dual function:

  • It enables data collection, storage, and transmission.
  • It can be configured to define what traces, logs and metrics are collected from the device and transmitted to the Cloud.

The SDK is integrated through the following quick and easy process:

  1. Import the library: Install the SDK into your firmware build or CI/CD system.
  2. Initialize the SDK: Connect the SDK to the Sternum Cloud and synchronize it with the device‍.
  3. Define/customize your traces: Choose from dozens of predefined trace configurations or customize your own, to fit your unique device and monitoring strategy.

  1. Send trace data: The inserted trace (see sample below) collects the data and the SDK sends the collected data to our Cloud for processing. In the case of gated or isolated devices, the SDK can also act as a proxy, storing data temporarily until the connection is available (more on that below).
Send trace data
STERNUM_ADS_TRACE(TRACE_TASK_STARTED,ARGUMENT_STRING(ARG_ROLE_NAME, 256, taskName))

  1. View the trace results: Using our dashboard, you can view and analyze the data, gain insights into trends and emerging concerns, investigate alerts, inspect event timelines and more.

With the Observability SDK integrated, and the custom traces in place, users can access our platform and use the collected data for:

  • Security monitoring and investigation (e.g., IP addresses of incoming or outgoing connections)
  • Product performance monitoring and analysis (e.g., reboot, CPU or memory usage)
  • Device or application functional analysis (e.g., routing information or metrics indicating how users use the product)
  • Product troubleshooting (e.g., investigating a persistent dropped-connection issue)

Moreover, the trace data from SDK is also used to fuel our Anomaly Detection engine that uses the information to identify:

  • Communication pattern violations
  • Abnormal presence of an event or several events together
  • Absence of an event (e.g., an unfulfilled update request)
  • Abnormal amount of events (e.g., an abnormal amount of update requests)
  • Unusual value of a variable detection (e.g., unfamiliar entity connecting to internal IPC)
  • Atypical combination of values of several variables
  • Sequence violation (e.g., command execution w/o authentication)
  • Spotting these facilitates easy comparison of anomalies with the typical individual device behaviors. For instance, below you can see details of a failed update request that was auto-detected by the AI engine. This is an actual event, spotted in the first few days of the feature rollout. If missed, this could leave the device exposed to security and operational issues.


These automated insights allow fleet operators and engineers to spot troubles early, determine root causes more quickly, and identify hidden dependencies to help solve complex issues in less time and with fewer resources.


3 Things You Probably Didn’t Know

If you are using our SDK, you probably already have a good sense of its capabilities. Still, there are some deeper functionalities you may not be aware of. Here are my top three:

1- Optimized for efficiency

The Sternum Observability SDK is optimized for resource-restricted IoT devices (e.g., RTOS). Here are some design choices that make it particularly efficient:

  • The SDK is coded in C, specifically for its low footprint and high-efficiency
  • It uses TLS-only communication to ensure low resource utilization when encrypting and sending out data to our Cloud
  • It transfers data using a proprietary algorithm that delivers 3X the compression efficiency of alternatives we tested (before deciding to create our own solution)

2- Universally effective The Observability SDK acts as an abstraction layer between the device and the Sternum Cloud. This enables it to work with every device type and OS flavor (e.g., RTOS, Zephyr, Linux, Android, iOS, etc). Regardless of data type or format, the Sternum SDK ‘translates’ that data on its way to the Cloud, so it can be digested and analyzed, allowing you to monitor, track, and pull insights from whatever information you need.

3- Acts as a proxy‍ IoT devices may be deployed in remote locations or with only intermittent connectivity. To support these deployment scenarios, the Observability SDK was built to act as a proxy, which allows it to be used for these various connectivity scenarios (fully connected, gateway connected, etc.)

When in proxy mode, the SDK will collect and store data until a connection is established, at which point it will transfer the data to our Cloud for tracking, analysis, detection, etc.

Buy NOT Build

The concept of “Buy not Build” is a well-established one, and it holds true when it comes to IoT observability.

Building an effective monitoring solution that provides all the relevant, granular information you need, for today and for the future, is a significant undertaking that requires a big commitment - both in terms of financial investment and developer time.

While organizations can develop their own DIY observability capabilities, it is unlikely they will be able to allocate enough resources to create something as mature and effective as a dedicated solution.

At Sternum, we have built a mature and effective platform, which has been perfected over several years by a focused team of experts. The math is simple: the direct and indirect costs of attempting to build and maintain such a solution far outweigh the price of ownership of our platform.

Interested in learning more? Want to see Sternum in action? Visit here to get your free demo