starCTF

0x00 simpleweb

之前*CTF在打的时候其实我没有时间去打,这篇是后来整理的。

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
var net = require('net');

flag='fake_flag';

var server = net.createServer(function(socket) {
socket.on('data', (data) => {
//m = data.toString().replace(/[\n\r]*$/, '');
ok = true;
arr = data.toString().split(' ');
arr = arr.map(Number);
if (arr.length != 5)
ok = false;
arr1 = arr.slice(0);
arr1.sort();
for (var i=0; i<4; i++)
if (arr1[i+1] == arr1[i] || arr[i] < 0 || arr1[i+1] > 127)
ok = false;
arr2 = []
for (var i=0; i<4; i++)
arr2.push(arr1[i] + arr1[i+1]);
val = 0;
for (var i=0; i<4; i++)
val = val * 0x100 + arr2[i];
if (val != 0x23332333)
ok = false;
if (ok)
socket.write(flag+'\n');
else
socket.write('nope\n');
});
//socket.write('Echo server\r\n');
//socket.pipe(socket);
});

HOST = '0.0.0.0'
PORT = 23333

server.listen(PORT, HOST);

可以看到,获取到输入之后,以空格为界限把数据存入了arr中,然后,把所有项的数据类型转换为数字。然后,一开始有点不懂slice(0)有什么用,看到Stack Overflow上面有个回答如下:imageslice(0)返回的数组与输入完全相同,所以这其实是一个复制数组的廉价方法。然后,分析可以知道:

1
((arr2[0]*0x100+arr2[1])*0x100+arr2[2])*0x100+arr2[3]=0x23332333

其中,arr2[i]=arr1[i]+arr1[i+1]

然后,列出含有五个未知数的四条方程:

  • arr1[0]+arr1[1]=35(0x23)
  • arr1[1]+arr1[2]=51(0x33)
  • arr1[2]+arr1[3]=35
  • arr1[3]+arr1[4]=51
    然后,还有一个条件,就是arr1是经过了sort()排序的,这里要提到一下JavaScript的sort()方法:image一目了然。可以得到,arr1是:
    1
    15 20 31 4 47

Smart?Contract

Yet another blockchain challenge with tokens in the smart contract. Be careful that the blockchain is stored in the cookie and a browser might ignore set-cookie header if it is too long, which prevents the blockchain being updated. So send the requests using scripts.

http://47.75.9.127:10012/6af948d659f0b7c5d3950a/

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
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# -*- encoding: utf-8 -*-
# written in python 2.7
__author__ = 'garzon'

import pickle
import hashlib, json, rsa, uuid, os
from flask import Flask, session, redirect, url_for, escape, request

app = Flask(__name__)
app.secret_key = '*********************'
url_prefix = '/6af948d659f0b7c5d3950a'

def FLAG():
return 'Here is your flag: *ctf{******************}'

def hash(x):
return hashlib.sha256(hashlib.md5(x).digest()).hexdigest()

def hash_reducer(x, y):
return hash(hash(x)+hash(y))

def has_attrs(d, attrs):
if type(d) != type({}): raise Exception("Input should be a dict/JSON")
for attr in attrs:
if attr not in d:
raise Exception("{} should be presented in the input".format(attr))

EMPTY_HASH = '0'*64

def addr_to_pubkey(address):
return rsa.PublicKey(int(address, 16), 65537)

def pubkey_to_address(pubkey):
assert pubkey.e == 65537
hexed = hex(pubkey.n)
if hexed.endswith('L'): hexed = hexed[:-1]
if hexed.startswith('0x'): hexed = hexed[2:]
return hexed

def gen_addr_key_pair():
pubkey, privkey = rsa.newkeys(384)
return pubkey_to_address(pubkey), privkey

bank_address, bank_privkey = gen_addr_key_pair()
hacker_address, hacker_privkey = gen_addr_key_pair()

def sign_input_utxo(input_utxo_id, privkey):
return rsa.sign(input_utxo_id, privkey, 'SHA-1').encode('hex')

def hash_utxo(utxo):
return reduce(hash_reducer, [utxo['id'], utxo['addr'], str(utxo['amount'])])

def create_output_utxo(addr_to, amount):
utxo = {'id': str(uuid.uuid4()), 'addr': addr_to, 'amount': amount}
utxo['hash'] = hash_utxo(utxo)
return utxo

