
Exploiting Predictable Addresses in Solidity
Today, let’s take on a quick Solidity challenge and unravel a smart contract with funds up for grabs. The catch? The prize is seemingly protected by a random function. But there’s a flaw. Can you spot it?
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
contract VulnerableEntropy {
uint256 public randomNumberInit;
constructor(uint256 _randomNumberInit) payable {
require(msg.value >= 1 ether, "Not enough ETH"); // Deployer must send at least 1 ETH
randomNumberInit = _randomNumberInit; // Set the random number for some entropy
}
function tryWinPrize() public {
require(generateRandomNumber() == 0, "Not lucky enough"); // Check if the random number is 0
payable(msg.sender).transfer(address(this).balance); // Send the prize to the winner
}
function generateRandomNumber() private view returns (uint256) {
return uint256(keccak256(abi.encodePacked(block.timestamp, randomNumberInit, msg.sender))) % 999;
}
}
The Flawed Random Function
Here’s the challenge: a contract is initialized with some funds. Participants can interact with it to win these funds. However, the prize is guarded by a random function that’s not as random as it seems.
Deconstructing the Random Function
Let’s break down the elements we can control in the contract:
block.time
: While we can wait to alter this, others might get lucky and win first.RandomNumberInit
: This is fixed and visible in the deployment logs.msg.sender
: Here’s the catch! It’s fixed to our address, but we can manipulate it using the create2 opcode, which allows deploying contracts at predictable addresses.
Using create2 to deploy the proxy contract
The create2 opcode specifies that a new contract will be deployed at an address derived from keccak256(0xff ++ address ++ salt ++ keccak256(init_code))[12:]
. To exploit this, we need:
- The bytecode of our proxy contract.
- The address of the vulnerable contract.
- A salt to brute-force the desired address.
We create a proxy contract to call the tryWinPrize function for us. Our goal is to deploy it at an address that satisfies the random function condition.
The Attacker Contract
Next, we need a contract to deploy our proxy. This contract will brute-force the salt used at deployment to precompute the address.
To precompute the address, we pass the bytecode of our proxy contract, and the salt that we want to use:
Using a loop, we compute the address for each new salt, until we find one that passes the generateRandomNumber
function’s test.
Testing and Execution
Using Foundry, we set up the test:
- Deploy the vulnerable contract.
- Deploy the attacker contract.
- Call attack, which brute-forces the best address and deploys the proxy, triggering tryWinPrize successfully.
Run the test, and voilà – we deployed the proxy at the correct address and claimed the prize.
Conclusion
This exercise demonstrates that using msg.sender
as a source of entropy for random functions is risky and can be manipulated. Always consider potential vulnerabilities when dealing with randomness in smart contracts!