蜜罐是传统安全领域中的一个概念,通常指安全人员设置一些陷阱(比较明显的漏洞),让攻击者自己掉入我们设置好的陷阱中,以便安全人员分析攻击者的作恶手法。
蜜罐合约(HoneyPots Contract)也是类似的概念,但对象变了,一般指合约开发者设置了看似容易获利的合约逻辑,但其实是陷阱,普通用户观察合约,发现有利可图,便与蜜罐合约交互,结果发现交互的资产无法被自己提出。
蜜罐合约在中文圈子有时也称为貔貅合约,本文将简单讨论一下,我看见的几种逻辑陷阱型蜜罐合约。
逻辑陷阱蜜罐合约
比较多项目会在transfer函数(转账相关)中实现一些业务逻辑,蜜罐合约可能在transfer函数中加入一些强行限制用户交易的逻辑,从而实现截取用户资产的效果。
这类合约,我称为逻辑陷阱型蜜罐合约,是比较好识别的一类,主要关注其交易相关的逻辑在合约实现上是否透明以及是否有限制则可,这里介绍三个逻辑陷阱型蜜罐合约:
黑名单蜜罐合约
可变合约构成的蜜罐合约
限时限卖的蜜罐合约
黑名单蜜罐合约
我们看到BSC上一个叫Moco合约:https://bscscan.com/address/0x9d4bDdd642529a588f910Aad405C07e066A908Cf#code
Moco合约继承了ERC20,即是一个代币合约,看到合约中的_transfer函数,部分代码如下:
function _transfer(address sender, address recipient, uint256 amount) private returns (bool) {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
require(!_isbclisted[recipient] && !_isbclisted[sender], "bclisted address");
// ...
在用户进行交易时,会调用_transfer函数,它有3个require做条件判断,主要看到第三个,判断sender与recipient都不在黑名单内,才能进行交易。
根据_isbclisted进行搜索,可以发现addBot函数实现了将用户地址加入黑名单的操作。
function addBot(address recipient) internal {
if (!_isbclisted[recipient]) _isbclisted[recipient] = true;
}
那什么地方调用了addBot函数呢?搜索一下,发现还是在_transfer函数中,相关代码如下:
if(sender == uniswapPair) {
if (block.number <= launchedBlock + killblock) {
addBot(recipient);
}
}
如果当前区块数小于launchedBlock + killblock,那么当前recipient会被加入黑名单,而launchedBlock与killblock这两个变量是被合约创建者控制的。
简而言之,合约创建者可以通过对launchedBlock与killblock的控制,将部分区块中发生交易的recipient地址加入黑名单,这样recipient后续就无法从Moco中转出代币了。
这便是典型的黑名单蜜罐,通常出现在土狗项目中,用于杀抢土狗机器人的。
很多土狗项目上线时,会进行拉盘操作,对普通用户而言,会发现土狗项目涨的很快,从而被利润吸引,参与到土狗的交互中,部分开发者也会发现这个土狗项目早期拉盘的特点,就开发了机器人,在开盘后立刻买入,然后等土狗项目方拉盘时,快速卖出,从而获得稳定的利润。
随后,项目方很快也发现土狗机器人的存在,这便进入了一个对抗过程,比如当前这种黑名单逻辑,因为土狗机器人交易速度很快,会抢到比较前的区块完成买入交易,一个土狗项目开盘后,可能会涌入大量机器人交易,通过黑名单的逻辑,就可以将机器人地址加入黑名单,直接让机器人损失本金。
可变合约制造蜜罐
我们看到BSC上,一个名叫SpaceXOver的合约:https://bscscan.com/address/0x60899ea0c86048eb5d8a7a2cf395c7cc58a9023a#code
先看transfer函数,代码如下:
function transfer(address to, uint256 amount) public returns (bool) {
Acc(Acc_address).acc_Transfer(msg.sender, to, amount);
emit Transfer(msg.sender, to, amount);
return true;
}
transfer函数中,真正进行转账交易的逻辑没有在当前合约实现,而是通过Acc合约中的acc_transfer去做的,有猫腻。
翻看一下当前合约,发现Acc合约是在部署时设置了该合约的地址,即当前合约没有Acc合约的逻辑,相关代码如下:
constructor(address _acc) public {
totalSupply_ = 8000000*10**18;
deployer = msg.sender;
// 设置 Acc 合约地址
Acc_address = _acc;
Acc(Acc_address).acc_setup(address(this), totalSupply_);
}
我们可以通过解析input data的形式,来获得部署SpaceXOver合约时,Acc合约的具体地址。
先尝试用bscscan提供的Decode Input Data
发现解析不出来,这里吐槽一下,感觉很多合约的input data都无法通过这种方式解析,不知道为何不去优化一下,对于开源合约,获得abi后,input data还是很好解析的。
这里,我们使用web3-input-decoder这个库来解析一下,先安装一下。
pip install web3-input-decoder
然后保存一下SpaceXOver合约的ABI用于解析input data,相关代码如下:
import json
from web3_input_decoder import decode_constructor, decode_function
# SpaceXOver的ABI JSON
p = '1.json'
with open(p, 'r', encoding='UTF-8') as f:
abi = f.read()
abiJson = json.loads(abi)
result = decode_constructor(abiJson, "0x6080604...(input data复制过来则可)")
print(result)
结果如下:
访问acc合约地址,发现这个地址下的合约是闭源的,无法看到合约代码,猫腻很大,因为我们不知道其交易时的具体逻辑是怎么样的,很有可能就是一个蜜罐合约,只可进,不可出。
限时限卖的蜜罐合约
我们看到BSC上名叫SW的合约:https://bscscan.com/address/0xa7d0741813d2ff189b172f68c1ce3f0aa101bd22#code
SW合约的中定义了多个mapping(address => uint256)类型的变量,通过这些变量来记录转账的值。
看到_transfer函数,有限制交易时间与限制交易金额比例的逻辑,相关代码如下:
// 限制交易时间
if(block.timestamp<_limitTime){
dayBuy[to]=dayBuy[to].add(amount);
require(dayBuy[to]<=300*10**18 ,"limit time max 300");
}
require(_isTrade ,"limit trade2 ");
// 限制交易金额比例,最多只能交易 90% 的金额
require(balanceOf(from).mul(90).div(100)>amount,"selle max 90%");
takeFee = true;
结尾
除了逻辑陷阱型蜜罐,还有其他类型的蜜罐合约,阅读【The Art of The Scam: Demystifying Honeypots in Ethereum Smart Contracts】论文可知还有如下几种类型的蜜罐合约:
级别 | 技巧 |
---|---|
Ethereum Virtual Machine(以太坊虚拟机级别) | Balance Disorder(平衡紊乱) |
Solidity编译器级别 | Inheritance Disorder(继承障碍) |
Skip Empty String Literal(跳过空字符串字面值) | |
Type Deduction Overflow(类别除法溢出) | |
Uninitialised Struct(未初始化结构) | |
以太扫描区块链探索器(Etherscan) | Hidden State Update(隐藏状态更新) |
Hidden Transfer(隐藏交易) | |
Straw Man Contract(稻草人合同) |
因为论文中讨论的比较细致且我自身没有进行复现验证,所以本文就不多讨论。
值得一提的是Skip Empty String Literal与Type Deduction Overflow已在高版本的Solidity中fix了,如果你用0.8.0以上版本的Solidity不会有这两个问题,其他的,需要自行验证一下。
相关参考:
【米斯特姚】的Youtube频道(https://www.youtube.com/watch?v=SAzZ61cvvn8)
【The Art of The Scam: Demystifying Honeypots in Ethereum Smart Contracts】论文(https://www.arxiv-vanity.com/papers/1902.06976/)