- Published
The inspiration for the Ethernaut CTF
- Authors
- Name
- Tony
- @sodexx7
CONTENTS
Some aspects of the smart contract programming
Summarize the Ethernaut puzzles
Analyze the puzzles based on my understanding
- The features of solidity
- The relationships with the blockchain characteristics
- The interaction calls among smart contracts
- The use of third-party contracts libraries
- Attack by consuming all the available gas
- The possibilities or the innovations of the business logic
- Smart contract's proxy upgrade mechanism
- Bytecode level
Closing thoughts
Other references
- the reference of the Ethernaut
- the related links
1. Some aspects of the smart contract programming
Some aspects have given me deep impressions since I touch the smart contracts.
1 Security. The classic Dao attack and on-chain hacker attack incidents keep demonstrating security's huge critical role.
2 Gas optimation.Recently gas is not a hot topic. The reasons may be that the current stage is the bear market or the layer2 ecosystem has matured, leading to the gas price decreasing. However, gas is fundamental for many things. In some situations, it will become more and more critical. To some degree, it seems like the requirements for speed in quantitative trading. With the exponential growth of the user numbers, the gas price will become the market's core attention again. On the other hand, each step of the smart contracts running has been broken down to the basic opcodes, and each opcode consumed fixed gas. All that is fundamental to the EVM, also goes for the Ethereum.
3 The sophisticated interactions among smart contracts.The interactions will become more and more sophisticated, along with business logic development. Sometimes, it's challenging to figure out the relationships cleanly.
For this article, the interaction means the calls among Contract accounts.
CA(Contract account): This contract was controlled by the code in this address.
EOA(Externally-owned account): This contract was controlled by the third party who owns the address's private key.
While going through the smart contract codes, only reading the static variables or the description of some functions doesn't guarantee enough understanding of the dynamic call flow. One case below shows the complex interactions.
Many hacked incidents occur in the interaction calls among smart contracts. Such as the classic hack incident:re-entrance.
4 Much new business logic will emerge based on smart contracts.As we know, blockchain's characteristics include Transparent, Unchangeable, and Unstopped. Considering the smart contract's features based on it, the other programming paradigm will emerge, such as how to develop or test based on the open Infrastructure、how to consider the design while taking the smart contract as the basic unit to think、how to upgrade the on-chain logic?... All these need people to adapt and practice, borrow ideas from others, and understand more possibilities.
2. Summarize the Ethernaut puzzles
As you can see, I always want to adapt to the new paradigm and better understand the difference between on-chain and traditional programming. The ethernaut CTF is an excellent challenge to test me and increase my understanding.
Below is my summary of the Ethernaut puzzles.
Tips: These analyses of the puzzles cover only some of the details from start to end. There also have some articles that explain very thoroughly. Some puzzles are challenging to solve, which takes me many hours to find the right clue, and sometimes I need others' guidance. If one begins to learn the smart contract, this article can act as an advanced topic reference. If one understands smart contracts well or is an advanced smart contract programmer, the contents below should be taken as the specific summary.
Because of my limited level, perhaps some content descriptions either lack or are incorrect.Welcome comments and discussions.
3. Analyze the puzzles based on my understanding
Ethernaut CTF shows the different security problems in the puzzle's format. First of all, the challenger can practice the security problems. Secondly, Although they all seem to be security problems, one can deepen many other aspects of understanding. Such as the solidity features、the derivative Issues related to the blockchain、the business logic design; Lastly, it enhanced the familiarity with smart contracts and felt the difference between the new and traditional architecture.
My attentions only focus on the below fields.
- The difficult problems I encounter while solving the puzzles.
- The general topics or the topic kept on developing. Such as the features of solidity.
- The Indispensable aspects, such as the proxy upgrade mechanism.
3.1 The features of solidity
1) overflow/underflow check
For example, the add or sub operations among the uint256 variables, the result might go over the max value or the min value of the uint256 Type. Related puzzle Token. This type of check has been added since solidity ^0.8.
2) fallback
While calling a smart contract function that doesn't exist in the called smart contract, Or when receiving ETH, the accepted smart contract doesn't have the received function, and the fallback function exists. In these two scenarios, the fallback function will be called. Based on this feature, fallback can come in some specific scenes. Such as re-entrance, while one contract received eth, this contract can do more logic in the fallback function. Another example is the proxy mechanism. The implementation contract only deals with the business logic and doesn't store the ultimate states. How to call the business logic by the proxy smart contract? The design uses the delegateCall function under the fallback function in the proxy smart contract.
3)Custom error
Good Samaritan puzzle involves dealing with a customer error during many interactions calling. One function can't judge where an error comes from.
4) selfdestruct
This function aims to destroy the smart contract while sending all the eth balance on the address to the target address. The original design's goal is to decrease gas consumption. But this leads to an unexpected result-no smart contract can guarantee that it cannot receive eth. When someone builds business logic on smart contract's eth balance, such as address(this).balance == 0, which has great potential risk and can be used by hackers. The newest solidity version suggests not using the function https://eips.ethereum.org/EIPS/eip-6049.
3.2 The relationships with the blockchain characteristics
1) Transparent
There are public, private, and internal while defining the solidity variables. But defining the variables as private does not mean others can't watch them, which means other on-chain smart contracts can't see them. The way to access the corresponding slot value also exists, such as the function provider.getStorageAt( addr , pos [ , blockTag = latest ] ) , which provided by the ethers.js. Can reference the puzzles: Vault ,Privacy .
2) Random number
Because the on-chain variables, even defined as private, can be accessed by some functions, miners have these abilities: control the blockchain hash, and the timestamp, whether including one transaction or not. All these can lead to potential risks while using the on-chain data generating the random number. One available solution is to import the off-chain random numbers, such as the chainlink. The related puzzle is Coin Flip. Of course, some business logic needs not only random numbers but also other business data, such as stock price, which can also be obtained by chainlink or a similar third party.
There is a new opcode-PREVRANDOA since the Ethereum merge, which can generate a random number and is more randomness than the blockhash.
3) the deterministic address
The function keccak256(address, nonce) can generate the smart contract's address. The details can be read this https://swende.se/blog/Ethereum_quirks_and_vulns.html. So one can send eth to one smart contract address that doesn't have the private keys and use the above function to acquire the corresponding eth. The related puzzle is Recovery.
4)The data structure of the smart contracts states
Below is the smart contract storage data structure. The smart contract includes 2^256 slots. Each slot's length is bytes32.
How to store the dynamic arrays? (The related puzzle is Alien Codex)
The variable codex's slot is the third. The third slot stores the length of the array(codex). Unexpectedly, each value of the array does not exist in the following slots. Firstly, which slot is the codex's first value's location? The answer is the location0 = keccak256(abi.encode(2)). And then the next value's slot is the previous slot add 1.
Puzzle Alien Codex, The trick to solving this puzzle is changing the owner's address to the hacker-controlled address.
While the above contract was deployed, the owner's address was set. You can see in the blew.
So, change slot-0's value to the desired address, and the contract's owner will change.
The first thought in my mind is how to make the keccakHashde(abi.encode(index)) == 0? What's the index value? But nowhere can I get it.
Now the design of how to store a dynamic array comes. As the location(slot) of the codex[0]'s value is known, the maximum value of the slots length is 2^256. So the codex[2^256- array[0]] equals location(2^256)'s value.
Suppose that the max length of the dynamic array codex is 2^256. If changed the codex[ 2^256- array[0]+1]'s value into the desired address, then the contract's first slot will store the desired address, and the contract's owner will be changed.
How to make the dynamic arrays' length 2^256. Call the below function can get the result.
3.3 The interaction calls among smart contracts
1) The basic interaction calls among smart contracts.
I break down the interaction into two types. One is sending ETH. Another is running the on-chain contract's logic. Of course, The two types can occur simultaneously, Running the contract's business logic while sending eth.
How to send or receive ETH? I have mentioned the selfdestruct, fallback in the solidity features section. There are some arguments in history. Functions including send, transfer, and call all can send ETH, but now call was suggested to be used while should be careful of the re-entrance attack.
Send or transfer all consumed the fixed gas(2300), But in the actual situations, each block's gas cost is not fixed, so it's not rational to fix consumed gas while calling other functions. The related reference(https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/ )
The related puzzle King.
There are three basic calls when one smart contract calls another: call,staticcall,delegateCall.
The call can be used when one smart contract wants to call another smart contract's function or send ETH; DelegateCall can also be used to call another smart contract's functions, but the difference is delegateCall will modify the states of the caller's address. As the delegateCall is often used in the proxy upgrade mechanism, I put the related contents in the Proxy upgrade mechanism section. Staticcall means not changing the blockchain's states while calling.
2) Some caller variables' modifications during the smart contract calls.
tx.origin means the original caller when one call occurs, always the EOA.
msg.sender: There always have many internal calls during one entire call. Each call involved one caller contract and the called contract. For the called contract, the caller contract is msg.sender.
related puzzle Telephone
3) The business logic relies on the third-party
the related puzzles: Elevator,shop,Re-entrancy
The business logic based on others leading to potential risks has frequently been found. No matter the called or caller contract, One can build the arbitrary logic in CA contracts and then get around the original check to exploit the target address.
related puzzle Elevator
This code's function (! building.isLastFloor(_floor) ) relies on the third-party's logic, So one attacker can modify the results when calling the attacker's address.
Puzzle Shop
_buyer.price() can also be controlled in an attacker's address.
Actually, the interaction relationships among the smart contracts are universal and complex; Sometimes, it's difficult to find the potential problem.
puzzle re-entrance classical attack
While executing the function msg.sender.call\{value:\_amount\}("")
, the msg.sender can call the above contract again, which can lead to recursive calls.
Just like: Contract(caller)B=》Contract(called)A=》ContractB(caller)=》ContractA(called)... can withdraw all the ETH in the contract A.
The blew is the on-chain case: The attacker address (ContractA): 0x.........d653666d The attacked address(ContractB): 0x.........90ac810
We can see the recursive call relationships: ContractA => ContractB => ContractA=>ContractB...
the related link:
3.4 The usage of third-party contracts libraries
We can apply many third-party standard libraries to our protocols, but some explosions can occur without enough careful.
the related puzzle Naught Coin
For the function transfer(), the modified LockToken checks the caller's conditions. But for the ERC20, two funcitons are sending ETH, one is transfer(), and another is transferFrom(). One can directly call the transferFrom() and then pass over the LockToken.
ERC20.sol(openzeppelin)
3.5 Attack by consuming all the available gas
This attack type seems like a smart contract's logic relying on others again, but for the outside, which means consuming all the gas available.
puzzle Denial
img
The call function exists, but it does not appoint how much gas can consume in the withdraw function. If the attacker consumes all the gas in its attacker's address, then the withdraw can't complete.
3.6 The possibilities or the innovations of the business logic
The famous formula of the defi is x*y=k. x represents token A's quantity, y represents token B's quantity, and k is constant. The formula can calculate the price of token A or token B, such as the price of token A per token B: y/x.
But as the puzzle dex show, some potential risks can occur.
img
This puzzle aims to withdraw all the token A or toke B in the dex pool. Because we can adjust the quantity of token A or token B, then manipulate the quantity until the price reaches the point we can swap one into all the other token's amounts.
As seen below shows, we can adjust the quantity of token A or token B through the swap function. When the rate of the tokenA/tokenB equals 110/45, and we have at least 45 token B, then we can withdraw all the tokenB by 45 tokenB.
As the above shows, I swapped five times. In the fifth swap, the rate of tokenA/tokenB equals 110/45. My 45 token B can swap into 110 token A.
It's obvious that defining the token price only through the two tokens quantity in the pool can be manipulated by the third party. Service providers such as chainlink offer the price feed, which can avoid this type of problem.
For the puzzle dex2. The aim is to withdraw all token A and token B. The difference between dex and dex2 is that there is no check for the token pairs in dex2. I feel it's very funny to solve it.
img
We can build another token pair, Create token C and send 10 toeken C to the dex2 address. Now tokenA/toekn C(100/10 ) , then we can get all the tokenA by only 10 token C. The same as tokenB: token B/token C(100/20).
3.7 Smart contract's proxy upgrade mechanism
The smart contracts codes can't be changed when deployed, but what can we do if we need to upgrade the code? Then the proxy upgrade mechanism comes. In a nutshell, the proxy upgrade mechanism will not change the original code but can also upgrade the code. Two types of contracts are involved in the mechanism: one is proxy contract, which stores the ultimate states, and another is an implementation contract, which implements the business logic.
There are two types proxy upgrade mechanism including Transparent and UUPS. The main differences is that which contract supply the interface to upgrade the code? Transparent is the proxy contract, UUPS is the implementation contract.
Puzzle Puzzle Walle, which is transparent type. The code is through the proxy contract when you need to modify some rights or upgrade. This puzzle aims to become the owner of the proxy contract.
The proxy contract: PuzzleProxy, The implementation contract:PuzzleWallet.
Proxy contract(PuzzleProxy): slot0,slot1=>pendingAdmin,admin.
implementation contract(PuzzleWallet): slot0,slot1=>owner, maxBalance.
The corresponding relationships between the two contracts as below:
implementation contract(PuzzleWallet): slot0:owner => Proxy contract(PuzzleProxy): slot0:pendingAdmin.
implementation contract(PuzzleWallet): slot1:maxBalance => Proxy contract(PuzzleProxy): slot1:admin.
We can become the PuzzleWallet's owner by setting the maxBalance as the desired address by calling the implementation contract's functions. But how to call? Withdraw all the ETH by using the mulitCall and deposit function, lastly call the setMaxBalance(uint256 _maxBalance) to change the address.
The details of how to use multicall,deposit,setMaxBalance as below, which someone explains throughly.
The above puzzles demonstrate a critical aspect when using proxy upgrade mechanism. By calling the implementation contract's function, one should be very careful about the collision between the proxy contract states and the implementation states.
https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies
puzzle Motorbike
Motorbike is the second type:UUPS. Upgrading the code by interacting with the implementation contract code and the gas of deploying the proxy contract went down. This puzzle aims to break down the Engine, so the proxy contract can't work through the Engine.
The proxy contract is Motorbike, implementation contract is Engine, which has a upgrateToAndCall() function that can upgrade the code.
The initialize() function of the Engine does not work in itself address while the Motorbike is deployed. So the states, including horsePower and upgrader, are zero in the Engine address.
From this point on, Everyone who calls the Engine's initialize () can become the Engine's owner. And then can call the upgradeToAndCall() function to destroy the Engine contract.
As the below flow chat shows, The hacker contract, including the function killMySelf, which executes the selfdestruct, can be called by the Engine contract. If that, the Engine itself will destroy itself.
Because the motorbike contract's corresponding implementation contract has been destroyed, it can't work.
So should be careful while using the UUPS type, although it brings some benefits.
More discussions can see below.
Ethernaut.openzeppelin
Ethernaut used the TransparentUpgradeableProxy pattern when they deployed. Upgrading the code is through by calling the proxy contract. I wanted to query all my statistical data after completing all the puzzles. Firstly, I directly call the implementation contract's function in its address; as we know, the ultimate data were stored in the proxy contract, So the result is nothing. But when I became aware the ethernaut is using the TransparentUpgradeableProxy, I called the implementation contract's abi in the proxy contract's address; that's OK. The link is below.
3.8 Bytecode level
No matter how long, the on-chain smart contracts' code is a hex string sequence. The EVM will execute the corresponding opcodes in the stack while manipulating the memory or the storage according to the pre-defined rules of the EVM, finally completing the tx.
on-chain smart contract's code
puzzle MagicNumber
MagicNumber is involved in how the EVM deal with the bytescode. It's a must require for the advanced solidity programmer. Many situations require this, such as manipulating the memory through the Yui or assembly. Many top protocols can see the usage, such as uniswap.
One reference can see below:
4. Closing thoughts
The blockchain's infrastructure has had a significant development in recent years. More and more third-party libraries appear, such as the frequently used tools: Openzeppelin, Chainlink. Development environment tools including hardhat,foundary. The ethernaut often supplies more content while I solve the above puzzles, which gives me more views about the blockchains' recent development, such as the discussion of proxy upgrade mechanism just beginning about two years ago.
One example is the puzzle DoubleEntryPoint mentions the forta.
Forta comprises a decentralized network of independent node operators who scan all transactions and block-by-block state changes for outlier transactions and threats. When an issue is detected, node operators send alerts to subscribers of potential risks, which enables them to take action.
Similar services, like the graph, can quickly access the on-chain data or functions for developers.
Another example about the analysis of a hacker incident.
These related discussions or some articles emerge in many different places, and the news tools or the new fundamental design... not only a source of great value but also a great help for personal corresponding technological skills.
5. Other references
the reference of the Ethernaut
Github address of ethernaut
https://github.com/OpenZeppelin/ethernaut/tree/master/contracts/contracts
The solutions supplied by the ethernaut
https://github.com/OpenZeppelin/ethernaut/tree/master/contracts/contracts/attacks
The flow chat of creating puzzle and submitting puzzle.
The ethernaut contract will call the level factory to create the corresponding instance and initiate the challenger with the related puzzles' data when the challenger tries to hack a puzzle.
The ethernaut contract will call the level factory to check the result is valid, then modify the challenger with the related puzzle's data if valid when the challenger submits a puzzle.
Proxy contract address:Statistics.sol(https://github.com/OpenZeppelin/ethernaut/blob/master/contracts/contracts/metrics/Statistics.sol); The on-chain address (https://goerli.etherscan.io/address/0x7000e0f2f5a389df14b50c6f84686123f19b27f6#code).
Implementation contract address:ProxyAdmin.sol:(https://github.com/OpenZeppelin/ethernaut/blob/master/contracts/contracts/proxy/ProxyStats.sol) ; The on-chain address (https://goerli.etherscan.io/address/0x7ae0655F0Ee1e7752D7C62493CEa1E69A810e2ed#code).
2.the related links
- https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7/
- https://blog.ethereum.org/2016/12/05/zksnarks-in-a-nutshell
- https://ethernaut.openzeppelin.com/level/0x573eAaf1C1c2521e671534FAA525fAAf0894eCEb
- https://medium.com/@dariusdev/how-to-read-ethereum-contract-storage-44252c8af925
- https://ethernaut.openzeppelin.com/level/0xb4B157C7c4b0921065Dded675dFe10759EecaA6D
- https://weka.medium.com/announcing-the-winners-of-the-first-underhanded-solidity-coding-contest-282563a87079
- https://ethernaut.openzeppelin.com/level/0x9CB391dbcD447E645D6Cb55dE6ca23164130D008
- https://ethernaut.openzeppelin.com/level/0x9CB391dbcD447E645D6Cb55dE6ca23164130D008
- https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca
- https://forum.openzeppelin.com/t/uupsupgradeable-vulnerability-post-mortem/15680
- https://forum.openzeppelin.com/t/uupsupgradeable-vulnerability-post-mortem/15680
- https://ethernaut.openzeppelin.com/level/0x9451961b7Aea1Df57bc20CC68D72f662241b5493
- https://ethernaut.openzeppelin.com/level/0x9451961b7Aea1Df57bc20CC68D72f662241b5493
- https://blog.openzeppelin.com/compound-tusd-integration-issue-retrospective/
- https://swende.se/blog/Ethereum_quirks_and_vulns.html
- https://compound.finance/governance/proposals/76
- https://blog.soliditylang.org/2021/04/21/custom-errors/
- https://medium.com/loom-network/ethereum-solidity-memory-vs-storage-how-to-initialize-an-array-inside-a-struct-184baf6aa2eb
- https://samczsun.com/
- https://docs.alchemy.com/reference/alchemy-simulateexecution