Integer overflow attacks involve exploiting bugs in software. When these integer overflow flaws are abused, it can lead to disastrous results, including infecting devices with spyware.
Integer overflows are a significant security threat. In 2023, they ranked 14th in the updated Common Weakness Enumeration (CWE) list of the most dangerous software weaknesses. The team behind the list ranked integer overflows just after “Improper Authentication”, due to the severity and prevalence of integer overflows.
Coded CWE-190, integer overflows have long been one of the most concerning software weaknesses, coming in at number 8 back in 2019.
While integer overflows have slowly been moving further down the list, this does not mean that they are a threat we no longer have to worry about. They are still prevalent and they can have significant consequences, which we will discuss in the “Examples of integer overflow attacks” section.
This brings us to the fundamental question:
What is an integer overflow?
It will be easiest to understand integer overflows if we start by explaining them through a metaphor.
A metaphor for integer overflows
Imagine you have an old car that you have been driving for thirty years. You love the car, take it on road trips and use it every day. It’s always been reliable, and you have spent a lot of time with it. So much time and so many miles that the odometer is already up to 999,999. You’ve taken good care of the car throughout the years and you are excited for the celebration.
The car only has a six digit analog odometer, so what’s going to happen when you drive that one extra mile? It can’t tick over to one million, because it simply doesn’t have the seventh digit needed to display values of one million or more.
Instead, when you take your car for a drive and go that one extra mile, the odometer will roll over, all the way back to 000,000 miles. Not one million. Zero. The reading on the odometer is exactly the same as on a car that’s fresh off the factory floor. Despite the good care that you have taken of your car, anyone can tell that it isn’t brand new. The seats are worn, some of the paint is chipped and the engine doesn’t purr quite as smoothly as it used to.
But it says 000,000. What happened?
This is an example of an integer overflow.
We see the same thing on our clocks every day. The seconds tick by and all of a sudden it’s 12 p.m. When one more hour passes, we are back at 1 pm. Why?
A clock ticking past 12 p.m. Clock Time Watch by designermariene and licensed under CC0.
It’s another integer overflow. Instead of continuing on to 13 p.m., 14 p.m. …98 p.m., 99 p.m., etc., the clock goes no higher than twelve (unless we are talking about 24 hour clocks, in which case the same thing happens, just at 24 instead of 12). Time doesn’t stop passing, and yet our count essentially resets and goes back to the start.
No, the car didn’t suddenly reverse all of those miles, nor did we time travel in between 12:59 pm and 1 pm. In both cases, there are practical reasons why we don’t continue counting after these limits.
Very few cars make it to a million miles, and no one would mistake a million-mile car for a new one, so the first instance doesn’t cause any major problems in the real world. We use clocks to help us keep track of our days and have other systems to keep track of time on a grander scale. Between adding a.m. and p.m. after the hour and using the date, month and year, everyone is able to coordinate just fine. We don’t need to go up to a billion o’clock for us to know that time doesn’t start all over again twice a day.
Yes, these are integer overflows, but they don’t really cause any complications. Now that we’ve demonstrated some integer overflows that we see in our everyday lives, let’s see how integer overflows affect computers and how they can lead to integer overflow attacks.
How do integer overflows work in computers?
At the lowest level, computers are basically just doing a lot of math. Whether you are looking at cat pictures online, typing up a document in Microsoft Word, adding decimal numbers in your calculator or coding in Python, deep down, it’s all just zeros and ones being manipulated in precise ways.
In all of the tasks we perform on our computers, they are constantly conducting countless binary additions and other mathematical operations.
These calculations are performed on binary integers, which are basically just strings of numbers in binary. The problem of integer overflows arises when the results of these operations are larger than the space that is available to store them.
If a program performs a calculation and the true answer is larger than the available space, it may result in an integer overflow. These integer overflows can cause the program to use incorrect numbers and respond in unintended ways, which can then be exploited by attackers.
Unlike our examples with the odometer and the clock, integer overflows can cause significant problems in computers. There can be serious consequences even in cases where there is no malicious activity from hackers. In the nineties, a simple integer overflow error was a significant reason for the explosion of a European Space Agency rocket that cost hundreds of millions of dollars.
How big can the integers be?
As an example, one of the most common designs for personal computers is x86-64 architecture. On this type of architecture, the general purpose registers are capable of directly operating on 64-bit integers (there are indirect ways in which they can operate on larger numbers, which is important for the large numbers we often use in cryptography, but we won’t cover those in this article). If your computer has a 64-bit processor, this means that it should be able to operate on integers of up to 264 in a single operation. Written out fully in binary, this means numbers of up to:
1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
For the non-robots out there, the equivalent decimal value is:
18,446,744,073,709,551,616
This is 17,179,869 terabytes of data, which is enormous given that the latest iPhone has a max of one terabyte, which is seen as more than enough for most users.
Signed integers, negative numbers and two’s complement
Computers also need to be able to store negative numbers for many purposes. At the lowest level, computers can’t just throw a minus sign in front of a number to indicate that it is negative in the way that humans do. They can only represent data with zeros and ones at that level—they can’t use any handy symbols.
The most common way they do this is through what’s known as the two’s complement. The two’s complement allows us to represent signed integers, which basically means that we have a way to use both positive and negative numbers rather than just positive numbers.
When the two’s complement is used, the leftmost binary digit no longer represents a part of the value of the number. Instead, it represents whether the number is positive or negative. The leftmost digit is a 0 if it is a positive number, and a 1 if it’s a negative number.
If we were just counting straight in binary:
0, 1, 2, 3
Would be represented as:
00, 01, 10, 11
With the two’s complement representation, we add another digit on the left, and this leftmost digit becomes the sign. These are now signed integers. This time, we’ll flip things around and show you the binary first:
000, 001, 010, 011, 100, 101, 110, 111
As we mentioned before, the extra zero on the leftmost place of the first four numbers just tells us that the numbers are positive. Apart from that, it does nothing to alter their value.
After this, things get a little trickier. We know that in two’s complement a 1 in the leftmost digit means that the number is negative, so 100, 101, 110 and 111 have to include a minus symbol. But where do we start?
In the two’s complement system, the largest binary number has the smallest negative value in the decimal system. When we are only dealing with three-bit numbers, this means that 111 is -1. From this point, we can go backwards. 110 is -2, 101 is -3 and 100 is -4.
As unintuitive as this might seem, in the two’s complement system, the sequential binary values of:
000, 001, 010, 011, 100, 101, 110, 111
Represent the following decimal numbers:
0, 1, 2, 3, -4, -3, -2, -1
Yes, it’s strange, but there are actually good reasons for it. Unfortunately, explaining them will take us off on too much of a tangent.
Two’s complement in action
Let’s take a look at what our 64-bit number from above would be in two’s complement representation. It was a series of 64 ones, and we now know that in two’s complement, the leftmost one means that it has to be a negative number. If we put the 64-bit string of 1s into a two’s complement converter (input 64 into the Number of bits field, and don’t forget to leave out the spaces), it gives us the decimal number of:
-1
If you have been following closely, you might have expected this result, because we did mention that the largest binary number has the smallest negative value in the decimal system, and a string of all 1s is the largest 64-bit value possible.
If we want to figure out the range of a 64-bit string in the two’s complement system, we can find the highest negative number by converting the smallest binary number that begins with a one. The smallest binary number beginning with a one is the following string:
1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
When we put it into the same converter (once more, you need to input 64 into the Number of bits field, and don’t forget to leave out the spaces), it gives us:
-9,223,372,036,854,775,808
To find out the highest positive number a 64-bit processor can handle (in a straightforward manner—there are other ways), we just need to enter the highest 64-bit number that begins with a 0 (to indicate that it is positive). Therefore, we need to put the following into the same calculator that we used in the previous operations:
0111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
This gives us:
9,223,372,036,854,775,807
In theory, a 64-bit register can directly handle signed numbers of any value between -9,223,372,036,854,775,808 and 9,223,372,036,854,775,807.
Limitations on integers in C
Integers aren’t just limited by the available registers. They can also be limited by the programming. As an example, here are some of C’s various integer types and the values they allow:
- SHRT_MIN (the minimum value for a short integer): -32,768 bits
- SHRT_MAX (the maximum value for a short integer): +32,767 bits
- USHRT_MAX (the maximum value for an unsigned short integer): 65,535 bits
- INT_MIN (the minimum value for an integer): -2,147,483,648 bits
- INT_MAX (the maximum value for an integer): +2,147,483,647 bits
- UINT_MAX (the maximum value for an unsigned integer): 4,294,967,295 bits
- LONG_MIN (the minimum value for a long integer): -9,223,372,036,854,775,808 bits
- LONG_MAX (the maximum value for a long integer): +9,223,372,036,854,775,807 bits
- ULONG_MAX (the maximum value for an unsigned long integer): 18,446,744,073,709,551,615 bits
Let’s take SHRT_MAX and INT_MAX as examples. If the result that needed to be stored in the allocated position exceeded +32,767 bits or +2,147,483,647 bits, respectively, this would be an integer overflow. Therefore, a result of +32,788 bits would be an integer overflow in the first case. The integer overflow could also be a significantly larger value, something like +3,000,000,000 bits in the second case. It works the same for LONG_MAX, except the maximum value is much higher.
Now, let’s look at SHRT_MIN. The minimum value is -32,768 bits, so what happens if we have a value of -32,769 or -40,000? You might be tempted to call it integer underflow, and many people do. The numbers are too low for the minimum value, so an underflow does seem to make sense.
However, it’s really just the same problem in the other direction: The value that we need to store takes up more characters than are allocated to it.
If you can get your hands on a copy of Robert Seacord’s Secure Coding in C and C++, the Integer Security section states that:
An integer overflow occurs when an integer is increased beyond its maximum value or decreased beyond its minimum value… Decreasing an integer beyond its minimum value is often referred to as an integer underflow, although technically this term refers to a floating point condition.
Therefore, it’s best to call this situation an integer overflow as well. The situation is similar for INT_MIN and LONG_MIN as well. When the results of an operation are lower than their respective minimum values, it will cause an integer overflow.
We have kept each of the unsigned variables separate for the moment, for reasons which will become apparent in the coming sections. So what happens if an operation results in values that exceed the limits of the USHRT_MAX, UINT_MAX and ULONG_MAX variables? Once more, the values would be too large to be stored in the allocated space. Despite this, the C standard specifically states that these situations “can never overflow”. However, it’s still not unusual for people to talk about these results for unsigned variables as integer overflows.
These unsigned cases differ not just by having maximum values that are roughly double that of their signed SHRT_MAX, INT_MAX and LONG_MAX brethren. They also result in different behavior.
What happens when there is an integer overflow in C (unsigned variables and wraparounds)?
If unsigned variables exceed the maximum value (such as those listed in USHRT_MAX, UINT_MAX and ULONG_MAX) in a given situation, it simply just wraps around using modular arithmetic. This is basically the same as the odometer in the car ticking over from 999,999, all the way back to zero.
For example, the USHRT_MAX is 65,535 bits, which is displayed in binary as:
1111 1111 1111 1111
If we were to add 1 to the maximum unsigned short integer value of 65,535 bits, it would simply roll over back to:
0000 0000 0000 0000
Under other circumstances, we would normally expect the result of this operation to be:
1 1111 1111 1111 1111
This wraparound to a string of zeros can be quite problematic in a range of situations. It’s an over-the-top example, but we’re sure you can imagine how devastating it would be if your bank account wrapped back around to zero.
When the maximum unsigned value causes a wraparound to a value that is very different to the expected result, it can cause a lot of complications, errors, crashes, infinite loops, data corruption and more. This makes it critical to design our programs to avoid unintentional wraparounds.
So what happens in the examples of signed integer overflows?
We can’t predict it.
This is because the C standard states that signed integer overflows result in undefined behavior.
What happens when there is an integer overflow in C (signed variables and undefined behavior)?
In an ideal world, developers are supposed to program their software so that undefined behavior does not ever occur. When compilers assume that undefined behavior doesn’t occur, it allows them to optimize code.
However, the world is not perfect, people make mistakes, and some developers write code that can result in undefined behavior.
When something like an integer overflow leads to undefined behavior, there is no pre-specified action that is set to take place. Essentially, undefined behavior means that the condition is unexpected, and we don’t know what the program will actually do.
This means that unlike the previous cases for unsigned values that exceed the limits, we can’t be sure how the program will actually act. The value may just wrap around and cause similar problems to those listed in the prior section. It could also result in a negative integer. In some situations, you may even get lucky and it could work out as expected despite the integer overflow.
It really depends on the individual circumstances, but undefined behavior is most likely going to result in serious errors. Needless to say, unpredictable behavior in programs is bad, because we don’t know what will happen, what bugs it will cause, or whether it will open up the door for attackers.
This is why programmers need to be incredibly careful when writing their code, so that they can avoid situations that may lead to undefined behavior.
Integer overflows in other situations
We have discussed integer overflows in C to give you a more concrete example, but they are really a much wider issue. As we saw in our discussion on C, a value that exceeds its allotted space can either wrap around or result in undefined behavior, both of which can cause serious problems. Similar issues can also occur in other circumstances. We will quickly cover how integer overflows are dealt with in some of the more common situations:
Clamping
In certain circumstances, values that exceed the limits may be clamped instead of wrapping around via modular arithmetic. Clamping essentially means that values are restricted so that they can only be between two pre-specified numbers. This is also known as saturation.
Let’s say that the minimum and maximum values are the following:
-32,768
+32,767 bits
We could avoid integer overflows by clamping any values that exceed either this minimum or maximum. As an example, an operation may result in a value of -32,770 bits. This exceeds the minimum allowable value by 2 bits, and could therefore result in an integer overflow.
If the program had been coded to clamp values between -32,678 and +32,767, the values that exceed them are clamped to these respective minimums and maximums. Our value of -32,770 bits would be clamped and instead stored as -32,678, which would prevent the integer overflow.
It would work similarly if the result of an operation was +40,000. The clamping would come into play and the result would be reduced to the maximum, +32,767.
When operations produce results with values between these lower and upper limits, they are not affected by the clamping. If an operation computed a value of -25,342 or +843, these results would not be clamped or changed in any way.
C#
When an integer overflow occurs in C#, it wraps around by default. In checked contexts, an OverflowException alerts you of the integer overflow. This helps you discover the logic issues in your program. If the default is set to check for overflows, you can temporarily avoid these checks using the unchecked keyword.
Java
In Java, the default behavior for integer overflows is for the values to wrap around without any overt indication. It does not throw an exception when integer overflows happen, which can make it difficult to discover these issues. However, as of Java 8 you can use addExact() to throw an exception if an integer overflow occurs
Python
Integers in python have arbitrary precision, which means that arbitrarily large numbers can be represented. The only limitation is the memory. This results in MemoryError instead.
This means that integer overflows don’t normally occur if the operations are purely performed in Python. Despite this, historical reasons may occasionally raise an OverflowError for integers that lie outside of the required range.
Operations performed in the pydata stack can result in integer overflows, because of the fixed-precision integers involved. Floating point operations are generally not checked, due to standardization issues surrounding floating point exception handling in C. This is because the fixed-precision integers in the pydata stack are based on the C programming language.
General avoidance of integer overflows
Wraparounds, undefined behavior and other outcomes are all undesirable, so it’s important to avoid integer overflows in the first place. With the right precautions, we can prevent integer overflows, as well as the bugs and potential attacks to which they can lead.
To begin with, developers should allocate their variables with large enough minimum and maximum values that they can store all possible values that could be computed through a given operation.
When the available space is limited, the operations need to be ordered carefully, and the values for the variables also require thorough checking. This process needs to include particular attention to:
- Potential differences between 32-bit and 64-bit representation.
- Discrepancies in byte size.
- Precision.
- How the language in use handles numbers that are either too small or too large for the limits.
- Not-a-number calculations.
In addition to the above prevention measures for integer overflows, we should also be concerned about the closely related issue of integer casts. Integer casts are when one type of integer gets interpreted as another type. When the source integer is interpreted as if it were the destination integer this interpreted value may be very different. Integer casts fall into several different subtypes:
- Integer truncations — These are when a larger integer is cast to an integer type with a shorter length, resulting in the larger value being truncated to the shorter length. Only the least-significant bits are retained, changing the value of the integer. When the value is not what it should be, this can lead to all sorts of unexpected results.
- Mismatches between signed and unsigned integers — Errors in signing can have significant consequences. We previously discussed how the left-most digit represents the sign of the number in the two’s complement system, with a 1 being negative and a 0 being positive. If a large unsigned number begins with a 1 and it is misinterpreted, it could instead be seen as a negative number, which could have significant effects on the outcome.
- Sign extension — This can happen when a signed integer is cast to a larger bit length. If the original number is negative, sign extension can lead to unexpected values which may cause complications.
While the above may not lead to integer overflows, they can produce errors that need to be addressed to ensure that the program runs smoothly.
For example, when an unexpected sign extension (CWE-194) occurs in code that operates directly on memory buffers, it could cause the program to write or read outside the boundaries of the intended buffer. The sign extension could thus produce a value that is much higher (or lower) than an application’s allowable range – which would cause problems for an e-commerce site or any site listing quantities or prices.
Detection of integer overflows
You can use static analysis tools like cppcheck to check for signed integer overflows in C and C++. Such static analysis tools use constraint-based techniques or data flow analysis to minimize false positives, resulting in highly effective detection methods.
Other mitigation techniques
Due to the severity of integer overflows, it’s important to consider these other measures for avoiding them:
- Develop the program with a language that limits integer overflows, such as Python. Other options are to use a language that makes integer overflows easier to avoid, or one that automatically checks the bounds.
- Use a trusted library that either prevents these weaknesses, or makes them more easily avoidable. IntegerLib for C and C++, or SafeInt for C++ are both good examples of libraries that make it easier to avoid the unexpected consequences that come from integer overflows.
Integer overflows vs buffer overflows
Integer overflows and buffer overflows are somewhat similar bugs. As we have stated, an integer overflow is produced when the result of an operation is too large for the space allocated to it, causing either a wraparound, undefined behavior or other errors.
Buffer overflows also occur at a similar level. However, instead of just starting over again like a car’s odometer, buffer overflows occupy the neighboring space, overwriting legitimate data.
Let’s use simplified examples to show the difference between the two. In both cases, we have two 8 byte buffers next to each other, both storing hexadecimal values (these are just different ways to represent binary numbers).
Integer overflow example
First, we will demonstrate an integer overflow:
FFFFFFFF 87D9676E
Let’s say that the buffer on the left is going to have an operation performed on it where an extra 1 will be added. The buffer on the right is storing data that will be needed in the future.
When we add the 1 to buffer on the left, we should get a result of:
100000000 87D9676E
(Note that in hexadecimal, FFFFFFFF acts somewhat similarly to 99999999 in decimal, so adding a one gives us 100000000.)
However, if there are only eight bits of space available and we run into an integer overflow, the outcome could involve a wraparound with modular arithmetic, erroneously giving us a value of:
00000000 87D9676E
It could also lead to undefined behavior and something stranger might occur. Either way, it leads to a flaw, and the developer should have done something to prevent this from occurring.
Buffer overflow example
Now, let’s look at the very same operation with the same two buffers and the same addition of a single 1:
FFFFFFFF 87D9676E
In this case, let’s say that the bounds checking is insufficient, and the operation will trigger a buffer overflow.
Once more, we would expect the following result:
100000000 87D9676E
However, due to the buffer overflow anomaly, we actually end up with:
10000000 17D9676E
In this example, the result of the operation hasn’t simply wrapped around like the integer overflow did. Instead, the buffer overflow has overwritten the first character of the right register, replacing the 8 with a 1, which changes its value significantly.
When the system goes to use the data in the right buffer, it will not be able to access the correct value, ultimately resulting in an error.
In situations where buffer overflows are possible, they can be exploited by attackers. Hackers can deliberately craft code to cause a buffer overflow that allows them to write in adjacent areas.
When areas that hold executable code are overwritten with malicious code, an attacker can run their own code and try to escalate their privileges in an attempt to gain access to the entire system.
Interestingly, integer overflows can actually lead to buffer overflows. If an integer overflow occurs for the variable that determines the bit-length of the buffer, this can result in a wraparound that produces a much smaller number.
When this causes the buffer to be too small for the values that need to be stored there, it lead to the data overflowing into adjacent buffers and overwriting the data. This means that integer overflows can cause very significant security risks.
Integer overflow attacks
One of the main integer overflow issues is that a process cannot check the result of a calculation after it has occurred, meaning that it can’t determine whether there is a discrepancy between what the result should be, and the result that was stored due to the integer overflow error.
However, integer overflows do not allow attackers to direct execution flow control, nor do they allow overwriting in a direct manner. Ultimately, this means that although integer overflows can cause significant errors, attackers cannot exploit them in the majority of cases.
Despite this, there are still instances in which attackers can abuse integer overflows. These include situations in which they can overflow the variable for the buffer length, which can ultimately result in a buffer overflow, as we discussed in the prior section.
Examples of integer overflow attacks
Integer overflows have been a component in a range of prominent attacks. Some of these include:
An integer overflow led to Pegasus spyware on a Saudi activist’s phone
In March 2021, Citizen Lab examined the phone of a Saudi Arabian activist. Citizen Lab is a Canadian organization that conducts research at the junction between human rights, global security and communication technologies. It conducts high-level investigations and it is renowned for its work on documenting sophisticated attacks against activists.
In this case, the organization was able to determine that the anonymous Saudi activist had been infected by the NSO Group’s Pegasus spyware via a zero-click exploit. This means that the recipient didn’t even have to click the wrong file or link in order for the malware to execute.
What is Pegasus spyware and who is behind it?
Pegasus is one of the most sophisticated publicly known examples of hacking software. It can be used to initiate much more advanced attacks than those that your run-of-the-mill hacker is capable of.
This is because the NSO Group, who created Pegasus, has a large and coordinated team of security professionals that has combed commonly used software for bugs. Rather than publicly disclosing them (as you would hope a responsible company would do), they package them into software tools that the company claims provide “…authorized governments with technology that helps them combat terror and crime.”
While this may be the case in some instances, the NSO Group has also been accused of allowing its tools to target human rights activists in Mexico, India, and a host of other countries. It has also been accused of playing a role in the death of fellow Saudi dissident, Jamal Kashoggi.
The point is that Pegasus is very serious malware renowned for having its tendrils in some shady and morally repugnant dealings.
The hack on the Saudi activist
In the case of this anonymous Saudi activist, Citizen Lab recently reanalyzed an iTunes backup that it had made of the device.
During this recent analysis, the investigators uncovered over 30 files appended with the .gif extension in the Library/SMS/Attachments. These were received just prior to the phone’s infection with the Pegasus spyware.
However, these files weren’t actually gifs. Instead, 27 of them were Adobe PSD files which caused an IMTranscoderAgent crash on the device. Four of the files were really Adobe PDF files which held JBOG2-encoded streams.
Citizen Lab noticed that some of the file formats matched crashes they had previously observed in another device that had been hacked with Pegasus spyware. They suspected that these “.gif” files contained aspects of what Citizen Labs has dubbed the FORCEDENTRY exploit chain.
This exploit took advantage of an integer overflow vulnerability that was present in Apple’s CoreGraphics image rendering library.
Apple issues a security patch
Citizen Lab sent its findings to Apple in September, and within a week Apple had confirmed that it was a zero-day exploit that affected both MacOS and iOS. The company also issued a security update that patched the flaw, to prevent it from being exploitable in future versions.
Apple’s update revealed that “Processing a maliciously crafted PDF may lead to arbitrary code execution.” It also stated that the update addressed an integer overflow with improved input validation.
The CVE states that the issue was addressed in “…Security Update 2021-005 Catalina, iOS 14.8 and iPadOS 14.8, macOS Big Sur 11.6, watchOS 7.6.2.”
Citizen Lab is currently limiting the technical information it will publish about the exploit, perhaps to avoid letting the NSO Group know which aspects of its exploit chain it has an understanding of.
In 2023, Citizen Lab again discovered Pegasus on the Apple device of an employee of a Washington-based civil society organization. The exploit chain — which the researchers dubbed BLASTPASS — involved PassKit attachments containing malicious images being sent from an attacker iMessage account to the victim.
In a statement, the researchers said that the exploit chain was “capable of compromising iPhones running the latest version of iOS (16.6) without any interaction from the victim”.
Apple responded by assigning two CVEs related to this exploit chain — CVE-2023-41064 and CVE-2023-41061. It also issued updates —Â iOS 16.6.1 and iPadOS 16.6.1Â —Â that addressed the vulnerabilities — one of which was a buffer overflow issue.
Stagefright
In 2015, researchers from Zimperium published news of a family of vulnerabilities in Android’s media library, libstagefright. The attack vector involved integer overflow vulnerabilities that were implemented as part of the Android Open Source Project and were involved in playing multimedia files such as MP4s.
One of these was an integer overflow in libstagefright that allowed attackers to remotely execute code through MP4 data. Another integer overflow in libstagefright enabled attackers to craft MPEG-4 data that would remotely execute arbitrary code.
Along with the other vulnerabilities, the situation allowed attackers to escalate their privileges. One of the more frightening aspects of the attack was a proof of concept which showed that carefully crafted MMS messages could be sent to devices and the malicious code could run without the need for any end-user actions.
Another major concern involved Android’s fragmentation.
Although Google had already released a patch for the latest Android devices by the time the bug was publicly announced, hundreds of millions of people were left vulnerable to the exploit due to the large number of phone manufacturers that modify the Android operating system and then take months or years to pass on the necessary security patches.
Users were able to take advantage of some mitigation strategies, such as disabling MMS, but this was hardly a valid solution for many users. Ultimately, it was a serious security issue that is the result of the complex way in which Android is distributed.
More recently, Google’s Chrome browser released an update that addressed an actively exploited integer overflow vulnerability.
CVE-2023-2136 allowed a remote attacker who had compromised the renderer process in Skia — an open-source graphics library for Chrome — to potentially “perform a sandbox escape via a crafted HTML page.”
Microsoft Word integer overflow attacks
In 2018, the Mimecast team detected an attack group which seemed to have Serbian origins.
The group was crafting Microsoft Word documents to exploit integer overflow errors in the way the word processor handles the OLE file format. This allowed the group to circumvent security solutions such as anti-malware software and sandboxing technologies.
Mimecast disclosed its findings to Microsoft alongside a proof-of-concept. According to Mimecast, Microsoft did acknowledge that the behavior that had been discovered was unintentional, however it chose not to release a security patch at that time. Mimecast states that Microsoft’s inaction was because the issue didn’t cause code execution or memory corruption.
The vulnerability is due to an issue with the headers for the OLE format. When there is a large sector ID, the formula involved in computing the offset results in an integer overflow.
Only the least-significant 32 bits of the offset are included, which means that the offset is much smaller than it should be. This bug could be used to deliver payloads via OLE files, which presented a significant threat when chained to other Microsoft Word Vulnerabilities.
The researchers noticed an attack that involved the exploitation of CVE-2017-11882, which is a bug in various versions of Microsoft Office Service Pack and Microsoft Office 2016. This is a Microsoft Office memory corruption vulnerability which allows attackers to run arbitrary code, because it does not handle objects in memory appropriately. Microsoft did release a patch for this issue in 2017, so the attack we are outlining only affected versions that were still unpatched.
The attackers were exploiting CVE-2017-11882 with an Equation Editor stream that was defined inside an OLE object. This OLE object made use of the integer overflow from the sector ID, which allowed the malicious code to slip past security solutions without detection.
The combination of the integer overflow with the Equation Editor Exploit led to Microsoft Word ignoring the higher bytes of the OLE sector ID. It then loaded the malicious code into memory, which dropped a variant of Java JACKSBOT. This is a backdoor that can activate and infect the target whenever Java is installed.
If Java gets installed and activates JACKSBOT, it can lead to devastating consequences. These include executing programs, running shell commands, logging keystrokes, stealing cached passwords and more.
Ethereum smart contract exploit
In 2018, a blockchain security startup known as PeckShield discovered an unusual transaction. The company had set up an automated system to analyze Ethereum-based (ERC20) token transfers, and for it to send out alerts whenever it discovered anomalous behavior.
The team was greeted by one of these alerts on the 22nd of April. It involved Beauty Chain (BEC), and across two transactions, a total of 8,000,000… (this number is so big that there are a total of 63 zeros in it—for context, a billion is just 9 zeros) …000 tokens were transferred.
Cryptoslate states that the coin was trading at $0.32 per token, but despite the relatively small value of each token, the sheer number of them makes this an unfathomably large number.
PeckShield began looking into the code for the underlying smart contract, which indicated that the transfer involved exploiting a vulnerability that had previously been unknown in the contract.
The company dubbed the vulnerability batchOverflow, which involves an integer overflow at its heart. The vulnerable part of the code was located in batch transfer. The parameter for value could be set to a 256-bit integer, which is roughly the total quantity of coins that was transferred.
This variable for value was involved in a formula to calculate the amount. Alongside some other poor coding decisions, having such a large number for value resulted in an integer overflow for amount, making it zero.
This amount value of zero allowed the attacker to pass the security checks, allowing the attacker to transfer the tokens out.
The security researchers found more than a dozen other ERC20 contracts that were also vulnerable to batchOverflow. While they did reach out to those involved in the vulnerable projects, the tenets of Ethereum and the lack of existing security response mechanisms made the process challenging.
A number of major exchanges announced a halt to ERC20 token deposits in response to the vulnerability. This included one of the major exchanges, OKEx. Ultimately, this integer overflow bug caused a hit to the wider cryptocurrency market, causing Ethereum to dip from $664 to $612 in the course of a single day.
Integer overflow attacks won’t go away
Integer overflow attacks are far from new. These bugs have been present in software for a long time, and will continue to haunt us into the future. Although languages like Python are far more resistant to integer overflows, it is far from ubiquitous.
Ultimately, there is little that an end user can do to avoid them, except perhaps trying to avoid the products of disreputable developers.
The responsibility to protect us is really in the hands of the programmers, and while we can hope that people will educate themselves and test their software more thoroughly, mistakes will continue to be made. This means that integer overflows are sticking around, at least for now.