本文作者:小驹[1]
SpaceGodzilla 是一个 ERC20 代币,项目没有官方网站,官方只在 twitter 上进行维护,项目介绍是“哥斯拉吸收了地球上所有的核能,向月球飞去!”
,然后官方 twitter 头图是张哥斯拉乘着火箭去月球的图片,目前官方有 300 人关注……..
就在前天,这个项目被黑客攻击了。此次黑客利用未授权的函数,扰乱了正常的 Godzilla 币与 USDT 的流动池,最终攻击大约套利了 2.6 万的 USDT。
一句话总结漏洞的原因是:函数权限设置不当
。swapAndLiquifyStepv1 用来向 LP 流动池中添加流动性,本只应该在向 LP 池转账时,应该设置成只能 internal 调用。但在实际代码中,却设置成了 public 属性,导致黑客 swapAndLiquifyStepv1 后,导致 LP 流动池中 token 数量混乱,产生了套利空间..
漏洞合约地址https://bscscan.com/address/0x2287c04a15bb11ad1358ba5702c1c95e2d13a5e0[2]
猜测下为什么这个函数的权限没有设置呢?
从函数名称 swapAndLiquifyStepv1 看,该函数是 v1 版本的函数,该函数在开发过程中应该为第一版本的函数方法,更像是一个本该废弃并删除的函数
。因为代码中还存在另外一个有相似功能且有调用逻辑存在的函数function swapAndLiquify() public
。
黑客攻击的调用链非常非常非常非常长,但大部分的调用都是进行闪电贷和归还闪电贷
的过程。
真实的漏洞利用过程就在下面的代码调用片段中,我们从闪电贷获得了 295.2 万的 USDT 开始分析漏洞利用过程。
黑客的漏洞利用过程
swapAndLiquifyStepv1漏洞函数
,将 LP 流动池中的 Godzilla 与 USDT 的兑换比例完全打乱
。我们可以分析下套利的过程(假设 AMM 中的 X 为 Godzilla,Y 为 USDT),初始的资金为295.2万
:
在调用 swapAndLiquifyStepv1 漏洞函数之前。
可以看到 GodzillaUSDT 流动池中的 Godzilla 与 USDT 的数量分别为(两者数量之比为:8.4亿:1
):X=76041697635825849047705725848735 Y=90478604689102338898952
K = XY = 6.8810^54
根据公式(X-DX)(Y+DY)=K 和公式 XY = K,可以计算出此时,黑客可以兑换出的 Godzilla 大约为(对应着公式中的 DX) 73780928010061370325334249251641
由公式推导出DX=X-(K/(Y+DY)),其中的
X = 76041697635825849047705725848735
Y = 90478604689102338898952
DY = 2952797730003166405412733 这就是初始的USDT的资金
计算得到的DX为73780928010061370325334249251641,与实际兑换出的73775430786944730258898675433018相差无几。
在调用 swapAndLiquifyStepv1 漏洞函数之后,可以看到
GodzillaUSDT 流动池中的 Godzilla 与 USDT 的数量分别为(两者数量之比为:74万:1
,与漏洞之前的8.4亿:1
相距甚大):
2,288,901,594,081,170,758,102,038,305,061 3,073,671,601,005,728,817,436,539
可以看到调用了swapAndLiquifyStepv1函数严重改变了LP流动池中的兑换比例
。
此时的K值变成了K’= 7.0310^54(相比之前的K 6.8810^54大约增加了1.5*10^53
)
在新的 K’下,将 1 中的 Godzilla 换回 USDT。
(X-DX)*(Y+DY)=K
相当于已知 X’, Y’, DX 为要兑换的 Godzilla,根据公式 X’_Y’=K’ , (X'+ DX)_(Y'-DY)=K', 求 DY。
由公式推导出DY =Y' - K'/(x'+DX),其中的
K' = 7035331827224036947372569921877688319008790342490023879
X' = 2288901594081170758102038305061
Y' = 3073671601005728817436539
DX = 71562167863336388351131715170010
计算得到2978407823799623479865461,与2978176485325154862214560(这就是最终黑客执行套利后的USDT的数量)
相差无几。
😀 细心的可能会发现,在 1 中将闪电贷换出了 73780928010061370325334249251641 Godzilla,在 3 中将 Godzilla 再换成 USDT 时的 Godzilla 的数量是 71562167863336388351131715170010,为什么这两个数字不同?
这是因为 Godzilla 在_transfer 过程中会收取%3的takeFee
,两个数字之间的差额就是被 Godzallia 合约收走的takeFee
,这个 takeFee 是在_transfer中被收取的
,因此在 LP 的swap函数的返回
中还是 7378..这个数,而 HackContract 到手的 Godzallia 币就只有 7156…这个数了。
对于没有正确设置权限的swapAndLiquifyStepv1()
函数,功能就是把Godzilla合约中的USDT和Godzilla币一起充到LP中
,正是这个充值到 LP 的行为导致了套利的出现。
// 0x55d398326f99059fF775485246999027B3197955 为USDT的地址,虽然代码中定义为ETH变量....
address _baseToken = address(0x55d398326f99059fF775485246999027B3197955);
ETH = IERC20(_baseToken);
function swapAndLiquifyStepv1() public {
uint256 ethBalance = ETH.balanceOf(address(this));
uint256 tokenBalance = balanceOf(address(this));
addLiquidityUsdt(tokenBalance, ethBalance);
}
合约中不用的函数、测试的函数在发布前都删掉吧….……
https://mobile.twitter.com/BlockSecTeam/status/1547456617414660096[3]
https://mp.weixin.qq.com/s/JF4bvVoHbOZkjbO2YwGiWw
https://versatile.blocksecteam.com/tx/bsc/0x7f183df11f1a0225b5eb5bb2296b5dc51c0f3570e8cc15f0754de8e6f8b4cca4[4]
https://www.haomeili.net/Math/BigMath?Method=Multiply[5]
[1]
小驹: https://learnblockchain.cn/people/9625
[2]
https://bscscan.com/address/0x2287c04a15bb11ad1358ba5702c1c95e2d13a5e0: https://bscscan.com/address/0x2287c04a15bb11ad1358ba5702c1c95e2d13a5e0
[3]
https://mobile.twitter.com/BlockSecTeam/status/1547456617414660096: https://mobile.twitter.com/BlockSecTeam/status/1547456617414660096
[4]
https://versatile.blocksecteam.com/tx/bsc/0x7f183df11f1a0225b5eb5bb2296b5dc51c0f3570e8cc15f0754de8e6f8b4cca4: https://versatile.blocksecteam.com/tx/bsc/0x7f183df11f1a0225b5eb5bb2296b5dc51c0f3570e8cc15f0754de8e6f8b4cca4
[5]
https://www.haomeili.net/Math/BigMath?Method=Multiply: https://www.haomeili.net/Math/BigMath?Method=Multiply