People often ask me "How did you learn how to hack?" The answer: by reading. This page is a collection of the blog posts and other articles that I have accumulated over the years of my journey. Enjoy!
AnyswapERC20 and AnyswapRouter assume that the function permit is implemented for all token contracts. This function is used to make the gasless approval of token transfers. Since some of these contracts had fallback functions, that was triggered instead of the permit code.from of the code. As a result, if there are ANY permits about an address, an attacker is able to steal these funds. To launch the attack, they need to trade a token that meets the criteria above. When they attempt to run this code for transferring funds from another user, there is no limitation on the amount of funds sent.fallback breaks this assumption. If you try to call code at an address that has no code, this will run STOP (which is a success) instead of REVERT as well.safeTransferFrom is used to send tokens from the depositor and into the contract. When doing this, the user needs to provide an ERC20 token address. transferFrom to remove funds of the user to put into the contract. Then, on the other blockchain, the user gets newly minted tokens. However, if no code was at this address, the transfer of funds would never occur but the minting would still take place. qXETH could be minted. This drained an estimated $80 million from the protocolload_instruction_at_checked instead of load_instruction_at was made. One of them makes the assumption that the verification has been done while the other does not. depositEth and the ETH20 deposit function. One of the functions validates that the callData and msg.value are the same. However, the other does not make this check in a require statement. callData without sending over any money. Then, they can withdraw this money, draining the contract of funds. depositByAddLiquidity contains a reentrancy vulnerability in it. depositByAddLiquidity, an internal call is made that transfers the caller deposit into a new pool. However, the pool contract can be controlled by an attacker. Once the flow is sent to the attacker contract, we can reenter this contract again. deposit can allow for moving the money TWICE. This can be repeated indefinitely to drain the contract of money. A twitter thread showing the code is at here from PeckShield. require statement, there is a validation that the user deposits enough other tokens in order to mint the new token. However, we are dealing with FSM, FTM and ETH are input, all at the same time. These require statements must be on point in order for this to work. msg.value (ETH) and not the minimum amount of FTM tokens. As a result, an attacker could ONLY send ETH and FSM tokens but send NO FSM tokens. This error allowed an attacker to mint XFTM without depositing any FTM. _minFtmIn variable contains ETH instead of FTM token minimum amount. Since this already passed, it was a major problem. The code is shown below:
require(_minFtmIn < ftmIn, "Pool::mint: Not enough FTM input");
XFTM without ever entering in any FTM. So, here is how they stole 2 million dollars:
XFTM token without entering in FTM tokens.XFTM token.XFTM token to FTM. Remember, we created these out of thin air.emergancyCommit function that happens in 24 hours. emergancyCommit(). Since the proposal was just sending the money to the Ukraine and the attacker, the money was gone. At this point, the attacker used the money to pay back the Flash loan. block.coinbase is the miner of the block, block.number is the height of the current block and so on. An attacker can manipulate these if they have enough resources. block.number and a combination of many others. blockhash is the verification hash of the block being mined. Why is this bad? blockhash.number will always yield 0, since the data is not known until after execution. blockhash.number - 1. For this one, an attacker can execute code within the same block of the smart contract. block.blockhash() seems great. However, Ethereum only keeps track of the most recent 256 block hashes. After this, 0 is return. As a result, the random number could be predicted, if the new code was executed 256 blocks later. This happened within a SmartBillions lottery. private modifier for a seed? That will not work either! Although this is private to the smart contracts, it is trivial to get this information off-chain then make the transaction with this information inside of it. Wallet and WalletLibrary. If the withdraw() function was not called, then the call was sent to the WalletLibrary code. What's the problem here? WalletLibrary can be called within the context of the Wallet. In particular, the function initWallet() could be called to change the owner of a contract through this arbitrary delegated call. Yikes!internal, then this would not have been possible. It is believed that the authors thought that since the function had NO modifiers for the outside visibility, they were safe.