def hash_tx(tx):
return reduce(hash_reducer, [
reduce(hash_reducer, tx['input'], EMPTY_HASH),
reduce(hash_reducer, [utxo['hash'] for utxo in tx['output']], EMPTY_HASH)
])

def create_tx(input_utxo_ids, output_utxo, privkey_from=None):
tx = {'input': input_utxo_ids, 'signature': [sign_input_utxo(id, privkey_from) for id in input_utxo_ids], 'output': output_utxo}
tx['hash'] = hash_tx(tx)
return tx

def hash_block(block):
return reduce(hash_reducer, [block['prev'], block['nonce'], reduce(hash_reducer, [tx['hash'] for tx in block['transactions']], EMPTY_HASH)])

def create_block(prev_block_hash, nonce_str, transactions):
if type(prev_block_hash) == type(u''): prev_block_hash = str(prev_block_hash)
if type(prev_block_hash) != type(''): raise Exception('prev_block_hash should be hex-encoded hash value')
nonce = str(nonce_str)
if len(nonce) > 128: raise Exception('the nonce is too long')
block = {'prev': prev_block_hash, 'nonce': nonce, 'transactions': transactions}
block['hash'] = hash_block(block)
return block

def find_blockchain_tail(blocks=None):
if blocks is None: blocks = session['blocks']
return max(blocks.values(), key=lambda block: block['height'])

class SRC20SmartContract:
def __init__(self, addr, privkey):
self.starTokenNum = 0
self.balanceOfAddr = {addr: 999999999}
self.addr = addr
self.privkey = privkey
self.owned_token_utxos = {}

def onCall_withdraw(self, tx):
# by calling this you can convert your StarTokens into StarCoins!
if len(tx['input']) == 1 and len(tx['output']) == 1 and len(tx['signature']) == 0 and tx['input'][0] in self.owned_token_utxos:
# which means that you would like to redeem StarCoins in the input utxo using your StarTokens
recv_addr = tx['output'][0]['addr']
amount_to_redeem = self.owned_token_utxos[tx['input'][0]]['amount']
self.sendTokenAtTx(tx, recv_addr, self.addr, amount_to_redeem)
tx['signature'].append(sign_input_utxo(tx['input'][0], self.privkey))

def onCall_buyTokens(self, utxos, tx):
# by calling this you can buy some StarTokens using StarCoins!
if len(tx['input']) == 1 and len(tx['output']) == 1 and tx['output'][0]['addr'] == self.addr:
self.sendTokenAtTx(tx, self.addr, utxos[tx['input'][0]]['addr'], tx['output'][0]['amount'])

def getTokenBalance(self, addr):
if addr not in self.balanceOfAddr: return 0
return self.balanceOfAddr[addr]

def sendTokenAtTx(self, tx, from_addr, to_addr, amount):
if self.getTokenBalance(from_addr) < amount: raise Exception("no enough StarToken at " + from_addr)
if to_addr == self.addr:
from_addr, to_addr = to_addr, from_addr
amount = -amount
utxo_used_to_record_SRCToken = create_output_utxo(to_addr, 0)
obj = {'utxo_id': utxo_used_to_record_SRCToken['id'], 'tokenNum': amount}
payload = json.dumps(obj)
signature = self.signSRCTokenUtxoPayload(payload)
info = signature + '$$$' + payload
utxo_used_to_record_SRCToken['extra'] = info
tx['output'].append(utxo_used_to_record_SRCToken)

def signSRCTokenUtxoPayload(self, payload):
return rsa.sign(payload, self.privkey, 'SHA-1').encode('hex')

def verifySRCTokenUtxoPayload(self, payload, signature):
try:
return rsa.verify(payload, signature.decode('hex'), addr_to_pubkey(self.addr))
except:
return False

def extractInfoFromUtxos(self, utxos):
for utxo_id, utxo in utxos.items():
if 'extra' in utxo:
info = utxo['extra']
if type(info) == type(u''): info = str(info)
if type(info) != type(''): raise Exception("unknown type of 'extra' in utxo")
if '$$$' not in info: raise Exception("signature of SRC20 token is not found")
signature = info[:info.index('$$$')]
payload = info[info.index('$$$')+3:]
if not self.verifySRCTokenUtxoPayload(payload, signature): raise Exception("this SRC20 token is fake")
obj = json.loads(payload)
if obj['utxo_id'] != utxo['id']: raise Exception("the id of utxo does not match the one on the token")
if utxo['addr'] not in self.balanceOfAddr: self.balanceOfAddr[utxo['addr']] = 0
self.balanceOfAddr[utxo['addr']] += obj['tokenNum']
if utxo['addr'] == self.addr: self.owned_token_utxos[utxo['id']] = utxo


