Site Color

Text Color

Ad Color

Text Color





Sign Up to Save Your Colors


Maliciously Manipulate Storage Variables in Solidity [A How-To Guide] by@Valid.Network

Maliciously Manipulate Storage Variables in Solidity [A How-To Guide]

Valid Network Research Hacker Noon profile picture

Valid Network Research

Leaders in Blockchain cybersecurity.

Smart contracts, in general, offer the ability to determine factors and expectations set out by the contract. In the field of programming, factors and expectations can be described as ‘variables’ and ‘conditions’ within the actual code.

As such, state changes throughout a smart contract should adhere only to the contract’s logic. While it may seem straight forward, we will examine a couple of cases that prove a capable attacker could potentially violate the terms set by the smart contract in the first place.

Our overall intentions for this blog are to promote cybersecurity awareness within the blockchain community and demonstrate best practices to help smart contract developers write more robust code.

To better understand the method of the attack, let’s assume we have a safe that’s inside a mansion. Also, the guards outside the mansion are instructed to allow only red vehicles. However, an attacker already has that information and therefore, they simply climb the wall on the opposite end of the mansion to get into the safe.

Moreover, imagine the guards are understood as upholding the smart contract terms (allow only red cars), and the safe inside that mansion as the actual monetary balance (considered a variable within the code) of the attacker.

This kind of attack damages the concept of trust. The transparency and self-explanatory aspect, commonly known as “what you see it’s what you get”, is irreparably damaged as a result.

In a worst-case scenario, the attacker can cause even greater and more direct damage; balance modification.
Let’s dive deeper into the attack itself, so we may understand arithmetic overflows/underflows of EVM.

In Solidity, the size of a storage slot is 32 bytes, which means statically sized variables can take 32 bytes at most. ‘uint256’ type, for example, represents a non-negative number in the range of [0,2²⁵⁶-1]. Respectively, the ‘uint8’ type represents a non-negative number in the range of [0,2⁸ -1].

The number presented after ‘uint’ represents the number of bits needed to store the variable. Arithmetic operations are supposed to act as under modulo(2**number_of_bits). For example, the arithmetic addition of a+b (two ‘uint256’ variables) is implemented as (a+b) modulo 2²⁵⁶.

An arithmetic overflow occurs when the product of an arithmetic operation is more than the largest number inside its type range. For example, let’s take uint8 x = 255, and then x = x + 1. After these two operations x will hold the value of 0, causing an overflow.

An arithmetic underflow occurs when the product of an arithmetic operation is less than the smallest number inside its type range. For example, let’s take uint8 x = 0, and then x = x -1. After these two operations x will hold the value of 255, causing an underflow.

At this point, it’s worth mentioning EVM enables a programmer to use arithmetic over/under-flows within the program, without throwing an exception.

Arithmetic over/under-flows caused a lot of troubles in the past. BEC token hack, for example, enabled the attacker to mint and steal trillions of Ethereum tokens, thus devaluing the token’s price to effectively zero. Therefore, it is imperative developers consider using a ‘SafeMath’ like libraries that reverts over/under-flows.

Now that we have a firmer understanding of arithmetic over/under-flows attacks, let’s explore the equally devastating vulnerability of storage variables manipulation. This can be achieved by these three methods:

Array length underflow

Dynamically-sized arrays in solidity store their length in a slot, and the values of the array are stored sequentially starting at the hash of the slot.

The location of an element of a dynamically-sized array

The array length property can be manipulated programmatically, which means one can modify the length of the array without changing the array’s values. The Solidity compiler adds a restriction that prevents access to slots that are not part of the array, by checking that index < array.length. Which means that theoretically, an attacker can modify any storage slot he wishes to just by choosing an index that satisfies:

index = (slotToModify - keccak256(arraySlot)) toTwosCompliment(256)Index < array.lengthArray length underflow example

Using the example above, the attacker holds a balance of 2. However, using less than 5 lines of code, he/she can effectively modify the balance and steal tokens by launching the attack below:

call ‘decreaseSize’ function (line 16), which changes the array length property from 0 to ((2**256) -1).compute the storage slot that holds the balance(slotToModify), by calling ‘mapLocation’ function with slot = 1 and key = attackerAddress. This computes to 40188104427714482242681265279535408119688761634073773811122280228267054098286.find the index that satisfies:

index = (slotToModify - keccak256(arraySlot)) toTwosCompliment(256) = 21618673952608599655092999141927839583015649660180456411662060369447791395339

call ‘setValue’ function (line 12) with index = 21618673952608599655092999141927839583015649660180456411662060369447791395339 and value = hacker’s desired balance 😈

Note that this vulnerability has been mitigated for solidity compiler versions >= 0.5.0

Uninitialized storage pointer

Storage pointers are very useful. Intended for non-scalar variables, their purpose is to avoid unnecessary copies from storage to memory.

Storage pointer usage example

The above example shows the usage of storage pointer to modify properties inside a struct.

Now let’s consider the following example:

Uninitialized storage pointer example

As you can see here, p is not initialized, which means it refers to slot 0 in storage. It also means that line 14 will overwrite the value inside of slot 0. In order for the attacker to modify its balance, all he has to do is call the ‘setHeight’ function(line 12) with height = hacker’s desired balance 😈

Note that this vulnerability has been mitigated for solidity compiler versions >= 0.5.0

Inside slot overflow

As mentioned before, in Solidity, the size of a storage slot is 32 bytes. Statically-sized variables that need less than 32 bytes are packed into a single storage slot. Developers utilize this technique to save gas by combining multiple reads or writes into a single operation, whenever possible.

Inside slot overflow example

What do you think might happen after invoking ‘incrementX’ function (line7)?

Normally, we would expect ‘x’ to turn ‘0’ without impacting ‘attackerBalance’. However, a compiler bug caused ‘x’ to turn ‘0’ but ‘attackerBalance’ had turned to ‘1’. Therefore, increasing the attacker’s original balance.

Note that this vulnerability has been mitigated for solidity compiler versions >= 0.4.4

To recap, we have reviewed three separate ways to maliciously modify one’s balance: Array length underflow, uninitialized storage pointer, and inside slot overflow. As we’ve demonstrated throughout, using our examples, one wrong line of code, can have a potentially devastating impact on a project. At Valid Network, we’ve handcrafted an intuitive platform which can identify these sort of vulnerabilities, among many others not mentioned.

Enjoying our content? Follow us on LinkedIn for more updates.

About Valid Network

Valid Network provides a comprehensive cybersecurity platform for DApps (Decentralized Applications). Valid Network’s unique value proposition is well defined in its DevSecOps approach that protects organizations from the first line of code developed, through supplying assurance and governance for live business transactions. Valid Network’s experience in distributed systems brought the team to exhibit several breakthrough technologies for detection, monitoring, and enforcement.