记一次隐蔽的恶意攻击事件追踪分析
2023年7月23日上午,零时科技团队收到来自社区用户的求助,IEGT项目在7月22日遭到攻击,项目团队正在寻求帮助,随后,零时科技安全团队及时对此安全事件进行分析;
2023年7月24日,安全团队发现此攻击事件可能为内部所为,并告知社区用户;
2023年7月25日上午,社区用户反馈IEGT项目团队已确认此次攻击事件为内部行为,并已进行排查。
通过社区用户提供信息,我们第一时间对其攻击详情进行深入分析;
被攻击项目IEGT合约地址:
0x8D07f605926837Ea0F9E1e24DbA0Fb348cb3E97D
合约部署者地址:
0xf5c1ce9afae4e58a8a4504485caf9fdd19ec363a
攻击者地址:
0x00002b9b0748d575cb21de3cae868ed19a7b5b56
其攻击交易为:
0x9cea0bd314181f42a359c26b68f8790add4daf83e2bfd2d349268538e4e1e2ca
通过链上的交易记录可以发现,攻击者在PancakeSwap上进行了两笔1,000,000,000token的卖出,获利114万美金,然后又把剩余的token转入黑洞地址。
但是通过IEGT项目合约得知,token总数为5,000,000个,可能第一时间想到的是合约中存在恶意增发功能被利用。通过对攻击者的交易记录分析发现,攻击者交易记录中没有增发的交易,也没有收到大量token的交易,对token合约分析也未发现恶意增发的合约代码,那么攻击者的token是怎么来的呢?
我们还是从攻击者交易开始入手,看看攻击交易的堆栈调用情况,堆栈调用详情如下链接:
通过对攻击交易堆栈数据进行分析,攻击者将大量 IGET 资金兑换为 usdt,确实未发现增发资金来源,不过对其合约转账等函数进行源码分析,发现攻击者执行的代码分支为不收取手续费直接转出资金。
通过对合约源码的深入分析,我们发现不收取手续费直接转出资金的用户角色为特权角色,仅可有合约管理员可以设置。
由此我们推断攻击者大概率与项目管理员有很大关系,不排除内部人员监守自盗。
进一步对链上状态数据进行分析,定位攻击者的特权角色以及大量token来源,发现攻击地址在合约创建时所在的区块高度就已经存在大量token余额,并且也已经设置了特权角色,如下图所示:
但是,因为该查询是通过区块高度查询到的状态数据,而不是通过单笔交易,接下来需要确认攻击者的特权角色设置和大量token余额设置这两个数据是否是合约创建时的交易中设置的。
这里我们要补充一下,在智能合约中,攻击者的特权角色设置和大量token余额设置等任何数据,本质上都只是修改了合约在链上存储的数据状态,比如这里的攻击者token余额,我们只需要修改攻击者地址的余额在合约中对应存储的插槽数据即可。
根据合约源码我们得到,攻击者大量token余额这个值的存储插槽位置在0,攻击者的特权角色这个值的存储插槽位置在10,如下图所示:
通过solidity储存结构我们通过公式 keccak256(h(k) . p) 计算合约在其两个变量中的具体存储键。
其中
0x9d1f25384689385576b577f0f3bf1fa04b6829457a3e65965ad8e59bd165a716 为攻击者在合约中 _balances 变量的具体存储key,用于存储攻击者具体资金余额,
0x70b9a82f1bc61b8dbca8540493592b90be59b6dac9a91986bcbc8eb7d2fc74d0 为攻击者在合约中 _Excludes 变量的具体存储key,用于存储该地址是否用于特权角色。
算出具体存储键后,我们通过对该合约创建交易的链上状态变更对比,确认攻击者地址所拥有的权限以及token余额都是通过在合约创建时进行设置。
在合约创建交易中并未存在相应的铸币或者权限设置的事件,并且分析合约源码中也未发现对其 _balances ,以及 _Excludes两个变量进行直接修改的代码,但在合约创建交易中确实存在存储键的变更,那么修改链上数据的操作到底在哪里进行的呢?
这里我们想到,由于修改存储键可以通过变量直接修改,也可以通过汇编代码直接对存储键直接修改,所以我们再次回过头对合约源码进行分析。
最终定位到其合约中的此段汇编代码,其中y做了一个复杂的十进制算数运算,如下:
随后通过使用汇编指令 mstore 将内存 0~64 字节的位置填充为如下内容:
# 执行两次 mstore 后 拼接了两个数据 一个是 攻击者地址,一个是 0x0 (_balances变量的存储插槽)
00000000000000000000000000002b9b0748d575cb21de3cae868ed19a7b5b560000000000000000000000000000000000000000000000000000000000000000
随后调用了写入链上存储的指令 sstore(keccak256(0, 64), exp(timestamp(), 6)) ,该指令计算出攻击者实际token余额的存储键,并将其设置为当前时间的6次方。
并在接下来又一次拼接 y 将内存 0~64 字节的位置填充为如下内容:
# 一个是 攻击者地址,一个是 0xa (_Excludes变量的存储插槽)
00000000000000000000000000002b9b0748d575cb21de3cae868ed19a7b5b56000000000000000000000000000000000000000000000000000000000000000a
并调用 sstore 将此存储键所在位置的数据修改为 1,通过对合约中的数据结构可得知此处存储数据类型应为bool,且值为true,也就是设置攻击者的特权角色。
到此为止,可以看出,攻击者在合约创建时就将攻击地址的余额和特权角色等数据硬编码到合约,并且通过一系列的数学运算对地址进行混淆,从而隐式的大量增发代币,并在项目上线后适当时机砸盘跑路。
通过零时科技安全团队对攻击者的套现金额进行追踪发现,攻击者将卖出获利的114万USDT转入0x000000481f40f88742399a627cbc2afb6ec34fed
地址
0x000000481f40f88742399a627cbc2afb6ec34fed
地址的资金流向如下:
攻击者
0x00002b9b0748d575cb21de3cae868ed19a7b5b56
的手续费来源如下:
可以发现攻击者的手续费来源有Binance交易所的痕迹。
其次,IEGT项目的合约创建者地址为0xf5c1ce9afae4e58a8a4504485caf9fdd19ec363a
,创建者的手续费来源地址为0xb795ad917daf9a1c98ee18e03e81fbbfb6d54355
,此地址的交易记录中也有大量痕迹,如下图有部分USDT流入Binance:
此次攻击事件中,攻击者利用比较隐蔽的手段为自己恶意发放代币,并在适当时间砸盘,将资金转移,本以为整个过程不会被社区发现,但是常在河边走,那有不湿鞋,目前还是留下了很多痕迹和证据,目前项目团队已经找到内部攻击者。
建议所有合约在上线前都尽量做到全面审计,减少合约漏洞缺陷,避免被恶意攻击者利用。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!