前言

RealWorld CTF 2019 的一道题目,基于 Real World CTF Finals 2019 - Montagy 复现的。

用于复现的合约地址 ropsten@0x6e0279efce38a05394e233198005ae96816dd0d3

源码

pragma solidity ^0.5.11;

contract Montagy{
    address payable public owner;
    mapping(bytes32=>bool) isOfficialChecksum;
    mapping(address=>bytes32) puzzleChecksum;
    address public lastchildaddr;
    string public winnerinfo;
    bool public _gameon;
    constructor() public payable{
        owner = msg.sender;
        _gameon = false;
    }
    modifier onlyOwner(){
        require(msg.sender == owner);
        _;
    }
    modifier onlyPuzzle(){
        require(puzzleChecksum[msg.sender] != 0);
        _;
    }
    modifier onlyGameOn(){
        require(_gameon);
        _;
    }
    
    function setGame(bool status) public onlyOwner{
        _gameon = status;
    }
    
    function registerCode(bytes memory a) public onlyOwner {
        isOfficialChecksum[tag(a)]=true;
    }
    
    function newPuzzle(bytes memory code) public onlyGameOn returns(address addr){
        bytes32 cs = tag(code);
        require(isOfficialChecksum[cs]);
        
        addr = deploy(code);
        lastchildaddr=addr;
        puzzleChecksum[addr] = cs;
    }
    
    function solve(string memory info) public onlyGameOn onlyPuzzle {
        owner.transfer(address(this).balance);
        winnerinfo = info;
    }
    
    function deploy(bytes memory code) private returns(address addr){
        assembly {
            addr := create(0,add(code,0x20), mload(code))
            if eq(extcodesize(addr), 0) { revert(0, 0) }
        }
    }
    
    function tag(bytes memory a) pure public returns(bytes32 cs){
        assembly{
            let groupsize := 16
            let head := add(a,groupsize)
            let tail := add(head, mload(a))
            let t1 := 0x21711730
            let t2 := 0x7312f103
            let m1,m2,m3,m4,p1,p2,p3,s,tmp
            for { let i := head } lt(i, tail) { i := add(i, groupsize) } {
                s := 0x6644498b
                tmp := mload(i)
                m1 := and(tmp,0xffffffff)
                m2 := and(shr(0x20,tmp),0xffffffff)
                m3 := and(shr(0x40,tmp),0xffffffff)
                m4 := and(shr(0x60,tmp),0xffffffff)
                for { let j := 0 } lt(j, 0x4) { j := add(j, 1) } {
                    s := and(add(s, 0x68696e74),0xffffffff)
                    p1 := sub(mul(t1, 0x10), m1)
                    p2 := add(t1, s)
                    p3 := add(div(t1,0x20), m2)
                    t2 := and(add(t2, xor(p1,xor(p2,p3))), 0xffffffff)
                    p1 := add(mul(t2, 0x10), m3)
                    p2 := add(t2, s)
                    p3 := sub(div(t2,0x20), m4)
                    t1 := and(add(t1, xor(p1,xor(p2,p3))), 0xffffffff)
                }
            }
            cs := xor(mul(t1,0x100000000),t2)
        }
    }
}

追踪合约相关的交易,可以看到该合约又部署了两个相关的合约:

ropsten@0xb4cd04709a2c134ed66875177cce2a897df90f7f

pragma solidity ^0.5.11;

contract Montagy{
    function solve(string memory info) public;
}

contract P1{
    Montagy public server;
    constructor() public{
        server = Montagy(msg.sender);
    }
    function hello() pure public returns(string memory){
        return "ReadWriteCaTFlag";
    }
    function day() pure public returns(int){
        return 0+0;
    }
    function demo(uint256 a, uint256 b, uint256 c, uint256 d, uint256 e, uint256 f, uint256 g, uint256 h) pure public returns(bool calc){
        require(a^b^c&d^e|f^g^h>=0x726365);
        calc=true;
    }
    function easy(bytes memory a, bytes memory b, bytes memory c, string memory info) public{
        require(keccak256(a)&keccak256(b)==bytes32(bytes8(0x1234567890abcdef)));
        require(keccak256(b)^keccak256(c)==bytes32(bytes8(0xabcdef1234567890)));
        require(keccak256(a)|keccak256(c)==bytes32(bytes8(0xa1b2c3d4e5f60987)));
        server.solve(info);
    }
}

ropsten@0x8855a20a14c7a43613cbd1ced033d7df6210c1b7

pragma solidity ^0.5.11;

contract Montagy{
    function setGame(bool status) public;
    
    function registerCode(bytes memory a) public;
    
    function newPuzzle(bytes memory code) public;
    
    function solve(string memory info) public;
    
    function deploy(bytes memory code) private;
    
    function tag(bytes memory a) pure public;
}

