Hacking Etherscan (2) : Creating fake Token Transfer.
Now, let’s play a bit more with the transfer event and see how we can fool Etherscan once again.
At first we will do a quick reminder of how Etherscan track the number of tokens for every holders of a smart contract.
Etherscan, use the transfer event in solidity to track every transfer on the blockchain.
Every time a token is transferred, Transfer event is fired with the address _from, the address _to and the amount as arguments.
The displayed balance on Etherscan is the sum (and the difference) of all Transfer events.
For more information, you can check the first article of this series about hacking Etherscan: https://medium.com/@TrustChain/hacking-etherscan-erc20-cheating-46dc229f5f8
1. Fake transfer in Etherscan.
What if we create a function called fake_transfer()
and invoke it, what will happen?
function fake_transfer(address from,address to,uint256 amount){
emit Transfer(from, to, amount);}
This is exactly the same function as _transfer()
on ERC20 smart contracts but only the event remains.
After the execution of the function, only the Transfer event will be recorded on the blockchain but the state of the smart contract (like balances) WON’T change.
If we deploy this contract, of course Etherscan will show bad results.
constructor() ERC20("MyToken", "MTK") { _mint(msg.sender,100000000000000000000000000000000000000000000);
// don't forget to remove the transfer event in the _mint functionfake_transfer(0x0000000000000000000000000000000000000000,msg.sender,1000);}
As the decimals are still 18, Etherscan show 10^(-18+3) = 10^-15 tokens transferred from 0 address to the owner. But we “minted” an amount which hove a lot more zeros….
Therefore the value you see on Etherscan MAY NOT BE VALID.
2. Finding the true balance of the owner (or not)
How to verify the true value of the contract owner? (in order to insure that the owner won’t dump the token like a shitcoin and drain all your money)
One of the ways to do that is to query the balanceOf()
function and see the result.
It can work easily, we can do it with ethers.js
,we can also use Metamask or we can query Etherscan for this specific address.
The good news is that we were able to get back the “true” value .(1000….000000000 tokens)
The reason is that these methods relies on balanceOf() instead of Transfer event which are more precise but harder to implement.
(More precise because, for example if we want to classify each hold by the number of to they hold, Etherscan need to loop for every possible address this is impossible in the real life as they are about 2¹⁶⁰ possible Ethereum addresses in total, so the Transfer
event was created)
3. Trapping the balance function
But what if we trap the function balanceOf()
?
I mean by “trapping”, modifying the return value of balanceOf()
by returning a fake value?
For example, instead of:
function balanceOf(address account) public view returns (uint256) {
return _balances[account];
}
We write:
function balanceOf(address account) public view returns (uint256) {if (account == 0x6166f7E....) {
return 1000;
} else {
return _balances[account];
}}
This smart contract return 1000
if account
is equal to the address 0x6166f7E...
even if balances[0x6166f7E...]
is equal to another value.
So 0x6166f7E
may fake his balance to anyone.
Moreover, in the constructor I can add as many token as i want for the address 0x6166f7E
in the constructor.
constructor() ERC20("MyToken", "MTK") { _mint(msg.sender,100000000000000000000000000000000000000000000);
// don't forget to remove the Transfer event in the _mint()
// function // msg.sender = 0x6166f7E fake_transfer(0x000000000...000000,msg.sender,1000);
// emit Transsfer(0 address,msg.sender,1000)}
As a result, the owner of the contract may hide his balance to the holders AND to Etherscan without anyone noticing anything.
Let’s deploy this smart contract and see what happens in Etherscan & Metamask:
Metamask and Etherscan shows the same value: 0.00000…1 (=10^-15 MTK)
But we minted, an incredible amount just before!
_mint(msg.sender,100000000000000000000000000000000000000000000);
So we succeeded to fool Metamask and Etherscan.(and any other wallet which relies on balanceOf()
function)
4. Can you still trust Etherscan?
Looks creepy right? Is there still a way to trust Etherscan or any crypto project which may be able to fool his holders?
In theory yes.
If the smart contract code is VERIFIED (and the constructor too) then it’s a lot harder to fool Etherscan . But if one of these 2 conditions is absent, it’s a lot easier to fool Etherscan.
The blockchain is transparent, this means that every data stored in the blockchain, every transaction recorded or any address balance is stored on the blockchain and thus freely available to anyone.
But in reality this is not that simple, especially if you lack technical skills.
In fact the only way to find out if the contract is “trapped”, is to reverse the byte-code of the smart contract byte code in order to understand the full logic of the contract and see what is happening inside.
If you want to understand how to reverse engineer smart contract on the blockchain. You can check my series about reversing and debugging smart contract which teaches you this (rare) skill: https://medium.com/@TrustChain/list/reversing-and-degugging-evm-smart-contracts-f4dd9195d07b
Any other way you may find isn’t flawless and may be easily circumventable…