技术详解:链上打新局中局,大规模Rug Pull手法解密

近日,CertiK 安全专家团队**检测到多起手法相同的“退出**”,也就是我们俗称的 Rug Pull。

在我们进行深入挖掘后发现,多起相同手法的事件都指向同一个团伙,**关联到超过 200 个 Token 退出**。这预示着我们可能发现了一个大规模自动化的,通过“退出**”方式进行资产收割的黑客团队。

在这些退出**中,攻击者会创建一个新的 ERC 20 **,并用创建时预挖的**加上**数量的 WETH 创建一个 Uniswap V2的流动性池。

当链上的打新机器人或用户在该流动性池购买**次数的新**后,攻击者则会通过凭空产生的**,将流动性池中的 WETH **耗尽。

由于攻击者在凭空获取的**没有体现在总供应量中(totalSupply),也不触发 Tran**er 事件,在 etherscan 是看不到的,因此外界难以感知。

攻击者不仅考虑了隐蔽性,还设计了一个局**,用来麻痹拥有初级技术能力,会看 etherscan 的用户,用一个小的问题来掩盖他们真正的目的……

深入**

我们以其中一个案例为例,详解一下该退出**的细节。

被我们检测到的实际上是攻击者用巨量**(偷偷 mint 的)耗干流动性池并获利的交易,在该交易中,项目方共计用 416, 483, 104, 164, 831 (约 416 万亿)个 MUMI 兑换出了约 9.736 个 WETH,耗干了池子的流动性。

然而该交易只是整个**的**一环,我们要了解整个**,就需要继续往前追溯。

部署**

3 月 6 日上午 7 点 52 分(UTC 时间,下文同),攻击者地址(0x 8 AF 8)Rug Pull 部署了名为 MUMI(全名为 MultiMixer AI)的 ERC 20 **(地址为0x 4894),并预挖了 420, 690, 000 (约 4.2 亿)个**且**分配给合约部署者。

预挖**数量与合约源码相对应。

添加流动性

8 点整(**创建 8 分钟后),攻击者地址(0x 8 AF 8)开始添加流动性。

攻击者地址(0x 8 AF 8)调用**合约中的 openTrading 函数,通过 uniswap v2 factory 创建 MUMI-WETH 流动性池,将预挖的所有**和 3 个 ETH 添加到流动性池中,**获得约 1.036 个 LP **。

从交易细节可以看出,原本用于添加流动性的 420, 690, 000 (约 4.2 亿)个**中,有 63, 103, 500 (约 6300 万)约个**又被发送回**合约(地址0x 4894),通过查看合约源码发现,**合约会为每笔转账收取**的手续费,而收取手续费的地址正是**合约本身(具体实现在“_tran**er 函数中”)。

奇怪的是,合约中已经设置了税收地址0x 7 ffb(收取转账手续费的地址),**手续费却被发到**合约自身。

因此**被添加到流动性池的 MUMI **数量为扣完税的 357, 586, 500 (约 3.5 亿),而不是 420, 690, 000 (约 4.3 亿)。

锁定流动性

8 点 1 分(流动性池创建 1 分钟后),攻击者地址(0x 8 AF 8)锁定了通过添加流动性获取的** 1.036 个 LP **。

LP 被锁定后,理论上攻击者地址(0x 8 AF 8)拥有的所有的 MUMI **便被锁定在流动性池内(除开作为手续费的那部分),因此攻击者地址(0x 8 AF 8)也不具备通过移除流动性进行 Rug Pull 的能力。为了让用户放心购买新推出的**,许多项目方都是将 LP 进行锁定,意思就是项目方在说:“我不会跑路的,大家放心买吧!”,然而事实真的是这样吗?显然不是,这个案例便是如此,让我们继续分析。

Rug Pull

8 点 10 分,出现了新的攻击者地址②(0x 9 DF 4),Ta 部署了**合约中声明的税收地址0x 7 ffb。

这里有三个值得一提的点:

1.部署税收地址的地址和部署**的地址并不是同一个,这可能说明项目方在有意减少各个操作之间与地址的关联性,提高行为溯源的难度

2.税收地址的合约不开源,也就是说税收地址中可能隐藏有不想**的操作

3.税收合约比**合约晚部署,而**合约中税收地址已被写死,这意味着项目方可以预知税收合约的地址,由于 CREATE 指令在确定创建者地址和 nonce 的情况下,部署合约地址是确定的,因此项目方提前就使用创建者地址模拟计算出了合约地址

