区块链,智能合约安全-整型溢出漏洞,从零基础到精通,收藏这篇就够了!

合约崩盘的导火索?聊聊整型溢出的“前世今生”

整型溢出?听起来像是编译器才会care的底层bug,但它在智能合约里,绝对是能让你一夜回到解放前的超级雷区。想象一下,你的代码里藏着个定时炸弹,随时可能因为一个简单的加减法,把你的智能合约炸得面目全非,资产归零,权限全乱…… 这可不是危言耸听,而是血淋淋的教训堆出来的经验!

简单来说,整型溢出就是数值运算超出了数据类型能表示的范围。这就像你用一个只能装8升水的水桶,硬要装9升,结果就是水漫金山。在Solidity里,你定义一个uint8,它就只能存0到255之间的整数。

uint a; // 相当于 uint256 a,土豪级的容量

溢出?溢出!上溢下溢,都是“要命”的溢出!

数值类型的存储空间是有限的,比如uint8,最多存255。所以,溢出分两种:

  • 上溢(Overflow): 数值太大,超过了最大值。
  • 下溢(Underflow): 数值太小,低于了最小值。

这两种溢出,都能让你的合约逻辑彻底跑偏。

上溢:从“富得流油”到“一贫如洗”的魔幻之旅

上溢的“底层逻辑”:二进制的“丢盔弃甲”

还是拿uint8说事儿。它的范围是0到255,也就是二进制的0000 00001111 1111。如果你让一个uint8变量等于255,然后加1,会发生什么?

// SPDX-License-Identifier: GPL-3.0
pragma solidity = 0.7.6;

contract Test {
    function test() public pure returns(uint8) {
        uint8 a = 255;
        return a + 1;
    }
}

猜猜结果?不是256,而是0!

img

为啥?因为255的二进制是1111 1111,加1就变成了1 0000 0000。但是uint8只有8个bit,最左边的1直接被扔掉了,只剩下0000 0000,也就是0。

如果加2呢?那就是1 0000 0001,丢掉最高位,结果就是1。

所以,上溢会让一个很大的数,瞬间变成一个很小的数,甚至直接归零!

电商平台的“血泪史”:上溢引发的“0元购”

想象一下,你开了一个区块链网店,代码大致如下(别吐槽我用uint8,只是为了方便演示):

// SPDX-License-Identifier: MIT
pragma solidity = 0.7.6;

contract OnlineStore {
    // 商品结构体
    struct Product {
        uint256 id;
        string name;
        uint8 price; // 单位 eth
    }

    // 商品存储不定长数组
    Product[] private products;

    // 初始化函数
    constructor() {
        // 创建三个示例商品
        products.push(Product(1, "Phone", 2));      // 2 ETH
        products.push(Product(2, "Laptop", 3));     // 3 ETH
        products.push(Product(3, "Headphones", 1)); // 1 ETH
    }

    // 价格计算函数
    function calculatePrice(uint8 _productId, uint8 _quantity)
    public
    view
    returns (uint8)
    {
        Product memory product = getProductById(_productId);
        require(product.id != 0, "Product not found");
        require(_quantity > 0, "Quantity must be greater than 0");

        return product.price * _quantity;
    }

    // 购买函数
    function purchase(uint8 _productId, uint8 _quantity) public payable  returns(string memory){
        uint8 totalPrice = calculatePrice(_productId, _quantity);
        // 检查支付金额
        require(msg.value == totalPrice * 10 ** 18, "Incorrect ETH amount");
        return "success";
    }

    // 根据ID查询商品(内部函数)
    function getProductById(uint256 _productId)
    internal
    view
    returns (Product memory)
    {
        for (uint256 i = 0; i < products.length; i++) {
            if (products[i].id == _productId) {
                return products[i];
            }
        }
        return Product(0, "", 0); // 返回空商品
    }
    function test(uint8 a) public pure returns(uint8) {
        return a + 1;
    }
}

如果有人一次性购买128个单价2 ETH的商品,totalPrice会是多少? 理论上是256,但由于uint8的限制,结果会变成0! 也就是说,用户可以不花一分钱,就能买走128个商品! 这酸爽,老板估计要哭晕在厕所。

img

img

友情提示: 上面的例子为了方便理解,用了uint8。实际开发中,一定要用uint256,但是,这并不意味着你就安全了。只要数值足够大,超过了2的256次方-1,照样会溢出!

下溢:从“一无所有”到“富可敌国”的惊天逆转

下溢的“数学原理”:补码的“乾坤大挪移”

下溢比上溢更隐蔽,更难发现。它涉及到计算机的补码运算。简单来说,计算机用补码来表示负数。

比如,我们要计算2-1,计算机会先把-1转换成补码。 假设是8位二进制:

1的二进制:0000 0001

取反:1111 1110

加1:1111 1111 这就是-1的补码

然后,把2的二进制和-1的补码相加:

0000 0010 + 1111 1111 = 1 0000 0001

去掉进位,结果就是0000 0001,也就是1。

