--- tags: Demi --- # 7/9 Honeypot 分享 ## Honeypot contract 蜜罐合約 蜜罐合約是一種特殊類型的智能合約,它在合約設計上看似有明顯的缺陷,如果用戶向合約中轉移了一定數量的資金,那麼該用戶就可以提取合約中的資金。然而,一旦用戶嘗試利用這個看似明顯的缺陷,就會陷入真正的陷阱之中,導致用戶投入合約中的資金無法取回。[1] ![](https://i.imgur.com/UOSn5Tr.png) ![](https://i.imgur.com/ANbCPzG.png) 1. The attacker deploys a seemingly vulnerable contract and places a bait in the form of funds; 1. The victim attempts to exploit the contract by transferring at least the required amount of funds and fails; 1. The attacker withdraws the bait together with the funds that the victim lost in the attempt of exploitation. --- **事前聲明: 僅供學術研究非鼓勵 版本為2019舊版** ![](https://i.imgur.com/BTGnag0.png) EVM --- * Balance Disorder 餘額錯亂 ``` solidity= contract MultiplicatorX3 { ... function multiplicate(address adr) payable { if (msg.value >= this.balance) adr.transfer(this.balance+msg.value); } } ``` https://etherscan.io/address/0x5aa88d2901c68fda244f1d0584400368d2c8e739#code 漏洞?:可以領出balance裡的錢 if 永遠不會成立 Solidity Compiler --- * Inheritance Disorder 繼承錯亂 ``` solidity= contract Ownable { address owner = msg.sender; modifier onlyOwner { require(msg.sender == owner); _; } } contract KingOfTheHill is Ownable { address public owner; ... function() public payable { if(msg.value>jackpot)owner=msg.sender; jackpot += msg.value; } function takeAll() public onlyOwner { msg.sender.transfer(this.balance); jackpot = 0; } } ``` 漏洞?: 可以變為owner 只會改到 line 9 的 owner line 9!= line 2 * Skip Empty String Literal 跳過空字符串文本 ``` solidity= contract DividendDistributorv3 { ... function loggedTransfer(uint amount,bytes32 msg,address target,address currentOwner){ if (!target.call.value(amount)()) throw; Transfer(amount,msg,target,currentOwner); } function invest() public payable { if (msg.value >= minInvestment) investors[msg.sender].investment+=msg.value; } function divest(uint amount) public { if (investors[msg.sender].investment == 0 || amount == 0) throw; investors[msg.sender].investment-=amount; this.loggedTransfer(amount,"",msg.sender,owner); } } ``` 漏洞?: 可以領出超額存款 line 5 參數會向左 shift * Type Deduction Overflow 型態推倒溢位 ``` solidity= contract For_Test { ... function Test() payable public { if (msg.value > 0.1 ether) { uint256 multi = 0; uint256 amountToTransfer = 0; for (var i = 0; i < 2*msg.value; i++) { multi = i*2; if (multi < amountToTransfer) { break; amountToTransfer = multi; } msg.sender.transfer(amountToTransfer); } } } ``` 漏洞?: 加倍奉還 line 7 type(i) = uint8 =0~255 line 7 為無限loop line 9 overflow 可以成立 line 13 最多領回 255 wei * Uninitialised Struct 未初始化结構體 ``` solidity= contract GuessNumber { uint private randomNumber=uint256(keccak256(now))%10+1; uint public lastPlayed; uint public minBet=0.1ether; struct GuessHistory { address player; uint256 number; } function guessNumber(uint256 _number)payable{ require(msg.value>=minBet&&_number<=10); GuessHistory guessHistory; guessHistory.player = msg.sender; guessHistory.number = _number; if (_number == randomNumber) msg.sender.transfer(this.balance); lastPlayed = now; } } ``` 漏洞?: private randomNumber 在鏈上是可以被查到的 https://solidity-by-example.org/hacks/accessing-private-data slot[0] = randomNumber slot[1] = lastPlayed ... guessHistory 是 Uninitialised Struct 所以 guessHistory.player->randomNumber randomNumber 會被overwrite成 msg.sender https://www.bookstack.cn/read/ethereumbook-en/spilt.16.c2a6b48ca6e1e33c.md 加上 memory or storage guessHistory Etherscan Blockchain Explorer --- * Hidden State Update 隱藏狀態更新 ``` solidity= contract Gift_1_ETH { bool passHasBeenSet = false; ... function SetPass(bytes32 hash) payable { if (!passHasBeenSet&&(msg.value>=1ether)) hashPass = hash; } function GetGift(bytes pass)returns(bytes32){ if (hashPass == sha3(pass)) msg.sender.transfer(this.balance); return sha3(pass); } function PassHasBeenSet(bytes32 hash) { if (hash==hashPass) passHasBeenSet=true; } } ``` https://etherscan.io/address/0x75041597d8f6e869092d78b9814b7bcdeeb393b4#code 漏洞?: passHasBeenSet 是 false,可以用 SetPass 改 hashPass ,再用 GetGift 領錢出來 Etherscan 可以顯示 Internal transactions 但不會顯示包含 empty transaction value 的 Internal transaction * Hidden Transfer ``` solidity= contract TestToken { ... function withdrawAll() payable { require(0.5 ether < total); if (block.number > 5040270 ) {if (_owner == msg.sender ){_owner.transfer(this.balance);} else {throw;}} msg.sender.transfer(this.balance); } } ``` 漏洞?: 看起來是個正常的withdrawAll() 利用 etherscan 顯示問題隱藏程式碼在後面 * Straw Man Contract ``` solidity= contract Private_Bank { ... function Private_Bank(address _log) { TransferLog = Log(_log); } function Deposit() public payable { if (msg.value >= MinDeposit) { balances[msg.sender]+=msg.value; TransferLog.AddMessage("Deposit"); } } function CashOut(uint _am) { if(_am<=balances[msg.sender]){ if(msg.sender.call.value(_am)()){ balances[msg.sender]-=_am; TransferLog.AddMessage("CashOut"); } } } } contract Log { ... function AddMessage(string _data) public { LastMsg.Time = now; LastMsg.Data = _data; History.push(LastMsg); } } ``` 漏洞?: 可以 reentrancy attack https://solidity-by-example.org/hacks/re-entrancy Checks Effects Interactions line 3 _log 是別份合約地址 所以line 21~都是假的 詳解 https://www.youtube.com/watch?v=d0q5zVnNLWs code:https://solidity-by-example.org/hacks/honeypot --- ## HONEYBADGER 蜜獾 ![](https://i.imgur.com/iRzkNpV.jpg) 蜜罐合約分析 ![](https://i.imgur.com/oDoVnPA.png) --- ## Reference 1. The Art of The Scam:Demystifying Honeypots in Ethereum Smart Contracts https://arxiv.org/abs/1902.06976 https://www.arxiv-vanity.com/papers/1902.06976/ https://www.usenix.org/system/files/sec19-torres.pdf 中文翻譯 https://zhuanlan.zhihu.com/p/72423392 ### 鏈上密罐合約 1. smart-contract-attack-vectors/attacks/honeypot.md https://github.com/KadenZipfel/smart-contract-attack-vectors/blob/master/attacks/honeypot.md 1. https://immunebytes.com/a-complete-breakdown-of-uninitialized-storage-parameters/ --- ## 延伸閱讀 ### 對抗性密罐合約 繞過密罐合約檢測器 1. [1]论文速递:An Adversarial Smart Contract Honeypot in Ethereum https://www.freebuf.com/articles/network/269557.html ![](https://i.imgur.com/vRPh3md.png) ![](https://i.imgur.com/eQMVmZU.png) ### Security 1. Smart Contract Security https://www.bookstack.cn/read/ethereumbook-en/c2a6b48ca6e1e33c.md 1. Security Considerations https://docs.soliditylang.org/en/v0.8.15/security-considerations.html 1. smart-contract-attack-vectors https://github.com/kadenzipfel/smart-contract-attack-vectors --- ## 寫爛的託管合約 (RUG Contract) ``` solidity= contract My_Escrow { address public owner; address[] public payees; address _tokenContract = 0xE69aBe2B0C222d0176E965DcF2Cbe01a78b44003; //Goerli ERC20 IERC20 token = IERC20(_tokenContract); constructor() { owner = msg.sender; } event Deposited(address indexed depositor, address indexed payee, uint256 weiAmount); event Withdrawn(address indexed payee, uint256 weiAmount); event Rewarded(address indexed payee, uint256 weiAmount); mapping(address => uint256) private _deposits; function depositsOf(address payee) public view returns (uint256) { return _deposits[payee]; } function deposit(address payee, uint256 amount) public { require(amount>0); require(token.balanceOf(msg.sender) >= amount, "You need to have tokens more than amount first!"); require(token.allowance(msg.sender,address(this)) >= amount, "You need to approve tokens more than amount first!"); token.transferFrom(msg.sender, address(this), amount); if(_deposits[payee]==0) { payees.push(payee); } _deposits[payee] += amount; emit Deposited(msg.sender, payee, amount); } function withdraw() public { address payee = msg.sender; uint256 payment = _deposits[payee]; _deposits[payee] = 0; token.transfer(payee, payment); emit Withdrawn(payee, payment); } function _get_largest_payee() public view returns(address){ address largest_payee; uint256 largest_payment = 0; uint256 length = payees.length; if(length == 0 ) { return address(0x0); } for(uint256 i=0; i < length;) { address payee = payees[i]; uint256 payment = _deposits[payee]; unchecked { ++i; } if(payment > largest_payment) { largest_payee = payee; largest_payment = payment; } } return largest_payee; } modifier onlyOwner { require(msg.sender == owner); _; } function reward_largest_payee(uint256 amount) public onlyOwner (){ address payee = _get_largest_payee(); require(token.balanceOf(address(this)) >=amount , "You need to send more tokens to the contract first!"); token.transfer(payee, amount); emit Rewarded(payee, amount); } } ``` https://goerli.etherscan.io/address/0xc6a3e20d2027a103f8b9e7460b9167b8fdb9651e#writeContract #### 使用步驟 使用 goerli 測試網 1. 領測試ETH https://faucets.chain.link/goerli 2. 領測試 ERC-20 token 連接錢包 https://goerli.etherscan.io/address/0xe69abe2b0c222d0176e965dcf2cbe01a78b44003#writeContract 使用 writeContract 用 faucets 函式 to (address) 填入錢包地址 amount (uint256) 填入要領的ERC20代幣數量 3. 先在 ERC-20 合約裡 approve 給 My_Escrow.sol 合約 使用 writeContract 用 approve 函式 填合約地址 跟 要領的數量 10^18=1顆 spender (address) = 0xc6A3E20d2027a103F8B9e7460b9167b8FdB9651e amont (uint256) = 1000000000000000000 4. 操作 Escrow 合約 https://goerli.etherscan.io/address/0xc6a3e20d2027a103f8b9e7460b9167b8fdb9651e#writeContract 合約功能 Read Contract - 1. _get_largest_payee: 顯示存款最多的 user - 2. depositsOf: user 存款額度 - 3. owner: 合約擁有者 - 4. payees: 存戶地址列表 Write Contract - 1. deposit: 填入存戶地址跟金額。可以幫別人存款 - 2. reward_largest_payee: 合約擁有者從 Escrow 發 token 給存款最多錢的 user。 要補 send token 到合約地址才不會發完錢不能領款。 - 3. withdraw: 一鍵提款。 只能提領自己帳戶內全部的錢走。 測試紀錄 Event Logs https://goerli.etherscan.io/address/0xc6a3e20d2027a103f8b9e7460b9167b8fdb9651e#events - 1. 幫自己存款 0.1 MTK - 2. 幫別人存款 0.2 MTK - 3. 提款 0.1 MTK 走 - 4. 發獎勵給存款最多的 user 0.1 MTK