def calculate_utxo(blockchain_tail):
starToken_contract = SRC20SmartContract(bank_address, bank_privkey)
curr_block = blockchain_tail
blockchain = [curr_block]
while curr_block['hash'] != session['genesis_block_hash']:
curr_block = session['blocks'][curr_block['prev']]
blockchain.append(curr_block)
blockchain = blockchain[::-1]
utxos = {}
for block in blockchain:
for tx in block['transactions']:
for input_utxo_id in tx['input']:
del utxos[input_utxo_id]
for utxo in tx['output']:
utxos[utxo['id']] = utxo
starToken_contract.extractInfoFromUtxos(utxos)
return utxos, starToken_contract

def calculate_balance(utxos):
balance = {bank_address: 0, hacker_address: 0}
for utxo in utxos.values():
if utxo['addr'] not in balance:
balance[utxo['addr']] = 0
balance[utxo['addr']] += utxo['amount']
return balance

def verify_utxo_signature(address, utxo_id, signature):
try:
return rsa.verify(utxo_id, signature.decode('hex'), addr_to_pubkey(address))
except:
return False


def append_block(block, difficulty=int('f'*64, 16)):
has_attrs(block, ['prev', 'nonce', 'transactions'])

if type(block['prev']) == type(u''): block['prev'] = str(block['prev'])
if type(block['nonce']) == type(u''): block['nonce'] = str(block['nonce'])
if block['prev'] != find_blockchain_tail()['hash']: raise Exception("You do not have the dominant mining power so you can only submit tx to the last block.")
tail = session['blocks'][block['prev']]
utxos, contract = calculate_utxo(tail)

if type(block['transactions']) != type([]): raise Exception('Please put a transaction array in the block')
new_utxo_ids = set()
for tx in block['transactions']:
has_attrs(tx, ['input', 'output', 'signature'])

for utxo in tx['output']:
has_attrs(utxo, ['amount', 'addr', 'id'])
if type(utxo['id']) == type(u''): utxo['id'] = str(utxo['id'])
if type(utxo['addr']) == type(u''): utxo['addr'] = str(utxo['addr'])
if type(utxo['id']) != type(''): raise Exception("unknown type of id of output utxo")
if utxo['id'] in new_utxo_ids: raise Exception("output utxo of same id({}) already exists.".format(utxo['id']))
new_utxo_ids.add(utxo['id'])
if type(utxo['amount']) != type(1): raise Exception("unknown type of amount of output utxo")
if utxo['amount'] < 0: raise Exception("invalid amount of output utxo")
if type(utxo['addr']) != type(''): raise Exception("unknown type of address of output utxo")
try:
addr_to_pubkey(utxo['addr'])
except:
raise Exception("invalid type of address({})".format(utxo['addr']))
utxo['hash'] = hash_utxo(utxo)

for new_id in new_utxo_ids:
if new_id in utxos:
raise Exception("invalid id of output utxo. utxo id({}) exists".format(utxo_id))

if type(tx['input']) != type([]): raise Exception("type of input utxo ids in tx should be array")
if type(tx['signature']) != type([]): raise Exception("type of input utxo signatures in tx should be array")

tx['input'] = [str(i) if type(i) == type(u'') else i for i in tx['input']]
for utxo_id in tx['input']:
if type(utxo_id) != type(''): raise Exception("unknown type of id of input utxo")
if utxo_id not in utxos: raise Exception("invalid id of input utxo. Input utxo({}) does not exist or it has been consumed.".format(utxo_id))

if contract is not None:
if 'call_smart_contract' in tx:
if tx['call_smart_contract'] == 'buyTokens': contract.onCall_buyTokens(utxos, tx)
if tx['call_smart_contract'] == 'withdraw': contract.onCall_withdraw(tx)