如果是0-1呢?

0的二进制:0000 0000

1的二进制:0000 0001

取反:1111 1110

加1:1111 1111 这就是-1的补码

0-1 -> 0000 0000 + 1111 1111 = 1111 1111

结果是1111 1111,也就是255!

所以,下溢会让一个很小的数,瞬间变成一个很大的数!

银行的“惊天漏洞”:下溢引发的“无限提款”

想象一下,你写了一个银行合约,代码如下:

contract Bank {
    mapping(address => uint256) public balanceOf;
    function withdraw(uint256 amount) public {
        require(balanceOf[msg.sender] - amount >= 0);
        balanceOf[msg.sender] -= amount;
        payable(msg.sender).transfer(amount);
    }
}

这段代码看起来没啥问题,对吧? 提款前会检查余额是否足够。但是,如果你的余额是0,你想取1个ETH,会发生什么?

balanceOf[msg.sender] - amount 会发生下溢,变成一个非常大的数(2的256次方-1),导致require检查永远通过。 也就是说,你可以无限提款,把银行的钱全部掏空!

img

img

img

实战演练:攻破TokenSaleChallenge合约

来,练练手。看看你能否攻破下面这个合约:

img

pragma solidity ^0.4.21;

contract TokenSaleChallenge {
    mapping(address => uint256) public balanceOf; // 存款
    uint256 constant PRICE_PER_TOKEN = 1 ether; // 单位

    function TokenSaleChallenge(address _player) public payable {
        require(msg.value == 1 ether); // 创建时 写入一个地址, 然后需要发送1 eth 存进来
    }

    function isComplete() public view returns (bool) {
        return address(this).balance < 1 ether; // 返回此合约内的 eth 余额是否小于 1 eth
    }

    function buy(uint256 numTokens) public payable {
        require(msg.value == numTokens * PRICE_PER_TOKEN); // 检查发送的eth 与标记发送的是否一致

        balanceOf[msg.sender] += numTokens; // 加上对应的余额
    }

    function sell(uint256 numTokens) public {
        require(balanceOf[msg.sender] >= numTokens); // 检查调用者的余额是否大于等于取款的余额

        balanceOf[msg.sender] -= numTokens; // 记账
        msg.sender.transfer(numTokens * PRICE_PER_TOKEN); //取款
    }
}

这个合约的漏洞藏在buy函数里。 正常情况下,你发送1个ETH,numTokens也应该是1。但是,如果我购买2**256 // 10**18 + 1个代币,会发生什么?

(2**256 // 10**18 + 1) * 10**18 的结果会大于2的256次方,导致上溢。溢出后的结果会变得很小,你可以用很少的ETH,买到大量的代币!

img

img

img

亡羊补牢:如何防范整型溢出?

SafeMath:老版本的“救命稻草”

在Solidity 0.8.0之前,最常用的方法是使用SafeMath库。它会在运算前后检查是否发生溢出,如果发生,就直接回滚交易。

using SafeMath for uint8; // 对uint8类型检查是否产生溢出
balances[msg.sender] = balances[msg.sender].sub(_amount);

Solidity 0.8.0:自带“金钟罩”

Solidity 0.8.0 以后,编译器默认开启了溢出检查。也就是说,只要发生溢出,交易就会自动回滚。

如果你想关闭溢出检查,可以使用unchecked关键字:

uint8 a = 255;
unchecked {
    a += 1; // 允许溢出,结果归零
}

但是,强烈不建议这样做!除非你非常清楚自己在做什么,并且有充分的理由要关闭溢出检查。

总结: 整型溢出是智能合约安全中一个非常重要的风险。理解它的原理,掌握防范方法,是每一个区块链开发者必备的技能。 不要让你的合约,成为黑客的提款机!
```

黑客/网络安全学习包

资料目录

  1. 成长路线图&学习规划

  2. 配套视频教程

  3. SRC&黑客文籍

  4. 护网行动资料

  5. 黑客必读书单

  6. 面试题合集

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

*************************************CSDN大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享*************************************

1.成长路线图&学习规划

要学习一门新的技术,作为新手一定要先学习成长路线图方向不对,努力白费

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图&学习规划。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。


因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

*************************************CSDN大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享*************************************

2.视频教程

很多朋友都不喜欢晦涩的文字,我也为大家准备了视频教程,其中一共有21个章节,每个章节都是当前板块的精华浓缩


因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

*************************************CSDN大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享*************************************

3.SRC&黑客文籍

大家最喜欢也是最关心的SRC技术文籍&黑客技术也有收录

SRC技术文籍:

黑客资料由于是敏感资源,这里不能直接展示哦!

4.护网行动资料

其中关于HW护网行动,也准备了对应的资料,这些内容可相当于比赛的金手指!

5.黑客必读书单

**

**

6.面试题合集

当你自学到这里,你就要开始思考找工作的事情了,而工作绕不开的就是真题和面试题。

更多内容为防止和谐,可以扫描获取~

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

*************************************CSDN大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享*********************************

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值