Skip to content
On this page

揭秘 Puppet:一场 DVT 的“空手套白狼”的 DeFi 冒险

在去中心化金融(DeFi)的世界里,聪明的套利和漏洞挖掘是永恒的主题。今天,我们将带你走进 Damn Vulnerable DeFi 的“Puppet”挑战,一步步揭示如何利用一个看似安全的借贷池,上演一场精彩的“空手套白狼”,最终将所有珍贵的 Damn Valuable Tokens (DVTs) 收入囊中。

挑战背景:一个“诱人”的借贷池

想象一下,有一个借贷池,你可以从中借走 Damn Valuable Tokens (DVTs)。但有个小小的“规矩”:你需要用两倍于借款金额的 ETH 作为抵押。这个池子目前拥有 100,000 个 DVTs 的充裕流动性。

更“巧合”的是,在古老的 Uniswap v1 交易所上,恰好有一个 DVT 市场,拥有 10 ETH 和 10 DVT 的流动性。

你的任务是:**拯救借贷池中所有的 DVTs,并将它们安全地存入指定的回收账户。**而你,作为挑战者,拥有 25 ETH 和 1000 DVTs 的初始资金。

核心漏洞:价格操纵与套利

仔细审视,你会发现两个关键点:

  1. 借贷池的“价格预言机”: PuppetPool 合约通过 Uniswap v1 的流动性来计算 DVT 的价格。它依赖于 uniswapPair.balance * (10 ** 18) / token.balanceOf(uniswapPair) 来获取 DVT 的 ETH 价格。
  2. Uniswap v1 的兑换逻辑: Uniswap v1 的兑换是基于恒定乘积做市商模型,即 x * y = k。这意味着,通过向池子中注入或取出代币,可以影响其价格。

关键洞察: 如果我们能在 Uniswap v1 上操纵 DVT 的价格,那么 PuppetPool 合约计算出的 DVT 价格也会随之改变。而 PuppetPool 的借款逻辑是根据这个“伪造”的价格来计算所需抵押物的。

攻击路径:一步步瓦解防御

我们的目标是清空 PuppetPool 中的 DVT。以下是实现这一目标的策略:

  1. 获取 DVT 的授权: 首先,我们需要让我们的攻击合约能够代表我们从 PuppetPool 中借出 DVT。而 PuppetPoolborrow 函数实际上是通过 token.transfer(recipient, amount) 来实现的。这意味着,如果攻击合约作为 recipient,它就可以直接获得 DVT。

  2. 引入“中间人”: PuppetPool 要求用 ETH 作为抵押物来借 DVT。但我们手上只有 DVT,而不是 ETH。这里就需要利用 Uniswap v1 了。我们可以通过 token.approve()exchange.tokenToEthSwapInput() 来将我们手上的 DVT 兑换成 ETH。

  3. 巧妙的“套利”:

    • 第一步:操纵价格。 我们用自己的一部分 DVT,在 Uniswap v1 中进行一个“不正当”的交易。通过 token.transferFrom(player, address(this), dvtValue),将一部分 DVT 转到我们攻击合约的地址,然后利用 token.approve(address(exchange), ...)exchange.tokenToEthSwapInput(dvtValue, 1, deadline),将大量的 DVT 换成 ETH。

      • 重点: 这个操作会显著增加 Uniswap v1 中 DVT 的数量,并减少 ETH 的数量。根据 x * y = k,这会极大地拉低 DVT 的 ETH 价格
    • 第二步:利用“低价”借贷。 现在,Uniswap v1 上的 DVT 价格已经非常低了。PuppetPool 会根据这个被操纵的价格来计算借款所需的 ETH 抵押物。

      • 我们计算出 PuppetPool 中剩余的 DVT 总量(token.balanceOf(address(pool)))。
      • 然后,调用 pool.calculateDepositRequired(amountToBorrow) 来获取这个“低价”下的所需抵押物。
      • 关键在于,此时我们手里有很多从 Uniswap v1 换来的 ETH,足以支付这个极低的抵押物金额。
      • 最后,调用 pool.borrow{value: deposit}(amountToBorrow, recovery),用这笔“微不足道”的 ETH 抵押物,从 PuppetPool 中借走所有的 DVT,并将它们直接发送到指定的 recovery 地址。
  4. 完成任务: 所有的 DVT 都被转移到了 recovery 地址,PuppetPool 中的 DVT 被清空。挑战成功!

关键代码解读

在提供的 MyContract 解决方案中,我们可以看到攻击逻辑的精髓:

  • token.permit()token.transferFrom() 允许攻击合约代为操作玩家的 DVT。
  • token.approve()exchange.tokenToEthSwapInput() 将 DVT 兑换成 ETH,并在此过程中操纵 Uniswap v1 的价格。
  • pool.calculateDepositRequired()pool.borrow{value: deposit}() 利用被操纵的低价格,用极少的 ETH 借走 PuppetPool 中所有的 DVT,并直接发送到 recovery 地址。

总结

“Puppet”挑战巧妙地利用了 DeFi 中两个核心组件——借贷池和去中心化交易所——之间的联动。通过对 Uniswap v1 价格的操纵,使得借贷池的抵押物计算出现偏差,从而实现了“空手套白狼”的壮举。这个挑战提醒我们,在 DeFi 世界中,任何依赖外部价格源的协议,都可能面临价格操纵的风险,必须谨慎设计和审计。

这场 DVT 的“Puppet”冒险,是一次对 DeFi 安全性、套利机制和合约漏洞的深刻体验。你准备好迎接下一个挑战了吗?


Built with AiAda