说明
在前面的文章中已经分析过了FDP存在的代币通缩类型的漏洞,今天继续分析Sheep的代币通缩漏洞。虽然两者的代码不完全一样,但是漏洞的原理却是一样的。
基本信息
- Attacker: 0x638b4779d1923cedf726ed4d717ee4169df53d4e
- Attack Contract: 0x2021932c4dde18727a827c15f4bc0d6e7de636aa
- Vulnerable Contract:
- 0x0025B42bfc22CbbA6c02d23d4Ec2aBFcf6E014d4
- Attack Tx:https://bscscan.com/tx/0x61293c6dd5211a98f1a26c9f6821146e12fb5e20c850ad3ed2528195c8d4c98e
- LOSS: 9.5BNB
漏洞函数
balanceOf
1 | function balanceOf(address account) public view override returns (uint256) { |
和之前分析的FDP漏洞一样,最终balancof的计算和合约中定义的_rTotal、_tTotal有关。几乎代码是完全一样。
burn
1 | function burn(uint256 _value) public{ |
相比之前的FDP漏洞,本次的漏洞函数是burn。直接通过burn()减少调用者的代币数量。最终实现的是:
_rOwned[_who] = _rOwned[_who].sub(_value),减少实际的代币数量_tTotal = _tTotal.sub(_value),减少_tTotal的值。
再次梳理下计算余额的逻辑:
- balanceof:
rAmount.div(currentRate) - rAmount:
_tOwned[account] _currentRate:_rTotal/_tTotal
最终的计算公式是:
$$
\frac{tOwned[account]}{\frac{rTotal}{tTotal}} = \frac{tOwned[account] \times tTotal}{rTotal}
$$
所以当_tTotal,计算得balanceof也会减少。
漏洞分析
DPPFlashLoanCall
通过闪电贷获得数量是38个BNB。
WBNBToSHEEP
将闪电贷得到的BNB全部换成SHEEP代币。
1 | function WBNBToSHEEP() internal { |
最终获得的闪电贷的数量是:25909852936496774794.
通过tenderly调试,查看此时实际得tOwned[account]、_rTotal、_tTotal
tOwned[account],21444426731118471075302039839993709279885360729966170145131391140234936981741_rTotal和rSupply相同,是59215460978135173158676612258487448007070402970606630179506034991946198000500_tTotal和tSupply相同,是71546043396158607241currentRate的计算方式是:
$$
currentRate = \frac{rSupply}{tSupply}
$$
最后计算得到的结果是:827655285565581984084899321096098984285313722211890882872
balance的计算是tOwned[account].div(currentRate)
即:
1 | 21444426731118471075302039839993709279885360729966170145131391140234936981741/827655285565581984084899321096098984285313722211890882872 |
得到的结果就是:25909852936496774794
其中的详细计算过程参见tenderly。
burn
1 | uint256 burnAmount = SHEEP.balanceOf(address(this)); |
下面就详细分析burn的过程。
1 | function burn(uint256 _value) public{ |
balanceof
按照前面的分析,此时_rOwned,_tTotal,_rTotal 三者的值分别是:
_rOwned[account],21444426731118471075302039839993709279885360729966170145108072272592089884427_tTotal,48227175753311509927_rTotal,保持不变是59215460978135173158676612258487448007070402970606630179506034991946198000500
通过前面的burn操作之后,最终计算的balanceof的结果是:
1 | function testHub() external { |
最终计算的结果是:17465103197837696269。
实际的结果和计算出来的结果一致,也侧面印证了我们的分析是正确的。
ForLoop
既然通过burn()方法之后,会减少_tTotal,那么就可以想到所有的SHEEP.balanof(address)都会减少。
利用这个特性,我们就可以通过For循环不断减少Pair中的SHEEP的数量抬高SHEEP的价格从而获利。
攻击者就通过这样的做法来实现攻击。
1 | while (SHEEP.balanceOf(address(Pair)) > 2) { |
最终循环的停止条件就是SHEEP.balanceOf(address(Pair))数量小于或等于2。
最后通过Pair.sync()更新Pair中的代币数量。
此时,因为Pair中的SHEEP代币数量非常少,这样就意味着SHEEP的价格就非常贵了。
攻击者通过将SHEEP兑换(swap)成为BNB,就可以获利。
此时,攻击者的代币数量是SHEEP.balanceOf(account=0x2021932c4dde18727a827c15f4bc0d6e7de636aa)是27
SWAP获利
攻击者最终通过swap将所有的SHEEP兑换为BNB,获利。
1 | function SHEEPToWBNB() internal { |
Pair中此时的两种代币的数量是:
SHEEP: 2BNB: 418470984903412245858
通过swapExactTokensForTokensSupportingFeeOnTransferTokens()之后,攻击者会将27个SHEEP代币全部转移到Pair中。
按照swap的计算公式,最终swap的到的BNB数量是:389543585964266838730。
获利
通过代码分析,攻击者通过swap的方式最终获得389543585964266838730,换掉闪电贷借的380000000000000000000。最终约为获利9.5个BNB。
通过资金流动的分析:
得出的结论也是一致的。