Blockchain Semantics Insights

Business Case |  Deep Tech |  Announcements |  Blockchain Glossary | 
Blockchain Semantics Blog ERC20 short address attack

ERC20 short address attack

By Viswanath Kapavarapu | Oct. 4, 2018, 2:21 p.m. GMT

Remember that time your money got stolen on that crypto exchange? Yes, the exchange is to blame for not implementing enough security measures, but as are you for not picking the one with the right security measures.

Crypto-exchanges have always faced criticism, mostly legitimately,  for not maintaining enough security measures to protect user funds. The number of cyber attacks on exchanges have increased significantly particularly in the recent years. One such example is a cyber-attack, orchestrated by hackers against CoinRail, a South Korean exchange which resulted in a theft of altcoins, equivalent to $40 million.

In the current article, we shall look at one such case where an exchange server allowed users to empty out ERC20 tokens from its wallet. Before we can proceed with the details related to the attack, it is important to understand the functioning of exchanges. Here’s a short primer on exchanges.

Functioning of exchanges:

Crypto-Exchanges function similar to banks. When a user sends coins to an exchange, these coins become the property of the exchange. Any balance that is reflected in his account is solely based on the exchange’s internal database system. He doesn’t own any UTXO/txn relating to his balance yet.

Each time the user makes a trade, this internal balance is debited and credited. Only when he withdraws coins from exchange, coins corresponding to the withdrawal amount are sent to his external wallet.

How can a user withdraw more than he holds with an exchange?

Exchanges vulnerable to this attack allowed taking Ethereum Addresses of less than 20 bytes as an input while processing withdrawals. Usually an Ethereum Address looks like the following: 0xc1A7CF78F7854681E52b986414D458390e2D3400

However, because of the vulnerability users could omit the trailing zeros from the address to which they want their coins withdrawn and still the withdrawal shall be processed by Exchange Server.

To understand in detail, let's take the case where user ‘A’ wants to withdraw 20 GNT tokens from the exchange. The following steps are ideally involved:

  1. User ‘A’ checks his GNT token balance on the exchange. Assume he holds 23 Tokens in his wallet.
  2. He then decides to withdraw 20 GNT tokens of the possible 23 to his address 0xc1A7CF78F7854681E52b986414D458390e2D3400.
  3. The exchange server compares User A’s withdrawal amount to User A’s GNT balance on record on its internal ledger.
  4. If the user holds sufficient balance, a transaction processing the withdrawal would be sent to the Blockchain.

function transfer(address _to, uint256 _value)

In the current scenario, if the user decides to skip the trailing zeroes the input arguments to the transaction would look like:

  • _to, the destination address : 0xc1A7CF78F7854681E52b986414D458390e2D34
  • _value, the amount to b transferred: 20

To supplement the missing data, Ethereum Virtual Machine will supply those extra bits by adding zeroes at the end. However appending zeros at the end shifts the transfer amount four places to the left (if 4 bits are moved to the left 1<<4, the new value becomes 16 times the original). But because the actual balance check is done internally on exchange and not on smart contract, the transfer would be validated. Thereby users could rip off millions of tokens from the exchange wallet.

How to exploit?

Let's say you want to exploit this.

  1. Transfer GNT(or any other ERC20) tokens to the exchange server with this vulnerability.
  2. Generate an Ethereum address with trailing zeros at the end. Since an Ethereum Address is generated at random, you may need several tries before you end up with an address having trailing zeroes.
  3. Request a withdrawal of tokens from the server to your newly generated address(the one with trailing zeroes).

While requesting withdrawal, leave off the trailing zeros.

For example, if the newly generated address is

0xc1a7cf78f7854681e52b986414d458390e2d3400 , just enter 0xc1a7cf78f7854681e52b986414d458390e2d34 leaving out the two zeros at the end and request withdrawal of 100 GNT Tokens. Make sure you have 100 GNT tokens in your balance with the exchange.

Function: transfer(address _to, uint256 _value)

Arguments(as is, entered by the user):

[0] Address:  0xc1a7cf78f7854681e52b986414d458390e2d3400

(Two zeroes  marked in red deliberately skipped by User)

[1] Value:  100

Since the server does not validate the address, it will "pack" everything together yielding a 67 byte argument to the transfer function when 68 is what's needed.

All these arguments are then passed to the transaction as

In particular, has:

  1. The function signature -- a hash of the function name along with the data type of arguments required. For the function transfer which takes in an address and token amount in uint256, the function signature would be first 4 bytes of keccak256 of ‘transfer(address,uint256)’. Symbolically it is equivalent to


This turns out to be a9059cbb.

  1. Remaining part of other 63 bytes) would be the two arguments- address and amount in their encoded form.

        address 31 bytes + transfer amount 32 bytes

** Address should actually be 32 bytes but since the two trailing zeroes are skipped, it turns out to be only of 31 bytes


Input Arguments converted to hex. form

Function: transfer(address _to, uint256 _value)

MethodID(Function Signature): a9059cbb

[0] Address:  000000000000000000000000c1a7cf78f7854681e52b986414d458390e2d3400

(Two zeroes skipped by User marked in RED)

[1] Value:  0000000000000000000000000000000000000000000000000000000000000064

Combined Input Data(Function Signature + Address + Value):


Input Data Length: 67 bytes

Input Data Length required as per EVM: 68 bytes

As per the function signature EVM requires the to be of 68 bytes in length. But because the data supplied is only of 67 bytes,1 byte of leading zeros is taken from the amount, and added to the shortened address. This leaves us with the same address as we started, so tokens sent here are still under the attackers control.

Input Data interpreted by EVM: a9059cbb000000000000000000000000c1a7cf78f7854681e52b986414d458390e2d3400000

(Appends 1 byte at the end to supplement missing data)

Now when the parser tries to decode the token transfer amount, there aren't enough bytes left to make a uint256 -- so it just adds zeros to the end to makeup the required length. Technically, this means you've multiplied your amount by 256, and crucially, after the exchange has checked your balance on their internal ledger.

Arguments interpreted by Smart Contract as per Formatted Input Data


[0] First 4 bytes as Function Signature:  a9059cbb

[1] Next 32 bytes as address:  000000000000000000000000c1a7cf78f7854681e52b986414d458390e2d3400

[2] Remaining 32 bytes as transfer amount:


(Two zeroes(1 Byte or 8 Bits) appended at the end of Original Transfer Amount intended


Transfer amount sets inflated by 256 times)

Possible Fixes:

  1. Within the smart contract’s ‘Transfer’ function or any other function that deals with send/receive of tokens, a check that len( is of the right size would prevent such instances. Here’s a sample decorator implementing the check before the transfer function gets executed:

modifier onlyPayloadSize() {

assert( == 68);



function transfer(address _to, uint256 _value) returns(bool success) onlyPayloadSize { // Transfer Function Code}                  

Exchanges themselves should check for the validity of the input data before being executed directly onto the smart contract. Make sure that the parameters like gas, gasprice, destination address and transaction data matches the expected values.

If you liked the post, give it a   0
Apply for Blockchain Jobs

Course 1

Introduction to
Blockchain and Bitcoin

Course 2

Developing Decentralized
Applications on Ethereum
Using Solidity

Course 3

Investing In Bitcoin
and Cryptocurrencies


Be the first to comment.