Appearance
S2C6: uint8 Overflow + Reentrancy
1. 問題
コントラクトにはポイントとレベルのシステムがあり、mintFlag は uint8(points << levels) == 32 をチェックします。points << levels が uint8 のオーバーフロー後にちょうど 32 になるような points と levels の組み合わせを見つける必要があります。
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(); // さらに 58 回リエントリ → 合計 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. 落とし穴の原因
3 つの条件が相互に制約し合います:
points < 10→ 最終ポイントは 10 未満でなければならないpoints * levels >= 30→ ポイント × レベル ≥ 30uint8(points << levels) == 32→ シフト後の uint8 切り詰め = 32
これら 3 つの条件を同時に満たす (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 回のアップグレード)