tot_input = 0
if len(tx['input']) != len(tx['signature']): raise Exception("lengths of arrays of ids and signatures of input utxos should be the same")
tx['signature'] = [str(i) if type(i) == type(u'') else i for i in tx['signature']]
for utxo_id, signature in zip(tx['input'], tx['signature']):
utxo = utxos[utxo_id]
if type(signature) != type(''): raise Exception("unknown type of signature of input utxo")
if not verify_utxo_signature(utxo['addr'], utxo_id, signature):
raise Exception("Signature of input utxo is not valid. You are not the owner of this input utxo({})!".format(utxo_id))
tot_input += utxo['amount']
del utxos[utxo_id]

tot_output = sum([utxo['amount'] for utxo in tx['output']])
if tot_output > tot_input:
raise Exception("You don't have enough amount of StarCoins in the input utxo! {}/{}".format(tot_input, tot_output))
tx['hash'] = hash_tx(tx)

block = create_block(block['prev'], block['nonce'], block['transactions'])
block_hash = int(block['hash'], 16)
#We are users in this challenge, so leave the Proof-of-Work thing to the non-existent miners
#if block_hash > difficulty: raise Exception('Please provide a valid Proof-of-Work')
block['height'] = tail['height']+1
if len(session['blocks']) > 10: raise Exception('The blockchain is too long. Use ./reset to reset the blockchain')
if block['hash'] in session['blocks']: raise Exception('A same block is already in the blockchain')
session['blocks'][block['hash']] = block
session.modified = True

def init():
if 'blocks' not in session:
session['blocks'] = {}

# At first, the bank issued some StarCoins, and give you 100
currency_issued = create_output_utxo(bank_address, 200)
airdrop = create_output_utxo(hacker_address, 100)
genesis_transaction = create_tx([], [currency_issued, airdrop]) # create StarCoins from nothing
genesis_block = create_block(EMPTY_HASH, 'The Times 03/Jan/2009 Chancellor on brink of second bailout for bank', [genesis_transaction])

session['genesis_block_hash'] = genesis_block['hash']
genesis_block['height'] = 0
session['blocks'][genesis_block['hash']] = genesis_block


def get_balance_of_all():
init()
tail = find_blockchain_tail()
utxos, contract = calculate_utxo(tail)
return calculate_balance(utxos), utxos, tail, contract

@app.route(url_prefix+'/')
def homepage():
announcement = ''
balance, utxos, _, contract = get_balance_of_all()
genesis_block_info = 'hash of genesis block: ' + session['genesis_block_hash']
addr_info = 'the bank\'s addr: ' + bank_address + ', your addr: ' + hacker_address + ', your privkey: ' + pickle.dumps(hacker_privkey).encode('hex')
balance_info = 'StarCoins balance of all addresses: ' + json.dumps(balance)
starcoins_utxo_info = 'All utxos: ' + json.dumps(utxos)
startokens_info = 'StarTokens balance of all addresses: ' + json.dumps(contract.balanceOfAddr)
blockchain_info = 'Blockchain Explorer: ' + json.dumps(session['blocks'])
view_source_code_link = "<a href='source_code'>View source code</a>"
return announcement+('.<br /><br />\r\n\r\n'.join([view_source_code_link, genesis_block_info, addr_info, balance_info, starcoins_utxo_info, startokens_info, blockchain_info]))


DIFFICULTY = int('00000' + 'f' * 59, 16)
@app.route(url_prefix+'/create_block', methods=['POST'])
def create_block_api():
init()
try:
block = json.loads(request.data)
append_block(block, DIFFICULTY)
msg = 'transaction finished.'
except Exception, e:
return str(e)

balance, utxos, tail, contract = get_balance_of_all()

if balance[hacker_address] == 200:
msg += ' Congratulations~ ' + FLAG()
return msg


# if you mess up the blockchain, use this to reset the blockchain.
@app.route(url_prefix+'/reset')
def reset_blockchain():
if 'blocks' in session: del session['blocks']
if 'genesis_block_hash' in session: del session['genesis_block_hash']
return 'reset.'

@app.route(url_prefix+'/source_code')
def show_source_code():
source = open('serve.py', 'r')
html = ''
for line in source:
line = line.decode('utf8', 'ignore')
html += line.replace('&','&amp;').replace('\t', '&nbsp;'*4).replace(' ','&nbsp;').replace('<', '&lt;').replace('>','&gt;').replace('\n', '<br />')
source.close()
return html

if __name__ == '__main__':
app.run(debug=False, host='0.0.0.0', port=10012)

区块链的题..这题我会找时间再看,区块链一定要懂的,继续努力。