contract P2{
    Montagy public server;
    constructor() public{
        server = Montagy(msg.sender);
    }
    uint256 a;
    uint256 b;
    uint256 c;
    uint256 d;
    uint256 e;
    uint256 f;
    uint256 g;
    uint256 h;
    uint256 i;
    function monica_init(uint256 _a, uint256 _b, uint256 _c, uint256 _d, uint256 _e, uint256 _f, uint256 _g, uint256 _h, uint256 _i) public {
        a=_a;
        b=_b;
        c=_c;
        d=_d;
        e=_e;
        f=_f;
        g=_g;
        h=_h;
        i=_i;
    }
    function loose() view public returns(bool){
        uint256 t1 = (a^b^c)+(d^e^f)+(g^h^i);
        uint256 t2 = (a+d+g)^(b+e+h)^(c+f+i);
        require(t1 + t2 < 0xaabbccdd);
        require(t1 > 0x8261e26b90505061031256e5afb60721cb); 
        require(0xf35b6080614321368282376084810151606401816080016143855161051756 >= t1*t2);
        require(t1 - t2 >= 0x65e670d9bd540cea22fdab97e36840e2);
        return true;
    }
    function harsh(bytes memory seed, string memory info) public{
        require(loose());
        if (keccak256(seed) == bytes32(bytes18(0x6111d850336107ef16565b908018915a9056))) {
            server.solve(info);
        }
    }
    
}

分析

通过合约代码可以知道,解决本题的必要条件是触发 solve() 函数的执行,而很明显要触发 solve() 的执行需要满足 onlyPuzzle 的限制,因此我们需要使用 owner 发布的两个合约来尝试触发。但通过阅读 P1、P2 两个合约的代码,可以发现 easy()harsh() 两个函数的要求基本是不能满足的,因此需要换一种思路。通过 newPuzzle() 来发布我们的合约,然后通过我们的合约来触发 solve() 函数,此时问题就变成了如何通过 newPuzzle() 来发布自己的合约。

再仔细看一下如何用该合约来发布我们自定义的合约:

  1. 通过 registerCode(0) 注册需要发布的合约代码
  2. 通过 newPuzzle(0) 发布新合约,发布的新合约必须在上一步被注册

很明显 registerCode(0) 由于有 onlyOwner 的限制无法调用,所以唯一的突破口在于利用 owner 之前部署的两个合约 P1 和 P2 注册好的哈希值,部署一个哈希值相同的攻击合约进行调用。

这里要明确一下的就是,在一个合约后面随意 padding 字符,其实是不会改变这个合约的功能的,所以我们可以尝试在我们希望部署的合约后面 padding 字符,然后使其最后计算出的哈希值同之前注册过的两个哈希值中的一个函数相同。

Solve

我们希望部署的合约是:

pragma solidity ^0.5.11;

contract Montagy{
    function solve(string memory info) public;
}

contract Solve{
    Montagy public server;
    constructor() public{
        server = Montagy(0x6e0279EfCe38A05394e233198005Ae96816Dd0D3);
    }
    
    function solve() public {
        server.solve("syang solve");
    }
}

对应的字节码:

608060405234801561001057600080fd5b50736e0279efce38a05394e233198005ae96816dd0d36000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506101a8806100746000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063890d69081461003b578063fd922a4214610045575b600080fd5b61004361008f565b005b61004d61014e565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166376fe1e926040518163ffffffff1660e01b815260040180806020018281038252600b8152602001807f7379616e6720736f6c7665000000000000000000000000000000000000000000815250602001915050600060405180830381600087803b15801561013457600080fd5b505af1158015610148573d6000803e3d6000fd5b50505050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff168156fea265627a7a723158209c9fe9ae135cdca5ef541d8fb75de29863503a323ffa094ed3c24deb87208fd664736f6c63430005110032

编写 python 脚本计算相应 padding:

from z3 import *

def tag(a):
    if len(a) % 32 != 0:
        a += '0'*(32 - len(a) % 32)

    t1 = 0x21711730
    t2 = 0x7312f103

    for i in range(0, len(a), 32):
        s = 0x6644498b
        tmp = int(a[i:i+32], 16)
        m1 = tmp & 0xffffffff
        m2 = (tmp >> 0x20) & 0xffffffff
        m3 = (tmp >> 0x40) & 0xffffffff
        m4 = (tmp >> 0x60) & 0xffffffff
        
        for j in range(4):
            s = (s + 0x68696e74) & 0xffffffff
            p1 = (t1 << 4) - m1
            p2 = t1 + s
            p3 = (t1 >> 5) + m2
            t2 = (t2 + (p1 ^ (p2 ^ p3))) & 0xffffffff
            p1 = (t2 << 4) + m3
            p2 = t2 + s
            p3 = (t2 >> 5) - m4
            t1 = (t1 + (p1 ^ (p2 ^ p3))) & 0xffffffff

    return hex((t1 << 0x20) ^ t2)[2::].zfill(16)

