区块链安全—守株待兔的蜜罐合约(二)
字数 1714 2025-08-22 12:22:30
区块链安全:蜜罐合约分析与防御指南
一、蜜罐合约概述
蜜罐合约是一种利用受害者投机心理设计的智能合约,通过表面上的高收益或高概率奖励吸引用户投入资金,但实际上普通用户几乎不可能从中获利。本文重点分析两种类型的蜜罐合约:基于Solidity结构体漏洞的高级蜜罐和传统概率操纵型蜜罐。
二、高级蜜罐合约分析(OpenAddressLottery)
1. 合约结构
pragma solidity ^0.4.19;
contract OpenAddressLottery{
struct SeedComponents{
uint component1;
uint component2;
uint component3;
uint component4;
}
address owner;
uint private secretSeed;
uint private lastReseed;
uint LuckyNumber = 1;
mapping (address => bool) winner;
// 构造函数
function OpenAddressLottery() {
owner = msg.sender;
reseed(SeedComponents((uint)(block.coinbase), block.difficulty, block.gaslimit, block.timestamp));
}
// 参与函数
function participate() payable {
if(msg.value<0.1 ether) return;
require(winner[msg.sender] == false);
if(luckyNumberOfAddress(msg.sender) == LuckyNumber){
winner[msg.sender] = true;
uint win=(msg.value/10)*19;
if(win>this.balance) win=this.balance;
msg.sender.transfer(win);
}
if(block.number-lastReseed>1000)
reseed(SeedComponents((uint)(block.coinbase), block.difficulty, block.gaslimit, block.timestamp));
}
// 计算幸运数
function luckyNumberOfAddress(address addr) constant returns(uint n){
n = uint(keccak256(uint(addr), secretSeed)[0]) % 2;
}
// 重置种子
function reseed(SeedComponents components) internal {
secretSeed = uint256(keccak256(
components.component1,
components.component2,
components.component3,
components.component4
));
lastReseed = block.number;
}
// 强制重置种子(测试用)
function forceReseed() {
require(msg.sender==owner);
SeedComponents s;
s.component1 = uint(msg.sender);
s.component2 = uint256(block.blockhash(block.number - 1));
s.component3 = block.difficulty*(uint)(block.coinbase);
s.component4 = tx.gasprice * 7;
reseed(s);
}
// 自毁函数
function kill() {
require(msg.sender==owner);
selfdestruct(msg.sender);
}
// 回退函数
function () payable {
if(msg.value>=0.1 ether && msg.sender!=owner)
participate();
}
}
2. 表面逻辑分析
合约表面上是一个彩票合约:
- 用户投入≥0.1 ETH参与
- 合约通过
luckyNumberOfAddress函数生成0或1的随机数 - 如果结果等于
LuckyNumber(初始为1),用户获得1.9倍奖励 - 表面上有50%的中奖概率
3. 隐藏漏洞分析
关键漏洞:Solidity结构体存储覆盖
在Solidity 0.4.x版本中,局部结构体变量如果没有显式指定存储位置,会被默认存储在storage中,从而可能覆盖合约的状态变量。
漏洞复现测试合约:
pragma solidity ^0.4.24;
contract test{
address public addr = 0xa;
uint public b = 555;
uint256 public c = 666;
bytes public d = "abcd";
struct Seed{
uint256 component1;
uint256 component2;
uint256 component3;
uint256 component4;
}
function change() public{
Seed s;
s.component1 = 1;
s.component2 = 2;
s.component3 = 3;
s.component4 = 4;
}
}
测试结果:
- 调用
change()函数后,合约的状态变量addr、b、c、d被意外修改 - 这是因为局部结构体
s默认使用storage,从slot 0开始存储,覆盖了已有状态变量
4. 漏洞在蜜罐合约中的利用
在OpenAddressLottery合约中:
forceReseed()函数创建了局部结构体变量s- 当
s被赋值时,会从slot 0开始覆盖状态变量 - 合约状态变量顺序:
- slot 0:
owner(address) - slot 1:
secretSeed(uint) - slot 2:
lastReseed(uint) - slot 3:
LuckyNumber(uint)
- slot 0:
攻击效果:
- 当owner调用
forceReseed()时:s.component1会覆盖owners.component2会覆盖secretSeeds.component3会覆盖lastReseeds.component4会覆盖LuckyNumber
- 结果是将
LuckyNumber从1修改为其他值(如7) - 由于
luckyNumberOfAddress()只能返回0或1,用户永远无法匹配LuckyNumber
三、传统蜜罐合约分析(CryptoRoulette)
1. 合约代码
pragma solidity ^0.4.19;
contract CryptoRoulette {
uint256 private secretNumber;
uint256 public lastPlayed;
uint256 public betPrice = 0.1 ether;
address public ownerAddr;
struct Game {
address player;
uint256 number;
}
Game[] public gamesPlayed;
function CryptoRoulette() public {
ownerAddr = msg.sender;
shuffle();
}
function shuffle() internal {
secretNumber = uint8(sha3(now, block.blockhash(block.number-1))) % 20 + 1;
}
function play(uint256 number) payable public {
require(msg.value >= betPrice && number <= 10);
Game game;
game.player = msg.sender;
game.number = number;
gamesPlayed.push(game);
if (number == secretNumber) {
msg.sender.transfer(this.balance);
}
shuffle();
lastPlayed = now;
}
function kill() public {
if (msg.sender == ownerAddr && now > lastPlayed + 1 days) {
suicide(msg.sender);
}
}
function() public payable { }
}
2. 欺骗机制分析
-
概率不对等:
secretNumber范围:1-20(%20 + 1)- 用户猜测范围:1-10(
number <= 10) - 实际中奖概率:10/20 = 50% → 错误!因为用户只能猜1-10
-
真实中奖概率:
- 当
secretNumber在11-20时,用户必输 - 真实中奖概率:10/20 * (1/10) = 5%
- 当
-
资金回收机制:
- owner可以在24小时不活动后调用
kill()取回所有资金
- owner可以在24小时不活动后调用
四、防御措施
1. 针对结构体漏洞
-
明确指定存储位置:
function safeReseed() public { require(msg.sender == owner); SeedComponents memory s; // 明确使用memory s.component1 = uint(msg.sender); s.component2 = uint256(block.blockhash(block.number - 1)); s.component3 = block.difficulty*(uint)(block.coinbase); s.component4 = tx.gasprice * 7; reseed(s); } -
升级Solidity版本:
- 使用0.5.0及以上版本,这些版本要求显式指定存储位置
-
状态变量顺序检查:
- 检查结构体是否可能覆盖重要状态变量
2. 针对概率操纵蜜罐
-
验证概率计算:
- 确保用户选择范围与合约随机数范围一致
- 检查是否所有可能结果都有公平的中奖机会
-
检查owner特权:
- 警惕合约中owner可以随时修改关键参数的函数
- 检查是否有不公平的资金回收机制
-
合约代码审计:
- 在投入资金前全面审计合约代码
- 特别关注随机数生成和奖励计算逻辑
五、总结
-
蜜罐合约特征:
- 表面提供高回报或高中奖概率
- 实际通过技术手段或概率操纵使用户无法获利
- 通常包含owner特权函数
-
检测方法:
- 分析随机数生成算法
- 验证中奖概率计算
- 检查状态变量可能被覆盖的风险
- 审查owner特权函数
-
安全建议:
- 使用最新版Solidity编译器
- 对存储操作保持警惕
- 避免参与未经严格审计的合约
- 在测试网充分测试后再投入真资
通过深入理解这些蜜罐合约的工作原理和漏洞利用方式,开发者可以编写更安全的智能合约,用户也能更好地识别和避免潜在的资金陷阱。