实际上有不少退出**都是通过税收地址进行,且税收地址的部署模式特征符合上述的 1、 2 点。

上午 11 点(**创建 3 小时后),攻击者地址②(0x 9 DF 4)进行了 Rug Pull。他通过调用税收合约(0x 77 fb)的“swapExactETHForTokens”方法,用税收地址中的 416, 483, 104, 164, 831 (约 416 万亿)个 MUMI **兑换出了约 9.736 个 ETH,并耗尽了池子中流动性。

由于税收合约(0x 77 fb)不开源,我们对其字节码进行反编译,反编结果如下:https://app.dedaub.com/decomPile?md5=01e2888c7691219bb7ea8c6b6befe11c查看完税收合约(0x 77 fb)的“swapExactETHForTokens”方法反编译代码后,我们发现实际上该函数实现的主要功能就是通过 uniswap V2 router 将数量为“xt”(调用者指定)的税收合约(0x 77 fb)拥有的 MUMI **兑换成 ETH,并发送给税收地址中声明的“_manualSwap”地址。

_manualSwap 地址所处的 storage 地址为0x 0 ,用 json-rpc 的 getStorageAt 命令进行查询后发现_manualSwap 对应的地址正是税收合约(0x 77 fb)的部署者:攻击者②(0x 9 DF 4)。

该笔 RugPull 交易的输入参数 xt 为 420, 690, 000, 000, 000, 000, 000, 000 ,对应 420, 690, 000, 000, 000 (约 420 万亿)个 MUMI **(MUMI **的 decimal 为 9)。也就是说,**项目方用 420, 690, 000, 000, 000 (约 420 万亿)个 MUMI 将流动性池中的 WETH 耗干,完成整个退出**。

然而这里有一个至关重要的问题,就是税收合约(0x 77 fb)哪来的这么多 MUMI **?

从前面的内容我们得知,MUMI **在部署时的**合约时的总供应量为 420, 690, 000 (约 4.2 亿),而在退出**结束后,我们在 MUMI **合约中查询到的总供应量依旧是 420, 690, 000 (下图中显示为 420, 690, 000, 000, 000, 000 ,需要减去 decimal 对应位数的 0 ,decimal 为 9),税收合约(0x 77 fb)中的远超总供应量的**(420, 690, 000, 000, 000 ,约 420 万亿)就仿佛凭空出现的一样,要知道,如上文所提,0x 77 fb 作为税收地址甚至没有被用于接收 MUMI **转账过程中产生的手续费,税收被**合约接收了。

