sec3团队在2022年Aptos CTF MOVEment比赛中获得第一名,展示了团队对Move语言的理解及其在安全领域的应用。文章描述了5个挑战及其解决方案,涉及的内容包括智能合约漏洞分析和应对策略,体现了团队的技术实力。
我们非常兴奋地宣布,我们的 sec3 团队 “Super Rookies” 在 Aptos Capture The Flag 竞争中获得了第一名 MOVEment with Aptos Dec 2022。在四个挑战中,除了完整性检查,我们获得了两个首血 (第一个玩家解决了一个挑战) 和两个次血 (第二个玩家解决了一个挑战),最终排名第一。
这次比赛非常有趣,让我们有机会展示我们对 Move 语言的理解以及我们如何帮助保护它们。在这篇博文中,我们将简要讨论挑战和我们的解决方案。有关漏洞和可能的修复等详细信息,请参考 我们团队的总结。
挑战 1 是一个完整性检查,让玩家熟悉使用 aptos-cli 与私有 Aptos 链进行通信,合约已在该链上部署。合约中有一个 get_flag 函数,一旦调用将会触发一个 Flag 事件。
在初始化一个账户并通过 aptos-cli 调用 get_flag 函数后,我们可以提交交易哈希。服务器将检查此交易是否触发了 Flag 事件。如果是,服务器将返回该标识。
挑战 2 是一个简单的挑战,让玩家熟悉 Move 语言。合约有五个函数: init_challenge、hash、discrete_log、add 和 pow 以及 get_flag。
init_challenge 函数用于通过发送一个包含 5 个成员的 Challenge 对象来初始化挑战,该对象的成员包括 balance = 10、q1 = false、q2 = false、q3 = false 和事件处理程序。
q1、q2 和 q3 这三个字段指示此挑战中 3 个子问题的解决状态,这些状态将在 get_flag 函数中检查。
如果我们调用 hash 函数并提供一个满足 len(guess)==4 && keccak256(guess+"move")=="d9ad5396ce1ed307e8fb2a90de7fd01d888c02950ef6852fbc2191d2baf58e79" 的 guess: vector<u8>,那么字段 q1 将被设置为 true。这可以通过编写一个简单的脚本来暴力破解所有可能的猜测来解决,答案是 good。
为了将 q2 设置为 true,我们需要提供一个满足 pow(10549609011087404693, guess, 18446744073709551616) == 18164541542389285005 的 guess: u128,这是一道经典的离散对数问题。我们可以在 sage 中用 discrete_log(18164541542389285005,Mod(10549609011087404693,18446744073709551616)) 来解决这个问题,答案是 3123592912467026955。
与其他已检查的算术实现类似,Move 语言中的 Shl 和 Shr 操作 如果移位量大于或等于操作数的位宽将抛出 ARITHMETIC_ERROR,这是 CPU 级别的未定义行为。而且,如果发生溢出,Shl 操作不会引发 ARITHMETIC_ERROR。因此,我们可以将当前余额 10 向左移位超过 8 位(例如,每次左移两次并移动 5 位)将余额设置为 0。
此目标合同实现了一个非常简单的交换协议,允许用户在两个代币 Coin1 和 Coin2 之间进行交换。用户可以调用函数 get_coin 来获得 5 Coin1 和 5 Coin2 的空投。函数 swap_12 和 swap_21 可用于在 Coin1 和 Coin2 之间进行交换。最后,函数 get_flag 检查保留账户中 Coin1 或 Coin2 的数量是否为 0。
漏洞在于 get_amouts_out 函数的设计。它根据储备中 Coin1 和 Coin2 的比例计算交换金额,这显然是不安全的。考虑以下 PoC:
用户: 5 Coin1、5 Coin2;
储备: 50 Coin1、50 Coin2
用户: 10 Coin1、0 Coin2;
储备: 45 Coin1、55 Coin2
用户: 0 Coin1、12 Coin2;
储备: 55 Coin1、43 Coin2
用户: 15 Coin1、0 Coin2;
储备: 40 Coin1、55 Coin2
通过重复这个过程,恶意用户可以几乎耗尽储备账户中的所有代币。
这个合同实现了一个类似于 Uniswap v2 的代币交换程序,允许用户在 TestUSDC 和 SimpleCoin 之间进行交换,收取 0.25 的费用和 0.1 的奖励。在初始化过程中,管理员添加了 10^10 TestUSDC 和 10^10 SimpleCoin 到池中。get_flag 函数将检查用户是否至少有 10^10 SimpleCoin。如果是,用户将获得该标识。
该合约中有两个漏洞。
结合这两个漏洞,攻击者可以首先索取大量 TestUSDC,然后每次将等于当前储备池的 TestUSDC 交换为 SimpleCoin,从而在获得 0.1 奖励的同时耗尽一半的储备池。在 n 次重复后,储备池中的 SimpleCoin 数量将为 (10^10)/(2^n) 。
该合约使用多项式生成一个数字,其系数由用脚本哈希和多个伪随机数加密的字符串生成。如果用户猜测正确的数字,将会触发标识事件。显然,几乎不可能猜测正确的数字,因为可能的猜测数量为 2^128。
漏洞在于伪随机数是通过以秒为单位的时间戳和一个计数器生成的。计数器初始化为 0,每次生成随机数时增加 1。因此,时间戳和计数器都是可预测的。攻击者可以直接重用目标合同中的大部分代码来生成相同的多项式和正确的数字。因为字符串是通过对脚本哈希和常量进行 XOR 加密的,我们需要通过脚本调用漏洞合约。
- 原文链接: sec3.dev/blog/we-won-the...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!