15.BFCToken@rewardLogic
2023-11-10 00:12:34
# 08.PoC
BFCToken@rewardLogic
- 时间:2023-09-09 19:16:35 (UTC)
- 损失金额:~$38K
交易
- 被攻击的合约:0x595eac4a0ce9b7175a99094680fbe55a774b5464
- 攻击事件hash:0x8ee76291c1b46d267431d2a528fa7f3ea7035629500bba4f87a69b88fcaf6e23
- 黑客EOA地址:0x7cb74265e3e2d2b707122bf45aea66137c6c8891
- 黑客用来攻击的合约:0x9180981034364f683ea25bcce0cff5e03a595bef
资金流向
攻击过程
整体的攻击过程
实际的攻击逻辑
攻击详细分析
整体的逻辑是闪电贷获取资金,然后swap换币,核心是BFC.transfer()
、BUSDT_BFC.skim()
、BFC.transfer()
这个循环,循环了若干次,然后导致池子中的BFC金额变小
也造成了BFC的_reserve
变小,所以推断出BFC的balanceOf()
是魔改的,价值会根据市场而变动。这个变动意味着价格操纵,从攻击上看,是使得BFC的价格升高,然后用手头上的BFC换取池子中的USD。
让我们来看看他是如何做到价格操纵的。其实他也就调用了_transfer()
,那么来看一下这个方法:这会进入到reward的计算
1 | function _transfer(...) internal override { |
我们可以发现,他的计算有关于LP
1 | function _distributeRewards(address addr) internal { |
看LP的具体计算,我们发现,LP的价值会受到BFC在池子中的价值影响,而BFC的价值balanceOf()
是动态的,随市场变动。
1 | function getTokenAmountByLp(uint256 lpAmount) { |
balanceOf()
会根据getUserMintAmount()
变动,getUserMintAmount()
又根据getTokenAmountByLp()
变动。
1 | function getUserMintAmount(address user) public view returns (uint256) { |
所以我们总结一下调用和状态变动逻辑
1 | transfer => _distributeRewards => getTokenAmountByLp => balanceOf => getUserMintAmount => getTokenAmountByLp |
看起来,这个攻击有这样的特点:
- 发送代币到池子,BFC价值变化,
skim()
取走,再次重复,使得BFC价值不断下降 - BFC价值下降,黑客拥有的代币价值、池子的BFC价值都下降。虽然都下降,但是相比价格操纵之前,黑客下降之后的代币价值可以在池子中swap到更多的USD。也就是说,价值同时下降,但是池子下降的比黑客下降的更剧烈,使得黑客通过价差有利可图。
不断的重复
BFC.transfer()
、BUSDT_BFC.skim()
、BFC.transfer()
到达了一定的程度,黑客才获利,前几次循环造成的价差,黑客没有获利反而受损,但是随着不断的重复此操作,黑客的价差变正,从而有利可图
最终操纵的价格:池子的BFC价值变为1:
核心是:transfer影响LP。通过skim()
可以不断重复transfer。重复操作到一定程度,价差从负变正,有利可图
复现
1 | // SPDX-License-Identifier: UNLICENSED |
建议
- 魔改的
balanceOf()
,主要注意价值的逻辑 - 警惕
skim()
可以重复调用的可能性,是否多次调用之后有利可图