手法揭秘
  • 税收合约哪来的**

    为了探究税收合约(0x 7 ffb)的**来源,我们查看了它的 ERC 20 转账事件历史。

    结果发现在** 6 笔关于0x 77 fb 的转账事件中,只有从税收合约(0x 7 ffb)转出的事件,而没有** MUMI **转入的事件,乍一看,税收合约(0x 7 ffb)的**还真是凭空出现的。

    所以税收合约(0x 7 ffb)地址中凭空出现的巨额 MUMI **有两个特点:

    1.没有对 MUMI 合约的 totalSupply 产生影响

    2.**的增加没有触发 Tran**er 事件

    那么思路就很明确了,即 MUMI **合约中**存在后门,这个后门直接对 balance 变量进行修改,且在修改 balabce 的同时不对应修改 totalSupply,也不触发 Tran**er 事件。

    也就是说,这是一个不标准的、或者说是恶意的 ERC 20 **实现,用户无法从总供应量的变化和事件中感知到项目方在偷偷 mint **。

    接着就是验证上面的想法,我们直接在 MUMI **合约源码中搜索关键字“balance”。

    结果我们发现合约中有一个 private 类型的“swapTokensForEth”函数,传入参数为 uint 256 类型的 tokenAmount,在该函数的第 5 行,项目方直接将_taxWallet,也就是税收合约(0x 7 ffb)的 MUMI 余额修改为 tokenAmount * 10**_decimals,也就是 tokenAmount 的 1, 000, 000, 000 (约 10 亿)倍,然后再从流动性池中将 tokenAmount 数量的 MUMI 兑换为 ETH 并存在**合约(0x 4894)中。

    再接着搜索关键字“swapTokenForEth“。

    “swapTokenForEth”函数在“_tran**er”函数中被调用,再细看调用条件,会发现:

    1.当转账的接收地址 to 地址为 MUMI-WETH 流动性池。

    2.当有其他地址在流动性池中购买 MUMI **的数量超过_preventSwapBefore(5 次)时,“swapTokenForEth”函数才会被调用

    3.传入的 tokenAmount 为**地址所拥有的 MUMI **余额和_maxTaxSwap 之间的较小值

    也就是说当合约检测到用户在池子中用 WETH 兑换成 MUMI **超过 5 次后,便会为税收地址偷偷 mint 巨量**,并将一部分**兑换成 ETH 存储在**合约中。

    一方面,项目方表面上进行收税并定期自动换成少量 ETH 放到**合约,这是给用户看的,让大家以为这就是项目方的利润来源。

    另一方面,项目方真正在做的,则是在用户交易次数达到 5 次后,直接修改账户余额,把流动性池**抽干。

    • 如何获利

      执行完“swapTokenForEth”函数后,“_tran**er”函数还会执行 sendETHToFee 将**地址中收税获得的 ETH 发送到税收合约(0x 77 fb)。

      税收合约(0x 77 fb)中的 ETH 可以被其合约内实现的“rescue”函数取出。

      现在再回看整个退出**中**一笔获利交易的兑换记录。

      获利交易**进行了两次兑换,**次是 4, 164, 831 (约 416 万)个 MUMI **换 0.349 个 ETH,第二次是 416, 483, 100, 000, 000 (约 416 万亿)个 MUMI **换 9.368 个 ETH。其中第二次兑换即为税收合约(0x 7 ffb)中“swapExactETHForTokens”函数内发起的兑换,之所以数量与输入参数代表的 420, 690, 000, 000, 000 (约 420 万亿)个**不符,是因为有部分**作为税收发送给了**合约(0x 4894),如下图所示:

      而**次兑换对应的,则是在第二次兑换过程中,当**从税收合约(0x 7 ffb)发送至 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 **的疑惑行为,都可以理解成是攻击者为了试图骗过链上各类打新机器人的反欺诈程序而做的伪装。

          我们通过追踪资金流后发现,攻击所获得的收益**全被攻击地址②(0x 9 dF 4)发送到了地址资金沉淀地址(0x DF 1 a)。而实际上我们最近检测到的多起退出**最初的资金来源以及**的资金去向都指向这个地址,因此我们对这个地址的交易进行了大致的分析和统计。

          **发现,该地址在约 2 个月前开始活跃,到今天为止已经发起了超过 7, 000 笔交易,并且该地址已经和超过 200 个**进行过交互。

          我们对其中的约 40 个**交易记录进行分析,然后发现我们查看的几乎所有**对应的流动性池中,**都会有一笔输入数量远大于**总供应量的兑换交易将流动性池中的 ETH 耗尽,且整个退出**的周期都较短。

          其中部分**(名烟中华)的部署交易如下:https://etherscan.io/tx/0x324d7c133f079a2318c892ee49a2bcf1cbe9b20a2f5a1f36948641a902a83e17

          https://etherscan.io/tx/0x 0 ca 86151 3d c 68 eaef 3017 e 7118 e 7538 d 999 f 9 b 4 a 53 e 1 b 477 f 1 f 1 ce 07 d 98 2d c 3 f因此我们可以认定,该地址实际上就是一个大规模的自动化“退出**”收割机,收割的对象就是链上的打新机器人。

          该地址现在仍在活跃。

          写在**

          如果一个**在 mint 时不对应修改 totalSupply,也不触发 Tran**er 事件,那么我们是很难感知项目方是否有在偷偷 mint **的,这也将加剧“**是否安全,**依赖于项目方是否自觉”的现状。

          因此我们可能需要考虑改进现有的**机制或者引入一种有效的**总量检测方案,来保障**数量变更的公开透明,现在凭借 event 来捕获**状态变更是不够的。

          并且我们需要警醒的是,尽管现在大家的防骗意识在提高,但是攻击者的反防骗手段也在提高,这是一场**停息的博弈,我们需要保持不断学习和思考,才能在这样的博弈中保全自身。

          • 本文使用工具

            查看交易基本信息:https://etherscan.io/

            合约反编译:app.dedaub.com/decompilejson-rpc:

            https://www.quicknode.com/docs/ethereum/eth_getStorageAt

标签: /
上一篇2024-03-20
下一篇 2024-03-20

相关推荐