ICO Smart contract Vulnerability: Short Address Attack

Selvakumar Esra
huzzle
Published in
2 min readMay 3, 2018

This is equivalent of minor SQL injection bug. EVM appends 0at the end of an address if it detects an underflow(smaller address that doesn’t make upto 256 bits) which happens when dealing with data types that can be up to 256 bits, EVM takes a sane approach to appends 0.

This was simply an input validation bug on the senders’ part, coupled with broken transaction generation code. If smart contract had been properly validated, sanitised, and padded, this would have never happened.

A malicious user can construct this attack by removing last Zeros from an ether address that ends with 0 or multiple0s. For example,

0xc3Bb35818d58FCA0C4943bA98938cb6F46A917B0

Just for clarity, a function such as Send(0x1234…67890, 10 , “10 tokens for you”) would have the signature Send(address, uint, string), possibly hashed to save space. address and uint are fixed at 20 and 32 bytes respectively, and string would probably store it's size (20 bytes) and probably an offset to where the data begins.

In order for this to work, the transaction must match the format of the signature of the smart contract function. Thus the transaction data must store 20 bytes for address, 32 bytes for uint, probably 32 bytes and 32 bytes for string length and offset depending on dev specification, and 19 bytes for stringdata.

All these arguments are passed around under the hood in the msg.data portion of a call. msg.data has three components -- the function signature -- a hash of the name of the function, then the two arguments, address and amount. In ERC20, amount is a uint256, so it has lots of leading zeros.

What happens in this attack is that one byte of leading zeros is taken from the amount, and given to the shortened address. This leaves us with the same address as we started with, so tokens sent here will be transferable.

When the parser is getting to the end of its bytes, it has an underflow — there aren’t enough bytes left to make a uint256 -- so it just adds zeros to the end and calls it a day. This means you've multiplied your amount by 1<<8 or 256, and crucially after the exchange has checked your balance on their internal ledger.

Solution: use a method to check

contract NonPayloadAttackableToken {
modifier onlyPayloadSize(uint size) {
assert(msg.data.length >= size + 4);
_;
}
function transfer(address _to, uint256 _value) onlyPayloadSize(2 * 32) {
// do stuff
}
}

--

--

Selvakumar Esra
huzzle
Writer for

Loves Blockchain, Machine Learning, Fintech, GameTech, Augmented reality, Virtual reality and tech entrepreneurship.