# 2020 程安 Crypto筆記4 (區塊鏈安全) ###### tags: `程式安全` `crypto` ## 區塊鏈 1. 去中心化的記帳本 - A紀錄一筆交易,並broadcast給其他人 - B可以偽造交易,broadcast給其他人 $\Rightarrow$ 因此每筆交易都需要簽章 - A用私鑰加密交易,形成簽章附在交易紀錄中,每個人都可以拿A的公鑰來verify交易紀錄 - B還是可以複製已經存在的交易來偽造交易 $\Rightarrow$ 因此簽章需要加上`time stamp`或`id (hash value)` - 先算出`nonce`的人擁有記帳權 - `nonce` : 會包含在block中,必須使整個block的`hash`值開頭為`0x00` - `block` : 一個block裡面會有好幾筆交易 - 前一個block的`hash`值會包含在下一個block中 - 如果同時有兩個人算出`nonce`,由`blockchain`較長的一方當作主鏈 - 其他決定記帳權的方式 - PoW `Proof of Work` - 解決一個複雜的問題 (ex. 上面算nonce的方式) - Bitcoin, Ethereum - PoS `Proof of Stake` - 持幣數量越多越有機會 - DPoS `Delegated Proof of Stake` - 每個節點投票(抵押代幣)選出記帳見證人 - 每一輪隨機決定一個順序,在所有見證人都出過塊後在進行下一輪 ### 51%攻擊 - 如果有個人擁有全網`51%`的算力,則他挖的通常會是最長的鏈 $\Rightarrow$ 他高機率比所有人都還要快算出`nonce` - 因此他可以竄改交易紀錄 > 攻擊者將自己錢包中的錢到交易所兌換成法幣 > 接著攻擊者開始算一個假的chain > 當這個chain比其他chain都還要長時 > 攻擊者就將假的chain廣播出去 > 這時假chain就會被視為主鏈(因為最長鏈共識) > 而原本的chain上的交易都會無效,錢會物歸原主 > 因此攻擊者的錢又回到錢包 > 但4攻擊者還是實際領到錢,錢直接翻倍 ### Ethereum (以太坊) - 錢包 - 冷錢包(硬體錢包) : 類似USB (`Ledger`, `Trezor`) - 熱錢包(軟體錢包) : browser插件 ([`MetaMask`](https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn), `Trust Wallet`) - Ethereum Address - Ex. my address (`MetaMask`)`0x3A60aFa681630A722948295970cB17fB02fAd846` - public key跟address是1-1 mapping - public key做`keccak256` hash之後取**後20bytes**就是address `Note: keccak256是SHA3的前身,差別在參數有點不同` - EVM (`Ethereum Virtual Machine`) - 去中心化的虛擬機 - `Turing Complete` - 以太坊區塊鏈上除了存放交易以外也可以存放程式碼,作為**驗證交易**使用(**智能合約**) - `EVM`用以執行這些程式碼 ### Smart Contract (智能合約) - User創建智能合約(一個class),存放在以太坊上,並呼叫其API來跟他互動 ![](https://i.imgur.com/P2tRZzH.png) - 使用`Solidity`(或`Vyper`)撰寫 - `Solidity`語法類似`javascript` - `Vyper`語法類似`python` - 但存放在以太坊上的其實是bytecode(類似assembly code) - Compiler將`Solidity`轉成bytecode - [EVM opcode](https://ethervm.io/) ```asm % example PUSH1 0x80 % 60 80 PUSH1 0x40 % 60 40 MSTORE % 52 CALLVALUE % 34 DUP1 % 80 ISZERO % 15 PUSH2 0x0010 % 61 00 10 JUMPI % 57 PUSH1 0x00 % 60 00 ... % 0x6080604052348015610010576000... ``` - 在`EVM`上執行bytecode是要錢的,依`opcode`計算費用 - 每個`opcode`值不同的`gas fee` - Example: `ADD`花費`3 gas` - `gas price`由**交易發起人**決定 - `gas price`是給礦工的小費,給得越高交易越快被verify - Example: 若`1 gas = 10 gwei`, 則`ADD`需花費`30 gwei` - `wei`為以太坊中的最小單位,`gwei` = $10^9$`wei`, `kwei` = $10^3$`wei` - 要跟合約互動,使用`web3.js` - `EVM`可以看成後端,`web3.js`可以看成前端 - `web3.js`是一個函式庫,將以太坊的`JSON-RPC`(一個遠端程式呼叫協定)重新封裝並增加實用的函式庫 - User可以透過`web3.js`的API跟`JSON-RPC Server`溝通,進一步跟以太坊溝通 - Example : - `web3.eth.Contract(abi, contract addr)`可以與智能合約互動 - 互動前需要先取得該合約的`ABI (Application Binary Interface)`: 類似`.h`檔的東西,說明有哪些函式以及使用方式 ```javascript const abi = require('./abi.json'); const contract_address = 'CONTRACT_ADDRESS'; contract_instance = new web3.eth.Contract(abi, contract_address); ``` - `web3`也可以用來查看區塊鏈上的資訊 - `web3.eth.getBalance()`用以取得帳戶餘額,default單位為`wei` - `web3.eth.sendTransaction()`用來傳送交易 - `web3.py` $\Rightarrow$ python版本的 - `ethers` $\Rightarrow$ 比`web3.js`還好用(? - [Remix](https://remix.ethereum.org) - 以太坊提供的線上IDE - 用來建立智慧合約 #### Solidity - [`Solidity`語法教學](https://cryptozombies.io) - 因為智慧合約也式程式碼,因此也會有漏洞 - [DSAP TOP 10](https://dasp.co) : 智慧合約的10大漏洞 - [SWC Registry](https://secregistry) : 分類了所有智慧合約的漏洞 - `View` vs `Pure` - `Solidity 0.4.17`後開始用`view`和`pure`取代`constant` - | | 保證不寫入storage | 保證不讀取storage | | ---- |:-----------------:|:-----------------:| | view | **Y** | **N** | | pure | **Y** | **Y** | - 因為去中心化,所以對智慧合約的操作都必須要連網, 且盡可能避免錯誤操作 - 因此若該function完全不需要讀取或寫入智慧合約的state,就會宣告function為`view`或`pure` - 這樣可以直接用compiler檔下可能的意外操作 - Example: 宣告`pure`卻讀取contract address owner ```solidity pragma solidity ^0.4.0; contract Ownable { address private owner; // owner為storage上的值 // 宣告view: 不會對storage上的值做修改 // 宣告returns(...): return type為address function getOwner() view returns(address) { return owner; //這邊如果對owner做修改,compiler編譯時就會報錯 } } ``` - Storage vs Memory - Storage為智慧合約存放state的地方,直接反映到區塊鏈上的值 - Global變數就會放在Storage中 - function的回傳值或參數通常則會放在Memory中,或是有些compiler會用stack來存(因為不花`gas`, `EVM`就是用stack來存) - 如果回傳值或參數是來自global變數,就會複製一份到memory中進行操作 - 但也可以在宣告時加上`storage`,這時就會直接對storage中的值做修改,直接反應到區塊鏈上 - function的local變數則是預設用`storage`處理,但也可以宣告`memory` - but 如果是memory陣列,會比較困難(無法使用`push`) - function modifier 函數修改器 ```solidity pragma solidity ^0.4.0; contract Ownable { address public owner = msg.sender; /*自定義一個函數修改器*/ modifier onlyOwner { require(msg.sender == owner); //require() = assert() _; /*若有funtion宣告此modifier,這邊會被改成該function的程式碼 以下面function為例,這邊就會執行 if (_newOwner == 0x0) throw; owner = _newOwner; */ } /*修改合約的owner*/ function changeOwner(address _newOwner) onlyOwner //宣告modifier { if(_newOwner == 0x0) throw; owner = _newOwner; } } ``` ## 區塊鏈常見漏洞 ### Underflow/Overflow - `uint`往下減會underflow - `uint = uint256` $\rightarrow$ `256bit = 32byte` - Example `(uint)`$0 - 1 = 2^{256}-1$ - 可以使用`Safemath`函式庫來處理`uint`的運算 - 在合約一開始宣告`using SafeMath for uint256;` ### Random Number - 區塊鏈本身很難產生安全的亂數 - 隨機的來源只有block.timestamp, block.difficulty, block.blockhash - 但其實還是可以預測,比如說deploy另一個合約做,在呼叫原本的合約之前先行取得資訊(因為時間接近,通常會在同一個block),就可以預測到亂數的值 - 因此最好的方法還是呼叫外面的中心化server來取得隨機數 ## 攻擊: Reentrancy Attack 1. 呼叫合約API : 會發送一個transaction給目標合約 - transaction的`data`欄位填目標API的`function signature` - `function signature` = `keccak256('fun(type)')`的前4byte - [回查function](https://www.4byte.directory) - 紀錄存在的function signature及其對應函式的DB ![](https://i.imgur.com/BhNIeZp.png) 2. `fallback` & `receive` - 合約要接收錢就一定要定義`receive`及`fallback`函式,否則會return異常並返還錢 - 若有人呼叫合約但是`data`欄位是空的 $\Rightarrow$ 執行`receive()` - 如果合約中沒有`receive()`,則執行`fallback()` - 若有人呼叫合約但是`data`欄位中的`function signatur`沒有match $\Rightarrow$ 執行`fallback()` - `receive` - 一個合約最多只有一個`receive` - 必須標示為`external` - 若要接收錢就必須標記`payable`屬性 (適用於所有函式) - 宣告方式為 ```solidity contract my_cont { receive() external payable { //what should do in receive function } } ``` - `fallback` - 一個合約最多只有一個`fallback` - 沒有名字,不能有參數,也沒有return value,必須標記為`external` ```solidity contract my_cont { function () external [payable] { //what should do in fallback function } } ``` 2. Reentrancy Attack - Example ```solidity= contract A{ function withdraw(uint _amount) public payable { require(balances[msg.sender] >= _amount); msg.sender.call{value: _amount}(""); //trigger fallback balances[msg.sender] -= _amount; //vul } } contract B{ //malicious contract A target; constructor() { target = A(my_addr); } function() externel payable { //fallback target.withdraw() } function pwn() { target.withdraw() //觸發最開始的交易 } } ``` - 因為合約跟錢包在同一個空間中,因此若交易是某地址送錢給另一個地址,其實有可能是**合約送錢給合約** - 而對合約而言,送錢給合約相當於呼叫合約的`fallback` - 若A合約有個API `withdraw`,B是攻擊者的惡意合約,他將`fallback`的內容都寫成再執行一次`withdraw` - 此時因為balance的更新寫在`fallback`被觸發之後,因此在A合約錢被掏空之前都不會執行到 ### `Delegatecall`