Guess the random number 题目描述 原题链接 原题目要求 GuessTheRandomNumberChallenge 合约的 ether 余额为 0。而调用 guess 并输入答案,如果答案等于 answer 即可转移 2 ether 出去。 运行 安装 Foundry 根据Foundry 官方文档配置好运行环境。 运行测试 功能简述 在 GuessTheRandomNumberChallenge 合约中,设置了一个 secret 变量 answer,并在部署时使用 blockhash,block.timestamp 等初始化: 因此我们有两个思路来完成挑战。 思路 1 既然部署时的 block.number, block.
原题目要求 GuessTheRandomNumberChallenge 合约的 ether 余额为 0。而调用 guess 并输入答案,如果答案等于 answer 即可转移 2 ether 出去。
安装 Foundry
根据Foundry 官方文档配置好运行环境。
运行测试
$ cd WTF-CTF $ forge test -C src/Capture_the_Ether/Lotteries/Guess_the_random_number -vvv
在 GuessTheRandomNumberChallenge 合约中,设置了一个 secret 变量 answer,并在部署时使用 blockhash,block.timestamp
等初始化:
contract GuessTheRandomNumberChallenge { uint8 answer; constructor() payable { require(msg.value == 1 ether); answer = uint8(uint(keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)))); } ... }
因此我们有两个思路来完成挑战。
思路 1
既然部署时的 block.number, block.timestamp 是可以拿到的,我们可以记录这些值,然后计算出 answer,这就是 testGuessTheRandomNumber1 的解决思路:
// Suppose several blocks have passed vm.roll(deployBlockNumber+10); uint8 answer = uint8(uint(keccak256(abi.encodePacked(blockhash(deployBlockNumber - 1), deployTime)))); emit log_named_uint("answer", answer); vm.startPrank(hacker); guessTheRandomNumberChallenge.guess{value: 1 ether}(answer); vm.stopPrank();
思路 2
answer 虽然是私有变量,但是我们可以通过 GuessTheRandomNumberChallenge 本地的 storage 布局以及 json-rpc 里面的 eth_getStorageAt 得到答案:
forge inspect ./src/Capture_the_Ether/Lotteries/Guess_the_random_number/GuessTheRandomNumberChallenge.sol:GuessTheRandomNumberChallenge storage --pretty | Name | Type | Slot | Offset | Bytes | Contract | | ------ | ----- | ---- | ------ | ----- | ----------------------------------------------------------------------------------------------------------------------- | | answer | uint8 | 0 | 0 | 1 | src/Capture_the_Ether/Lotteries/Guess_the_random_number/GuessTheRandomNumberChallenge.sol:GuessTheRandomNumberChallenge |
可以看出 slot 0 就存储了 answer。在本地测试中,我们使用 foundry 的 cheatcode: vm.load() 来代替 eth_getStorageAt,这是 testGuessTheRandomNumber2 的思路:
// 获取 slot 0 里面的值,这里就是 answer。 uint answer = uint(vm.load(address(guessTheRandomNumberChallenge), bytes32(uint(0)))); emit log_named_uint("answer", answer); vm.startPrank(hacker); guessTheRandomNumberChallenge.guess{value: 1 ether}(uint8(answer)); vm.stopPrank();