Appearance
S2C6: uint8 Overflow + Reentrancy
1. 问题
合约有积分和等级系统,mintFlag 检查 uint8(points << levels) == 32。需要找到 points 和 levels 的组合,使 points << levels 在 uint8 溢出后恰好等于 32。
2. 原因
合约使用 uint8 转换来截断位移结果。由于 uint8 只有 8 位(0-255),左移后的值如果超过 255 会溢出回绕。同时合约存在重入漏洞,可以无限获取积分。
3. 方案
- 利用重入漏洞获取 59 个积分(59 次
claimPoints) - 升级 5 次(消耗 50 积分,剩余 9 积分,等级 5)
- 验证:
uint8(9 << 5) = uint8(288) = 288 - 256 = 32 ✓ 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. 陷阱的原因
三个条件相互约束:
points < 10→ 最终积分必须 < 10points * levels >= 30→ 积分 × 等级 ≥ 30uint8(points << levels) == 32→ 位移后 uint8 截断 = 32
需要找到同时满足这三个条件的 (points, levels) 组合和到达该组合的路径。
6. 如何解决
数学分析:
9 << 5 = 288,uint8(288) = 288 % 256 = 32points=9 < 10 ✓,9*5=45 >= 30 ✓- 到达路径:59 次 claimPoints → points=59,5 次 upgradeLevel → points=59-50=9
- 关键:找到
points << levels在 uint8 下等于 32 的最优组合
uint8 位移溢出查找表:
| points | levels | p<<l | uint8(p<<l) |
|---|---|---|---|
| 9 | 5 | 288 | 32 ✓ |
| 1 | 5 | 32 | 32 ✓ |
| 2 | 4 | 32 | 32 ✓ |
选择 9 和 5 因为:points * levels = 45 >= 30 且 points = 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 次升级)