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

币安下载

Binance币安交易所

全球最大加密货币交易所,注册100%可领取100USDT奖励!通过本站注册不仅可以享受手续费折扣同时有机会获得币安周边

点击注册 更多线路

更多交易所入口

一站式注册各大交易所、点击进入加密世界、永不失联,币安Binance/欧易OKX/GATE.IO芝麻开门/Bitget/抹茶MEXC/火币Huobi

点击进入 永不失联

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

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

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

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

由于攻击者在凭空获取的代币没有体现在总供应量中(totalSupply),也不触发 Transfer 事件,在 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),通过查看合约源码发现,代币合约会为每笔转账收取一定的手续费,而收取手续费的地址正是代币合约本身(具体实现在“_transfer 函数中”)。

奇怪的是,合约中已经设置了税收地址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.代币的增加没有触发 Transfer 事件

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

    也就是说,这是一个不标准的、或者说是恶意的 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”函数在“_transfer”函数中被调用,再细看调用条件,会发现:

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

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

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

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

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

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

目录[+]