def solve_pad(current, target):
    t1, t2 = int(current[:8:], 16), int(current[8::], 16)
    target_t1, target_t2 = int(target[:8:], 16), int(target[8::], 16)

    s = 0x6644498b

    m1 = BitVec('m1', 256)
    m2 = BitVec('m2', 256)
    m3 = BitVec('m3', 256)
    m4 = BitVec('m4', 256)


    for j in range(4):
        s = (s + 0x68696e74) & 0xffffffff
        p1 = (t1 << 4) - m1
        p2 = t1 + s
        p3 = (t1 >> 5) + m2
        t2 = (t2 + (p1 ^ (p2 ^ p3))) & 0xffffffff
        p1 = (t2 << 4) + m3
        p2 = t2 + s
        p3 = (t2 >> 5) - m4
        t1 = (t1 + (p1 ^ (p2 ^ p3))) & 0xffffffff

    solver = Solver()
    solver.add(And(t1 == target_t1, t2 == target_t2))

    if solver.check():
        m = solver.model()
        m_values = list(map(lambda x: m[x].as_long(), [m1, m2, m3, m4]))
        pad = 0
        for i in range(4):
            pad |= m_values[i] << (0x20 * i)

        return hex(pad)[2::].zfill(32)

target_bytecode = '608060405234801561001057600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506106e0806100606000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c806319ff1d211461005c5780635d831619146100df5780637b76ac911461016c57806381a20b0a1461018a578063fd922a421461040a575b600080fd5b610064610454565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100a4578082015181840152602081019050610089565b50505050905090810190601f1680156100d15780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61015260048036036101008110156100f657600080fd5b810190808035906020019092919080359060200190929190803590602001909291908035906020019092919080359060200190929190803590602001909291908035906020019092919080359060200190929190505050610491565b604051808215151515815260200191505060405180910390f35b6101746104c1565b6040518082815260200191505060405180910390f35b610408600480360360808110156101a057600080fd5b81019080803590602001906401000000008111156101bd57600080fd5b8201836020820111156101cf57600080fd5b803590602001918460018302840111640100000000831117156101f157600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192908035906020019064010000000081111561025457600080fd5b82018360208201111561026657600080fd5b8035906020019184600183028401116401000000008311171561028857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803590602001906401000000008111156102eb57600080fd5b8201836020820111156102fd57600080fd5b8035906020019184600183028401116401000000008311171561031f57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192908035906020019064010000000081111561038257600080fd5b82018360208201111561039457600080fd5b803590602001918460018302840111640100000000831117156103b657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506104c9565b005b610412610686565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60606040518060400160405280601081526020017f526561645772697465436154466c616700000000000000000000000000000000815250905090565b600062726365828486181886888a168b8d1818181710156104b157600080fd5b6001905098975050505050505050565b600080905090565b671234567890abcdef60c01b77ffffffffffffffffffffffffffffffffffffffffffffffff191683805190602001208580519060200120161461050b57600080fd5b67abcdef123456789060c01b77ffffffffffffffffffffffffffffffffffffffffffffffff191682805190602001208480519060200120181461054d57600080fd5b67a1b2c3d4e5f6098760c01b77ffffffffffffffffffffffffffffffffffffffffffffffff191682805190602001208580519060200120171461058f57600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166376fe1e92826040518263ffffffff1660e01b81526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561061c578082015181840152602081019050610601565b50505050905090810190601f1680156106495780820380516001836020036101000a031916815260200191505b5092505050600060405180830381600087803b15801561066857600080fd5b505af115801561067c573d6000803e3d6000fd5b5050505050505050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff168156fea265627a7a723158206302876a93a2f8c254feea0484f042ce2eeea0380fd5fca199b197d71e2e656e64736f6c634300050b0032'
target = tag(target_bytecode)

src_bytecode = "608060405234801561001057600080fd5b50736e0279efce38a05394e233198005ae96816dd0d36000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506101a8806100746000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063890d69081461003b578063fd922a4214610045575b600080fd5b61004361008f565b005b61004d61014e565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166376fe1e926040518163ffffffff1660e01b815260040180806020018281038252600b8152602001807f7379616e6720736f6c7665000000000000000000000000000000000000000000815250602001915050600060405180830381600087803b15801561013457600080fd5b505af1158015610148573d6000803e3d6000fd5b50505050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff168156fea265627a7a723158209c9fe9ae135cdca5ef541d8fb75de29863503a323ffa094ed3c24deb87208fd664736f6c63430005110032"
current = tag(src_bytecode)

pad = solve_pad(current, target)
print(pad)

src_bytecode += '0'*(32-len(src_bytecode)%32)
print(tag(src_bytecode + pad))
# print(tag(src_bytecode + pad2))
assert(tag(src_bytecode+pad) == target)

print(src_bytecode+pad)

Contract

在合约部署成功后在对应合约内调用 solve() 即可。

参考链接