本文将揭开一个大规模自动化的,通过 “退出骗局” 方式进行资产收割的黑客团队。
作者:Certik
封面:Photo by Milad Fakurian on Unsplash
近日,CertiK 安全专家团队频繁检测到多起手法相同的 “退出骗局”,也就是我们俗称的 Rug Pull。
在我们进行深入挖掘后发现,多起相同手法的事件都指向同一个团伙,最终关联到超过 200 个 Token 退出骗局。这预示着我们可能发现了一个大规模自动化的,通过 “退出骗局” 方式进行资产收割的黑客团队。
在这些退出骗局中,攻击者会创建一个新的 ERC20 代币,并用创建时预挖的代币加上一定数量的 WETH 创建一个 Uniswap V2 的流动性池。
当链上的打新机器人或用户在该流动性池购买一定次数的新代币后,攻击者则会通过凭空产生的代币,将流动性池中的 WETH 全部耗尽。
由于攻击者在凭空获取的代币没有体现在总供应量中(totalSupply),也不触发 Transfer 事件,在 etherscan 是看不到的,因此外界难以感知。
攻击者不仅考虑了隐蔽性,还设计了一个局中局,用来麻痹拥有初级技术能力,会看 etherscan 的用户,用一个小的问题来掩盖他们真正的目的……
深入骗局
我们以其中一个案例为例,详解一下该退出骗局的细节。
被我们检测到的实际上是攻击者用巨量代币(偷偷 mint 的)耗干流动性池并获利的交易,在该交易中,项目方共计用 416,483,104,164,831(约 416 万亿)个 MUMI 兑换出了约 9.736 个 WETH,耗干了池子的流动性。
然而该交易只是整个骗局的最后一环,我们要了解整个骗局,就需要继续往前追溯。
部署代币
3 月 6 日上午 7 点 52 分(UTC 时间,下文同),攻击者地址(0x8AF8)Rug Pull 部署了名为 MUMI(全名为 MultiMixer AI)的 ERC20 代币(地址为 0x4894),并预挖了 420,690,000(约 4.2 亿)个代币且全部分配给合约部署者。
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/c5474898b28d301dc72819f845728abb.png)
预挖代币数量与合约源码相对应。
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/4b0047e451dc55124dd4a707323f67b8.png)
添加流动性
8 点整(代币创建 8 分钟后),攻击者地址(0x8AF8)开始添加流动性。
攻击者地址(0x8AF8)调用代币合约中的 openTrading 函数,通过 uniswap v2 factory 创建 MUMI-WETH 流动性池,将预挖的所有代币和 3 个 ETH 添加到流动性池中,最后获得约 1.036 个 LP 代币。
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/63d691744402fe6f1c8cabfdbaab5f1b.png)
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/8444230243413b8d26ed68285127248b.png)
从交易细节可以看出,原本用于添加流动性的 420,690,000(约 4.2 亿)个代币中,有 63,103,500(约 6300 万)约个代币又被发送回代币合约(地址 0x4894),通过查看合约源码发现,代币合约会为每笔转账收取一定的手续费,而收取手续费的地址正是代币合约本身(具体实现在 “_transfer 函数中”)。
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/5b7fe4c52ed8cbf1b1ed57f8c5086dcd.png)
奇怪的是,合约中已经设置了税收地址 0x7ffb(收取转账手续费的地址),最后手续费却被发到代币合约自身。
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/6a7aabbdbf54592b8d3c205666b97862.png)
因此最后被添加到流动性池的 MUMI 代币数量为扣完税的 357,586,500(约 3.5 亿),而不是 420,690,000(约 4.3 亿)。
锁定流动性
8 点 1 分(流动性池创建 1 分钟后),攻击者地址(0x8AF8)锁定了通过添加流动性获取的全部 1.036 个 LP 代币。
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/88d253fa332d58350e5fbdf3a66c6ba2.png)
LP 被锁定后,理论上攻击者地址(0x8AF8)拥有的所有的 MUMI 代币便被锁定在流动性池内(除开作为手续费的那部分),因此攻击者地址(0x8AF8)也不具备通过移除流动性进行 Rug Pull 的能力。
为了让用户放心购买新推出的代币,许多项目方都是将 LP 进行锁定,意思就是项目方在说:“我不会跑路的,大家放心买吧!”,然而事实真的是这样吗?显然不是,这个案例便是如此,让我们继续分析。
Rug Pull
8 点 10 分,出现了新的攻击者地址②(0x9DF4),Ta 部署了代币合约中声明的税收地址 0x7ffb。
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/77f61fea18a93ee37839beef00c1f556.png)
这里有三个值得一提的点:
1. 部署税收地址的地址和部署代币的地址并不是同一个,这可能说明项目方在有意减少各个操作之间与地址的关联性,提高行为溯源的难度
2. 税收地址的合约不开源,也就是说税收地址中可能隐藏有不想暴露的操作
3. 税收合约比代币合约晚部署,而代币合约中税收地址已被写死,这意味着项目方可以预知税收合约的地址,由于 CREATE 指令在确定创建者地址和 nonce 的情况下,部署合约地址是确定的,因此项目方提前就使用创建者地址模拟计算出了合约地址
实际上有不少退出骗局都是通过税收地址进行,且税收地址的部署模式特征符合上述的 1、2 点。
上午 11 点(代币创建 3 小时后),攻击者地址②(0x9DF4)进行了 Rug Pull。他通过调用税收合约(0x77fb)的 “swapExactETHForTokens” 方法,用税收地址中的 416,483,104,164,831(约 416 万亿)个 MUMI 代币兑换出了约 9.736 个 ETH,并耗尽了池子中流动性。
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/8069812e98521252b0f6120ad4f105e0.png)
由于税收合约(0x77fb)不开源,我们对其字节码进行反编译,反编结果如下:
https://app.dedaub.com/decompile?md5=01e2888c7691219bb7ea8c6b6befe11c
查看完税收合约(0x77fb)的 “swapExactETHForTokens” 方法反编译代码后,我们发现实际上该函数实现的主要功能就是通过 uniswapV2 router 将数量为 “xt”(调用者指定)的税收合约(0x77fb)拥有的 MUMI 代币兑换成 ETH,并发送给税收地址中声明的 “_manualSwap” 地址。
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/7e164f5885b626de15f53f6898346634.png)
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/d252d0ad306ed9194c9b1eee03e6adba.png)
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/c03a71f488aae508a5d3cd1e80ee9406.png)
_manualSwap 地址所处的 storage 地址为 0x0,用 json-rpc 的 getStorageAt 命令进行查询后发现_manualSwap 对应的地址正是税收合约(0x77fb)的部署者:攻击者②(0x9DF4)。
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/c2d445952c5f41a7d625995c9165f93b.png)
该笔 Rug Pull 交易的输入参数 xt 为 420,690,000,000,000,000,000,000,对应 420,690,000,000,000(约 420 万亿)个 MUMI 代币(MUMI 代币的 decimal 为 9)。
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/fcd4fb6d7c95058dca33b69ac235a972.png)
也就是说,最终项目方用 420,690,000,000,000(约 420 万亿)个 MUMI 将流动性池中的 WETH 耗干,完成整个退出骗局。
然而这里有一个至关重要的问题,就是税收合约(0x77fb)哪来的这么多 MUMI 代币?
从前面的内容我们得知,MUMI 代币在部署时的代币合约时的总供应量为 420,690,000(约 4.2 亿),而在退出骗局结束后,我们在 MUMI 代币合约中查询到的总供应量依旧是 420,690,000(下图中显示为 420,690,000,000,000,000,需要减去 decimal 对应位数的 0,decimal 为 9),税收合约(0x77fb)中的远超总供应量的代币(420,690,000,000,000,约 420 万亿)就仿佛凭空出现的一样,要知道,如上文所提,0x77fb 作为税收地址甚至没有被用于接收 MUMI 代币转账过程中产生的手续费,税收被代币合约接收了。
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/f86299d65e8df0e7c26606c4738ed03f.png)
手法揭秘
- 税收合约哪来的代币
为了探究税收合约(0x7ffb)的代币来源,我们查看了它的 ERC20 转账事件历史。
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/25cefdc8b24ab90767fe9f56b6916e50.png)
结果发现在全部 6 笔关于 0x77fb 的转账事件中,只有从税收合约(0x7ffb)转出的事件,而没有任何 MUMI 代币转入的事件,乍一看,税收合约(0x7ffb)的代币还真是凭空出现的。
所以税收合约(0x7ffb)地址中凭空出现的巨额 MUMI 代币有两个特点:
1. 没有对 MUMI 合约的 totalSupply 产生影响
2. 代币的增加没有触发 Transfer 事件
那么思路就很明确了,即 MUMI 代币合约中一定存在后门,这个后门直接对 balance 变量进行修改,且在修改 balabce 的同时不对应修改 totalSupply,也不触发 Transfer 事件。
也就是说,这是一个不标准的、或者说是恶意的 ERC20 代币实现,用户无法从总供应量的变化和事件中感知到项目方在偷偷 mint 代币。
接着就是验证上面的想法,我们直接在 MUMI 代币合约源码中搜索关键字 “balance”。
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/a33209d68c645e21888d20dbf76b310f.png)
结果我们发现合约中有一个 private 类型的 “swapTokensForEth” 函数,传入参数为 uint256 类型的 tokenAmount,在该函数的第 5 行,项目方直接将_taxWallet,也就是税收合约(0x7ffb)的 MUMI 余额修改为 tokenAmount * 10**_decimals,也就是 tokenAmount 的 1,000,000,000(约 10 亿)倍,然后再从流动性池中将 tokenAmount 数量的 MUMI 兑换为 ETH 并存在代币合约(0x4894)中。
再接着搜索关键字 “swapTokenForEth“。
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/9cf4192f6bbab00ff320ee63d26626c1.png)
“swapTokenForEth” 函数在 “_transfer” 函数中被调用,再细看调用条件,会发现:
1. 当转账的接收地址 to 地址为 MUMI-WETH 流动性池。
2. 当有其他地址在流动性池中购买 MUMI 代币的数量超过_preventSwapBefore(5 次)时,“swapTokenForEth” 函数才会被调用
3. 传入的 tokenAmount 为代币地址所拥有的 MUMI 代币余额和_maxTaxSwap 之间的较小值
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/31c1e5fd79322e832bdc531daa060376.png)
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/6858829a14b0601d3bb6d561afcf2d70.png)
也就是说当合约检测到用户在池子中用 WETH 兑换成 MUMI 代币超过 5 次后,便会为税收地址偷偷 mint 巨量代币,并将一部分代币兑换成 ETH 存储在代币合约中。
一方面,项目方表面上进行收税并定期自动换成少量 ETH 放到代币合约,这是给用户看的,让大家以为这就是项目方的利润来源。
另一方面,项目方真正在做的,则是在用户交易次数达到 5 次后,直接修改账户余额,把流动性池全部抽干。
- 如何获利
执行完 “swapTokenForEth” 函数后,“_transfer” 函数还会执行 sendETHToFee 将代币地址中收税获得的 ETH 发送到税收合约(0x77fb)。
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/1e42fc18cf58820ff651fe309f745ff9.png)
税收合约(0x77fb)中的 ETH 可以被其合约内实现的 “rescue” 函数取出。
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/aba24a26a5d11634e9b88f6e8e1f08ba.png)
现在再回看整个退出骗局中最后一笔获利交易的兑换记录。
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/8069812e98521252b0f6120ad4f105e0.png)
获利交易中共进行了两次兑换,第一次是 4,164,831(约 416 万)个 MUMI 代币换 0.349 个 ETH,第二次是 416,483,100,000,000(约 416 万亿)个 MUMI 代币换 9.368 个 ETH。其中第二次兑换即为税收合约(0x7ffb)中 “swapExactETHForTokens” 函数内发起的兑换,之所以数量与输入参数代表的 420,690,000,000,000(约 420 万亿)个代币不符,是因为有部分代币作为税收发送给了代币合约(0x4894),如下图所示:
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/7dfea7b7f4997f5fdc4231a77f49907f.png)
而第一次兑换对应的,则是在第二次兑换过程中,当代币从税收合约(0x7ffb)发送至 router 合约时,由因为满足代币合约内的后门函数触发条件,导致触发 “swapTokensForEth” 函数所发起的兑换,并非关键操作。
- 背后的大镰刀
从上文中可以看出,MUMI 代币从部署,到创建流动性池,再到 Rug Pull,整个退出骗局周期才约 3 个小时,但是却以不到约 6.5 个 ETH 的成本(3 ETH 用于添加流动性,3 ETH 用于从流动性池中兑换 MUMI 以作诱导,不到 0.5 ETH 用于部署合约和发起交易)获得了 9.7 个 ETH,利润超过 50%。
攻击者用 ETH 换 MUMI 的交易有 5 笔,前文中并没有提到,交易信息如下:
- https://etherscan.io/tx/0x62a59ba219e9b2b6ac14a1c35cb99a5683538379235a68b3a607182d7c814817
- https://etherscan.io/tx/0x0c9af78f983aba6fef85bf2ecccd6cd68a5a5d4e5ef3a4b1e94fb10898fa597e
- https://etherscan.io/tx/0xc0a048e993409d0d68450db6ff3fdc1f13474314c49b734bac3f1b3e0ef39525
- https://etherscan.io/tx/0x9874c19cedafec351939a570ef392140c46a7f7da89b8d125cabc14dc54e7306
- https://etherscan.io/tx/0x9ee3928dc782e54eb99f907fcdddc9fe6232b969a080bc79caa53ca143736f75
通过分析在流动性中进行操作的 eoa 地址后发现,相当一部分的地址为链上的 “打新机器人”,结合整个骗局快进快出的特点,我们有理由认为,这整个骗局针对的对象正是链上十分活跃的各种打新机器人、打新脚本。
因此无论是代币看似没必要但是复杂的合约设计、合约部署、流动性锁定流程,还是中途攻击者相关地址主动用 ETH 换取 MUMI 代币的疑惑行为,都可以理解成是攻击者为了试图骗过链上各类打新机器人的反欺诈程序而做的伪装。
我们通过追踪资金流后发现,攻击所获得的收益最后全被攻击地址②(0x9dF4)发送到了地址资金沉淀地址 (0xDF1a)。
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/636a3923a72ace4a51d723c427ef977d.png)
而实际上我们最近检测到的多起退出骗局最初的资金来源以及最后的资金去向都指向这个地址,因此我们对这个地址的交易进行了大致的分析和统计。
最终发现,该地址在约 2 个月前开始活跃,到今天为止已经发起了超过 7,000 笔交易,并且该地址已经和超过 200 个代币进行过交互。
我们对其中的约 40 个代币交易记录进行分析,然后发现我们查看的几乎所有代币对应的流动性池中,最后都会有一笔输入数量远大于代币总供应量的兑换交易将流动性池中的 ETH 耗尽,且整个退出骗局的周期都较短。
其中部分代币(名烟中华)的部署交易如下:
https://etherscan.io/tx/0x324d7c133f079a2318c892ee49a2bcf1cbe9b20a2f5a1f36948641a902a83e17
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/b49675e414c4bc27c486c2cc70e67ae8.png)
https://etherscan.io/tx/0x0ca861513dc68eaef3017e7118e7538d999f9b4a53e1b477f1f1ce07d982dc3f
![Web3安全警示丨 链上打新局中局,大规模Rug Pull手法解密](https://web3caff.com/wp-content/uploads/2024/06/6b8f2055abd8cbfb6488a958f93feee8.png)
因此我们可以认定,该地址实际上就是一个大规模的自动化 “退出骗局” 收割机,收割的对象就是链上的打新机器人。
该地址现在仍在活跃。
写在最后
如果一个代币在 mint 时不对应修改 totalSupply,也不触发 Transfer 事件,那么我们是很难感知项目方是否有在偷偷 mint 代币的,这也将加剧 “代币是否安全,完全依赖于项目方是否自觉” 的现状。
因此我们可能需要考虑改进现有的代币机制或者引入一种有效的代币总量检测方案,来保障代币数量变更的公开透明,现在凭借 event 来捕获代币状态变更是不够的。
并且我们需要警醒的是,尽管现在大家的防骗意识在提高,但是攻击者的反防骗手段也在提高,这是一场永不停息的博弈,我们需要保持不断学习和思考,才能在这样的博弈中保全自身。
免责声明:作为区块链信息平台,本站所发布文章仅代表作者及嘉宾个人观点,与 Web3Caff 立场无关。文章内的信息仅供参考,均不构成任何投资建议及要约,并请您遵守所在国家或地区的相关法律法规。