Skip to content
On this page

S2C6: uint8 Overflow + Reentrancy

1. 问题

合约有积分和等级系统,mintFlag 检查 uint8(points << levels) == 32。需要找到 points 和 levels 的组合,使 points << levels 在 uint8 溢出后恰好等于 32。

2. 原因

合约使用 uint8 转换来截断位移结果。由于 uint8 只有 8 位(0-255),左移后的值如果超过 255 会溢出回绕。同时合约存在重入漏洞,可以无限获取积分。

3. 方案

  1. 利用重入漏洞获取 59 个积分(59 次 claimPoints
  2. 升级 5 次(消耗 50 积分,剩余 9 积分,等级 5)
  3. 验证:uint8(9 << 5) = uint8(288) = 288 - 256 = 32 ✓
  4. points(9) < 10 ✓points × levels(45) >= 30 ✓
solidity
function solve() public {
    counter = 0;
    challenge.resetPoints();
    challenge.claimPoints(); // reenters 58 more times → total 59
    // 5 x upgradeLevel: points=9, levels=5
    for (uint i = 0; i < 5; i++) challenge.upgradeLevel();
    challenge.mintFlag(); // uint8(9<<5)=32 ✓
}

receive() external payable {
    if (counter < 58) { counter++; challenge.claimPoints(); }
}

4. 遇到的陷阱

重入次数与溢出参数匹配: 需要精确计算重入次数和升级次数,使最终参数满足所有检查。初始计算错误可能导致不满足条件。

5. 陷阱的原因

三个条件相互约束:

  1. points < 10 → 最终积分必须 < 10
  2. points * levels >= 30 → 积分 × 等级 ≥ 30
  3. uint8(points << levels) == 32 → 位移后 uint8 截断 = 32

需要找到同时满足这三个条件的 (points, levels) 组合和到达该组合的路径。

6. 如何解决

数学分析

  • 9 << 5 = 288, uint8(288) = 288 % 256 = 32
  • points=9 < 10 ✓, 9*5=45 >= 30 ✓
  • 到达路径:59 次 claimPoints → points=59,5 次 upgradeLevel → points=59-50=9
  • 关键:找到 points << levels 在 uint8 下等于 32 的最优组合

uint8 位移溢出查找表

pointslevelsp<<luint8(p<<l)
9528832 ✓
153232 ✓
243232 ✓

选择 9 和 5 因为:points * levels = 45 >= 30points = 9 < 10

7. 技术要点

要点说明
uint8 溢出左移结果超过 255 时自动截断:uint8(x) == x % 256
重入漏洞外部调用在状态更新前 → 无限重入
参数空间搜索穷举 points(1-255) × levels(0-255) 找到满足 uint8(p<<l)==32 的组合

核心代码solutions/season2/Challenge6SolutionV2.sol(59 次重入 + 5 次升级)

Built with AiAda